2021牛客暑期多校训练营10 F.Train Wreck(栈,并查集,优先队列,贪心)

LINK

题意

n n n个元素,第 i i i个元素的颜色为 c o l o r i color_i colori

给定一个长度为 2 ∗ n 2*n 2n的进出栈括号序列(每个元素恰好进栈一次出栈一次)

要求你构造长度为 n n n的序列 a n s ans ans,其中 a n s i ans_i ansi表示第 i i i次入栈的元素颜色

满足每次刚加入 a n s i ans_i ansi后栈中形成的颜色序列抽出来(显然有 n n n个颜色序列)

n n n个颜色序列是各不相同的


对这个进出栈的括号序列进行模拟

记录最后一次退栈时退出的元素是 x 1 x_1 x1

那么当下次如果是让元素 x 2 x_2 x2入栈,必须满足 x 1 x_1 x1 x 2 x_2 x2的颜色不同

因为这两个序列的前缀都是相同的了

这样一来,现在的前缀已经不同,所以下次入栈就没有限制,我们令 x 1 = 0 x_1=0 x1=0

但一旦出现退栈,我们又需要记录 x 1 x_1 x1,这样反复做

我们可以得到哪些元素的颜色需要互不相同,做一个并查集即可

于是现在得到了 k k k个集合,第 i i i个集合内有 s i z i siz_i sizi个元素,需要满足集合内的颜色互不相同

这就是一个经典的贪心,我们按照每种颜色多少排个序,倒序枚举

设第 j j j中颜色有 z z z个,我们给当前前 z z z大的集合都分配一个颜色 j j j

按照这样一直做,集合的动态大小使用优先队列维护即可

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
int n,top,fa[maxn],shu[maxn],nor[maxn],ind[maxn],sta[maxn];
char a[maxn<<1];
int find(int x){ return x==fa[x]?x:fa[x]=find( fa[x] );}
void join(int q,int w){ fa[find(q)]=find(w); }
priority_queue<pair<int,int> >q,color;
void OVER()
{
	cout << "NO";
	exit( 0 );
}
vector<int>ans[maxn];
int main()
{
	ios::sync_with_stdio( false );
	cin >> n >> ( a+1 );
	for(int i=1;i<=n;i++)
	{
		int col; cin >> col;
		shu[col]++;
	}
	for(int i=1;i<=n;i++)
		if( shu[i] )	color.push( {shu[i],i} );	
	for(int i=1;i<=n;i++)	fa[i] = i;
	int id = 0, las = 0;
	for(int i=1;i<=2*n;i++)
	{
		if( a[i]=='(' )
		{
			sta[++top] = ++id;
			if( las )	join( las,id ), las = 0;
		}
		else	las = sta[top--];
	}
	for(int i=1;i<=n;i++)	fa[i] = find( fa[i] );
	for(int i=1;i<=n;i++)	nor[ fa[i] ]++;
	for(int i=1;i<=n;i++)	
		if( nor[i] )	q.push( { nor[i],i } );
	while( !color.empty() )
	{
		int cap = color.top().first, type = color.top().second;
		color.pop();
		vector<pair<int,int> >vec;
		for(int i=1;i<=cap;i++)
		{
			if( q.empty() )	OVER();
			int fi = q.top().first, se = q.top().second;
			ans[se].push_back( type );
			q.pop();
			if( fi>1 )
				vec.push_back( {fi-1,se} );
		}
		for(auto v:vec )	q.push( v );
	}
	cout << "YES\n";
	for(int i=1;i<=n;i++)
		cout << ans[fa[i]][ind[fa[i]]++] << " ";
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值