洛谷P2150 寿司晚宴

来源:互联网 发布:威海东大妇科乎有人 编辑:程序博客网 时间:2024/04/24 15:37

链接

  https://www.luogu.org/problem/show?pid=2150

题解

  考虑暴力,当n30时,素数有2 3 5 7 11 13 17 19 23 29,一共10个,状压dp可行,f[i][j]表示每个人选数的状态,然后暴力转移就好了,复杂度O(22N)。然后我就想不出来了……
  有一个常用到的性质就是,对于任何一个数字x,其大于x的约数只能有一个或者没有,利用这个性质,可以做这道题。500=22,而小于等于22的最大的素数是19,从1到19一共只有8个素数。
  可以只压这8个素数,f[i][j]表示第一个人的选数状态为i,第二个人为j的方案数。
  将[2,500]的所有数分解质因数,大于19的素数最多有一个,记下来,记作gp[i](giant prime),小素数们压进二进制数,记作tp[i](tiny primes)。
  接下来是转移。
  对于gp相等的数,我们划分到一组里去单独dp,因为这一组中只能让一个人选数,一个人选了另一个人就不能再这组里选了。
  对于一个组,其giant prime是相同的,令g[i][j]表示已经考虑了组中的所有数和前面做完的组的所有数,强制让第一个人选择这个组中的数(一个数不选的情况也纳入考虑),第二个人不能选,这样的方案数。
  初始状态g[i][j]=f[i][j]
  那么我们每次加入组中的一个数x,令k=tp[x],转移就是g[i bitor k][j]+=g[i][j](当i bitand k==0),注意循环顺序要倒序。
  做完一组之后,要将gf数组合并,f[i][j]=g[i][j]+g[j][i]f[i][j],减去f[i][j]是因为两个人都啥也不选的情况重复了。
  这样有BUG,没有大素数的那一组不能这样转移,这样转移方案数会变少,因为这一组中只要两个人不选相同的数字就行,和前面不一样。
  这时直接f[i bitor k]+=f[i][j]f[i][j bitor k]+=f[i][j]
  答案就是所有状态加起来。

代码

//状压DP #include <cstdio>#include <algorithm>#define maxn 1000#define ll long longusing namespace std;ll N, P, f[maxn][maxn], g[maxn][maxn], tp[maxn], hp[maxn], mark[maxn], prime[maxn],    num[maxn], h[maxn][maxn];bool cmp(ll a, ll b){return hp[a]<hp[b];}void init(){    ll i, j;    scanf("%lld%lld",&N,&P);    for(i=2;i<=N;i++)    {        if(!mark[i])prime[++prime[0]]=i;        for(j=1;j<=prime[0] and i*prime[j]<=N;j++)        {            mark[i*prime[j]]=1;            if(i%prime[j]==0)break;        }    }    for(i=2;i<=N;i++)    {        for(j=1;prime[j]*prime[j]<=N;j++)if(i%prime[j]==0)tp[i]|=(1<<j-1);        for(;prime[j]<=i and j<=prime[0];j++)if(i%prime[j]==0)hp[i]=prime[j];    }    for(i=2;i<=N;i++)num[i]=i;    sort(num+2,num+N+1,cmp);}void dp(){    ll l, r, p, i, j, k, ans=0;    f[0][0]=1;    for(l=2;l<=N;l=r+1)    {        for(r=l;hp[num[r]]==hp[num[l]] and r<=N;r++);r--;        if(hp[num[l]]!=0)        {            for(i=0;i<256;i++)for(j=0;j<256;j++)g[i][j]=f[i][j];            for(p=l;p<=r;p++)            {                k=tp[num[p]];                for(i=255;i>=0;i--)                    for(j=255;j>=0;j--)                    {                        if((i&j)or(j&k))continue;                        g[i|k][j]+=g[i][j];                    }            }            for(i=0;i<256;i++)for(j=0;j<256;j++)f[i][j]=(g[i][j]+g[j][i]-f[i][j])%P;        }        else        {            for(p=l;p<=r;p++)            {                k=tp[num[p]];                for(i=255;i>=0;i--)                    for(j=255;j>=0;j--)                    {                        if(i&j)continue;                        f[i][j]%=P;                        if((j&k)==0)f[i|k][j]+=f[i][j];                        if((i&k)==0)f[i][j|k]+=f[i][j];                    }            }        }    }    for(i=0;i<256;i++)for(j=0;j<256;j++)ans=(ans+f[i][j])%P;    printf("%lld",((ans%P)+P)%P);}int main(){    init();    dp();    return 0;}
0 0
原创粉丝点击