1. 程式人生 > 其它 >Codeforces Round #756 (Div. 3)

Codeforces Round #756 (Div. 3)

C. Polycarp Recovers the Permutation

有一個元素個數為 \(n\) 的陣列 \(p\),我們每次執行以下操作:

\(p\) 最左邊和最右邊的數中較小的一個,如果選的是最左邊,就加入新陣列 \(a\) 的左邊,否則加入 \(a\)的右邊。

現在給出 \(a\),求 \(p\)。如果無解,輸出 \(-1\) ;如果多組解,輸出任意一組即可。

多測,有 \(t\) 組資料 .

\(1\leq t\leq 10^4,1\leq n\leq 2\cdot 10^5,1\leq a_i\leq n,\sum n\leq 2\cdot 10^5\)

sol1

考慮,最後剩下的兩個/一個一定在開頭/結尾 . 所以,直接一層一層的撥下來 . 但是期間遇到了很多問題 .

比如說奇數序列我分情況討論了 \(4\) 鍾 . div3c 一個 difficulty 1000 的題目我寫了 20 min . 相當自閉 .

時間複雜度 : \(O(n)\)

空間複雜度 : \(O(n)\)

code
#include<bits/stdc++.h>
using namespace std;
int t;
int n;
deque<int>a,na,p,b,ans;
inline void init(){
	a.clear();b.clear();p.clear();ans.clear();
}
bool ok(){
	for(int i=0;i<n;i++)if(a[i]!=b[i])return false;
	return true;
}
void solve(){
	init();
	cin>>n;
	for(int i=0;i<n;i++){
		int x;cin>>x;
		a.push_back(x);
	}
	na=a;
	while(!a.empty()){
		p.push_front(a.front());a.pop_front();
		if(!a.empty())p.push_back(a.back()),a.pop_back();
	}
	a=na;
	ans=p;b.clear();
	while(!p.empty()){
		if((int)p.size()==1){
			b.push_back(p.back());
			if(ok()){
				for(int i=0;i<(int)ans.size();i++)cout<<ans[i]<<" ";
				cout<<"\n";
				return;
			}
			b.pop_back();
			b.push_front(p.back());
			if(ok()){
				for(int i=0;i<(int)ans.size();i++)cout<<ans[i]<<" ";
				cout<<"\n"; 
				return;
			}
			p.pop_back();
			break;
		}
		if(p.front()<p.back())b.push_front(p.front()),p.pop_front();
		else b.push_back(p.back()),p.pop_back();	
	}
	while(!a.empty()){
		p.push_front(a.front());a.pop_front();
		if(!a.empty())p.push_front(a.back()),a.pop_back();
	}
	a=na;
	ans=p;b.clear();
	while(!p.empty()){
		if((int)p.size()==1){
			b.push_back(p.back());
			if(ok()){
				for(int i=0;i<(int)ans.size();i++)cout<<ans[i]<<" ";
				cout<<"\n";
				return;
			}
			b.pop_back();
			b.push_front(p.back());
			if(ok()){
				for(int i=0;i<(int)ans.size();i++)cout<<ans[i]<<" ";
				cout<<"\n"; 
				return;
			}
			p.pop_back();
			break;
		}
		if(p.front()<p.back())b.push_front(p.front()),p.pop_front();
		else b.push_back(p.back()),p.pop_back();	
	}
	cout<<"-1\n";
} 
int main(){
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}
/*inline? ll or int? size? min max?*/
sol2

但是這個做法非常複雜,有更簡便的做法嗎?分析一下,發現,\(n\) 必然在隊首或者隊尾 .

因為,在操作過程中 ,\(n\) 如果為現在佇列的隊首或者隊尾,與其他元素比較,其他元素必然會被比到前面去,那麼,\(n\) 就會到了最後才被放到最終佇列的對頭/隊尾 . 所以,\(n\) 必然是隊頭/隊尾 . 否則無解 .

因此,考慮一個比較極端的情況,在原來的序列裡,\(n\) 就是隊頭 / 隊尾,那麼剩下的元素則是依次放入現在得到的序列,相當於 reverse 一下了序列 . 感覺十分巧妙,果真我是智障 .

時間複雜度 : \(O(n)\)

空間複雜度 : \(O(n)\)

code
#include<bits/stdc++.h>
using namespace std;
int t;
int n;
int a[200010];
void solve(){
	cin>>n;
	for(int i=0;i<n;i++)cin>>a[i];
	if(a[0]!=n&&a[n-1]!=n)cout<<"-1\n";
	else{
		for(int i=n-1;i>=0;i--)cout<<a[i]<<" ";
		cout<<"\n";
	}
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}
/*inline? ll or int? size? min max?*/
D. Weights Assignment For Tree Edges

