倉鼠的DP課 學習筆記


Part1 一點雜題

agc034_e Complete Compress

題目鏈接

枚舉最終這些棋子被移到了哪個節點,把這個終點拿出來作為根\(root\)

我們一次操作一定是把兩個棋子各向根移動一步,這需要這兩個棋子不是“祖先-后代”的關系。則一個節點\(u\)需要操作的次數是\(dis(u,root)\)。我們把每個初始時有棋子的節點\(u\)看做\(dis(u,root)\)個小石子。考慮根的每個“兒子的子樹”。我們一次操作可以選擇兩個不同的子樹,然后把它們的總石子數各減少\(1\)(這樣可以保證選擇的兩個石子所在的節點不是“祖先-后代”關系)。目標是要讓每個兒子的總石子數都為\(0\)

這就涉及到一個經典的模型。有\(n\)堆石子,每堆石子有\(a_i\)個。每次可以選擇兩個不同的堆,同時取走一枚石子。問能否通過若干次操作取完所有石子(即讓每堆的石子總數都變為\(0\))。設所有堆的石子總數為\(sum\),最大的那一堆的石子數為\(max\)

  • \(max>sum-max\)時,顯然無論怎么操作都無法取完所有石子。因為最大的那一堆一定會剩下\(max-(sum-max)\)個石子。
  • \(max\leq sum-max\)時,有方法可以取完所有石子(或者在\(sum\)為奇數時讓石子只剩\(1\)個),構造如下:把所有石子排成一排,同一堆內的石子放在連續的一段。我們把石子按如下方法兩兩配對:\((1,1+\lfloor\frac{sum}{2}\rfloor),(2,2+\lfloor\frac{sum}{2}\rfloor),\dots,(\lfloor\frac{sum}{2}\rfloor,\lfloor\frac{sum}{2}\rfloor+\lfloor\frac{sum}{2}\rfloor)\)。顯然,因為沒有一堆石子的數量超過\(\lfloor\frac{sum}{2}\rfloor\),所以每一對石子都來自不同的堆。

在本題中,取一個石子就相當於向根走一步,因此,在第二種情況下,一定能把所有石子都移動到根。

考慮第一種情況,此時\(max\)的子樹內,石子太多了,我們要讓它內部消化掉一些。於是我們可以遞歸考慮\(max\)這個節點的所有兒子的子樹。重復上面所描述的判斷。

具體地,我們記一個\(f(u)\)表示在\(u\)的子樹內,最多能進行多少次“把兩個棋子同時向上移”的操作,也即最多能消掉多少對石子。顯然,實際上我們可以選擇進行\([0,f(u)]\)之間的任意次操作。

在以一個節點\(u\)為根的子樹內,求出每個兒子的石子數。

  • \(max>sum-max\)時,設最大的那個兒子為\(v\),則\(f(u)=sum-max+\min(f(v),2max-sum)\)
  • \(max\leq sum-max\)時,\(f(u)=\lfloor\frac{sum}{2}\rfloor\)

如果根節點處的\(sum\)是偶數,且\(f(root)=\frac{sum}{2}\),則有解,否則以\(root\)為根時無解。有解時,答案對\(f(root)\)\(\min\)

這個DP是\(O(n)\)的。因為要枚舉根。故總時間復雜度\(O(n^2)\)

參考代碼:

//problem:agc034_e
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=2000,INF=1e9;
int n,s[MAXN+5],num[MAXN+5],f[MAXN+5];
char str[MAXN+5];
vector<int>G[MAXN+5];
void dfs(int u,int fa){
	int son=0;
	s[u]=0;num[u]=(str[u]=='1');
	for(int i=0;i<(int)G[u].size();++i){
		int v=G[u][i];
		if(v==fa)continue;
		dfs(v,u);
		num[u]+=num[v];
		s[v]+=num[v];
		s[u]+=s[v];
		if(!son||s[v]>s[son]){
			son=v;
		}
	}
	if(!son){f[u]=0;return;}
	
	if(s[son]<=s[u]-s[son]){
		f[u]=s[u]/2;
	}
	else{
		f[u]=s[u]-s[son]+min(f[son],(s[u]-(s[u]-s[son]))/2);
	}
}
int main() {
	scanf("%d%s",&n,str+1);
	for(int i=1,u,v;i<n;++i)scanf("%d%d",&u,&v),G[u].pb(v),G[v].pb(u);
	int ans=INF;
	for(int rt=1;rt<=n;++rt){
		dfs(rt,0);
		if(s[rt]&1)continue;
		if(f[rt]>=s[rt]/2){
			ans=min(ans,s[rt]/2);
		}
	}
	if(ans==INF)puts("-1");
	else printf("%d\n",ans);
	return 0;
}

CF908G New Year and Original Order

題目鏈接

考慮每個數碼\(d(1\leq d\leq9)\)對答案的貢獻。

考慮朴素的數位DP。設\(dp[i][j][k][0/1]\)表示從高到低考慮了前\(i\)位,有\(j\)個等於\(d\)的數位,有\(k\)個大於\(d\)的數位,當前數是否\(=X\),這些條件下的方案數。轉移時枚舉下一位填什么數。DP的復雜度為\(O(n^3\cdot 10)\),考慮優化。

\(c(d)\)\(d\)對答案貢獻的系數,則\(c(d)\)形式上應該是\(\sum10^i\)。則\(ans=\sum_{d=1}^{9}d\cdot c(d)=\sum_{d=1}^{9}\sum_{i=d}^{9}c(i)\)。這是因為考慮每個\(c(i)\)會被計算\(i\)次,即在\(\leq i\)的每個\(d\)的位置都被計算到一次。

考慮枚舉\(d\),求\(\sum_{i=d}^{9}c(i)\)。這相當於求所有\(\geq d\)的數位的\(10^i\)之和。而按題目要求把數位排序后,\(\geq d\)的數位一定是從最低位開始的連續的一段。於是我們只要記錄\(\geq d\)的數位有多少個即可。設\(dp[i][j][0/1]\)表示考慮了前\(i\)位,\(\geq d\)的數位有\(j\)個的方案數。DP的復雜度變為\(O(n^2\cdot 10)\)。因為要枚舉\(d\),故總時間復雜度\(O(n^2\cdot 10\cdot 9)\)

