大致题意:给你一个m*n的表,其中为Q的位置表示为气球。每次,我扎破一个气球会导致与其相同行和列的气球一起爆炸。现在,询问给你扎1..k次,对于每个扎气球的次数i,问你有多少种方式使得可以把所有的气球扎破。
数据范围M和N一个是12一个是20,显然的一个状态压缩dp,但是这个状态却不是那么好表示。按照正常思路,由于m的范围更小,所以我用一个12位的数字表示每个行是否有气球被扎破状态。如果用普通的二进制表示,那么只能是某行气球被扎破或者没有被扎破。但是还可能有些行本身并没有气球,不存在是否被扎破的状态,因此,每一位要有三个状态表示,所以说得用一个12位的三进制数字来表示。如此,我们约定,用0表示该行没有气球,1表示该行有气球但是还未被扎破,2表示该行有气球且被扎破了。
有了状态的表示,我们就来考虑状态的转移。令dp[i][j]表示当前考虑到第i列,且各个行气球的状态为j时候的方案数。那么状态(i,j)就可以转移到下一列。根据下一列的表中的状态来确定可转移的点。首先,考虑该列要扎破气球,对于该列任意第x行的气球,如果x行在此之前没有气球,那么可以选择扎破这个气球,对应转移:
即把对应位的数字改为2。其次,考虑该列不扎破气球,那么这一列所有对应行没有气球扎破的气球,所对应的行的位置都要变成1。对应的转移:
即一次要把所有满足条件的气球的位置变成1。如此一来,本题的时间复杂度就是,但是在实际操作中会有很多为0的无效状态,对于这种状态我们直接continue即可。即便如此,在常数上可能还是会被卡成TLE,所以我们考虑做一些小的优化。
比如说,我们在寻找第i+1列的气球的时候,我们其实可以不用枚举每一列。如果我们在初始输入的时候,用一个二进制数字表示对应列的每一行是否有气球,1表示有,2表示无。这样我们在转移的时候只需要找那些对用位置为1的位置,而不用一个个的枚举了。具体来说,对于一个二进制数字,我们找其对应的1的位置,可以用lowbit,每次找到最低位的1的位置,然后处理它,之后减去这个1,再继续lowbit,以此类推找到所有的气球的位置。如此,理论复杂度就可以降到。然后,在枚举状态的时候,如果某个状态1的个数即有气球但是未被扎破的行数大于剩下未处理的行数,那么这个状态肯定不能转移到最后,直接可以舍弃。还有就是那些2的个数大于k的状态也是一样。还有一些常数优化的小细节,具体一会儿看代码。
最后就是本题最后的答案要乘上一个扎的次数的阶乘。因为我扎的顺序可以任意,对应也是不同的扎的方案。然后相乘的时候应该是会爆long long的,所以还是要用__int128,然后对于__int128的输出可以好好了解一下。具体见代码:
#include<bits/stdc++.h>
#define LL long long
#define mod 1000000007
#define pb push_back
#define lb lower_bound
#define ub upper_bound
#define INF 0x3f3f3f3f
#define sf(x) scanf("%lld",&x)
#define sc(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define clr(x,n) memset(x,0,sizeof(x[0])*(n+5))
#define IO ios::sync_with_stdio(0);cin.tie(0); cout.tie(0)
#define file(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
using namespace std;
const int N = 531441;
const LL outbuf = 1e12;
int t[N][3],num[N][13],p3[21],ss[21],lg[4097];
LL ans[21],fac[21],dp[21][N];
char s[13][21];
void init()
{
p3[0]=fac[0]=1;
for(int i=0;i<=12;i++) lg[1<<i]=i; //预处理2的幂的次数
for(int i=1;i<=20;i++) fac[i]=fac[i-1]*i; //预处理阶乘
for(int i=1;i<=12;i++) p3[i]=p3[i-1]*3; //预处理3的幂
for(int i=0;i<p3[12];i++)
{
int tot=0;
for(int x=i;x;x/=3) //预处理每一个状态每一位是什么数字
t[i][num[i][tot++]=x%3]++; //以及每种数字出现次数
}
}
int main()
{
file(k);
init();
int T; sf(T);
while(T--)
{
int m,n,k;
sc(m,n,k);
for(int i=0;i<m;i++)
scanf("%s",s[i]+1);
for(int i=1;i<=n;i++)
{
ss[i]=0;
for(int j=0;j<m;j++)
if (s[j][i]=='Q') ss[i]|=1<<j;
}
int up=p3[m];
for(int i=1;i<=20;i++) ans[i]=0;
for(int i=0;i<=n;i++)
for(int j=0;j<up;j++) dp[i][j]=0;
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<up;j++)
{
if (!dp[i-1][j]||t[j][1]>n-i+1||t[j][2]>k) continue; //跳过不合法状态
int nxt=j;
for(int x=ss[i],y,p;x;x-=y)
{
y=x&-x; p=lg[y]; //lowbit求最低位的1对应的二进制数
nxt+=(num[j][p]==0)*p3[p]; //再取log找到位置
if (num[j][p]==2) continue;
dp[i][j+(2-num[j][p])*p3[p]]+=dp[i-1][j]; //对应位置变成2
}
dp[i][nxt]+=dp[i-1][j]; //所有气球的位置变为1
}
}
for(int i=0;i<up;i++)
if (dp[n][i]&&t[i][1]==0) ans[t[i][2]]+=dp[n][i];
for(int i=1;i<=k;i++) //__int128的输出,了解一下
{
__int128 tmp=ans[i]; tmp*=fac[i];
if (tmp>=outbuf) printf("%lld%012lld\n",(LL)(tmp/outbuf),(LL)(tmp%outbuf));
else printf("%lld\n",(LL)tmp);
}
}
return 0;
}