Educational Codeforces Round 168 (Rated for Div. 2) 題解

zsc985246發表於2024-07-31

本文網址:https://www.cnblogs.com/zsc985246/p/18333684 ,轉載請註明出處。

D 題不知道為什麼掛了一發。臨時排名 155。

F 題解請等待後續更新。

傳送門

Educational Codeforces Round 168 (Rated for Div. 2)

A.Strong Password

題目大意

給定一個字串 \(a\),你需要在中間(可以是開頭或結尾)插入一個字元,使得滿足 \(a_i \neq a_{i+1}\)\(i\) 最多。

多組測試,\(1 \le |a| \le 10, T \le 10^3\)

思路

如果有兩個字元相鄰且相同,直接在這它們之間插入一個不同於它們的字元。否則在字串結尾插入一個不同於結尾的字元。

程式碼實現

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;

ll n,m,k;
char a[N],b[N];

void mian(){
	
	scanf("%s",a+1);
	
	n=strlen(a+1);
	ll flag=0;
	For(i,1,n-1){
		putchar(a[i]);
		if(a[i]==a[i+1]&&flag==0){
			if(a[i]=='a')putchar('b');
			else putchar('a');
			flag=1;
		}
	}
	putchar(a[n]);
	if(flag==0){
		if(a[n]=='a')putchar('b');
		else putchar('a');
	}
	putchar('\n');
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

B.Make Three Regions

題目大意

有一個 \(2 \times n\) 的網格,上面有些格子是障礙物,有些是空地。保證空地四連通。

求有多少個空地滿足把它變成障礙物後,剩餘的空地被分為三個四連通塊。

多組測試,\(1 \le n \le 2 \times 10^5, T \le 10^4, \sum n \le 2 \times 10^5\)

思路

發現只有如下兩種情況:

x.x        ...
...        x.x

特判即可。

程式碼實現

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;

ll n,m,k;
char a[N],b[N];

void mian(){
	
	ll ans=0;
	scanf("%lld",&n);
	scanf("%s",a+1);
	scanf("%s",b+1);
	For(i,1,n-2){
		if(a[i]=='x'&&a[i+1]=='.'&&a[i+2]=='x'&&b[i]=='.'&&b[i+2]=='.'){
			++ans;
		}
		if(b[i]=='x'&&b[i+1]=='.'&&b[i+2]=='x'&&a[i]=='.'&&a[i+2]=='.'){
			++ans;
		}
	}
	
	printf("%lld\n",ans);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

C.Even Positions

題目大意

一個合法括號序列的權值定義為匹配括號的距離和。

有一個長度為 \(n\) 的合法括號序列,但所有的奇數位置的括號都遺失了。你需要找出所有可能的原序列中,權值最小的為多少。

多組測試,\(n\) 為偶數,\(2 \le n \le 2 \times 10^5, T \le 5000, \sum n \le 2 \times 10^5\)

思路

發現對於一個右括號,我們會盡可能在它前面放左括號,而對於一個左括號,我們會在它後面放右括號。

手玩發現,為了使括號序列合法,我們會先滿足左括號,再考慮右括號。

也就是說,一個左括號出現時,它後面最近的右括號的匹配括號會左移 \(2\),最後權值也會增加 \(2\)

統計左括號的數量 \(x\)\(\frac{n}{2}+2x\) 即為答案。

程式碼實現

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;

ll n,m,k;
char a[N],b[N];

void mian(){
	
	ll ans=0;
	scanf("%lld",&n);
	scanf("%s",a+1);
	ans=n/2;
	
	For(i,1,n){
		if(a[i]=='('){
			ans+=2;
		}
	}
	
	printf("%lld\n",ans);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

D.Maximize the Root

題目大意

給定一棵 \(n\) 個點的樹,根為 \(1\)。第 \(i\) 個點有一個權值 \(a_i\)

你可以多次操作:選擇一個點,讓其子樹所有點(不包括自己)權值減 \(1\),自己權值加 \(1\)

權值不能為負。求最後根節點的最大權值。

多組測試,\(n\) 為偶數,\(2 \le n \le 2 \times 10^5, 0 \le a_i \le 10^9, T \le 10^4, \sum n \le 2 \times 10^5\)

思路

明顯不能貪心。考慮 dp。

我們要讓根節點儘可能大,那麼就要讓根節點能進行的運算元儘可能多。

而運算元取決於子樹中權值最小的點的權值

\(dp_i\) 表示 \(i\) 的子樹操作完之後最小值的最大值。

轉移時,令 \(mn\)\(x\) 的兒子節點的 \(dp\) 值的最小值,分類討論:

  • \(mn < a_x\)。此時操作只會讓最小值更小。\(dp_x=tmp\)

  • \(mn \ge a_x\)。此時我們可以操作直到 \(mn \le a_x\),這樣最小值會變大。\(dp_x=\lfloor\frac{mn+a_x}{2}\rfloor\)

注意我們是要讓根節點權值最大,所以在根節點上不需要考慮子樹最小值最大,直接無腦操作即可。

程式碼實現

根節點記得特判。

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;

ll n,m,k;
ll a[N],b[N];
ll fa[N];
vector<ll>e[N];

void dfs(ll x){
	ll mn=1000000001;
	for(ll y:e[x]){
		if(y==fa[x])continue;
		dfs(y);
		mn=min(mn,b[y]);
	}
	if(mn==1000000001){
		b[x]=a[x];
		return;
	}
	if(x==1){
		a[x]+=mn;
		return;
	}
	if(a[x]<mn)b[x]=(a[x]+mn)/2;
	else b[x]=mn;
}

void mian(){
	
	ll ans=0;
	scanf("%lld",&n);
	For(i,1,n){
		scanf("%lld",&a[i]);
		e[i].clear();
		b[i]=0;
	}
	For(i,2,n){
		scanf("%lld",&fa[i]);
		e[i].pb(fa[i]),e[fa[i]].pb(i);
	}
	dfs(1);
	
	printf("%lld\n",a[1]);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

E.

題目大意

有一個長度為 \(n\) 的序列 \(a\) 和整數 \(x,k\)\(x\) 一開始等於 \(1\)

一次操作定義為:依次對 \(i = 1 \to n\),每 \(k\) 次滿足 \(a_i \ge x\)\(x \larr x + 1\)

現在告訴你序列 \(a\),有 \(q\) 次詢問,每次詢問給定 \(p,t\),求在 \(k=t\)、操作進行到 \(i=p\) 時,\(a_p \ge x\) 是否滿足。

思路

沒有修改操作,首先想到預處理。

手玩發現,對於每個 \(i\)滿足條件的 \(k\) 是一個範圍,即大於等於一個閾值 \(b_i\)

所以我們需要做的,就是快速求出每個 \(i\) 的閾值。

如果求單個 \(i\) 的閾值,我們可以使用二分答案。這樣做的複雜度瓶頸在 check 函式上。

那麼為了降低複雜度,我們需要尋找共用的資訊

我們發現,check 的本質其實是在找之前有多少下標已經滿足條件。

那麼我們直接開一顆權值線段樹,記錄當前的每個 \(k\) 值對應的滿足條件的下標數。

每次計算出一個 \(b_i\),將線段樹上 \(b_i \to n\) 整體加 \(1\)。查詢時單點查詢。

二分答案套上一個線段樹,複雜度 \(O(n log^2 n)\)

程式碼實現

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define Yes printf("Yes\n")
#define No printf("No\n")
#define pb push_back
const ll N=1e6+10;
using namespace std;

ll n,m,k,q;
ll a[N],b[N];

#define lson rt<<1
#define rson rt<<1|1
ll c[N],lazy[N];
void build(ll rt,ll l,ll r){
	if(l==r){
		c[rt]=lazy[rt]=0;
		return;
	}
	ll mid=(l+r)>>1;
	build(lson,l,mid);
	build(rson,mid+1,r);
}
void pushdown(ll rt,ll l,ll r){
	ll mid=(l+r)>>1;
	if(lazy[rt]){
		lazy[lson]+=lazy[rt];
		lazy[rson]+=lazy[rt];
		c[lson]+=lazy[rt]*(mid-l+1);
		c[rson]+=lazy[rt]*(r-mid);
		lazy[rt]=0;
	}
}
void change(ll rt,ll l,ll r,ll x,ll y,ll z){
	if(x<=l&&r<=y){
		lazy[rt]+=z;
		c[rt]+=z*(r-l+1);
		return;
	}
	pushdown(rt,l,r);
	ll mid=(l+r)>>1;
	if(x<=mid)change(lson,l,mid,x,y,z);
	if(y>mid)change(rson,mid+1,r,x,y,z);
	c[rt]=c[lson]+c[rson];
}
ll query(ll rt,ll l,ll r,ll x){
	if(l==r)return c[rt];
	pushdown(rt,l,r);
	ll mid=(l+r)>>1;
	if(x<=mid)return query(lson,l,mid,x);
	else return query(rson,mid+1,r,x);
}

void mian(){
	
	ll ans=0;
	scanf("%lld%lld",&n,&q);
	For(i,1,n){
		scanf("%lld",&a[i]);
		b[i]=0;
	}
	build(1,1,n);
	change(1,1,n,1,n,1);
	For(i,2,n){
		ll l=1,r=n,res=0;
		while(l<=r){
			ll mid=(l+r)>>1;
			if(query(1,1,n,mid)/mid+1>a[i])l=mid+1;
			else res=mid,r=mid-1;
		}
		b[i]=res;
		change(1,1,n,res,n,1);
	}
	while(q--){
		ll x,y;
		scanf("%lld%lld",&x,&y);
		if(b[x]<=y)Yes;
		else No;
	}
	
}

int main(){
	int T=1;
	while(T--)mian();
	return 0;
}

F.

題目大意

思路

程式碼實現


尾聲

如果有什麼問題,可以直接評論!

相關文章