給定一個含有 \(n\) 個點的有根樹和一個長度為 \(n\) 的排列 \(p\),你需要給這棵樹的每一條邊賦權,使得對於所有的 \(1 \leq i<j \leq n\) ,點 \(p_{i}\) 到根的距離小於點 \(p_{j}\) 到根的距離。

你需要保證每條邊的邊權都是 \([1,1 \times 10^{9}]\) 內的整數。

多測,有 \(t\) 組資料。

若無解,輸出 \(-1\)。否則輸出 \(n\) 個整數,第 \(i\) 個整數 \(w_{i}\) 表示點 \(i\) 到其父親的邊權為 \(w_{i}\) ,特別地,預設根到其父親的邊權為 \(0\)

\(1\leq t\leq 10^4,1\leq n\leq 2\cdot 10^5,1\leq b_i\leq n,1\leq p_i\leq n\)

第一眼看上去會有點懵,冷靜一下,發現,考慮從 \(p_0\)\(p_{n-1}\) 依次考慮,首先,這棵樹不能有賦值 . 其次,考慮加入第一個點,\(p_0\)\(root\) 上都賦值為 \(1\) 即可 . 現在,再加入 \(p_1\) …… 這樣一個過程 .

\(dis(p_i)\) 必須比 \(dis(p_0),dis(p_1),\)$\cdots $$,dis(p_{i-1})$ 都大,其中,得到 \(dis(p_0)<dis(p_1)<\cdots <dis(p_{n-1})\) .

所以,加入 \(p_i\) 上必定有一條邊沒有被賦值過 . 否則則無解 . 考慮將這條邊的邊權設為 \(\max(1,dis(p_{i-1})-dis(fa(i))+1)\) . 即可 .

時間複雜度 : \(O(n)\)

空間複雜度 : \(O(n)\)

code
#include<bits/stdc++.h>
using namespace std;
int t;
int n,rt;
int b[200010],p[200010];
long long sum[200010],ans[200010];
void init(){
	for(int i=0;i<n;i++)ans[i]=sum[i]=0;
}
void solve(){
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>b[i];
		b[i]--;
		if(i==b[i])rt=i;
	}
	for(int i=0;i<n;i++)cin>>p[i],p[i]--;	
	long long tmp=-1;
	for(int i=0;i<n;i++){
		tmp++;
		long long now=0; 
		vector<int>v;
		int x=p[i];
		while(b[x]!=x){
			if(ans[x]!=0){
				now=sum[x];
				break;
			}
			v.push_back(x);
			x=b[x];
		}
		if((int)v.size()==0&&i!=0){
			cout<<"-1\n";
			init();
			return;
		}
		if((int)v.size()==0)continue;
		reverse(v.begin(),v.end());
		tmp-=now;
		for(int j=0;j<(int)v.size();j++)ans[v[j]]=1,tmp--;
		if(tmp>0)ans[v[0]]+=tmp;
		sum[v[0]]=now+ans[v[0]];
		for(int j=1;j<(int)v.size();j++)sum[v[j]]=sum[v[j-1]]+ans[v[j]];
		tmp=sum[v[(int)v.size()-1]];
	}
	for(int i=0;i<n;i++)cout<<ans[i]<<" ";cout<<endl;
	init(); 
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}
/*inline? ll or int? size? min max?*/
E2. Escape The Maze (hard version)

現在有一棵樹,其中 \(1\) 號節點是樹根,也就是小 v 所在的點,樹上邊都是雙向邊。

樹上有 \(n\) 個點,您有 \(k\) 位朋友。每個時刻,小 v 和小 v 的朋友都會順著樹上的邊走向另一個節點。

小 v 會獲勝當且僅當小 v 走到了葉子節點。

小 v 會輸當且僅當您被一個朋友在小v 獲勝之前在任何房間或走廊遇到。

問:至少在樹中保留多少個朋友才能使小 v 在遊戲中失敗?

多測,有 \(t\) 祖資料 .

\(1\leq t\leq 10^4,1\leq k<n\leq 2\cdot 10^5,\sum n\leq 2\cdot 10^5\)

考慮每個朋友都向根走都是最優的 .

因此,設計 \(f(i)\) 表示在子樹 \(i\) 失敗的最少保留節點個數 . \(g(i)\) 表示子樹 \(i\) 中距離 \(i\) 距離最小的有朋友所在的節點 .

除了平常的轉意外,還要考慮 \(g(i)\) 中儲存的節點是否可以使得 \(i\) 不能到達 .

