Codeforces1286F Harry The Potter【轉換 + 子集卷積】

題目描述:

在這裏插入圖片描述
n20,ai1015n\le20,|a_i|\le10^{15}

題目分析:

將操作2看做一條邊,如果最後的方案連出了一個環,那麼將這個環上所有的邊換成操作1不會更劣。

所以最優方案一定是操作二連成森林,然後剩下的點用操作一。

因爲一棵樹可以減少1次操作,所以我們想要分出儘量多的連通塊。

考慮怎麼判斷集合SS能否使用S1S-1操作二清零:

隨便選一個點作根,假設每條邊兩端減掉的值都是xx,從葉子開始依次減,那麼減到根的時候將根此時的權值用變量表示,那麼與根深度奇偶性相同的點的變量帶的符號就是正號,相異的就是負號:
在這裏插入圖片描述
我們希望根節點的值爲0,那麼就是 奇數深度節點的權值和 = 偶數深度節點的權值和。

然後考慮每條邊可以給兩端的任意一端+1,總共有S1S-1條邊可以用,那麼SS集合可以用操作二清零的條件就是可以將SS分爲兩個非空集合A,BA,B,滿足abs(sum[A]sum[B])<S  sum[S]S1abs(sum[A]-sum[B])<|S|~且~sum[S]的奇偶性與S-1相同

判斷一個集合SS是否滿足上面的條件,可以O(3n)O(3^n),但是太慢。問題相當於是將SS中的數帶上正負號,考慮折半然後合併。設S的左半部分的所有狀態爲sl[]sl[],右半部分的所有狀態爲sr[]sr[],因爲只需要判斷是否存在解,所以先將slslsrsr分別排序(這個可以在求的時候排好),然後設一個指針RR指向srsr的最右端,LL指向slsl的最左端,如果sl[L]+sr[R]<(S1)sl[L]+sr[R]<-(S-1),那麼L++L++,知道滿足條件後,forfor循環LL判斷是否滿足sl[L]+sr[R]<S1sl[L]+sr[R]<S-1。(這一部分結合代碼理解一下)

這樣對於一個狀態SScheckcheck的複雜度就是O(2S2)O(2^{\frac {|S|}2}),求個和就是官方題解中的(1+2)n(1+\sqrt 2)^n

然後考慮怎麼求答案,官方題解的做法是:如果SS可行,那麼令AS=1A_S=1,然後對AApp次子集卷積,如果ApA^{p}中存在某一位不爲0,說明存在一種分pp個集合的方案,於是要做的就是找到最小的pp滿足ApA^p中所有位都爲0,最終的答案就是n(p1)n-(p-1)

求這個pp可以考慮倍增,如果超過了就退,這樣複雜度是O(2nn2logn)O(2^n*n^2\log n)的,還有點常數。

這樣做顯然比較麻煩,我們考慮暴力的子集卷積是O(3n)O(3^n)的,但是其實完全沒有必要,對於一個由多個可行集合組成的SS,我們只需要在它最小的那個可行子集處更新它。所以只有當集合TT是可行集合,且不能由其它可行集合組合而成時,我們用它去更新f[TT]f[T\in T']。這樣剪枝之後雖然複雜度沒有什麼保證,但是實際運行效果非常好,Codeforces上目前最快的寫法大都是這樣寫的。

Code:

#include<bits/stdc++.h>
#define maxn 20
#define LL long long
using namespace std;
const int N = 1<<20|5;
int n,f[N];
LL a[maxn];
LL b[maxn],sl[N],sr[N];
void getque(LL *s,int l,int r){
	int m=1; s[1]=0;
	static LL pos[N],neg[N];
	for(int i=l;i<=r;i++,m<<=1){
		for(int j=1;j<=m;j++) pos[j]=s[j]+b[i],neg[j]=s[j]-b[i];
		for(int x=1,y=1,k=1;k<=m<<1;k++)
			s[k]=x>m?neg[y++]:y>m?pos[x++]:pos[x]<neg[y]?pos[x++]:neg[y++];
	}
}
bool check(int S){
	int sz=0; LL sum=0;
	for(int i=1;i<=n;i++) if(S>>i-1&1) sum+=a[i],b[++sz]=a[i];
	if(sum-(sz-1)&1) return 0;
	getque(sl,1,sz/2),getque(sr,sz/2+1,sz);
	int L=1<<(sz/2),R=1<<(sz-sz/2),need=1+(abs(sum)<sz)*2;
	for(int i=R,j=1;i>=1;i--){
		while(j<=L&&sl[j]+sr[i]<=-sz) j++;
		for(int k=j;k<=L&&need&&sl[k]+sr[i]<sz;k++) need--;
	}
	return !need;
}
int main()
{
	scanf("%d",&n); int m=0;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),a[i]&&(a[++m]=a[i]);
	n=m; int all=(1<<n)-1;
	for(int s=1;s<=all;s++) if(!f[s]&&check(s)){//!f[s] can cut tons of situations.
		int r=all^s; f[s]=1;
		for(int t=r;t;--t&=r) f[s|t]=max(f[s|t],f[t]+1);
	}
	printf("%d\n",n-f[all]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章