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;
}