如果 \(dep(i)+1\geq dep(g(i))-dep(i)+1\) 即是會在 \(i\)\(i\) 的祖先上相遇,那麼節點 \(i\) 就不可被到達 .

時間複雜度 : \(O(n)\)

空間複雜度 : \(O(n)\)

code
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9+10;
int t;
int k,n;
bool vis[200010];
vector<int>nei[200010];
int dep[200010];
int f[200010],g[200010];
void init(){
	for(int i=0;i<n;i++)vis[i]=false;
	for(int i=0;i<n;i++)nei[i].clear();
	for(int i=0;i<n;i++)dep[i]=0;
}
void get_dep(int x,int fa){
	for(int i=0;i<(int)nei[x].size();i++){
		int to=nei[x][i];
		if(to==fa)continue;
		dep[to]=dep[x]+1;
		get_dep(to,x);
	}
}
void dfs(int x,int fa){
	int sum=0;int legal=0;
	for(int i=0;i<(int)nei[x].size();i++){
		int to=nei[x][i];
		if(to==fa)continue;
		if(legal==0)legal=1;
		dfs(to,x);
		sum+=f[to];
		if(f[to]==inf)legal=2;
		if(g[to]!=-1){
			if(g[x]==-1||dep[g[x]]>dep[g[to]]){
				g[x]=g[to];
			}
		}
	}
	if(legal==1)f[x]=min(f[x],sum);
	if(vis[x])g[x]=x,f[x]=1;
	if(g[x]!=-1&&dep[x]+1>=dep[g[x]]-dep[x]+1)f[x]=1;
}
void solve(){
	cin>>n>>k;
	for(int i=0;i<k;i++){
		int x;
		cin>>x;
		x--;
		vis[x]=true;
	}
	for(int i=0;i<n-1;i++){
		int u,v;
		cin>>u>>v;
		u--;v--;
		nei[u].push_back(v);
		nei[v].push_back(u);
	}
	get_dep(0,-1);
	for(int i=0;i<n;i++)f[i]=inf;
	for(int i=0;i<n;i++)g[i]=-1;
	dfs(0,-1);
	if(f[0]==inf)cout<<"-1\n";
	else cout<<f[0]<<"\n";
	init();
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}
/*inline? ll or int? size? min max?*/
F. ATM and Students

給定一個長度為 \(n\) 的序列 \(a\) 和一個整數 \(s\),求一段最長的區間 \([l,r]\) 滿足

\[\forall i\in[l,r],s+\sum\limits_{j=l}^i a_j\geq 0 \]

多測,有 \(t\) 組資料 .

\(1\leq t\leq 10^4,1\leq n\leq 2\cdot 10^5,0\leq s\leq 10^9,-10^9\leq a_i\leq 10^9\)

用一個線段樹維護字首和 .

考慮從後往前加入 \(a_i\) ,相當於區間加 \(a_i\) . 線上段樹上二分第一個字首和 \(<0\) 的位置即可 .

時間複雜度 : \(O(n\log n)\)

空間複雜度 : \(O(n)\)

