bzoj 4197: [Noi2015]寿司晚宴

看到题,就想到要利用质因子分解进行状压dp,但是质因子太多了,不能直接搞,于是考虑按最大质因子<sqrt(n)和>sqrt(n)分别讨论。对于最大质因子>sqrt(n)的每个数,只有一个质因子>sqrt(n),而<sqrt(n)的质因子是可以状压的。
f[s1][s2]表示a选的集合恰好为s1,b选的集合恰好为s2的方案数,然后就想不到了。
把所有数按照最大质因子分类,每一类分别转移。
令dp[0/1][s1][s2]表示a/b选这个集合的数,a目前的集合是s1,b目前的集合是s2的方案数。
dp[0][s1][s2]=dp[1][s1][s2]=f[s1][s2]
枚举这一类的数
s2&s=0: dp[0][s1|s][s2]+=dp[0][s1][s2]
s1&s=0: dp[1][s1][s2|s]+=dp[1][s1][s2]

f [s1][s2]=dp[0][s1][s2]+dp[1][s1][s2]-f[s1][s2] 因为不选这类中的任何一个数在两类里都是合法的。

其实这道题相当于一个二进制的分组背包,一些物品分成一些组,把他们放到背包中,满足一些限制。
   
   
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll long long
#define inf 1e9
#define eps 1e-8
using namespace std;
int f[270][270],g[2][270][270];
bool mark[510];
int S[510],P[510],ss[510],t[270],q[510];
vector<int> vec[510];
int md,m;
const int mxs=256,sq=20;
inline void add(int &a,int b)
{
a+=b; if (a>=md) a-=md;
}
 
void solve()
{
//printf(":: %d %d %d\n",m,q[1],q[2]);
for (int s1=0;s1<mxs;s1++)
for (int s2=0;s2<mxs;s2++)
g[0][s1][s2]=g[1][s1][s2]=f[s1][s2];
for (int i=1;i<=m;i++)
{
int x=q[i];
for (int s1=mxs-1;s1>=0;s1--)
for (int s2=mxs-1;s2>=0;s2--)
{
if ((x&s2)==0) add(g[0][s1|x][s2],g[0][s1][s2]);
if ((x&s1)==0) add(g[1][s1][s2|x],g[1][s1][s2]);
}
 
}
for (int s1=0;s1<mxs;s1++)
for (int s2=0;s2<mxs;s2++)
{
f[s1][s2]=g[0][s1][s2]+g[1][s1][s2]-f[s1][s2];
if (f[s1][s2]>=md) f[s1][s2]-=md;
if (f[s1][s2]<0) f[s1][s2]+=md;
}/*
for (int s1=0;s1<4;s1++){
for (int s2=0;s2<4;s2++)
printf("%d ",f[s1][s2]);
printf("\n");}*/
}
 
int main()
{
int n,w=0;
scanf("%d%d",&n,&md);
for (int i=2;i<=n;i++)
{
if (!mark[i])
{
ss[++w]=i;
S[i]= i>sq?0:(1<<(w-1));
P[i]=i;
}
for (int j=1;ss[j]*i<=n;j++)
{
int p=ss[j],x=i*p;
mark[x]=1; S[x]=S[i]|S[p]; P[x]=P[i];
if (i%p==0) break;
}
}
for (int i=2;i<=sq;i++) mark[i]=1;
t[0]=1;
for (int i=2;i<=n;i++)
if (P[i]<=sq)
{
vec[1].push_back(S[i]);
for (int j=mxs-1;j>=0;j--) add(t[j|S[i]],t[j]);
}
else vec[P[i]].push_back(S[i]);
//for (int i=2;i<=3;i++) printf("%d %d %d\n",mark[i],P[i],S[i]); printf("\n");
f[0][0]=1;
for (int i=1;i<=n;i++)
if (!mark[i])
{
if (i==1)
{
for (int j=vec[i].size()-1;j>=0;j--)
{
m=1; q[1]=vec[i][j]; solve();
}
}
else
{
m=0;
for (int j=vec[i].size()-1;j>=0;j--) q[++m]=vec[i][j];
solve();
}
}
int ans=0;
for (int s1=0;s1<mxs;s1++)
for (int s2=0;s2<mxs;s2++)
add(ans,f[s1][s2]);
printf("%d\n",ans);
return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值