暑假集訓CSP提高模擬4

HaneDaniko發表於2024-07-21

A.White and Black

暴力的 \(O(nq)\) 做法比較顯然,因為對於根節點來說,只有它自己可以改變自己的顏色,因此如果它是黑色則一定需要更改自己,同時把更改傳下去(應該沒有那種每次真的更改所有節點然後寫 \(O(nqn^{n})\) 的吧),同理,假如根節點更改結束,次級節點就同理了,這是一個連鎖的反應,因此搜一遍下來就行了.

這道題的最佳化點主要是在減少白點的列舉次數上,注意到資料範圍中給出的黑點數量非常少,因此我們考慮將列舉白點變成列舉子節點總數減去黑點總數,這樣就能把複雜度降下來.

這樣還是不好做,考慮預處理出每個節點的兒子總數,如果節點變黑,則其每個兒子對它作 \(1\) 的貢獻,如果兒子也是黑色的,說明該兒子不再對該父節點做貢獻,臨時減掉即可.

這道題寫了部分分但是沒拿到,因為題目裡只說了是菊花,沒說根節點是誰,判斷的時候漏判了.

#include<bits/stdc++.h>
using namespace std;
int n,q;
int sons[200001],fa[200001],black[200001],isblack[200001];
int main(){
	scanf("%d %d",&n,&q);
	for(int i=2;i<=n;++i){
		scanf("%d",&fa[i]);
		sons[fa[i]]++;
	}
	for(int i=1;i<=q;++i){
		int x;
		scanf("%d",&x);
		int ans=0;
		for(int j=1;j<=x;++j){
			scanf("%d",&black[j]);
			isblack[black[j]]=true;
		}
		for(int j=1;j<=x;++j){
			ans+=sons[black[j]];
		}
		for(int j=1;j<=x;++j){
			if(!isblack[fa[black[j]]]) ans++;
			else ans--;
		}
		printf("%d\n",ans);
		for(int j=1;j<=x;++j){
			isblack[black[j]]=false;
		}
	}
}

B.White and White

還是比較顯然的 \(O(n^2 k)\) 暴力,對於此題顯然可以設計 \(f_{i,j}\) 表示選到第 \(j\) 位,上次劃分在 \(i\) 位置的最小值,可以得到:

\[f_{i,j}=\min_{1\le k\lt j} \{ f_{i-1,k}+(\sum^{x}_{k+1\le x\le j} a_{x})\mod p \} \]

發現這個 \(\mod p\) 非常噁心,阻止我們進一步使用優先佇列進行最佳化.

因此考慮從這個 \(\mod p\) 入手,因為我們每次進行取模都只是剪掉了一個 \(kp\ (k\in Z)\),即 \(f_{i,j}\equiv \sum^{x}_{1\le x\le j} a_{x}\pmod p\)

在我們進行狀態轉移時(即上式),對於兩個 \(k=a,b\),將同餘式傳遞,有

\[f_{i,j}\equiv f_{i-1,a}+\sum^{x}_{a\le x\le j}a_{x}\equiv f_{i-1,b}+\sum^{x}_{b\le x\le j}b_{x}\pmod p \]

假設當 \(f_{i-1,a}+\sum^{x}_{a\le x\le j}a_{x}\lt f_{i-1,b}+\sum^{x}_{b\le x\le j}b_{x}\) 時,有 \(f_{i-1,a}\lt f_{i-1,b}\),此時移項有:

\[\sum^{x}_{a\le x\le j}a_{x}\lt f_{i-1,b}+\sum^{x}_{b\le x\le j}b_{x}-f_{i-1,a} \]

剛才我們知道

\[\sum^{x}_{a\le x\le j}a_{x}\equiv f_{i-1,a}-f_{i-1,b}+\sum^{x}_{b\le x\le j}b_{x}\pmod p \]

這兩個方程聯立無解,因此假設不成立,我們得到:

\[f_{i-1,a}+\sum^{x}_{a\le x\le j}a_{x}\lt f_{i-1,b}+\sum^{x}_{b\le x\le j}b_{x}\rightarrow f_{i-1,a}\ge f_{i-1,b} \]

透過此式判斷最優決策點即可

此外本題卡空間,需要用滾動陣列進行最佳化

#include<bits/stdc++.h>
using namespace std;
int n,k,p;
const int mod=1e9+7,inf=0x3f3f3f3f;
int sum[500001];
int f[500001][101];
int g[2][101]; //維護決策最小值
int main(){
	cin>>n>>k>>p;
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=n;++i){
		int x;cin>>x;
		sum[i]=(sum[i-1]+x)%p;
		f[0][0]=0;
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=k;++j){
			f[i][j]=f[g[i&1][j-1]][j-1]+((sum[i]-sum[g[i&1][j-1]])%p+p)%p;
		}
		for(int j=0;j<=k;++j){
			if(f[i][j]<f[g[i&1][j]][j]) g[!(i&1)][j]=i;
			else g[!(i&1)][j]=g[i&1][j];
		}
	}
	cout<<f[n][k]<<endl;
}

UPD: 剛剛發現其實決策陣列只需要開一維就行了

