題解 CF1340 A,B,C Codeforces Round #637 (Div. 1)


比賽鏈接

CF1340A Nastya and Strange Generator

如題目所定義地,我們設\(r_j\)表示\(\geq j\)的位置中,第一個沒有被占用的位置。特別地,我們認為位置\(n+1\)永遠不被占用。即:如果\([j,n]\)都已經被占用了,那么\(r_j=n+1\)

題目還定義了,\(\text{count}_t\) 表示有多少個 \(j\)\(r_j=t\)。要求我們從小到大,每次填的位置必須是 \(\text{count}\) 值最大的位置。

如果\(r_j>j\),我們就從\(j\)\(r_j\)連一條邊,則該圖成為一個森林。\(\text{count}_t\),就是森林里以\(t\)為根的這棵樹的大小。當我們在\(p\)位置填入一個數后,原本\(r_j=p\)的,就會變為\(r_j\leftarrow r_{p+1}\)。這就相當於把森林里的一整棵樹,掛到另一棵樹的某個節點下。

可以用並查集維護這個森林。用一個\(\texttt{multiset}\)維護森林里所有樹的大小。從小到大考慮所有數字,設當前數字要被放在位置\(p\),我們直接看以\(p\)為根的樹,大小是否是\(\texttt{multiset}\)里的最大值即可。

時間復雜度\(O(n\log n)\)

參考代碼:

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

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

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

const int MAXN=1e5;
int n,a[MAXN+5],pos[MAXN+5],fa[MAXN+5],sz[MAXN+5];
int get_fa(int x){
	return (x==fa[x])?x:(fa[x]=get_fa(fa[x]));
}
multiset<int>s;
void union_s(int x,int y){
	x=get_fa(x);
	y=get_fa(y);
	if(x!=y){
		fa[x]=y;
		s.erase(s.find(sz[x]));
		if(y!=n+1)s.erase(s.find(sz[y]));
		sz[y]+=sz[x];
		if(y!=n+1)s.insert(sz[y]);
	}
}
int main() {
	int T;cin>>T;while(T--){
		cin>>n;
		s.clear();
		for(int i=1;i<=n;++i)cin>>a[i],pos[a[i]]=i;
		for(int i=1;i<=n+1;++i){
			fa[i]=i;
			if(i<=n)sz[i]=1,s.insert(1);
		}
		bool fail=false;
		for(int i=1;i<=n;++i){
			int u=pos[i];
			assert(get_fa(u)==u);
			if(sz[u]!=*s.rbegin()){
				fail=true;
				break;
			}
			union_s(pos[i],pos[i]+1);
		}
		if(fail)cout<<"NO"<<endl;
		else cout<<"YES"<<endl;
	}
	return 0;
}

CF1340B Nastya and Scoreboard

可以預處理一個數組,\(\text{dis}[i][j]\),其中\(0\leq i<2^7\)\(0\leq j\leq9\),表示數字屏的一個數位,從\(i\)這種狀態,變成\(j\)這個數字,需要點亮多少燈管。特別地,如果對於某個燈管,在\(i\)中是\(1\)而在\(j\)所在的狀態中是\(0\),即:從\(i\)變成\(j\)需要熄滅某個燈管,則\(i\)永遠不可能變成\(j\),此時令\(\text{dis}[i][j]=\inf\)

我們先判斷可行性。做一個DP:設\(dp[i][j]\)為一個\(\texttt{bool}\)型變量,表示考慮了數字屏的\(i\)位數字(為什么要從后往前DP,下面會講),已經點亮了\(j\)根燈管,是否存在這樣的方案。轉移是比較簡單的,枚舉把第\(i\)位改成數字\(k\) (\(0\leq k\leq9\)),則\(dp[i][j+\text{dis}[a_i][k]]\texttt{|=}dp[i+1][j]\)。容易發現,問題有解當且僅當\(dp[1][K]=\texttt{true}\)

確定有解后,我們從前向后,依次構造出每一位。維護一個變量\(\text{used}\),表示已經使用了多少燈管。我們貪心地從\(9\)\(0\)考慮當前位變成幾。當前位,能變成數字\(k\) (\(0\leq k\leq 9\)),當且僅當\(dp[i+1][K-(\text{used}+\text{dis}[a_i][k])]=\texttt{true}\)。確定了當前位能變成哪個數字后,令\(\text{used}\texttt{+=}k\)即可。

現在,大家就很容易明白我們為什么要從后往前DP。因為在構造答案時,是貪心地從左向右構造。所以會需要判斷后若干位,還剩若干次操作時的可行性。

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

參考代碼:

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

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

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

