[NOIP2023] 雙序列擴充 題解

mountzhu發表於2024-10-07

qaq
首先我們考慮其實這個條件就是要滿足 \(f\) 嚴格比 \(g\) 大或 \(f\) 嚴格比 \(g\) 小。

在這裡只討論大於。

然後考慮到對於一個 \(i\) 如果不滿足,我們可以把對應陣列向右移一位看是否滿足,如果還是不滿足就無解了。
考慮對於現在滿足的 \(i\) ,我們可以分別把兩個指標向右移一位或者都移一位,所以很容易設計出狀態及轉移。
\(dp_{i,j}\) 表示 \(f\) 陣列的指標到了第 \(i\) 位, \(g\) 陣列的指標到了第 \(j\) 位能否匹配,轉移:\(dp_{i,j} \ |= dp_{i-1,j} \ | \ dp_{i,j-1} \ | \ dp_{i-1,j-1}\),分別對應上面的三種操作。
至此就 \(\Theta(nmq)\) 有35pts的高分了

#include<bits/stdc++.h>
using namespace std; 
int c,n,m,q,kx,ky,x,y;
int a[2005],b[2005],ca[2005],cb[2005];
bool f[2005][2005],dp[2005][2005];
void work(){
	if(a[1]<=b[1]||a[n]<=b[m]) f[n][m]=0;
	else{
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j) f[i][j]=0;
		}
		f[1][1]=1;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				if(a[i]>b[j]) f[i][j]|=(f[i-1][j]|f[i][j-1]|f[i-1][j-1]);
			}
		}
	//	for(int i=1;i<=n;++i){
	//		for(int j=1;j<=m;++j) printf("%d",f[i][j]);
	//		printf("\n");
	//	}
	//	printf("\n");
	}
	if(a[1]>=b[1]||a[n]>=b[m]) dp[m][n]=0;
	else{
		for(int i=1;i<=m;++i){
			for(int j=1;j<=n;++j) dp[i][j]=0;
		}
		dp[1][1]=1;
		for(int i=1;i<=m;++i){
			for(int j=1;j<=n;++j){
				if(a[j]<b[i]) dp[i][j]|=(dp[i-1][j]|dp[i][j-1]|dp[i-1][j-1]);
			}
		}
	//	for(int i=1;i<=m;++i){
	//		for(int j=1;j<=n;++j) printf("%d",dp[i][j]);
	//		printf("\n");
	//	}
	//	printf("\n");
	}
	printf("%d",f[n][m]|dp[m][n]);
}
int main(){
	scanf("%d %d %d %d",&c,&n,&m,&q);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),ca[i]=a[i];
	for(int i=1;i<=m;++i) scanf("%d",&b[i]),cb[i]=b[i];
	work();
	while(q--){
		for(int i=1;i<=n;++i) a[i]=ca[i];
		for(int i=1;i<=m;++i) b[i]=cb[i];
		scanf("%d %d",&kx,&ky);
		for(int i=1;i<=kx;++i) scanf("%d %d",&x,&y),a[x]=y;
		for(int i=1;i<=ky;++i) scanf("%d %d",&x,&y),b[x]=y;
		work();
	}
	return 0;
}