參考代碼:

//problem:CF908G
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=700,MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}

char s[MAXN+5];
int n,dp[MAXN+5][MAXN+5][2];
int solve(int d){
	memset(dp,0,sizeof(dp));
	dp[0][0][1]=1;
	for(int i=0;i<n;++i){
		// dp[i] -> dp[i+1]
		for(int j=0;j<=i;++j){
			for(int t=0;t<=1;++t)if(dp[i][j][t]){
				for(int x=0;x<=(t?s[i+1]-'0':9);++x){
					add(dp[i+1][j+(x>=d)][t&&(x==(s[i+1]-'0'))],dp[i][j][t]);
				}
			}
		}
	}
	int cur=1,sum=0,res=0;
	for(int j=1;j<=n;++j){
		add(sum,cur);
		add(res,(ll)sum*mod1(dp[n][j][0]+dp[n][j][1])%MOD);
		cur=10LL*cur%MOD;
	}
	return res;
}
int main() {
	scanf("%s",s+1);n=strlen(s+1);
	int ans=0;
	for(int i=1;i<=9;++i){
		add(ans,solve(i));
	}
	printf("%d\n",ans);
	return 0;
}

agc024_f Simple Subsequence Problem

題目鏈接

因為所有可能的答案只有\(2^{n+1}-1\)種,故考慮求出每個長度\(\leq n\)的01串分別是多少個給定串的子序列。

考慮識別一個串\(A\)是不是另一個串\(B\)的子序列,我們可以做一個簡單的貪心。維護一個指向\(B\)的下標的“指針”\(p\),初始時是\(0\)。對於\(A\)的每一位,把\(p\)移到\(p\)后面第一個等於\(A\)的這一位的位置。

考慮用一個自動機來描述這個貪心的過程。自動機的一個節點\((S,T)\)表示當前已經匹配好的\(A\)的前綴是\(S\),剩下的部分是\(T\)的一個子序列。當接下來要匹配\(A\)的某一位時,如果這一位是1,則轉移到\((S+'1',T_1)\),否則轉移到\((S+'0',T_0)\)。其中\(T_c\)表示串\(T\)的第一個\(c\)后面的部分。如\(T=\)00110時,\(T_1=\)10\(T_0=\)0110

這條路徑的起點為\((\emptyset,B)\),終點為\((A,\emptyset)\)\(\emptyset\)表示空串)。

我們以所有題目給出的集合中的串為起點,就可以得到一張圖。因為在走的過程中\(S\)長度嚴格遞增,\(T\)長度嚴格遞減,所以一定不會走出環。因此這個圖是一個DAG。

又因為我們匹配的過程是基於“每次走到接下來第一個能匹配的位置”的貪心,因此從某個起點出發,識別一個串\(A\)時走的路徑是唯一的。也即從每個起點到點\((A,\emptyset)\),要么沒有路徑,要么有一條唯一的路徑

因此,我們可以在DAG上做DP。求出到每個終點的路徑數,就是這個串是多少個起點的子序列。

\(dp(S,T)\)表示走到DAG上\((S,T)\)這個節點的方案數。對於每個題目給出的集合中的串\(s\),初始化\(dp(\emptyset,s)=1\)。在具體實現中,我們開一個二維數組,\(dp[i][j]\),其中\(i\)表示\(T\)這個串的長度,\(j\)\(S+T\)這個串的二進制狀壓。因為\(S+T\)這個串可能有前導零,我們可以在最高位的下一位放一個\(1\),表示這里是最高位。

DP時先枚舉\(T\)的長度\(i\)(因為一定是從長到段轉移),再枚舉\(S+T\)的長度\(j(j\geq i)\),再枚舉\(S+T\)這個串\(k\)。則當前的狀態就是\(dp[i][k|(1<<j)]\)。轉移有三種:直接結束(走到節點\((S,\emptyset)\)),匹配到下一個\(1\),或匹配到下一個\(0\)

因為總狀態數是\(O(2^nn)\)的。轉移時找下一位\(0/1\)的復雜度為\(O(n)\),故總時間復雜度\(O(2^nn^2)\)(常數小可過)。如果預處理每個長度\(\leq n\)的二進制串的下一個\(0/1\)在哪一位,則可以優化到\(O(2^nn)\)

參考代碼:

//problem:agc024_f
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

/*  ------  by:duyi  ------  */ // myt天下第一
int n,K,dp[21][1<<21];
char s[(1<<20)+5];
int main() {
	scanf("%d%d",&n,&K);
	for(int i=0;i<=n;++i){
		scanf("%s",s);
		for(int j=0;j<(1<<i);++j)if(s[j]=='1'){
			dp[i][j|(1<<i)]=1;
		}
	}
	for(int i=n;i>=1;--i){//未確定部分的長度
		for(int j=i;j<=n;++j){//整個串的長度
			int b=(1<<j);
			for(int k=0;k<b;++k){
				int tmp=dp[i][k|b],p=-1,q=-1;if(!tmp)continue;
				for(int t=i-1;t>=0;--t)if(((k>>t)&1)==1){p=t;break;}
				for(int t=i-1;t>=0;--t)if(((k>>t)&1)==0){q=t;break;}
				dp[0][(k+b)>>i]+=tmp;
				if(p!=-1)dp[p][((((k>>i)<<1)|1)<<p)|(((1<<p)-1)&k)|(1<<(j-(i-p)+1))]+=tmp;
				if(q!=-1)dp[q][(((k>>i)<<1)<<q)|(((1<<q)-1)&k)|(1<<(j-(i-q)+1))]+=tmp;
			}
		}
	}
	for(int i=n;i>=0;--i){
		for(int j=0;j<(1<<i);++j){
			if(dp[0][j|(1<<i)]>=K){
				for(int k=i-1;k>=0;--k)if((j>>k)&1)putchar('1');else putchar('0');
				puts("");
				return 0;
			}
		}
	}
	assert(0);
}

