HDU 6407 Pop the Balloons (状压dp + 剪枝 。。。 结果大数 int128就行 )

 

 

具体 看代码吧:

#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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值