1. 程式人生 > 其它 >洛谷 P7718 -「EZEC-10」Equalization(差分轉化+狀壓 dp)

洛谷 P7718 -「EZEC-10」Equalization(差分轉化+狀壓 dp)

差分轉化+狀壓 dp,interesting tea

洛谷題面傳送門

一道挺有意思的題,現場切掉還是挺有成就感的。

首先看到區間操作我們可以想到差分轉換,將區間操作轉化為差分序列上的一個或兩個單點操作,具體來說我們設 \(b_i=a_{i+1}-a_i\),那麼對於一次形如 \(\forall i\in[l,r],a_i\leftarrow a_i+x\) 的操作三元組 \((l,r,x)\),我們有:

  • \(l=1,r=n\),等於啥也沒幹,那麼我們顯然不會選擇這樣的區間進行操作,否則就會浪費一次操作次數,所以我們 duck 不必考慮這種情況。
  • \(l=1,r\ne n\):相當於令 \(b_{r}\leftarrow b_r-x\)
  • \(l\ne 1,r=n\)
    :相當於令 \(b_{l-1}\leftarrow b_{l-1}+x\)
  • \(l\ne 1,r\ne n\):相當於令 \(b_{l-1}\leftarrow b_{l-1}+x,b_r\leftarrow b_r-x\)

於是問題轉化為:給定長度為 \(n-1\) 的差分序列,每次可以進行以下兩種操作之一:修改一個單點的值,或者指定三個整數 \(x,y,z\),將 \(b_x\) 改為 \(b_x+z\)\(b_y\) 改為 \(b_y-z\),最少需要多少次操作才能將所有數變為 \(0\)

直接處理不太容易,因此考慮挖掘一些性質。做過這道題的同學應該不難想到,我們可以在每次操作的兩個點之間連一條邊,如果咱們操作的是一個單點那麼就連一條自環,這樣顯然會得到一張點數為 \(n-1\)

,邊數等於操作次數的圖。那麼有一個性質:在最優策略連出的圖中,對於每個連通塊 \((V',E')\),記 \(S=\sum\limits_{x\in V'}b_x\),有

  • 如果 \(S=0\),那麼該連通塊必然是一棵樹,耗費 \(|V'|-1\) 次操作。
  • 如果 \(S\ne 0\),那麼該連通塊必然是一棵樹加一個自環,耗費 \(|V'|\) 次操作。

證明不會,感性理解一下即可

觀察到這個性質之後,求解第一問就易如反掌了,注意到 \(n\) 很小,因此考慮狀壓,記 \(dp_S\) 表示將 \(S\) 中的元素變為 \(0\) 的最少操作次數,列舉子集轉移即可,複雜度 \(3^{n-1}\)

接下來考慮第二問,顯然每次操作都是不同的,因此我們可以只用考慮操作方案的集合有哪些,最後乘個操作次數的階乘即可。其次,如果我們能夠知道對於一個連通塊而言,將它按照最優策略變為 \(0\) 的方案數,我們就能用乘法原理一邊 DP 一遍記錄將每個集合變為 \(0\) 的最優策略的方案數了。因此考慮將每個集合變為 \(0\) 的方案數,還是分 \(S=0\)\(S\ne 0\) 兩種情況處理:

  • \(S=0\),那麼該連通塊是一棵無根樹,那麼可以很自然地猜到應該跟什麼有標號樹計數有關,Prufer 序列算一算結果是 \(|V'|^{|V'|-2}\),事實上結論也的確如此,這裡稍微口胡一下證明:顯然一個操作集合唯一對應一棵樹,而對於一棵無根樹而言,能夠得到它的操作集合也是唯一的,證明可以通過構造方案說明:我們假設這棵樹中編號最大(其實大不大無所謂,只要形成一個嚴格的偏序關係即可)的葉子節點為 \(u\),與其相連的點為 \(v\),那麼我們必須讓 \(u\) 的權值變為 \(0\),因為否則進行完此次操作之後,點 \(u\) 就孤立了,無法再次通過兩點的操作變回 \(0\) 了,因此這次操作 \(u\) 的權值必須減去 \(a_u\)\(v\) 的權值也就必然加上 \(a_u\),如此進行下去直到還剩一個點為止,而由於該連通塊中權值之和為 \(0\),因此最終剩下的那個點權值也是 \(0\),故我們構造出的方案合法。又因為我們每一次操作唯一,因此操作集合唯一;如果我們改變下操作的邊集的順序那麼顯然操作集合不會變,因此操作集合與無根樹形成雙射關係,證畢。
  • \(S\ne 0\),其實不過是在無根樹的基礎上加了一個自環,加這個自環的侯選位置總共有 \(|V'|\) 個,再加上對於這個自環而言有兩個區間能夠改變差分序列上這個點的值(假設我們要改變 \(b_x\),那麼我們可以操作 \([1,x]\),也可以操作 \([x+1,n]\)),因此還需乘個 \(2\),總方案數 \(2|V'|^{|V'|-1}\),如果你力求極致、追求嚴謹,那麼也可以仿照 \(S=0\) 的證明方式。

時間複雜度 \(\mathcal O(3^{n-1})\)

using namespace fastio;
const int MAXN=18;
const int MAXP=1<<17;
const int MOD=1e9+7;
const int INF=0x3f3f3f3f;
int n,a[MAXN+3],b[MAXN+3],m=0,c[MAXN+3],cnt[MAXP+5],fac[MAXN+5];
int f[MAXN+5],g[MAXN+5];
ll sum[MAXP+5];pii dp[MAXP+5];
inline int high(int x){return (!x)?-1:(31-__builtin_clz(x));}
int qpow(int x,int e){
	int ret=1;
	for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
	return ret;
}
void upd(pii &x,pii y,int v,int z){
	return (x.fi<y.fi+v)?void():(((x.fi==y.fi+v)?(x.se=(x.se+1ll*y.se*z)%MOD):
	(x.fi=y.fi+v,x.se=1ll*y.se*z%MOD)),void());
}
int main(){
	scanf("%d",&n);dp[0]=mp(0,1);
	for(int i=(fac[0]=1);i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD;
	for(int i=2;i<=n;i++) g[i]=qpow(i,i-2),f[i]=2ll*g[i]*i%MOD;f[1]=2;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<n;i++) b[i]=a[i+1]-a[i];
	for(int i=1;i<n;i++) if(b[i]) c[++m]=b[i];
	for(int i=1;i<(1<<m);i++) dp[i].fi=INF;
	for(int i=1;i<(1<<m);i++){
		int pos=32-__builtin_clz(i&(-i));
		sum[i]=sum[i&(i-1)]+c[pos];
		cnt[i]=cnt[i&(i-1)]+1;
	}
	for(register int i=0;i<(1<<m);i++){
		register int rst=((1<<m)-1)^i;
		for(register int j=rst;j;j=(j-1)&rst){
			if(high(j)<high(i)) break;
			(!sum[j])?upd(dp[i|j],dp[i],cnt[j]-1,g[cnt[j]]):
			upd(dp[i|j],dp[i],cnt[j],f[cnt[j]]);
		}
	} printf("%d\n%d\n",dp[(1<<m)-1].fi,1ll*dp[(1<<m)-1].se*fac[dp[(1<<m)-1].fi]%MOD);
	return 0;
}