zoukankan      html  css  js  c++  java
  • 题解 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)

    题目还定义了,( ext{count}_t)表示有多少个(j)(r_j=t)。要求我们从小到大,每次填的位置必须是( ext{count})值最大的位置。

    如果(r_j>j),我们就从(j)(r_j)连一条边,则该图成为一个森林。( ext{count}_t),就是森林里以(t)为根的这棵树的大小。当我们在(p)位置填入一个数后,原本(r_j=p)的,就会变为(r_jleftarrow r_{p+1})。这就相当于把森林里的一整棵树,挂到另一棵树的某个节点下。

    可以用并查集维护这个森林。用一个( exttt{multiset})维护森林里所有树的大小。从小到大考虑所有数字,设当前数字要被放在位置(p),我们直接看以(p)为根的树,大小是否是( exttt{multiset})里的最大值即可。

    时间复杂度(O(nlog 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

    可以预处理一个数组,( ext{dis}[i][j]),其中(0leq i<2^7)(0leq jleq9),表示数字屏的一个数位,从(i)这种状态,变成(j)这个数字,需要点亮多少灯管。特别地,如果对于某个灯管,在(i)中是(1)而在(j)所在的状态中是(0),即:从(i)变成(j)需要熄灭某个灯管,则(i)永远不可能变成(j),此时令( ext{dis}[i][j]=inf)

    我们先判断可行性。做一个DP:设(dp[i][j])为一个( exttt{bool})型变量,表示考虑了数字屏的(i)位数字(为什么要从后往前DP,下面会讲),已经点亮了(j)根灯管,是否存在这样的方案。转移是比较简单的,枚举把第(i)位改成数字(k) ((0leq kleq9)),则(dp[i][j+ ext{dis}[a_i][k]] exttt{|=}dp[i+1][j])。容易发现,问题有解当且仅当(dp[1][K]= exttt{true})

    确定有解后,我们从前向后,依次构造出每一位。维护一个变量( ext{used}),表示已经使用了多少灯管。我们贪心地从(9)(0)考虑当前位变成几。当前位,能变成数字(k) ((0leq kleq 9)),当且仅当(dp[i+1][K-( ext{used}+ ext{dis}[a_i][k])]= exttt{true})。确定了当前位能变成哪个数字后,令( ext{used} exttt{+=}k)即可。

    现在,大家就很容易明白我们为什么要从后往前DP。因为在构造答案时,是贪心地从左向右构造。所以会需要判断后若干位,还剩若干次操作时的可行性。

    时间复杂度(O(n^2cdot 10+ncdot 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)秒。问题转化为,在这(mcdot gleq 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),就把新节点( exttt{push_front}),否则就( exttt{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;
    }
    
  • 相关阅读:
    迭代器、生成器、装饰器(转)
    Python小数据池
    接阿里云oss有感
    VSCode快捷键
    前端跨域调请求 nginx反向代理
    Git生成密钥
    【westorm系列之二】配置格式化
    钉钉安卓端无法渲染数据
    express 写接口
    js正则匹配身份证号 有坑
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/12767379.html
Copyright © 2011-2022 走看看