Luogu P11726 [JOIG 2025] 最悪の記者 5 / Worst Reporter 5 [ 蓝 ] [ 图论建模 ] [ 分类讨论 ]
最悪の記者 5 / Worst Reporter 5:有点难度的图论建模。
注意到只有相邻的两个人才能交换位置,所以此题唯一的限制就是交换的两个人位置需要相邻。
考虑如何刻画这个限制,因为我们是要求最终的序列,同时交换操作又是可逆的,所以我们可以倒序操作。具体地,当我们从后往前遍历到某个交换操作的时候,说明这两个人当前所在的位置是相邻的。因此我们只需要找到该操作后面的操作中,这两个位置分别被换成了什么人,这两个人在最后的序列上就是相邻的。
维护分别被换成了什么人,直接记录一个映射,交换的时候把映射交换即可。
求出相邻限制后,显然就是可以图论建模的了:把相邻限制看做无向边,则每个连通块都必须是一条链,否则一定无解。同时注意重边是合法的。在最小化字典序的时候以链两端的最小编号为关键字排序即可。
实现上,可以使用并查集求连通块,并维护每个连通块的点数、去除重边后的边数、每个点的度数就可以快速判断是否是一条链了。
时间复杂度 \(O(n\log n)\)。
#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
using pi=pair<int,int>;
const int N=500005;
const int inf=0x3f3f3f3f;
int n,m,s[N],a[N],b[N],f[N],sz[N],ds[N],szd[N],idx=0,id[N],ltk[N];
vector<int>g[N];
map<pi,bool>vis;
void init()
{
for(int i=1;i<=n;i++)
{
f[i]=i;
sz[i]=0;
szd[i]=1;
}
}
int findf(int x)
{
if(f[x]!=x)f[x]=findf(f[x]);
return f[x];
}
void combine(int x,int y)
{
int fx=findf(x),fy=findf(y);
if(fx!=fy)
{
f[fx]=fy;
sz[fy]+=sz[fx];
szd[fy]+=szd[fx];
}
}
bitset<N>viss;
void solve(int s)
{
int p=s;
while(1)
{
cout<<p<<'\n';
viss[p]=1;
int orip=p;
for(auto v:g[p])
{
if(viss[v]==0)
{
p=v;
break;
}
}
if(orip==p)break;
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
init();
for(int i=1;i<=m;i++)
{
cin>>a[i]>>b[i];
}
for(int i=1;i<=n;i++)
{
s[i]=i;
}
for(int i=m;i>=1;i--)
{
int u=s[a[i]],v=s[b[i]];
if(u==v)
{
cout<<"No";
return 0;
}
if(vis[{min(u,v),max(u,v)}]==0)
{
vis[{min(u,v),max(u,v)}]=1;
combine(u,v);
sz[findf(u)]++;
ds[u]++;ds[v]++;
g[u].push_back(v);
g[v].push_back(u);
}
swap(s[a[i]],s[b[i]]);
}
for(int i=1;i<=n;i++)
{
if(ds[i]>=3)
{
cout<<"No";
return 0;
}
if(szd[findf(i)]!=sz[findf(i)]+1)
{
cout<<"No";
return 0;
}
if(findf(i)==i)
{
id[i]=++idx;
}
}
for(int i=1;i<=idx;i++)
{
ltk[i]=inf;
}
for(int i=1;i<=n;i++)
{
if(ds[i]<=1)
{
ltk[id[findf(i)]]=min(ltk[id[findf(i)]],i);
}
}
sort(ltk+1,ltk+idx+1);
cout<<"Yes\n";
for(int i=1;i<=idx;i++)
{
solve(ltk[i]);
}
return 0;
}

浙公网安备 33010602011771号