HDU 6407 2018HDU多校赛 第八场 Pop the Balloons(状态压缩dp)

大致题意:给你一个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行在此之前没有气球,那么可以选择扎破这个气球,对应转移:

                                 \large dp[i+1][j+(2-\lfloor\frac{j}{3^x}\rfloor\%3)*3^x]+=dp[i][j]

即把对应位的数字改为2。其次,考虑该列不扎破气球,那么这一列所有对应行没有气球扎破的气球,所对应的行的位置都要变成1。对应的转移:

                    dp[i+1][j+\sum_{k=0}^{m}3^k*[\ s[i+1][k]==Q \ \&\&\ \lfloor\frac{j}{3^k}\rfloor\%3==0]\ ]+=dp[i][j]

即一次要把所有满足条件的气球的位置变成1。如此一来,本题的时间复杂度就是O(n*m*3^m),但是在实际操作中会有很多为0的无效状态,对于这种状态我们直接continue即可。即便如此,在常数上可能还是会被卡成TLE,所以我们考虑做一些小的优化。

比如说,我们在寻找第i+1列的气球的时候,我们其实可以不用枚举每一列。如果我们在初始输入的时候,用一个二进制数字表示对应列的每一行是否有气球,1表示有,2表示无。这样我们在转移的时候只需要找那些对用位置为1的位置,而不用一个个的枚举了。具体来说,对于一个二进制数字,我们找其对应的1的位置,可以用lowbit,每次找到最低位的1的位置,然后处理它,之后减去这个1,再继续lowbit,以此类推找到所有的气球的位置。如此,理论复杂度就可以降到O(n*\log m*3^m)。然后,在枚举状态的时候,如果某个状态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;
}

 

猜你喜欢

转载自blog.csdn.net/u013534123/article/details/81808148
今日推荐