HDU 6407 Pop the Balloons (状压dp + 剪枝 。。。 结果大数 int128就行 )
程序员文章站
2022-07-01 11:27:31
...
具体 看代码吧:
#include <bits/stdc++.h>
using namespace std;
#ifdef __LOCAL_DEBUG__
# define _debug(fmt, ...) fprintf(stderr, "\033[94m%s: " fmt "\n\033[0m", \
__func__, ##__VA_ARGS__)
#else
# define _debug(...) ((void) 0)
#endif
#define rep(i, n) for (int i=0; i<(n); i++)
#define Rep(i, n) for (int i=1; i<=(n); i++)
#define range(x) (x).begin(), (x).end()
typedef long long LL;
typedef unsigned long long ULL;
constexpr int MAXM = 12;
LL dp[32][1<<MAXM], fact[32];
LL ans[32];
unsigned a[32];
char g[32][32];
int rcnt[32];
int m, n, k;
void print64mul(LL x, LL y) {
#if __SIZEOF_INT128__ == 16
__int128 res = __int128(x) * y;
char ch[64];
int ptr = 0;
if (res == 0) {
puts("0");
return;
}
while (res) {
ch[ptr] = (res % 10) + '0';
ptr++;
res /= 10;
}
while (ptr)
putchar(ch[--ptr]);
putchar('\n');
#else
constexpr LL limit = 1000000000;
LL dig[4] = {0};
dig[0] = x % limit;
dig[1] = x / limit;
dig[0] *= y;
dig[1] *= y;
dig[1] += dig[0] / limit;
dig[0] %= limit;
dig[2] += dig[1] / limit;
dig[1] %= limit;
int pos = 2;
while (pos > 0) {
if (dig[pos]) break;
pos--;
}
printf("%d", dig[pos]);
while (pos > 0) {
pos--;
printf("%09d", dig[pos]);
}
puts("");
#endif
}
int main()
{
int T;
scanf("%d", &T);
fact[0] = 1;
Rep (i, 20) fact[i] = fact[i-1] * i;
while (T--)
{
//输入
scanf("%d%d%d", &m, &n, &k);
rep (i, m) scanf("%s", g[i]);
memset(a, 0, sizeof a);
Rep (j, n) rep (i, m)
{
//将每列转为二进制 气球为1
a[j] = a[j] << 1 | (g[i][j-1] == 'Q');
}
memset(ans, 0, sizeof ans);
//枚举所有 想要扎爆的行的状态为fin dp[r][mask] 表示 前r行 已经扎爆的行为mask的情况
for (unsigned fin = 1; fin < (1u << m); fin++)
{
//一开始都为0 除了什么都没有的时候 其他的清零在下面做了
dp[0][0] = 1;
//遍历每一列
Rep (r, n)
{
bool flag = false;
//遍历每一个mask mask代表的扎爆的行 一定是fin里想要扎爆的行 mask = (mask - 1) & fin手模下就会发现 位运算真秀
for (unsigned mask = fin; ; mask = (mask - 1) & fin)
{
//只考虑这种状态存在的情况 剪枝
if (dp[r - 1][mask])
{
//如果 第r列的气球 都在我想要扎爆的行上 我可以选择不在第r列扎爆某个气球(不扎爆某个行)并打上标记表示这列气球我可能扎完
if ((a[r] & fin) == a[r])
{
if (dp[r][mask] += dp[r-1][mask]) flag = true;
}
// dif &= dif - 1 是删去二进制里最右边的1 bit = dif & -dif 是取得最右边1的位置
//这里我们找 想要扎爆的 但是还没扎爆的行 并且 与第r列的气球 中有行相同的(如果没有行相同的就说明我不能把这列气球扎完 这种情况不可取)
for (unsigned dif = (fin ^ mask) & a[r]; dif; dif &= dif - 1)
{
unsigned bit = dif & -dif;
//更新 选第r列某一行的气球扎爆后的方案数
dp[r][mask | bit] += dp[r-1][mask];
flag = true;
}
// 更新完第r列清空第r-1列
dp[r-1][mask] = 0;
}
if (mask == 0) break;
}
// 不能扎完气球 所以跳出
if (flag == false) break;
}
//fin的二进制中1的个数x 表示我们扎了x个气球 更新答案
ans[__builtin_popcount(fin)] += dp[n][fin];
//清0
for (unsigned mask = fin; ; mask = (mask - 1) & fin) {
dp[n][mask] = 0;
if (mask == 0) break;
}
}
//输出要乘与阶乘 所以用大数乘法
Rep (i, k) print64mul(ans[i], fact[i]);
}
return 0;
}
上一篇: 第四十五题
下一篇: 不累你们站着吧,我喝水去了