[NWRRC2015] Insider's Information

一、题目

点此看题

二、解法

我们从两侧往中间插入,那么三元组 \((a,b,c)\) 合法的必要条件是:\(a\)\(c\) 先于 \(b\) 插入。

发现我们可以很轻易地让所有三元组都满足必要条件,方法是连边 \(a\rightarrow b\)\(c\rightarrow b\)但是只让 \(b\) 的入度增加 \(1\) ,然后跑拓扑排序,只要按照拓扑序插入即可保证 \(a\)\(c\) 先于 \(b\) 插入。由于题目条件 保证存在一个排列满足所有限制,可知任何时刻都会有入度为 \(0\) 的点,那么一定也存在这样的拓扑序。

下一步是依照拓扑序,决定每个点是放最左边还是放在最右边。考虑三元组 \((a,b,c)\) 在拓扑序上可能的顺序有:(a,b,c),(a,c,b),(c,b,a),(c,a,b),由于 \(a,c\) 是对称的,我们不妨只考虑 \(a\) 已经被放在最左边的情况:

  • 若顺序是 (a,b,c),那么产生贡献的充要条件是 \(b\) 被放在最左边。
  • 若顺序是 (a,c,b),那么产生贡献的充要条件是 \(c\) 被放在最右边。

这说明:我们只需要在将要加入第二个元素的时候考虑这个限制。实现时把拓扑排序和决定位置同时进行,假设现在要插入点 \(u\),我们考虑 \(u\) 作为 \(b\) 的限制(对应的 \(a\) 已经被插入),和 \(u\) 作为 \(c\) 的限制(对应的 \(a\) 已经被插入),可以统计 \(u\) 放在最左边的贡献和 \(u\) 放在最右边的贡献,去较大的那边放置即可。

时间复杂度 \(O(n)\),由于每次都是少数服从多数,被满足的限制至少有 \(\lceil\frac{m}{2}\rceil\) 个。

三、总结

我觉得本题的最大亮点就是把限制放在单点上,为达成这一目的,我们可能需要先拆解限制(比如本题一开始的必要条件),然后在若干先决条件的基础上可以在单点上进行决策。类似精妙拆限制的题还有:JOISC 2015 Day2 Keys

#include <cstdio>
#include <vector>
#include <iostream>
#include <queue>
using namespace std;
const int M = 100005;
#define pb push_back
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[M],b[M],c[M],d[M],p[M],ans[M];
vector<int> g[M],s[M];queue<int> q;
signed main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)
	{
		a[i]=read();b[i]=read();c[i]=read();
		d[b[i]]++;s[b[i]].pb(i);
		g[a[i]].pb(i);g[c[i]].pb(i);
	}
	for(int i=1;i<=n;i++)
		if(!d[i]) q.push(i);
	int l=1,r=n;
	while(!q.empty())
	{
		int u=q.front(),w[2]={};q.pop();
		for(int v:g[u])
		{
			if(p[a[v]] || p[c[v]])
				w[p[u^a[v]^c[v]]<l]++;
			else if(!--d[b[v]]) q.push(b[v]);
		}
		for(int v:s[u])
			if(!(p[a[v]] && p[c[v]]))
				w[(p[a[v]]^p[c[v]])>r]++;
		p[u]=w[0]<w[1]?r--:l++;
	}
	for(int i=1;i<=n;i++) ans[p[i]]=i;
	for(int i=1;i<=n;i++) printf("%d ",ans[i]);
	puts("");
}
posted @ 2022-06-09 11:25  C202044zxy  阅读(150)  评论(1编辑  收藏  举报