然後感覺這個東西好像很熟悉,這不就是網格走路嗎。
考慮一個 \(n\times m\) 的網格,每次可以向右、下、右下三個方向走,中間如果小於等於就不可走,問能否從起點到達終點。
既然都分析成這樣了,我們考慮怎麼最佳化掉這個 \(n\times m\)
看一眼特殊性質,好奇怪啊,跟max和min有什麼關係。仔細想一想,發現我確實只需要知道兩個數列max和min的關係,因為max和min的討論一定更加極限,是最後的必要條件,到達性一定可以透過max和min轉移過來,換句話說,它們兩個是充要條件。可以走到一定沒有全堵住的行和全堵住的列,而全堵住的最有可能的就是max對min,反之亦然。
然後考慮我現在要保證嚴格大於,那麼我的數列一定有 \(f_{max}>g_{max}\)\(f_{min}>g_{min}\)
結合上面的網格,我們思考其實就是如果 $f_{min} \leq g_{min} $ 或者 \(f_{max} \leq g_{max}\) ,那麼一定不可到達的。
所以我們其實可以逐步縮小範圍,就是你考慮如果從起點可以走到 \(max,min\) 這樣的交點,然後從這個點又可以走到終點,那麼就可以到達。
具體地,每次選擇當前區間內 \(f_{max}\)\(g_{min}\) ,如果當前滿足條件就向左向下縮,不斷遞迴,直到有一個不滿足的或者是到達了目標行或目標列。
於是我們成功將問題轉化為兩部分的到達性處理,而這兩部分的到達性問題我們又可以向內分治,再做一個字首最大最小,縮的次數最多是 \(n+m\) 於是時間複雜度轉化為 \(\Theta(q \ (n+m) )\)
qaq給我調破防了

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int c,n,m,q,kx,ky,x,y;
int a[MAXN],b[MAXN],ca[MAXN],cb[MAXN];
struct W{
	int pos,val;
};
W preai[MAXN],preaa[MAXN];//f的字首最小最大 
W prebi[MAXN],preba[MAXN];//g的字首最小最大 
W ai[MAXN],aa[MAXN];//f的字尾最小最大 
W bi[MAXN],ba[MAXN];//g的字尾最小最大 
void pre(){
	preaa[0].val=-1;preba[0].val=-1;
	preai[0].val=1e9+1;prebi[0].val=1e9+1;
	aa[n+1].val=-1;ba[m+1].val=-1;
	ai[n+1].val=1e9+1;bi[m+1].val=1e9+1;
	for(int i=1;i<=n;++i){
		if(preai[i-1].val>a[i]) preai[i].pos=i,preai[i].val=a[i];
		else preai[i]=preai[i-1];
		if(preaa[i-1].val<a[i]) preaa[i].pos=i,preaa[i].val=a[i];
		else preaa[i]=preaa[i-1];
	}
	for(int i=n;i>=1;--i){
		if(ai[i+1].val>a[i]) ai[i].pos=i,ai[i].val=a[i];
		else ai[i]=ai[i+1];
		if(aa[i+1].val<a[i]) aa[i].pos=i,aa[i].val=a[i];
		else aa[i]=aa[i+1];
	}
	for(int i=1;i<=m;++i){
		if(prebi[i-1].val>b[i]) prebi[i].pos=i,prebi[i].val=b[i];
		else prebi[i]=prebi[i-1];
		if(preba[i-1].val<b[i]) preba[i].pos=i,preba[i].val=b[i];
		else preba[i]=preba[i-1];
	}
	for(int i=m;i>=1;--i){
		if(bi[i+1].val>b[i]) bi[i].pos=i,bi[i].val=b[i];
		else bi[i]=bi[i+1];
		if(ba[i+1].val<b[i]) ba[i].pos=i,ba[i].val=b[i];
		else ba[i]=ba[i+1];
	}
}
bool check1(int x,int y,int ac){
	if(ac){
		if(x==1||y==1) return true;
		if(preai[x-1].val>prebi[y-1].val) return check1(x,prebi[y-1].pos,ac);
		if(preaa[x-1].val>preba[y-1].val) return check1(preaa[x-1].pos,y,ac);
		return false;
	}
	else{
		if(x==1||y==1) return true;
		if(preai[x-1].val<prebi[y-1].val) return check1(preai[x-1].pos,y,ac);
		if(preaa[x-1].val<preba[y-1].val) return check1(x,preba[y-1].pos,ac);
		return false;
	}
	return 0;
}

bool check2(int x,int y,int ac){
	if(ac){
		if(x==n||y==m) return true;
		if(ai[x+1].val>bi[y+1].val) return check2(x,bi[y+1].pos,ac);
		if(aa[x+1].val>ba[y+1].val) return check2(aa[x+1].pos,y,ac);
		return false;
	}
	else{
		if(x==n||y==m) return true;
		if(ai[x+1].val<bi[y+1].val) return check2(ai[x+1].pos,y,ac);
		if(aa[x+1].val<ba[y+1].val) return check2(x,ba[y+1].pos,ac);
		return false;
	}
}
bool work(){
	if(a[1]<b[1]&&a[n]<b[m]){
		if(preaa[n].val>=preba[m].val||preai[n].val>=prebi[m].val) return false;
		return check1(preai[n].pos,preba[m].pos,0)&&check2(preai[n].pos,preba[m].pos,0);
	}
	else if(a[1]>b[1]&&a[n]>b[m]){
		if(preaa[n].val<=preba[m].val||preai[n].val<=prebi[m].val) return false;
		return check1(preaa[n].pos,prebi[m].pos,1)&&check2(preaa[n].pos,prebi[m].pos,1);
	}
	return false;
}
int main(){
	scanf("%d %d %d %d",&c,&n,&m,&q);
	for(int i=1;i<=n;++i) preai[i].val=2e9;for(int i=1;i<=m;++i) prebi[i].val=2e9;
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),ca[i]=a[i];
	for(int i=1;i<=m;++i) scanf("%d",&b[i]),cb[i]=b[i];
	pre();
	printf("%d",work());
	while(q--){
		for(int i=1;i<=n;++i) a[i]=ca[i];
		for(int i=1;i<=m;++i) b[i]=cb[i];
		scanf("%d %d",&kx,&ky);
		for(int i=1;i<=kx;++i) scanf("%d %d",&x,&y),a[x]=y;
		for(int i=1;i<=ky;++i) scanf("%d %d",&x,&y),b[x]=y;
		pre();
		printf("%d",work());
	}
	return 0;
}

相關文章