Luogu P7056 Insider's Information

题目传送门

发现要求一个数在另外两个数中间的限制基本不可做,考虑进行转化。

既然一个数在两个数之间,那我们如果从两边向中间填数,就变成了一个数填的次序在另外两个之后。

这个东西我们非常熟悉,就是拓扑排序。

但是,这道题显然不会这么简单,因为如果我们满足 \(b\)\(a\) 之后放,\(b\) 放的位置就直接决定了能不能造成贡献。换句话说,我们只需要满足 \(b\)\(a\) 后放或者 \(b\)\(c\) 后放。

那么又出现了一个问题,\(a,c\) 谁应该先放呢?

其实这并不重要,因为 \(a,c\) 可以认为是对称的。

现在我们考虑要放一个数 \(x\),应该怎么办。

显然 \(x\) 要么放左边要么放右边,那么我们只需要放贡献更大的那一边就可以满足一半的限制了。

做到这里,我们就只需要算一下放哪边的贡献更大了。

考虑 \(x\) 作为三元组中的哪个数进行贡献。

\(x\) 作为 \(b\)

  • \(a,c\) 均未确定位置:不可能存在此情况。

  • \(a,c\) 均确定位置:之前已经计算。

  • 其余情况:\(x\) 放到已经放的数放的那边。

\(x\) 作为 \(a,c\)

  • \(a,c\) 均未确定位置:\(x\) 的入度减去 \(1\)

  • \(a,c\) 均确定位置:不可能存在此情况。

  • 其余情况:\(x\) 放到已经放的数放的那边的反方向。

然后我们就做完了。

AC code:

#include<bits/stdc++.h>
#define int long long
#define N 100005
#define pii pair<int,int>
#define x first
#define y second
#define mod 1000000007
#define inf 2e18
using namespace std;
int T=1,n,m,a[N],b[N],c[N],d[N],p[N],res[N];
vector<int>f[N],g[N];
void solve(int cs){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>a[i]>>b[i]>>c[i];
		f[b[i]].push_back(i);
		g[a[i]].push_back(i);
		g[c[i]].push_back(i);
		d[b[i]]++;
	}
	queue<int>q;
	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();
		q.pop();
		int s1=0,s2=0;
		for(auto i:f[u]){
			if(p[a[i]]&&p[c[i]])continue;
			if(p[a[i]]){
				if(p[a[i]]<=l)s1++;
				else s2++;
			}
			else{
				if(p[c[i]]<=l)s1++;
				else s2++;
			}
		}
		for(auto i:g[u]){
			if(p[a[i]]&&!p[b[i]]){
				if(p[a[i]]<=l)s2++;
				else s1++;
			}
			if(p[c[i]]&&!p[b[i]]){
				if(p[c[i]]<=l)s2++;
				else s1++;
			}
			if(!p[a[i]]&&!p[c[i]]){
				if(!--d[b[i]]){
					q.push(b[i]);
				}
			}
		}
		if(s1>s2)p[u]=l++;
		else p[u]=r--;
	}
	for(int i=1;i<=n;i++){
		res[p[i]]=i;
	}
	for(int i=1;i<=n;i++){
		cout<<res[i]<<' ';
	}
	cout<<'\n';
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
//	cin>>T;
//	init();
	for(int cs=1;cs<=T;cs++){
		solve(cs);
	}
	return 0;
}
posted @ 2025-04-15 20:39  zxh923  阅读(6)  评论(0)    收藏  举报