Luogu P1053 [NOIP 2005 提高组] 篝火晚会 题解 [ 蓝 ] [ 图论建模 ] [ 贪心 ] [ 置换环 ]
篝火晚会:挺不错的思维题。
考虑如何表示所有人的愿望,显然可以把“相邻”的条件转化为图论角度考虑,如果两人相邻则在他们之间连边。那么这张图合法当且仅当所有人都在一个连通块中,且这张图是一个环。
继续考虑有没有可能出现无法进行操作使局面变成这个环的情况。发现这是不可能的,因为如果每次都进行 \(k=2\) 的操作,就一定可以使当前局面变成任意的局面。
首先对于判定环相同的条件有点难做,于是先断环为链,枚举链的位置,则原序列与链匹配上了即为环相等。注意环可以正着也可以反着,因此要做两遍。
那么怎样的操作才是最优的呢?注意到链确定后每个人要去的位置也随之固定了,并且操作支持将任意位置的人循环移位,于是就形成了一个置换环。每个置换环内进行一次移动,置换环上的所有人便和链匹配上了。
因此一个链的最小操作代价就是 \(n-x\),其中 \(x\) 表示不在任何一个置换环中的人的个数,即 \(pos=i\) 的个数。
当前的算法是 \(O(n^2)\) 的,考虑如何优化到线性。发现一个位置的人不在置换环中,只发生在 \(pos=i\) 的情况,因此对应的链也是唯一的。所以可以不去枚举链,而是让每个人反过来贡献链。于是每种链的代价即可算出来,取最小值即可。
时间复杂度 \(O(n)\)。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N=100005;
int f[N],n,x[N],y[N],a[N],v[N],cnt=0,ans=0x3f3f3f3f;
bitset<N>vis;
void init()
{
for(int i=1;i<=n;i++)
f[i]=i;
}
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);
f[fx]=fy;
}
void dfs(int u)
{
a[++cnt]=u;
vis[u]=1;
if(vis[x[u]]==0)
dfs(x[u]);
else if(vis[y[u]]==0)
dfs(y[u]);
}
int main()
{
//freopen("sample.in","r",stdin);
//freopen("sample.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
init();
for(int i=1;i<=n;i++)
{
cin>>x[i]>>y[i];
combine(i,x[i]);
combine(i,y[i]);
}
int tot=0;
for(int i=1;i<=n;i++)
tot+=(findf(i)==i)+(x[x[i]]!=i&&y[x[i]]!=i)+(x[y[i]]!=i&&y[y[i]]!=i);
if(tot>1)
{
cout<<-1;
return 0;
}
dfs(1);
fill(v,v+n+1,n);
for(int i=1;i<=n;i++)
v[(i-a[i]+n)%n]--;
for(int i=0;i<=n;i++)
ans=min(ans,v[i]);
reverse(a+1,a+n+1);
fill(v,v+n+1,n);
for(int i=1;i<=n;i++)
v[(i-a[i]+n)%n]--;
for(int i=0;i<=n;i++)
ans=min(ans,v[i]);
cout<<ans;
return 0;
}

浙公网安备 33010602011771号