code
#include<bits/stdc++.h>
using namespace std;
int t;
int n,s;
int a[200010];
long long ts[200010<<2],tag[200010<<2];
void build(int x,int l,int r){
	if(l==r){
		ts[x]=tag[x]=0;
		return;
	}
	int mid=(l+r)>>1;
	build(x<<1,l,mid);
	build(x<<1|1,mid+1,r);
	ts[x]=tag[x]=0;
}
inline void pd(int x){
	if(!tag[x])return;
	ts[x<<1]+=tag[x];
	tag[x<<1]+=tag[x];
	ts[x<<1|1]+=tag[x];
	tag[x<<1|1]+=tag[x];
	tag[x]=0;
}
void upd(int x,int l,int r,int cl,int cr,int val){
	if(l==cl&&r==cr){
		if(l!=r)pd(x);
		ts[x]+=val;
		tag[x]+=val;
		return;
	}
	pd(x);
	int mid=(l+r)>>1;
	if(cr<=mid)upd(x<<1,l,mid,cl,cr,val);
	else if(mid+1<=cl)upd(x<<1|1,mid+1,r,cl,cr,val);
	else{
		upd(x<<1,l,mid,cl,mid,val);
		upd(x<<1|1,mid+1,r,mid+1,cr,val);	
	}
	ts[x]=min(ts[x<<1],ts[x<<1|1]);
}
int qry(int x,int l,int r,int cl,int cr){
	if(l==cl&&r==cr){
		if(l!=r)pd(x);
		if(s+ts[x]>=0)return r;
		if(l==r)return -1;
		int mid=(l+r)>>1;
		if(s+ts[x<<1]<0)return qry(x<<1,l,mid,cl,mid);
		int res=qry(x<<1|1,mid+1,r,mid+1,cr);
		if(res!=-1)return res;
		return mid;
	}
	pd(x);
	int mid=(l+r)>>1;
	if(cr<=mid)return qry(x<<1,l,mid,cl,cr);
	else if(mid+1<=cl)return qry(x<<1|1,mid+1,r,cl,cr);
	else{
		int r1=qry(x<<1,l,mid,cl,mid);
		if(r1!=mid)return r1;
		int r2=qry(x<<1|1,mid+1,r,mid+1,cr);
		return max(r2,mid);
	}	
}
int qry(int x,int l,int r,int pos){
	if(l==r)return ts[x];
	pd(x);
	int mid=(l+r)>>1;
	if(pos<=mid)return qry(x<<1,l,mid,pos);
	else return qry(x<<1|1,mid+1,r,pos);
}
void solve(){
	cin>>n>>s;
	for(int i=0;i<n;i++)cin>>a[i];
	build(1,0,n-1);
	int ans=0,l=0,r=0;
	for(int i=n-1;i>=0;i--){
		upd(1,0,n-1,i,n-1,a[i]);
		int pos=qry(1,0,n-1,i,n-1);
		if(pos!=-1){
			if(ans<pos-i+1){
				ans=pos-i+1;
				l=i;
				r=pos;
			}
		}
	}
	if(ans==0){
		cout<<"-1\n";
		return;
	}
	cout<<l+1<<" "<<r+1<<"\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}
/*inline? ll or int? size? min max?*/
G. Robot and Candies

給定一張 \(n \times m\)\(01\) 網格圖,每次選一個點 \((1,x)\) 出發,可以從 \((x,y)\)\((x+1,y+1)\) 或者 \((x+1,y-1)\) ,問至少出發多少次才能經過每個 \(1\) 至少一次。

多測,有 \(t\) 組資料 .

\(1\leq t\leq 10^4,nm\leq 10^6,\sum nm\leq 10^6\)

如果是恰好一次,應該就是網路流了,但是不是 .

一開始有個錯誤的貪心思路 .

首先,將網格黑白染色 . 觀察到道路最多有 \(m+1\) 個 . 所以,考慮維護從開頭到上一行的道路最後節點 . 能到達的範圍對應著一個區間,考慮當前點選擇包含它的右端點最小的區間 .

但是這個做法是錯誤的 . 題目中的第 3 個樣例 . 當前這行的結果會對下一行造成影響 . 這樣選不一定是最優的 .

此時,考慮每一條路徑從上到下依次選擇的是什麼 .

考慮儲存每一行最小的 \(1\) 節點 . 從小到大排序,依次加入當前路徑,如果可以加入則加入,不可以則不加入 . 即為最優 .

時間複雜度 : \(O(nm\log n)\)

空間複雜度 : \(O(nm)\)

code
#include<bits/stdc++.h>
using namespace std;
int t;
int n,m;
vector<string>board;
set<int>::iterator it;
vector<int>v[1000010];
int ans=0;
void solv(int val){
	for(int i=0;i<n;i++)v[i].clear();
	for(int i=0;i<n;i++)for(int j=m-1;j>=0;j--)if((i+j)%2==val){
		if(board[i][j]=='1')v[i].push_back(j);
	}
	for(int T=0;T<m;T++){
		set<int>s;
		vector<pair<int,int> >vec;
		for(int i=0;i<n;i++)if((int)v[i].size()>0){
			vec.push_back(make_pair(v[i].back(),i));
		}
		if((int)vec.size()==0)break;
		++ans;
		sort(vec.begin(),vec.end());
		s.insert(vec[0].second);
		for(int i=0;i<(int)vec.size();i++){
			bool ok=true;
			it=s.lower_bound(vec[i].second);
			if(it!=s.end()){
				if(abs(v[*it].back()-vec[i].first)>abs(*it-vec[i].second))
					ok=false;
			}
			if(it!=s.begin()){
				it--;
				if(abs(v[*it].back()-vec[i].first)>abs(*it-vec[i].second))
					ok=false;
			}
			if(ok)s.insert(vec[i].second);
		}
		for(set<int>::iterator it=s.begin();it!=s.end();it++)v[*it].pop_back();
	}
}
void solve(){
	board.clear();ans=0; 
	cin>>n>>m;
	for(int i=0;i<n;i++){
		string s;
		cin>>s;
		board.push_back(s);
	}
	solv(0);
	solv(1);
	cout<<ans<<endl;
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}
/*inline? ll or int? size? min max?*/