1. 程式人生 > >[BZOJ2064]分裂

[BZOJ2064]分裂

題目連結:

BZOJ2064.

一道神奇的狀壓\(DP\)

首先,次數的上限很好計算,最多就是把\(n1\)的數全部合併,再拆成\(n2\)個數,上限即\(n1+n2-2\)

但是並不一定要全部合起來,假設兩個集合中各有子集相對應,和相等,那麼就可以對這個子集單獨處理,次數就可以\(-2\)(少合併,分裂一次)。

問題就轉化成了求最多有多少子集相匹配,若有\(x\)個則答案為\(n1+n2-2*x\)

那麼就可以用狀壓\(DP\)解決了,子集和可以預處理。

\(f_{[i][j]}\)表示初狀子集\(i\)和末態子集\(j\)的最大匹配。

第一種轉移是兩個子集和不同,則可以由前面的狀態轉移,從\(i\)

\(j\)中去掉一個,取\(Max\)

第二種是子集和相同,則\(f_{[i][j]}+1\)

時間複雜度 \(O(2^{n1+n2})\)

#include <cstdio>

inline int Max(int a,int b){return a>b?a:b;}

int n1,n2,s1[1<<10],s2[1<<10],f[1<<10][1<<10];

int main()
{
    scanf("%d",&n1);
    for(int i=0;i<n1;++i)scanf("%d",&s1[1<<i]);
    scanf("%d",&n2);
    for(int i=0;i<n2;++i)scanf("%d",&s2[1<<i]);
    for(register int i=0;i<(1<<n1);++i)//利用二進位制預處理子集和
        s1[i]=s1[i^(i&-i)]+s1[i&-i];
    for(register int i=0;i<(1<<n2);++i)
        s2[i]=s2[i^(i&-i)]+s2[i&-i];
    for(register int i=1;i<(1<<n1);++i)
        for(register int j=1;j<(1<<n2);++j)
        {
            for(register int k=0;k<n1||k<n2;++k)
            {
                if(i>>k&1)f[i][j]=Max(f[i][j],f[i^(1<<k)][j]);
                if(j>>k&1)f[i][j]=Max(f[i][j],f[i][j^(1<<k)]);
            }
            if(s1[i]==s2[j])++f[i][j];
        }
    printf("%d\n",n1+n2-(f[(1<<n1)-1][(1<<n2)-1]<<1));
    return 0;
}