const int MAXN=2000,INF=1e9;
const string s[10]={"1110111", "0010010", "1011101", "1011011", "0111010", "1101011", "1101111", "1010010", "1111111", "1111011"};
int n,K,dis[1<<7][10],b[MAXN+5],ans[MAXN+5];
bool dp[MAXN+5][MAXN+5];
string a[MAXN+5];
int main() {
	for(int i=0;i<(1<<7);++i){
		for(int j=0;j<10;++j){
			bool fail=false;
			for(int k=0;k<7;++k)if(((i>>k)&1)&&s[j][k]=='0'){fail=true;break;}
			if(fail){
				dis[i][j]=INF;
				continue;
			}
			for(int k=0;k<7;++k)if(!((i>>k)&1)&&s[j][k]=='1')dis[i][j]++;
		}
	}
	cin>>n>>K;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		for(int j=0;j<7;++j)if(a[i][j]=='1')b[i]|=(1<<j);
	}
	dp[n+1][0]=1;
	for(int i=n;i>=1;--i){
		for(int j=0;j<=K;++j)if(dp[i+1][j]){
			for(int k=0;k<10;++k){
				if(dis[b[i]][k]==INF)continue;
				if(j+dis[b[i]][k]<=K)dp[i][j+dis[b[i]][k]]=1;
			}
		}
	}
	if(!dp[1][K]){
		cout<<-1<<endl;return 0;
	}
	int used=0;
	for(int i=1;i<=n;++i){
		int dig=-1;
		for(int j=9;j>=0;--j){
			if(dis[b[i]][j]==INF)continue;
			if(used+dis[b[i]][j]>K)continue;
			//cout<<j<<endl;
			if(!dp[i+1][K-(used+dis[b[i]][j])])continue;
			dig=j;
			break;
		}
		assert(dig!=-1);
		ans[i]=dig;
		used+=dis[b[i]][dig];
	}
	for(int i=1;i<=n;++i)cout<<ans[i];cout<<endl;
	return 0;
}

CF1340C Nastya and Unexpected Guest

把一個安全島,拆成\(g\)個點。\((i,left)\)表示在到達安全島\(i\)時,綠燈時間還剩\(left\)秒。問題轉化為,在這\(m\cdot g\leq 10^7\)個點之間,求從\(0\)\(n\)的最短路。

如果\(n\)不是安全島沒有關系。我們每到達一個狀態,就嘗試能不能用剩下的時間,從當前安全島走到\(n\)。若可以走到,就更新答案。

從每個狀態,只需要向相鄰的兩個安全島走。因為走到更遠的安全島必然會經過相鄰的。

在這張圖上做bfs。求出答案。

emmm...我寫着寫着就寫成了spfa。考場上AC了,但復雜度我也不會證明(如果有會證明的神仙請教教我!)。不過卡掉spfa的一般是網格圖。spfa在這種近似一維的線段上應該還是相當優秀的。

update:看了官方題解。我們把每\((g+r)\)秒視為一個時間單位,則兩個節點之間轉移的邊權要么是\(0\),要么是\(1\)。所以做01 bfs即可。時間復雜度嚴格的\(O(n+m)\)

所謂01 bfs,就是這種邊權只有\(0\), \(1\)的圖上求最短路的方法。具體做法是維護一個雙端隊列,如果邊權為\(0\),就把新節點\(\texttt{push_front}\),否則就\(\texttt{push_back}\)。這樣,相當於在線性的時間里,實現了dijkstra的效果。

參考代碼(spfa寫法):

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

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

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

const int MAXN=1e6,MAXM=1e4,MAXG=1000;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,m,d[MAXM+5],G,R,dis[MAXM+5][MAXG+5];
bool inq[MAXM+5][MAXG+5];
int main() {
	cin>>n>>m;
	for(int i=1;i<=m;++i)cin>>d[i];
	cin>>G>>R;
	sort(d+1,d+m+1);
	memset(dis,0x3f,sizeof(dis));
	queue<pii>q;
	if(d[1]!=0){
		if(d[1]>G){
			cout<<-1<<endl;return 0;
		}
		if(G-d[1]==0){
			dis[1][G]=G+R;
			q.push(mk(1,G));
		}
		else{
			dis[1][G-d[1]]=d[1];
			q.push(mk(1,G-d[1]));
		}
	}
	else{
		dis[1][G]=0;
		q.push(mk(1,G));
	}
	ll ans=INF;
	while(!q.empty()){
		int u=q.front().fi;
		int le=q.front().se;
		q.pop();
		//cout<<u<<" "<<le<<endl;
		inq[u][le]=0;
		ll D=dis[u][le];
		if(n-d[u]<=le){
			ans=min(ans,D+n-d[u]);
			//return 0;
		}
		if(u!=1 && d[u]-d[u-1]<=le){
			if(d[u]-d[u-1]==le){
				ll nd=D+le+R;
				if(nd<dis[u-1][G]){
					dis[u-1][G]=nd;
					if(!inq[u-1][G]){
						inq[u-1][G]=1;
						q.push(mk(u-1,G));
					}
				}
			}
			else{
				ll nd=D+d[u]-d[u-1];
				if(nd<dis[u-1][le-(d[u]-d[u-1])]){
					dis[u-1][le-(d[u]-d[u-1])]=nd;
					if(!inq[u-1][le-(d[u]-d[u-1])]){
						inq[u-1][le-(d[u]-d[u-1])]=1;
						q.push(mk(u-1,le-(d[u]-d[u-1])));
					}
				}
			}
		}
		if(u!=m && d[u+1]-d[u]<=le){
			if(d[u+1]-d[u]==le){
				ll nd=D+le+R;
				if(nd<dis[u+1][G]){
					dis[u+1][G]=nd;
					if(!inq[u+1][G]){
						inq[u+1][G]=1;
						q.push(mk(u+1,G));
					}
				}
			}
			else{
				ll nd=D+d[u+1]-d[u];
				if(nd<dis[u+1][le-(d[u+1]-d[u])]){
					dis[u+1][le-(d[u+1]-d[u])]=nd;
					if(!inq[u+1][le-(d[u+1]-d[u])]){
						inq[u+1][le-(d[u+1]-d[u])]=1;
						q.push(mk(u+1,le-(d[u+1]-d[u])));
					}
				}
			}
		}
	}
	if(ans==INF)cout<<-1<<endl;
	else cout<<ans<<endl;
	return 0;
}


免責聲明!

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



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