NOI 2015 壽司晚宴 (狀壓DP+分組揹包)

題目大意:兩個人從2~n中隨意取幾個數(不取也算作一種方案),被一個人取過的數不能被另一個人再取。兩個人合法的取法是,其中一個人取的任何數必須與另一個人取的每一個數都互質,求所有合法的方案數

(數據範圍畢竟很小,乍一看也不是啥打表找規律的題)

和我之前做過的一道題很類似hdu 6125,但這道題由於題面看起來很玄學,所以正解更難想

但還是 狀壓DP+分組揹包 的套路

因爲500以內的任何一個數,只會有一個大於19的質因子,所以對2 3 5 7 11 13 17 19這8個質數進行狀壓,然後每個數都質因數分解,把小於等於19的質因子存入狀態,剩下的因子分組揹包搞搞就行了,注意如果剩下的因子是1要單獨算一組,否則會出大事情,比如2和3並不是同一組的,如果再來一個4,和2是同一組的,轉移就會出錯

具體DP的實現呢,定義dp[s1][s2]是第一個人取了狀態爲s1的數,第二個人取了狀態爲s2的數

分組揹包要把同一組的東西放到連續的一段序列上

對於這道題而言,如果某個人取了某一組的任何一個,那麼這一組的其它物品也只能被這個人取/不取

所以額外定義兩個狀態f1,f2,含義和dp的意義是一樣的,只不過在同一組內是f1和f2這兩個狀態自己和自己轉移,然後把答案貢獻給dp,即這一組對整體的貢獻,然後把dp重新賦給f1,f2,再進行下一組揹包

方程f1[s1|K][s2]+=f1[s1][s2]   f2[s1][s2|K]+=f2[s1][s2]

由於f1,f2爲了下一層轉移,都被加了一次dp值,所以最後要減掉一個dp

dp[s1][s2]=f1[s1][s2]+f2[s1][s2]-dp[s1][s2]

而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;
}


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章