nflsoj49 【清華集訓2017】某位歌姬的故事

題目鏈接-nflsoj49

題目鏈接-loj2331

對序列的每個位置\(i\),我們維護一個\(up_i\),表示這個位置最大能填幾。那么一個限制\((l,r,w)\),就相當於讓\(i\in[l,r]\)的每個\(up_i\)的值對\(w\)\(\min\)

當然,還可能有一些位置從始至終未被任何一個限制覆蓋到。記這樣的位置有\(cnt\)個,則我們最后把答案乘以\(A^{cnt}\)即可。

題目里的每個限制\((l,r,w)\),相當於如下兩條要求:

  • \(\forall i \in[l,r]\ a_i\leq w\)

  • \(\exist i\in[l,r]\ a_i=w\)

如果讓所有\(a_i\)做到\(a_i\leq up_i\),則第一條限制就已經滿足了。考慮如何滿足第二條限制。

考慮每個\(w\)。我們發現\(\exist i\in[l,r]\ a_i=w\)只能由\(up_i=w\)\(i\)來實現。因為\(up_i<w\)\(i\)顯然無法實現(否則與第一條要求矛盾);而\(up_i>w\)\(i\)說明這個位置根本沒被\([l,r]\)覆蓋到。

於是,我們枚舉每個\(w\),把\(up_i=w\)的這些點單獨拿出來做DP。設\(dp[i][j]\)表示考慮了前\(i\)\(up_i=w\)的位置,最后一個滿足\(a_i=w\)(取到了這個上界)的位置在\(j\)時的方案數。對於每個位置\(i\),我們可以維護一個\(L[i]\)表示它的上一個被覆蓋的位置最遠可以在哪。則從\(dp[i][j]\)轉移到\(dp[i+1]\)時,轉移有兩種:

  • \(j\geq L[i+1]\),則可以從\(dp[i][j]\)轉移到\(dp[i+1][j]\)
  • 在任何情況下,我們都能從\(dp[i][j]\)轉移到\(dp[i+1][i+1]\)

\(L[i]\):我們先初始化所有\(L[i]\)\(0\)。然后對於每個限制\((l,r,w)\),讓\(L[r]\)\(l\)\(\max\)即可。

將所有位置都離散化后,一次DP的時間復雜度為\(O(Q^2)\)。故總時間復雜度\(O(Q^3)\)

參考代碼:

