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

bzoj2064分裂(dp)

題目大意:
給定一個初始集合和目標集合,有兩種操作:1.合併集合中的兩個元素,新元素為兩個元素之和 2.分裂集合中的一個元素,得到的兩個新元素之和等於原先的元素。要求用最小步數使初始集合變為目標集合,求最小步數。

其中初始集合和目標集合的元素個數都不超過10個

這是一道非常值得紀念的好題

首先一看到這個資料範圍,第一反應就是狀壓dp了

我們首先這麼考慮

如果說直接暴力的合併和分裂的話,最多需要的次數是\(n+m-2\)次,那麼我們是不是可以在這個基礎上進行優化呢。舉個例子來看\(1,5,7,2\)-->\(2,4,3,6\)那麼我們完全可以把\(1,5\)單獨處理,不用將他們再跟\(2,7\)

合併了,也不用額外的刪除,那麼這樣其實就是相當於分組了。

那我們對於整體的集合,分成幾個小集合,每個小集合內部又可以做同樣的優化,這不就是dp的最優子結構嗎

假設初始和目標都可以分成k組,那麼答案應該就是\(n-m-2\times k\)了這裡應該沒有問題吧。

我們令\(f[i][j]\)表示初始集合中選擇的元素集合是\(i\),目標集合是\(j\)的分成的組數的最大值

對於\(f[i][j]\)我們先令他等於能轉移到\(f[i][j]\)的狀態中的最大值,如果當前的\(sa[i]==sb[j]\)那麼就說明他可以自己單獨成一組,就可以++

但這裡其實有一個問題就是,當\(i\)\(j\)

\(sum\)不相同的時候,其實也有可能會++但是我們不考慮,這裡有一種理解方式,是的,實際上就是因為這中間存在很多狀態的合成,我們把無用的合成的狀態忽略,卻不會忽略用來合成它的狀態,所以最後答案不會遺漏最優情況,換句話說,可以理解為就是每個不相等的情況 一定可以被不相等到相等再到不相等。其實本質是一樣的

直接看程式碼吧

感覺這個題還是不太好理解呀

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>

using namespace std;

inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

const int maxn = 11;

int n,m;
int sa[1 << maxn],sb[1 << maxn];
int f[1 << maxn][1 << maxn];
int a[maxn],b[maxn];

int counta(int x)
{
    int cnt=0;
    for (int i=1;i<=n;i++)
    if (x & (1 << (i-1))) cnt+=a[i];
    return cnt;
}

int countb(int x)
{
    int cnt=0;
    for (int i=1;i<=m;i++)
    if (x & (1 << (i-1))) cnt+=b[i];
    return cnt;
}

int main()
{
  scanf("%d",&n);
  for (int i=1;i<=n;i++) scanf("%d",&a[i]);
  m=read();
  for (int i=1;i<=m;i++) b[i]=read();
  for (int i=1;i<(1 << n);i++) sa[i]=counta(i);
  for (int i=1;i<(1 << m);i++) sb[i]=countb(i);
  for (int i=1;i<(1 << n);i++)
    for (int j=1;j<(1 << m);j++)
    {
        for (int k =1;k<=n;k++)
        {
            int p = 1 << (k-1);
            if (p&i) f[i][j]=max(f[i][j],f[i-p][j]);
        }
        for (int k=1;k<=m;k++)
        {
            int p = 1 << (k-1);
            if (p&j) f[i][j]=max(f[i][j],f[i][j-p]);
        }
        if (sa[i]==sb[j]) f[i][j]++;
    }
  printf("%d",n+m-2*f[(1<<n)-1][(1<<m)-1]);
  return 0;
}