題目大意:兩個人從2~n中隨意取幾個數(不取也算作一種方案),被一個人取過的數不能被另一個人再取。兩個人合法的取法是,其中一個人取的任何數必須與另一個人取的每一個數都互質,求所有合法的方案數
(數據範圍畢竟很小,乍一看也不是啥打表找規律的題)
和我之前做過的一道題很類似hdu 6125,但這道題由於題面看起來很玄學,所以正解更難想
但還是 狀壓DP+分組揹包 的套路
因爲500以內的任何一個數,只會有一個大於19的質因子,所以對2 3 5 7 11 13 17 19這8個質數進行狀壓,然後每個數都質因數分解,把小於等於19的質因子存入狀態,剩下的因子分組揹包搞搞就行了,注意如果剩下的因子是1要單獨算一組,否則會出大事情,比如2和3並不是同一組的,如果再來一個4,和2是同一組的,轉移就會出錯
具體DP的實現呢,定義是第一個人取了狀態爲s1的數,第二個人取了狀態爲s2的數
分組揹包要把同一組的東西放到連續的一段序列上
對於這道題而言,如果某個人取了某一組的任何一個,那麼這一組的其它物品也只能被這個人取/不取
所以額外定義兩個狀態f1,f2,含義和dp的意義是一樣的,只不過在同一組內是f1和f2這兩個狀態自己和自己轉移,然後把答案貢獻給dp,即這一組對整體的貢獻,然後把dp重新賦給f1,f2,再進行下一組揹包
方程
由於f1,f2爲了下一層轉移,都被加了一次dp值,所以最後要減掉一個dp
即
而f1,f2轉移也有技巧,常規的自己和自己轉移爲了避免傳遞性,要另外開一個數組進行轉移。但因爲這道題的轉移方程都是位與|操作,具有遞增性,所以倒序枚舉就可以減少一些常數
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ui unsigned int
#define ll long long
#define il inline
#define N (1<<8)+3
#define inf 0x3f3f3f3f
using namespace std;
int n;
ll p;
ll f1[N][N],f2[N][N],dp[N][N];
int pr[]={2,3,5,7,11,13,17,19};
struct node{
int w,f;
friend bool operator < (const node &a,const node &b){
if(a.w!=b.w) return a.w<b.w;
else return a.f<b.f;
}
}s[N];
void get_son()
{
for(int i=2;i<=n;i++)
{
int x=i;
for(int j=0;j<8;j++)
{
if(x%pr[j]==0) x/=pr[j],s[i].f|=(1<<j);
while(x%pr[j]==0) x/=pr[j];
}s[i].w=x;
}
}
int main()
{
scanf("%d%lld",&n,&p);
get_son();
sort(s+2,s+n+1);
f1[0][0]=f2[0][0]=dp[0][0]=1;
int m=(1<<8)-1;
for(int i=2;i<=n;i++)
{
for(int s1=m;s1>=0;s1--)
for(int s2=m;s2>=0;s2--){
if(!((s1|s[i].f)&s2)) f1[s1|s[i].f][s2]=(f1[s1|s[i].f][s2]+f1[s1][s2])%p;
if(!(s1&(s2|s[i].f))) f2[s1][s2|s[i].f]=(f2[s1][s2|s[i].f]+f2[s1][s2])%p;}
if(s[i].w==1||s[i+1].w!=s[i].w)
for(int s1=m;s1>=0;s1--)
for(int s2=m;s2>=0;s2--)
f1[s1][s2]=f2[s1][s2]=dp[s1][s2]=(f1[s1][s2]+f2[s1][s2]-dp[s1][s2]+p)%p;
}
ll ans=0;
for(int s1=m;s1>=0;s1--)
for(int s2=m;s2>=0;s2--)
ans+=dp[s1][s2],ans%=p;
printf("%lld\n",ans);
return 0;
}