2064: 分裂
Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 652 Solved: 401
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
3 1 2 3
Sample Output
數據範圍:
對於100%的數據,n1,n2<=10,每個數<=50
對於30%的數據,n1,n2<=6,
HINT
Source
又是一道思路好題... 這道題有用到了一種分治的思想, 將一個問題分成幾個小問題.
設原集合爲s1, 目標集合爲s2. 首先我們知道, 最壞情況下只需要合併n + m - 2次. 只需要先n-1合併成一個大數, 再m-1次分裂即可. 但是顯然如果s1有個子集和s2有個子集和相等, 那麼那麼就可以將s1拆成這個子集和這個子集的補集, 然後再按最壞情況分裂, 這就是n + m - 4. 同理, 如果s1有k個集合對應s2的k個集合, s1的這k個子集分別分裂合併成對應的k個s2子集, 也就成了s2了, 同理只需要n + m - 2 * k次. 那麼我們一個貪心思路就是把s1分成儘量多的集合和s2配對.
先姑且不討論貪心的正確性, 我們討論一下怎麼算最多有多少個集合能夠配對. 設f[i]表示爲s1, s2分別選取某些數最多能配對多少. 爲什麼只用i一個二進制數就能表示?只需要把s2的狀態放到s1之後就可以了. 那麼轉移的話就是f[i] = max(f[i], f[i ^ pw[j - 1]]); 就是s1或者s2你拿走一個數之後最多能配對多少. 爲什麼不用分開枚舉s1, s2的子集來轉移呢? 因爲假設sum[s1] == sum[s2]的話, 假設最大匹配k, 我拿走一個元素一定匹配數-1. 那麼也就是說我可以從一個拿走當前一個數的狀態轉移過來然後+1(前提是sum[s1] == sum[s2], 很明顯只要相等一定能再增1對). 這裏將s2設爲負數只有sum[i]爲0就說明當前兩集合選擇的數sum相等.
那麼貪心的正確性呢? 我們發現貪心下的最優狀態一定是分成了最多k個子集. 那麼每個集合變化成對應配對的集合就需要這個子集大小+對應子集大小-2. 有沒有可能更少呢. 現在我們已經分成了k個子問題(即子集), 要保證每個子問題最優即可. 考慮一個子問題. 假設某個子集ss大小爲p, 對應s2裏的子集gg的大小爲q. 最壞情況是需要p+q - 2次操作. 現在我們來證明這也是最優的操作. 我們原來最壞情況下的操作是先合併再分裂. 此時我們可以想象可能最優方法可能是合併分裂合併分裂合併合併夾雜. 那麼爲了證明此時最壞就是最優, 我們需要簡化情況--有沒有可能合併分裂合併分裂 等價於 合併合併.... 分裂分裂...即先合併再分裂, 像最壞情況那樣呢? 是的. 因爲比如說我先把a分裂成a1, a2, 再把a2和b合併. 這樣的操作等價於合併a和b, 再分離成a1和a2+b, 操作數相等. 我們現在已經知道操作順序相鄰之間分裂可以與之後的合併互換. 那麼一個合併分裂合併合併分裂合併的操作序列一定可以轉化成合併合並... 分裂分裂即先合併再分裂的序列. 那麼任意操作序列都可以變成最壞情況下的操作模式. 那麼我們假設最優操作序列比我們最壞情況優, 肯定是合併次數少(因爲要變成的都是gg, 大小一樣, 合併次數少也就意味着分裂次數少). 那麼爲什麼合併會少呢? 這就說明合併到某個時候沒必要再合併了. 我們設最優情況不必再合併準備開始分裂的時候, 剩餘沒合併過得數的集合爲s3 .因爲只能分裂, 這就說明了s3的和等於gg裏的某個子集. 但是我們已經保證當前全局最優情況已經分的不能再分, 這個s1的子集ss不可能再有子集s3與gg裏的子集相等, 不然還可以再分裂. 所以說最優情況只能一直合併到只剩一個數... 這就是最壞情況的方式啊?? 所以說貪心正確.
整道題的思路: 考慮最壞情況 -> 發現子集對應相等可以減少次數 -> 考慮貪心分成儘可能多的子集 -> 發現貪心正確必須要保證每個子集分裂最優 -> 證明貪心, 發現就是最壞情況的合併次數 -> 貪心正確, 證得最優答案就是n + m - 2 * k (k爲最大配對數) -> 狀壓求解.
#include<stdio.h>
#include<algorithm>
using namespace std;
int n, m, num, tmp, f[1 << 21], sum[1 << 21], pw[21];
int main() {
register int i, j;
pw[0] = 1;
for (i = 1; i <= 20; ++ i) pw[i] = pw[i - 1] << 1;
scanf("%d", &n);
for (i = 1; i <= n; ++ i) scanf("%d", &sum[pw[i - 1]]);
scanf("%d", &m);
for (i = 1; i <= m; ++ i){
scanf("%d", &sum[pw[n + i - 1]]);
sum[pw[n + i - 1]] = - sum[pw[n + i - 1]];
}
num = pw[n + m] - 1;
for (i = 1; i <= num; ++ i) {
tmp = i & -i;
sum[i] = sum[tmp] + sum[i ^ tmp];
for (j = 1; j <= n + m; ++ j)
if (i & pw[j - 1])
f[i] = max(f[i], f[i ^ pw[j - 1]]);
if (!sum[i]) f[i] ++;
}
printf("%d\n", n + m - (f[num] << 1));
return 0;
}