「NOI 2015」寿司晚宴

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4197


Description

n1 n − 1 个寿司,第 i i 个寿司的美味度为 i+1。小 G G 和小 W W 每人选择一些寿司来品尝。规定一种方案为不和谐的当且仅当:小 G G 和小 W W 品尝的寿司中分别存在美味度为 x x y 的寿司,且 x x y 不互质。求一共有多少种方案是和谐的。

2n500 2 ⩽ n ⩽ 500


Solution

问题的本质为:两个人在 2n 2 ∼ n 中选取数字,第一个人和第二个人选择的数字的质因子集合分别为 S1 S 1 S2 S 2 ,满足 S1S2= S 1 ∩ S 2 = ∅ 的选择方案数。

30 ptsn30 30   pts n ⩽ 30

  注意到 30 30 以内的质数只有 10 10 个,考虑使用状压 DP DP 计算答案。可以将每个数分解质因数并压成二进制数 S S (其中 S 的第 i i 位表示是否有第 i 个质因子)。
  设 f[i][S1][S2] f [ i ] [ S 1 ] [ S 2 ] 表示考虑到第 i i 个数,第一个人选择的数的质因子集合为 S1,第二个人选择的数的质因子为 S2 S 2 ,有如下状态转移方程:
   f[i][S1|k][S2]+=f[i1][S1][S2] f [ i ] [ S 1 | k ] [ S 2 ] + = f [ i − 1 ] [ S 1 ] [ S 2 ] kS2= k ∩ S 2 = ∅
   f[i][S1][S2|k]+=f[i1][S1][S2] f [ i ] [ S 1 ] [ S 2 | k ] + = f [ i − 1 ] [ S 1 ] [ S 2 ] kS1= k ∩ S 1 = ∅
  其中第一维可以压缩,答案为 S1S2=f[n][S1][S2] ∑ S 1 ∩ S 2 = ∅ f [ n ] [ S 1 ] [ S 2 ]

100 ptsn500 100   pts n ⩽ 500

  按照之前的状态压缩显然不可行,考虑如何将状态继续压缩。
  注意每个数字 x x 至多只有一个大于 x 的质因子,因此我们可以将不大于 x x 的质因子进行 DP DP ,单独考虑大于 x x 的质因子。
  小于 xmax=50022.4 x max = 500 ≈ 22.4 的质因子只有 8 8 个,于是我们对这些质因子按照一般的状压 DP 进行计算。
  考虑大于 x x 的质因子 p p ,如果其中一个人选择了含有质因子 p 的某个数,那么另一个人就不能选择任何含有质因子 p p 的数字。设 g[k][p][S1][S2] 表示第 k k 个人选择了质因子 p,第一个人和第二个人选择的小于 n n 的质因子集合分别为 S1 S 1 S2 S 2 ,转移显然。
  在计算完 g g 数组后,再更新 f 的答案。考虑如何将两个数组合并:(稍有常识的人就能发现)
   f[S1][S2]=g[0][S1][S2]+g[1][S1][S2]f[S1][S2]f[S1][S2] f [ S 1 ] [ S 2 ] = g [ 0 ] [ S 1 ] [ S 2 ] + g [ 1 ] [ S 1 ] [ S 2 ] − f [ S 1 ] [ S 2 ] − f [ S 1 ] [ S 2 ]
最后减去 f[S1][S2] f [ S 1 ] [ S 2 ] 是因为两个人都不选这个集合的数的情况被统计了 2 2 次。

  时间复杂度Θ(n216)


Code

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=505;
const int P=8;
const int prime[P]={2,3,5,7,11,13,17,19};
int n,p,f[1<<P][1<<P],g[2][1<<P][1<<P];
pair<int,int> s[N];

int main() {
    scanf("%d%d",&n,&p);
    for(int i=2;i<=n;++i) {
        int tmp=i;
        for(int j=0;j<8;++j) while(tmp%prime[j]==0) s[i].second|=(1<<j),tmp/=prime[j];
        s[i].first=tmp;
    }
    std::sort(s+1,s+n+1);
    f[0][0]=1;
    for(int i=2;i<=n;++i) {
        if(i==2||s[i].first==1||s[i].first!=s[i-1].first) {
            memcpy(g[0],f,sizeof(f));
            memcpy(g[1],f,sizeof(f));
        }
        for(int s1=(1<<P)-1;s1>=0;--s1)
            for(int s2=(1<<P)-1;s2>=0;--s2) {
                if(s1&s2) continue;
                if((s2&s[i].second)==0) (g[0][s1|s[i].second][s2]+=g[0][s1][s2])%=p;
                if((s1&s[i].second)==0) (g[1][s1][s2|s[i].second]+=g[1][s1][s2])%=p;
            }
        if(i==n||s[i].first==1||s[i].first!=s[i+1].first) {
            for(int s1=(1<<P)-1;s1>=0;--s1)
                for(int s2=(1<<P)-1;s2>=0;--s2)
                    if((s1&s2)==0) f[s1][s2]=((g[0][s1][s2]+g[1][s1][s2]-f[s1][s2])%p+p)%p;
        }
    }
    int ans=0;
    for(int i=(1<<P)-1;i>=0;--i)
        for(int j=(1<<P)-1;j>=0;--j)
            if((i&j)==0) (ans+=f[i][j])%=p;
    printf("%d\n",ans);
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值