2021牛客暑期多校训练营10 F题: Train Wreck

F题: Train Wreck

原题链接:https://ac.nowcoder.com/acm/contest/11261/F

题目大意

有一个旧火车站,其只有一个死胡同轨道,可以视为一个栈,可以将火车入栈或者出栈。
n ( 1 ≤ n ≤ 1 0 6 ) n(1\le n\le 10^6) n(1n106) 辆彩色火车进站,第 i i i 辆火车的颜色是 k i ( ) 1 ≤ k t ≤ n k_i()1\le k_t\le n ki()1ktn ,进出栈的顺序已定,现在你需要为每次入栈操作分配一辆火车,使得每次入栈时栈中的火车颜色序列唯一。

题解

显然,若入栈时原有的火车前缀不同,则此时的火车序列一定不同(废话了属于是)。我们可以将每次入栈时的序列绘制成一棵树(帮助理解),如下例(此时数字表示入栈序号,并不是颜色):
在这里插入图片描述
(树上的序号实际上是通过入/出栈实现了一次深搜,因为一个结点只有当它的子节点递归全部返回(表现为其后入栈的火车全部出栈)时,才有可能再次入栈新火车并延伸出一个新的子节点)
对于每个序号点入栈时的序列可以看作一条从根出发到该节点的链(如3为1 2 3),那么我们只需要保证对于任意的兄弟节点的入栈火车颜色都不同,就可以保证每一条链都是不同的。
我们采取贪心策略,在 d f s dfs dfs 向下染色时优先采取当前数量最多的颜色(将数量多的同种颜色火车分在多层入栈,即 d f s dfs dfs 向下递归(表现为入栈过程)),以尽量避免兄弟间出现相同颜色,若此时出现兄弟染色时颜色不足的情况(如此时有 3 3 3 个节点为兄弟,而剩下的火车只有 2 2 2 种颜色),判定无解输出 − 1 -1 1 即可。
实际代码实现过程中不必要建立这样的一棵树,我们可以在入/出栈(即树上深搜)的过程中用向量维护当前链每一层的结点及其兄弟(兄弟用于染色和无解判断),入栈时当前层放入链所选的序号并往下递归一层,出栈时对当前链的最深层进行染色(并判断是否无解)并回溯一层。

参考代码

#include<bits/stdc++.h>
#define For(i,n,m) for(int i=n;i<=m;i++)
#define FOR(i,n,m) for(int i=n;i>=m;i--)
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
void read(int &x){int ret=0;char c=getchar(),last=' ';while(!isdigit(c))last=c,c=getchar();while(isdigit(c))ret=ret*10+c-'0',c=getchar();x=last=='-'?-ret:ret;}

const int MAXN=1e6+5;
int n,sum[MAXN<<1],k[MAXN],cnt[MAXN],ans[MAXN];//sum存储入/出栈顺序,cnt[i]记录i颜色火车的数量,ans记录合法方案的入栈顺序
priority_queue<pair<int,int> >q;//pair的first记录该项火车数(优先队列优先比较),second记录该项颜色
vector<int>vec[MAXN];//维护每一层链的节点与其兄弟
int main()
{
	read(n);
	char c;
	For(i,1,2*n){
		scanf(" %c",&c);
		if(c=='(')sum[i]=sum[i-1]+1;//若是入栈则数量+1
		else sum[i]=sum[i-1]-1;//若是出栈则数量-1
	}
	For(i,1,n){
		read(k[i]);
		cnt[k[i]]++;
	}
	For(i,1,n){
		if(cnt[i])q.push(mp(cnt[i],i));//若存在该种颜色的火车,把该项存入优先队列中
	}
	int tot=1,lev=0;//tot表示入栈序号,lev表示位于第几层
	For(i,1,2*n){
		if(sum[i]>sum[i-1]){//该次操作为入栈
			vec[lev].pb(tot++);//存入该层的节点
			vec[++lev].clear();//清空下一层的节点,因为当前层链的所选节点改变了,注意递归向下时lev指向的是一个新的空层(无节点)
		}
		else{//该次操作为出栈
			vector<pair<int,int> >tmp;//暂存染色过程中用过的颜色项与其剩余的火车数
			pair<int,int> v;
			if(vec[lev].empty()){//如果当前层是空层,直接返回即可
				lev--;
				continue;
			}
			For(i,0,vec[lev].size()-1){//不是空层,遍历该层的节点与其兄弟
				if(q.empty()){//如果颜色种数不足,无解
					puts("NO");
					return 0;
				}
				v=q.top(),q.pop();//取出优先队列顶端元素(当前数量最多的颜色)
				ans[vec[lev][i]]=v.se;//记录当前入栈序号的火车的颜色
				v.fi--;//该种颜色的火车数量减少
				if(v.fi)tmp.push_back(v);//如果还有剩余火车数则暂存入tmp中
			}
			if(tmp.size())For(i,0,tmp.size()-1)q.push(tmp[i]);//若tmp中有暂存的项则压回到优先队列中
			lev--;//返回上一层
		}
	}
	if(vec[lev].size()){//对第一层做判断(貌似这一步没有必要)
		vector<pair<int,int> >tmp;//暂存染色过程中用过的颜色项与其剩余的火车数(最后一次返回貌似没必要记录)
		pair<int,int> v;
		For(i,0,vec[lev].size()-1){
			if(q.empty()){
				puts("NO");
				return 0;
			}
			v=q.top(),q.pop();//取出优先队列顶端元素(当前数量最多的颜色)
			ans[vec[lev][i]]=v.se;//记录当前入栈序号的火车的颜色
			v.fi--;//该种颜色的火车数量减少
			if(v.fi)tmp.push_back(v);//如果还有剩余火车数则暂存入tmp中
		}
		if(tmp.size())For(i,0,tmp.size()-1)q.push(tmp[i]);//若tmp中有暂存的项则压回到优先队列中(后面不会用到,貌似也没必要)
	}
	puts("YES");//有解
	For(i,1,n)printf("%d%c",ans[i]," \n"[i==n]);//输出ans中存储的火车颜色
	return 0;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值