#include<bits/stdc++.h>
using namespace std;
int n,k,p;
const int mod=1e9+7,inf=0x3f3f3f3f;
int sum[500001];
int f[500001][101];
int g[101];
int main(){
	cin>>n>>k>>p;
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=n;++i){
		int x;cin>>x;
		sum[i]=(sum[i-1]+x)%p;
		f[0][0]=0;
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=k;++j){
			f[i][j]=f[g[j-1]][j-1]+((sum[i]-sum[g[j-1]])%p+p)%p;
		}
		for(int j=0;j<=k;++j){
			if(f[i][j]<f[g[j]][j]) g[j]=i;
		}
	}
	cout<<f[n][k]<<endl;
}

C.Black and Black

TBA

D.Black and White

最暴力的做法是單次 \(n^{2}\) 列舉點對求距離最大值. \(O(qn^{2}\log n)\)

比較暴力的顯然是直接單次兩遍 DFS 求樹的直徑. \(O(qn^{2})\)

直接說正解,考慮到根據原樹的 DFS 序將整棵樹壓縮成一個括號序列. \(O(q\log n)\)

注意到,自身封閉的括號序列一定是一顆完整的子樹,因此,當我們需要求解 \(x,y\) 之間的距離時,應該考慮分別找到 \(x,y\) 的位置,然後提取出 \([pos_{x},pos_{y}]\) 之間的括號序列. 假如該序列中存在封閉的子括號序列,說明最短路徑顯然不會進入這顆子樹(因為 \(x,y\) 均不在這個子樹內,因此走進去是多餘的),所以考慮消去路徑上全部封閉的子括號序列,餘下的括號個數即為路徑長.

顯然,這樣的複雜度不夠優秀,注意到區間括號序列是具有可合併性的,當我們合併兩個括號序列的時候,根據上述所需,我們完全可以預先消去兩個子區間內的全部封閉子括號序列,其次進行合併,再將新形成的括號序列繼續與其他括號序列進行合併.

基於這樣的思想,我們使用線段樹來維護消除完畢的括號序列(即任意兩點間距離,透過查詢 \([l,r]\) 區間和即可獲得 \(dis_{l,r}\)).

顯然我們需要維護的有:DFS 序,各點在 DFS 序列上的位置,線段樹(儲存每個區間的左括號數目,右括號數目(特別地,由於維護跨區間距離的需要,我們還需要維護該樹的子樹的括號情況),以及該區間左右端點間距離(即剩餘括號數)).

此外還需要判 \(-1\)\(0\),記一個 \(cnt\) 即可.

#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
int num,s[3000001],pos[1000001];
int n,m;
int cnt,tot;
bool c[100001];
vector<int>e[200001];
void dfs(int now,int fa){
	s[++tot]=-1;
	s[++tot]=now;
	pos[now]=tot;
	for(int i:e[now]){
		if(i!=fa){
			dfs(i,now);
		}
	}
	s[++tot]=-2;
}
#define tol (id*2)
#define tor (id*2+1)
#define mid(l,r) mid=((l)+(r))/2
struct tree{
	int a,b;
	int l1,l2,r1,r2;
	int dis;
}t[1200005];
void push(int id,int x){
	t[id].a=t[id].b=0;
	t[id].l1=t[id].l2=t[id].r1=t[id].r2=t[id].dis=-inf;
	if(s[x]==-1){
		t[id].b=1;
	}
	else if(s[x]==-2){
		t[id].a=1;
	}
	else if(!c[s[x]]){
		t[id].l1=t[id].r1=t[id].r2=t[id].l2=0;
	}
}
void merge(int id){
	if(t[tol].b>t[tor].a){
		t[id].a=t[tol].a,t[id].b=t[tol].b-t[tor].a+t[tor].b;
	}
	else{
		t[id].a=t[tol].a+t[tor].a-t[tol].b,t[id].b=t[tor].b;
	}
	t[id].l1=max({t[tol].l1,t[tor].l1+t[tol].a-t[tol].b,t[tor].l2+t[tol].a+t[tol].b});
	t[id].l2=max(t[tol].l2,t[tor].l2-t[tol].a+t[tol].b);
	t[id].r1=max({t[tor].r1,t[tol].r1-t[tor].a+t[tor].b,t[tol].r2+t[tor].a+t[tor].b});
	t[id].r2=max(t[tor].r2,t[tol].r2+t[tor].a-t[tor].b);
	t[id].dis=max({t[tol].r1+t[tor].l2,t[tol].r2+t[tor].l1,t[tol].dis,t[tor].dis});
}
void build(int id,int l,int r){
	if(l==r){
		push(id,l);
		return;
	}
	int mid(l,r);
	build(tol,l,mid);
	build(tor,mid+1,r);
	merge(id);
}
void modify(int id,int l,int r,int x){
	if(l==r){
		push(id,l);
		return;
	}
	int mid(l,r);
	if(x<=mid){
		modify(tol,l,mid,x);
	}
	else{
		modify(tor,mid+1,r,x);
	}
	merge(id);
}
int main(){
	scanf("%d",&n);
	cnt=n;
	for(int i=1;i<=n-1;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs(1,0);
	build(1,1,tot); 
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		char op='\n';int x;
		while(op!='G' and op!='C') op=getchar();
		if(op=='C'){
			scanf("%d",&x);
			if(c[x]){
				c[x]=0;
				cnt++;
			}
			else{
				c[x]=1;
				cnt--;
			}
			modify(1,1,tot,pos[x]);
		}
		else if(cnt==0){
			printf("-1\n");
		}
		else if(cnt==1){
			printf("0\n");
		}
		else{
			printf("%d\n",t[1].dis);
		}
	}
}

相關文章