1. 程式人生 > >bzoj2064: 分裂(狀壓dp)

bzoj2064: 分裂(狀壓dp)

壓力 如何 using esp 二進制表示 res rip emp urn

Description

背景: 和久必分,分久必和。。。 題目描述: 中國歷史上上分分和和次數非常多。。通讀中國歷史的WJMZBMR表示毫無壓力。 同時經常搞OI的他把這個變成了一個數學模型。 假設中國的國土總和是不變的。 每個國家都可以用他的國土面積代替, 又兩種可能,一種是兩個國家合並為1個,那麽新國家的面積為兩者之和。 一種是一個國家分裂為2個,那麽2個新國家的面積之和為原國家的面積。 WJMZBMR現在知道了很遙遠的過去中國的狀態,又知道了中國現在的狀態,想知道至少要幾次操作(分裂和合並各算一次操作),能讓中國從當時狀態到達現在的狀態。

Input

第一行一個數n1,表示當時的塊數,接下來n1個數分別表示各塊的面積。 第二行一個數n2,表示現在的塊,接下來n2個數分別表示各塊的面積。

Output

一行一個數表示最小次數。

Sample Input

1 6
3 1 2 3

Sample Output

2
數據範圍:
對於100%的數據,n1,n2<=10,每個數<=50
對於30%的數據,n1,n2<=6,
這狀壓的思路真是神仙啊…… 首先,最壞的情況就是把上面的合成一堆,然後再分成下面的,於是次數為$n+m-2$ 然後考慮如何減少次數。如果上面的可以被合成2塊,下面的也可以合成2塊,且上下兩塊的值分別相等,那麽就可以減少一次合並和一次分開,總次數減少了2 於是發現如果上下可以被分成值相等的$k$塊,那麽總的次數是$n+m-2*k$
然後現在就是要求$k$的最大值了 我們用二進制表示某一個數選或不選。如果一個數是上面的,令它值為正,下面的則值為負 然後如果上面的一塊和下面的一塊值對應相等的話,就是所有的值加起來為0 這樣如果某一個子集的和為0,就說明它可以互相變化 於是令$dp[i]$表示選的狀態為$i$時的最大的$k$,因為只有在$sum[i]==0$時,$dp[i]++$,其他狀態都只能直接轉移
 1 //minamoto
 2 #include<bits/stdc++.h>
 3 using namespace std;
 4 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
 5
char buf[1<<21],*p1=buf,*p2=buf; 6 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;} 7 inline int read(){ 8 #define num ch-‘0‘ 9 char ch;bool flag=0;int res; 10 while(!isdigit(ch=getc())) 11 (ch==-)&&(flag=true); 12 for(res=num;isdigit(ch=getc());res=res*10+num); 13 (flag)&&(res=-res); 14 #undef num 15 return res; 16 } 17 const int N=2e6+5; 18 int n,m,sum[N],dp[N],lim; 19 int main(){ 20 // freopen("testdata.in","r",stdin); 21 n=read();for(int i=1;i<=n;++i) sum[1<<(i-1)]=read(); 22 m=read();for(int i=1;i<=m;++i) sum[1<<(i+n-1)]=-read(); 23 lim=1<<(n+m); 24 for(int i=1;i<lim;++i){ 25 int tmp=i&-i;sum[i]=sum[tmp]+sum[i-tmp]; 26 for(int j=1;j<=n+m;++j) 27 if(i&(1<<(j-1))) cmax(dp[i],dp[i-(1<<(j-1))]); 28 if(!sum[i]) ++dp[i]; 29 } 30 printf("%d\n",n+m-2*dp[lim-1]); 31 return 0; 32 }

bzoj2064: 分裂(狀壓dp)