//problem:nflsoj49
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,MAXN,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
#endif
inline int read(){
	int f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline ll readll(){
	ll f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=500,INF=0x3f3f3f3f,MOD=998244353;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

int len,n,A,a[MAXN*2+5],pos[MAXN*2+5],val[MAXN*2+5],cnt,cnt_w;
struct Limits{int l,r,m;}q[MAXN+5];
bool fail;
int solve(int w){
	static int p[MAXN*2+5],L[MAXN*2+5],dp[MAXN*2+5][MAXN*2+5];
	int cnt_p=0;
	for(int i=1;i<cnt;++i)if(a[i]==w)p[++cnt_p]=i,L[cnt_p]=0;
	for(int i=1;i<=n;++i)if(q[i].m==w){
		int l=lob(p+1,p+cnt_p+1,q[i].l)-p,
			r=lob(p+1,p+cnt_p+1,q[i].r)-p-1;
		if(l>r){fail=1;return 0;}
		L[r]=max(L[r],l);
	}
	memset(dp,0,sizeof(dp));
	dp[0][0]=1;
	for(int i=1;i<=cnt_p;++i){
		int v1=pow_mod(w,pos[p[i]+1]-pos[p[i]]),v2=pow_mod(w-1,pos[p[i]+1]-pos[p[i]]);
		for(int j=0;j<i;++j){
			if(j>=L[i])add(dp[i][j],(ll)dp[i-1][j]*v2%MOD);
			add(dp[i][i],(ll)dp[i-1][j]*mod2(v1-v2)%MOD);
		}
	}
	int res=0;
	for(int j=0;j<=cnt_p;++j)add(res,dp[cnt_p][j]);
	return res;
}
int main() {
	int Testcases=read();while(Testcases--){
		len=read();n=read();A=read();
		cnt=cnt_w=0;
		memset(a,0x3f,sizeof(a));
		for(int i=1;i<=n;++i){
			q[i].l=read(),q[i].r=read()+1,q[i].m=read();
			pos[++cnt]=q[i].l,pos[++cnt]=q[i].r,val[++cnt_w]=q[i].m;
		}
		pos[++cnt]=1;pos[++cnt]=len+1;
		sort(pos+1,pos+cnt+1);
		cnt=unique(pos+1,pos+cnt+1)-(pos+1);
		
		sort(val+1,val+cnt_w+1);
		cnt_w=unique(val+1,val+cnt_w+1)-(val+1);
		
		for(int i=1;i<=n;++i){
			q[i].l=lob(pos+1,pos+cnt+1,q[i].l)-pos;
			q[i].r=lob(pos+1,pos+cnt+1,q[i].r)-pos;
			for(int j=q[i].l;j<q[i].r;++j)a[j]=min(a[j],q[i].m);
		}
		fail=0;
		int ans=1;
		for(int i=1;i<=cnt_w;++i){
			ans=(ll)ans*solve(val[i])%MOD;
			if(fail)break;
		}
		if(fail){puts("0");continue;}
		for(int i=1;i<cnt;++i){
			if(a[i]==INF)ans=(ll)ans*pow_mod(A,pos[i+1]-pos[i])%MOD;
		}
		printf("%d\n",ans);
	}
	return 0;
}

Part2 笛卡爾樹DP

loj2688 「POI2015」洗車 Car washes

題目鏈接

先把權值離散化。

考慮原序列的笛卡爾樹(以最小值為根)。笛卡爾樹的每個子樹對應原序列的一個區間。本題中,我們用一個區間來表示一棵子樹,在寫法上類似於區間DP。對於笛卡爾樹上一個子樹\([l,r]\),設它的最小值所在位置為\(p\)(也就是子樹\([l,r]\)的根節點為\(p\)),最小值為\(v\),則我們在當前子樹上統計所有完全包含在本區間內,且經過根節點的車產生的貢獻。即\(l\leq a_i\leq p\leq b_i\leq r\)的這些車。把這個貢獻記為\(cost(l,r,p,v)\)

\(dp[l][r][v]\)表示整個\([l,r]\)子樹內,最小值為\(v\)時,產生的貢獻的最大值。轉移時枚舉最小值的位置\(p\),則:

\[dp[l][r][v]=\max_{p=l}^{r}\{dp[l][p-1][x\geq v]+dp[p+1][r][y\geq v]+cost(l,r,p,v)\} \]

可以發現,\(cost(l,r,p,v)\)就等於滿足\(l\leq a_i\leq p\leq b_i\leq r\)\(c_i\geq v\)的車的數量,乘以\(v\)。而這個數量可以做三維前綴和\(O(1)\)查詢。我們求完每個子樹\(dp[l][r][\dots]\)后,預處理出DP數組關於最后一維的后綴最大值,就可以對每個 \(p\) \(O(1)\) 計算了。

時間復雜度\(O(n^3m)\)

參考代碼:

//problem:loj2688
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,MAXN,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
#endif
inline int read(){
	int f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline ll readll(){
	ll f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=50,MAXM=4000;
int n,m,a[MAXM+5],b[MAXM+5],c[MAXM+5],val[MAXM+5],cnt,s[MAXN+5][MAXN+5][MAXM+5],f[MAXN+5][MAXN+5][MAXM+5];
pii g[MAXN+5][MAXN+5][MAXM+5];
inline int cost(int i,int p,int j,int v){
	/*
	i<=a<=p<=b<=j
	c>=v
	*/
	//int res=0;for(int t=1;t<=m;++t)if(i<=a[t] && a[t]<=p && p<=b[t] && b[t]<=j && c[t]>=v)res++;return res;
	return (s[p][j][cnt]-s[p][p-1][cnt]-s[i-1][j][cnt]+s[i-1][p-1][cnt])
			-(s[p][j][v-1]-s[p][p-1][v-1]-s[i-1][j][v-1]+s[i-1][p-1][v-1]);
}
struct node{
	int lv,rv,p;
	node(){}
	node(int _lv,int _rv,int _p){lv=_lv,rv=_rv,p=_p;}
}tr[MAXN+5][MAXN+5][MAXM+5];
int res[MAXN+5];
void get_ans(int l,int r,int v,int p){
	//cout<<l<<" "<<r<<" "<<v<<" "<<p<<endl;
	//assert(v);assert(p);
	res[p]=v;
	if(p!=l){
		get_ans(l,p-1,tr[l][r][v].lv,tr[l][p-1][tr[l][r][v].lv].p);
	}
	if(p!=r){
		get_ans(p+1,r,tr[l][r][v].rv,tr[p+1][r][tr[l][r][v].rv].p);
	}
}
int main() {
	//freopen("1.in","r",stdin);
	n=read();m=read();
	for(int i=1;i<=m;++i)a[i]=read(),b[i]=read(),c[i]=read(),val[i]=c[i];
	sort(val+1,val+m+1);cnt=unique(val+1,val+m+1)-(val+1);
	for(int i=1;i<=m;++i)c[i]=lob(val+1,val+cnt+1,c[i])-val,s[a[i]][b[i]][c[i]]++;//離散化
	
	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)for(int k=1;k<=cnt;++k)s[i][j][k]+=s[i][j][k-1];
	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)for(int k=1;k<=cnt;++k)s[i][j][k]+=s[i][j-1][k];
	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)for(int k=1;k<=cnt;++k)s[i][j][k]+=s[i-1][j][k];//三維前綴和
	
	for(int i=1;i<=n;++i){
		for(int j=1;j<=cnt;++j)f[i][i][j]=cost(i,i,i,j)*val[j],tr[i][i][j].p=i;
		for(int j=cnt;j>=1;--j)g[i][i][j]=max(g[i][i][j+1],mk(f[i][i][j],j));
	}
	for(int len=2;len<=n;++len){
		for(int i=1;i+len-1<=n;++i){
			int j=i+len-1;
			for(int p=i;p<=j;++p){
				for(int v=1;v<=cnt;++v){
					int l=(p==i?0:g[i][p-1][v].fst);
					int r=(p==j?0:g[p+1][j][v].fst);
					if(l+r+cost(i,p,j,v)*val[v]>=f[i][j][v]){
						f[i][j][v]=l+r+cost(i,p,j,v)*val[v];
						int _l=(p==i?0:g[i][p-1][v].scd);
						int _r=(p==j?0:g[p+1][j][v].scd);
						tr[i][j][v]=node(_l,_r,p);
					}
					//f[i][j][v]=max(f[i][j][v],l+r+cost(i,p,j,v)*val[v]);
				}
			}
			for(int v=cnt;v>=1;--v){
				g[i][j][v]=max(g[i][j][v+1],mk(f[i][j][v],v));
			}
		}
	}
	int ans=0,v=0;
	for(int i=1;i<=cnt;++i)if(f[1][n][i]>=ans)ans=f[1][n][i],v=i;
	printf("%d\n",ans);
	get_ans(1,n,v,tr[1][n][v].p);
	for(int i=1;i<=n;++i)printf("%d ",val[res[i]]);puts("");
	return 0;
}

bzoj2616 SPOJ PERIODNI

題目鏈接

定義柱狀圖的一個子圖是柱狀圖橫坐標的一個區間,截掉了下面的一定高度得到的柱狀圖,要求子圖中每列高度至少為\(1\)。按定義,我們從完整的原圖開始遞歸,每次找出當前區間中高度的最小值,把高度最小的這些列去掉之后,分出若干個更小的區間,將它們的下面(等於最小列高度的部分)砍掉,繼續遞歸這些小子圖。遞歸的邊界是如果當前區間內所有列高度相同,則不再繼續遞歸。按此方法,顯然可以划分出\(O(n)\)個子圖,這些子圖之間形成了樹狀的結構。我們可以把這個結構理解為一種廣義的“笛卡爾樹”,雖然它並不是二叉樹。

注意到:原序列的一個區間、柱狀圖的一個子圖、樹上的一個節點這三個概念現在是等價的。

我們在這個樹形結構上DP。用區間\([l,r]\)來表示樹上的一個節點(也就是一個子圖)。則這個子圖可以被划分為下方的一個極大完整矩形,和上面的若干小子圖(也就是它的兒子)。設\(dp[l,r][k]\)表示考慮了\([l,r]\)這個子圖,在里面放置了\(k\)個車的方案數。

先遞歸所有兒子,顯然這些兒子子圖之間互不影響,所以把它們用背包合並起來。

然后考慮下方的極大完整矩形。假設這個矩形大小為\(x\times y\)。如果我們在所有兒子中一共放了\(k\)個車,則會有\(k\)列是不能再放的了。那么在下面再放\(i\)個車方案數就是\({x\choose i}{y-k\choose i}i!\),我們枚舉\(i,k\),用類似於卷積的方法暴力合並即可。

本題的關鍵思想是:先算上方小區間(兒子),再算下方大區間(父親),因為大區間里一定包括了小區間的所有列,這樣計算時直接減去這\(k\)個用過的列即可。而不用關心具體是那些列被用過了,這就大大提高了效率。

背包暴力合並是\(O(n^2)\)的,因為共有\(O(n)\)個子圖,故總時間復雜度\(O(n^3)\)

參考代碼:

//problem:bzoj2616
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,MAXN,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
#endif
inline int read(){
	int f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline ll readll(){
	ll f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=500,MOD=1000000007,MAXH=1000000;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int fac[MAXH+5],invf[MAXH+5];
inline int comb(int n,int k){
	if(n<k)return 0;
	return (ll)fac[n]*invf[k]%MOD*invf[n-k]%MOD;
}

int n,K,a[MAXN+5],dp[MAXN+5][MAXN+5],id[MAXN+5][MAXN+5],cnt,tmp[MAXN+5];
void solve(int l,int r,int h){
	//cout<<l<<" "<<r<<endl;
	int x;assert(!id[l][r]);x=id[l][r]=++cnt;
	int mn=MAXH+1;
	vector<int>pos;
	for(int i=l;i<=r;++i){
		if(a[i]<mn)mn=a[i],pos.clear();
		if(a[i]==mn)pos.pb(i);
	}
	assert(mn>h);
	if(pos.size()==r-l+1){
		for(int i=0;i<=min(mn-h,r-l+1);++i)dp[x][i]=(ll)comb(mn-h,i)*comb(r-l+1,i)%MOD*fac[i]%MOD;
		return;
	}
	pos.pb(r+1);
	int lst=l-1;
	dp[x][0]=1;
	for(int i=0;i<(int)pos.size();++i){
		if(pos[i]>lst+1){
			solve(lst+1,pos[i]-1,mn);
			int y=id[lst+1][pos[i]-1];
			for(int j=0;j<=r-l+1;++j){
				tmp[j]=0;
				for(int k=0;k<=j;++k){
					add(tmp[j],(ll)dp[x][k]*dp[y][j-k]%MOD);
				}
			}
			for(int j=0;j<=r-l+1;++j)dp[x][j]=tmp[j];
		}
		lst=pos[i];
	}
	memset(tmp,0,sizeof(tmp));
	for(int i=0;i<=min(mn-h,r-l+1);++i){
		for(int j=0;j+i<=r-l+1;++j){
			add(tmp[i+j],(ll)dp[x][j]*comb(r-l+1-j,i)%MOD*comb(mn-h,i)%MOD*fac[i]%MOD);
		}
	}
	for(int i=0;i<=r-l+1;++i)dp[x][i]=tmp[i];
}
int main() {
	fac[0]=1;
	for(int i=1;i<=MAXH;++i)fac[i]=(ll)fac[i-1]*i%MOD;
	invf[MAXH]=pow_mod(fac[MAXH],MOD-2);
	for(int i=MAXH-1;i>=0;--i)invf[i]=(ll)invf[i+1]*(i+1)%MOD;
	
	n=read();K=read();if(K>n){cout<<0<<endl;return 0;}
	for(int i=1;i<=n;++i)a[i]=read();
	solve(1,n,0);
//	while(1){
//		int l=read(),r=read(),x=read();
//		cout<<dp[id[l][r]][x]<<endl;
//	}
	cout<<dp[id[1][n]][K]<<endl;
	return 0;
}

agc026_d Histogram Coloring

題目鏈接

考慮一個完整矩形的情況。如果第一行是01交替出現的,則下一行的每個位置可以和第一行相同,也可以不同;否則每個位置必須與前一行不同

和上一題類似地,我們定義柱狀圖的子圖。以及這些子圖間構成了樹形結構。這里不再贅述。

\(dp1[H]\)表示子圖\(H\)的最下面一行按01間隔染色時整個子圖的合法染色方案數,\(dp2[H]\)表示子圖\(H\)最下面一行沒有限制時整張圖的合法染色方案數。設\(H\)下方的極大完整矩形寬度為\(x\)(也就是\(H\)里最低的一列高度為\(x\)),\(H\)中有\(w\)列高度為\(x\)(並列最低),將\(H\)從下面截取\(x\)的高度后得到若干個子圖(也就是\(H\)的兒子)為\(c_1,c_2,\dots,c_k\)

\(dp1[H]=2^x\prod_{i=1}^{k}dp1[c_i]\),因為第一行交替染色時圖中某一列可以任意染色,染好這一列后其他位置的顏色都可以確定。

\(dp2[H]=2^w\prod_{i=1}^{k}(dp1[c_i]+dp2[c_i])+(2^x-2)\prod_{i=1}^{k}dp1[c_i]\)。后半部分是欽定每一列都不交替染色,則一列有\((2^x-2)\)種染法,此時第一行必須01交替,因此截下的子圖\(c_i\)的第一行也是交替的(\(dp1\))。前半部分讓每一列都01交替,則要么0開頭,要么1開頭,有\(2\)種染法。因此不在\(c_i\)下方的部分方案數為\(2^w\)。如果\(c_i\)第一行01交替的話\(c_i\)正下方的矩形部分會有\(2\)種染色方法,所以要\(dp1[c_i]\)要被計算\(2\)次(其中有一次已經包含在\(dp2[ci]\)中了)。

時間復雜度\(O(n^2)\)

參考代碼(寫的略顯繁瑣。但本題實現不難,讀者可以嘗試自己實現):

//problem:agc026_d
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;

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

const int MAXN=1005,MOD=1e9+7;
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int n,h[MAXN];
vector<int>vec,w[MAXN];
int solve1(int l,int r,int k,int low){
	//cout<<"dp1 "<<l<<" "<<r<<" "<<k<<endl;
	int mx=0,mn=2e9;
	for(int i=l;i<=r;++i)mx=max(mx,h[i]),mn=min(mn,h[i]);
	if(mn>k)return solve1(l,r,mn,low);
	if(mx==k&&mx==mn)return pow_mod(2,vec[k]-low);
	
	int ans=1,st=lob(w[k].begin(),w[k].end(),l)-w[k].begin(),ed=lob(w[k].begin(),w[k].end(),r)-w[k].begin();
	ed=min(ed,(int)w[k].size()-1);
	if(w[k][ed]>r)ed--;
	int lst=l;
	for(int i=st;i<=ed;++i){
		assert(w[k][i]>=l&&w[k][i]<=r);
		if(lst<=w[k][i]-1)ans=(ll)ans*solve1(lst,w[k][i]-1,k+1,vec[k])%MOD;
		lst=w[k][i]+1;
	}
	if(lst<=r)ans=(ll)ans*solve1(lst,r,k+1,vec[k])%MOD;
	ans=(ll)ans*pow_mod(2,vec[k]-low)%MOD;
	return ans;
}
int solve2(int l,int r,int k,int low){
	//cout<<l<<" "<<r<<" "<<k<<endl;
	int mx=0,mn=2e9;
	for(int i=l;i<=r;++i)mx=max(mx,h[i]),mn=min(mn,h[i]);
	if(mn>k)return solve2(l,r,mn,low);
	if(mx==k&&mx==mn)return (pow_mod(2,r-l+1)-2+pow_mod(2,vec[k]-low))%MOD;
	
	int st=lob(w[k].begin(),w[k].end(),l)-w[k].begin(),ed=lob(w[k].begin(),w[k].end(),r)-w[k].begin();
	ed=min(ed,(int)w[k].size()-1);
	if(w[k][ed]>r)ed--;
	vector<int>dp1,dp2;
	int lst=l,t=0;
	for(int i=st;i<=ed;++i){
		assert(w[k][i]>=l&&w[k][i]<=r);
		t++;
		if(lst<=w[k][i]-1)dp1.pb(solve1(lst,w[k][i]-1,k+1,vec[k])),dp2.pb(solve2(lst,w[k][i]-1,k+1,vec[k]));
		lst=w[k][i]+1;
	}
	if(lst<=r)dp1.pb(solve1(lst,r,k+1,vec[k])),dp2.pb(solve2(lst,r,k+1,vec[k]));
	int ans1=1,ans2=1;
	for(int i=0;i<(int)dp1.size();++i)ans1=(ll)ans1*(dp1[i]+dp2[i])%MOD,ans2=(ll)ans2*dp1[i]%MOD;
	ans1=(ll)ans1*pow_mod(2,t)%MOD;
	ans2=(ll)ans2*(pow_mod(2,vec[k]-low)-2)%MOD;
	return (ans1+ans2)%MOD;
}
int main() {
//	freopen("data.txt","r",stdin);
	n=read();
	for(int i=1;i<=n;++i)h[i]=read(),vec.pb(h[i]);
	vec.pb(0);
	sort(vec.begin(),vec.end());
	vec.erase(unique(vec.begin(),vec.end()),vec.end());
	for(int i=1;i<=n;++i)h[i]=lob(vec.begin(),vec.end(),h[i])-vec.begin(),w[h[i]].pb(i);
	cout<<solve2(1,n,1,0)<<endl;
	return 0;
}

loj2743 「JOI Open 2016」摩天大樓

題目鏈接

考慮把值按從大到小的順序插入序列中。那么每插入一個元素,其所在的連續段就是以其為根的笛卡爾樹上的子樹。

\(a\)序列按從大到小排序(現在 \(a_i \geq a_{i + 1}\))。我們把絕對值這個貢獻做差分,然后攤到每次加入的數上。比方說加入\(a_i\)后當前序列中共有\(j\)個連續段,則差分的貢獻就是\(2\cdot j\cdot(a_i - a_{i + 1})\),因為每個連續段兩側會有兩個必定\(\leq a_{i+1}\)的數。特別地,當有某個連續段位於最左邊或最右邊時,要特判。

於是可以設計DP,令\(dp[i][j][k][x\in\{0,1\}][y\in\{0,1\}]\)表示考慮了前\(i\)大的數,當前序列中共有\(j\)個連續段,當前的差分總貢獻是\(k\),序列的左、右端點有沒有被加入。根據這些信息可以計算出,被拉大差值的兩個相鄰元素的數目,也就是對 \(k\) 新增的差分貢獻。轉移時,考慮新加入的元素,分“單獨成為一段”、“緊貼着某個原來的段”、“合並了兩個原來的段”三種情況討論。

時間復雜度\(O(n^2L)\)

參考代碼:

//problem:loj2743
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
	if(S==T){
		T=(S=buf)+fread(buf,1,MAXN,stdin);
		if(S==T)return EOF;
	}
	return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
	#define getchar Fread::getchar
#endif
inline int read(){
	int f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline ll readll(){
	ll f=1,x=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=100,MAXL=1000,MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

int n,L,a[MAXN+5],dp[MAXN+5][MAXN+5][MAXL+5][2][2];

int main() {
	n=read();L=read();
	for(int i=1;i<=n;++i)a[i]=read();
	sort(a+1,a+n+1);reverse(a+1,a+n+1);
	if(n==1){cout<<1<<endl;return 0;}
	dp[0][0][0][0][0]=1;
	for(int i=0;i<n;++i){
		for(int j=0;j<=i;++j){
			for(int k=0;k<=L;++k){
				for(int tl=0;tl<=1;++tl){
					for(int tr=0;tr<=1;++tr)if(dp[i][j][k][tl][tr]){
						//把第i+1個數放進來
						int v=dp[i][j][k][tl][tr];
						//case1:單獨一段
						if(k+((j+1)*2-tl-tr)*(a[i+1]-a[i+2])<=L)if(j+(!tl)+(!tr)+i+1<=n)
							add(dp[i+1][j+1][k+((j+1)*2-tl-tr)*(a[i+1]-a[i+2])][tl][tr],(ll)v*(j-1+(!tl)+(!tr))%MOD);
						if(!tl)if(k+((j+1)*2-1-tr)*(a[i+1]-a[i+2])<=L)if(j+(!tr)+i+1<=n)
							add(dp[i+1][j+1][k+((j+1)*2-1-tr)*(a[i+1]-a[i+2])][1][tr],v);
						if(!tr)if(k+((j+1)*2-tl-1)*(a[i+1]-a[i+2])<=L)if(j+(!tl)+i+1<=n)
							add(dp[i+1][j+1][k+((j+1)*2-tl-1)*(a[i+1]-a[i+2])][tl][1],v);
						//case2:緊貼着一段
						if(j&&k+(j*2-tl-tr)*(a[i+1]-a[i+2])<=L)if(j-1+(!tl)+(!tr)+i+1<=n)
							add(dp[i+1][j][k+(j*2-tl-tr)*(a[i+1]-a[i+2])][tl][tr],(ll)v*(j*2-tl-tr)%MOD);
						if(!tl)if(j&&k+(j*2-1-tr)*(a[i+1]-a[i+2])<=L)if(j-1+(!tr)+i+1<=n)
							add(dp[i+1][j][k+(j*2-1-tr)*(a[i+1]-a[i+2])][1][tr],v);
						if(!tr)if(j&&k+(j*2-tl-1)*(a[i+1]-a[i+2])<=L)if(j-1+(!tl)+i+1<=n)
							add(dp[i+1][j][k+(j*2-tl-1)*(a[i+1]-a[i+2])][tl][1],v);
						//case3:合並兩段
						if(j>=2&&k+((j-1)*2-tl-tr)*(a[i+1]-a[i+2])<=L)if(j-2+(!tl)+(!tr)+i+1<=n)
							add(dp[i+1][j-1][k+((j-1)*2-tl-tr)*(a[i+1]-a[i+2])][tl][tr],(ll)v*(j-1)%MOD);
					}
				}
			}
		}
	}
	int ans=0;
	for(int i=0;i<=L;++i)add(ans,dp[n][1][i][1][1]);
	cout<<ans<<endl;
	return 0;
}

loj3228 「USACO 2019.12 Platinum」Tree Depth

題目鏈接

考慮一個經典模型:求\(1\dots n\)的恰有\(K\)個逆序對的排列數。通常有兩種做法(它們本質相同):

  • 做法一:從小到大枚舉每個數,把當前數\(i\)插入排列中。此時比\(i\)小的所有數的相對位置關系已經確定,而\(i\)可以插到它們之間的任意位置。所以插入\(i\)新增的逆序對數可以為\([0,i-1]\)中任意整數。
  • 做法二:從左到右考慮每個位置。對於當前考慮的位置\(i\),前\(i-1\)個位置上值的相對大小關系已經確定。而當前位置上的值在前\(i\)個值中的排名可以任意指定,故新增的逆序對數也是\([0,i-1]\)中任意整數。

本題中,題目定義的構造過程就是構造一個笛卡爾樹。容易知道,對於兩個位置\(i,j\)\(j\)\(i\)在笛卡爾樹上的祖先,當且僅當\(\min_{k=\min(i,j)}^{\max(i,j)}\{p_k\}=p_j\)

根據期望的線性性,我們枚舉\(i,j\),求出\(j\)\(i\)的祖先的合法方案數后累加到\(i\)的答案中。

考慮上述經典模型的第二種做法,但不是從左到右插入,而把插入的過程分為兩部分。第一部分先從\(i\)開始,向\(j\)的方向,一直走到序列的某一端,依次決定每個位置上元素的相對大小關系;第二部分從\(i\)開始,向\(j\)反方向,一直走到序列的某一端,依次確定每個位置上元素的相對大小關系。具體地:

  • \(j<i\)時,我們先從\(i\)\(1\);再從\(i+1\)\(n\)
  • \(j>i\)時,我們先從\(i\)\(n\);再從\(i-1\)\(1\)

這樣,每個新位置所新增的逆序對數是:\([0,0],[0,1],[0,2],\dots,[0,n-1]\),除了\(j\)這個位置比較特殊,它需要保證是\([\min(i,j),\max(i,j)]\)這段區間內最小的。因此當\(j<i\)時,位置\(j\)新增的逆序對數為\(0\)\(j>i\)時,位置\(j\)新增的逆序對數為\(j-i\)

朴素地做一次這樣的DP,復雜度是\(O(n^2K)\)的。又因為要枚舉位置\(i,j\),總復雜度為\(O(n^4K)\)

發現DP的轉移相當於是乘以了一個形如\(x^0+x^1+\dots+x^d\)的生成函數。因為生成函數的最高次項是\(O(K)\)的,且所有項系數都為\(1\),故乘法可以\(O(K)\)實現。(也可以理解為是前綴和優化DP)。此時DP的復雜度降為\(O(nK)\)

我們可以先\(O(nK)\)預處理好所有生成函數的積。然后枚舉\(i,j\),用預處理好的積除以一個最高次項為\(j-i\)的生成函數,再乘以\(x^0\)\(x^{j-i}\)。和乘法同理,這個除法也可以\(O(K)\)實現。又發現我們要考慮的只有\(j-i\)的差值,因此不用枚舉\(i,j\),只需要枚舉\(j-i\)的差即可。

時間復雜度\(O(nK)\)

參考代碼:

//problem:loj3228
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

/*  ------  by:duyi  ------  */ // myt天下第一
const int MAXN=300;
int n,K,MOD;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

int f[MAXN*MAXN+5],D,ans[MAXN+5];
void mul(int d){
	//乘以 (x^0+x^1+...+x^{d-1})
	for(int i=D;i<D+d-1;++i)f[i]=0;
	D+=d-1;
	static int s[MAXN*MAXN+5];
	s[0]=f[0];
	for(int i=1;i<D;++i)s[i]=mod1(s[i-1]+f[i]);
	for(int i=0;i<D;++i)f[i]=mod2(s[i]-(i-(d-1)>0?s[i-(d-1)-1]:0));
}
void div(int d){
	//除以 (x^0+x^1+...+x^{d-1})
	static int g[MAXN*MAXN+5];
	assert(D>=d);
	for(int i=D-d,s=0;i>=0;--i){
		if(i+d<=D-d)sub(s,g[i+d]);
		g[i]=mod2(f[i+(d-1)]-s);
		add(s,g[i]);
	}
	for(int i=D-(d-1);i<D;++i)f[i]=0;
	D-=(d-1);
	for(int i=0;i<D;++i)f[i]=g[i];
}
int main() {
	scanf("%d%d%d",&n,&K,&MOD);
	f[0]=1;D=1;
	for(int i=2;i<=n;++i)mul(i);
	//for(int i=0;i<D;++i)cout<<f[i]<<" ";cout<<endl;
	for(int d=1;d<n;++d){//(i,j)的距離 abs(i-j)=d
		div(d+1);
		for(int i=d+1;i<=n;++i)add(ans[i],f[K]);
		if(K>=d)for(int i=1;i+d<=n;++i)add(ans[i],f[K-d]);
		mul(d+1);
	}
	for(int i=1;i<=n;++i)printf("%d ",mod1(ans[i]+f[K]));puts("");
	return 0;
}

Part3 DP套DP

DP套DP是給定一個DP問題A,用另一個DP去計算一種可能的A的輸入,使得A的DP結果為x。

說白了就是,外層的DP的狀態是另一個DP的結果

這樣的問題,往往需要深入挖掘內層DP的性質,有時候還要對狀態數有一個合理的估計甚至是大膽的猜想。

loj6274 數字

題目鏈接

考慮這樣一個問題:給定兩個數字\(P,Q\),求是否存在\(x,y\),滿足\(L_x\leq x\leq R_x,L_y\leq y\leq R_y\),使得\(x\operatorname{OR}y=P,x\operatorname{AND}y=Q\)

這個問題可以用數位DP解決,令\(f[i][a][b][c][d]\ (a,b,c,d\in\{0,1\})\)表示從高到低考慮到第\(i\)位,\(x\)是否大於\(L_x\),是否小於\(R_x\)\(y\)是否大於\(L_y\),是否小於\(R_y\)。轉移時枚舉\(x,y\)的第\(i\)位分別是什么即可。且因為我們只需要判斷\(x,y\)的存在性,所以\(f\)數組的取值為\(\{0,1\}\)

回到原問題。我們令\(F[i][s]\)表示從高到低考慮到第\(i\)位,小\(f\)數組是\(s\)時,共有多少個\(Q\)能達到此狀態。至於我們怎么用一個整數\(s\)來描述小\(f\)數組,因為發現小\(f\)數組下標共\(2^4\)種,取值共\(2\)種,所以把每個下標對應的取值壓到一起就好了,共\(2^{2^4}=2^{16}\)種狀態。

轉移時枚舉\(Q\)的第\(i\)位是什么即可。

時間復雜度\(O(60\cdot2^{16}\cdot\text{一大堆常數})\)

參考代碼:

//problem:loj6274
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

/*  ------  by:duyi  ------  */ // myt天下第一
ull P,Lx,Rx,Ly,Ry,dp[61][1<<16];
int state(int a,int b,int c,int d){
	return a+(b<<1)+(c<<2)+(d<<3);
}
#define forbit(i) for(int i=0;i<=1;++i)
int main() {
	cin>>P>>Lx>>Rx>>Ly>>Ry;
	dp[60][1<<state(0,0,0,0)]=1;
	for(int i=59;i>=0;--i)for(int s=0;s<(1<<16);++s)if(dp[i+1][s]){
		int p=((P>>i)&1ull);
		int lx=((Lx>>i)&1ull),rx=((Rx>>i)&1ull),ly=((Ly>>i)&1ull),ry=((Ry>>i)&1ull);
		forbit(q){
			int new_s=0;
			forbit(a)forbit(b)forbit(c)forbit(d)if(s&(1<<state(a,b,c,d))){
				forbit(x)forbit(y){
					if((x|y)!=p||(x&y)!=q)continue;
					if(!a&&x<lx)continue;
					if(!b&&x>rx)continue;
					if(!c&&y<ly)continue;
					if(!d&&y>ry)continue;
					new_s|=(1<<state(a||x>lx,b||x<rx,c||y>ly,d||y<ry));
				}
			}
			dp[i][new_s]+=dp[i+1][s];
		}
	}
	ull ans=0;
	for(int s=1;s<(1<<16);++s)ans+=dp[0][s];
	cout<<ans<<endl;
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM