NOIP2024 RP++.|

一位XXS

园龄:5个月粉丝:1关注:1

P1053 [NOIP2005 提高组] 篝火晚会

Problem


Solve

无解情况

首先我们来看一眼什么情况是-1
显然,如果你想和别人坐,别人必须得想要和你做(我们老班经典发言awa"你一直对座位纠结,想要和xxx坐,但是别人想和你做吗?我怎么就没看见别人想换座位!")
满足这个条件后就没什么问题了

指令使用

来考虑怎么使用这个指令
假设我们知晓最后要怎么换,即得到了一个数组来记录每个人最后去哪里
一开始想到每次从某个人开始为\(b_1\),他的目的地为\(b_2\),然后\(b_2\)去他的目的地\(b_3\),最后再转回起点
思考发现这样容易浪费一个单位代价,然后由此想到如果这个\(b_n\)的目的地为\(b_1\),这样不会浪费,形成一个完美的环,必然是最优操作的一种的一部分
注意到,去除不用换的人,每个人的位置看成一个点,构建有向图,那么每个点的出度必然是1
然后发现如果A走了,必然存在一个B来替补,所以入度也为1
一个出入度皆为1的点构成的图是跑不了拓扑排序的,所以必然存在环,而且全是环!!!
所以总代价$$m=n-k$$其中k是本来就不用换的人数

最终情况构造

如果你想和xxx坐,xxx必然还有另外一个邻居,然后由此形成一条链
只有第一个切环点有两种选择,分别是他希望的两个人,所以能构造的就是两种情况
然后看到这个队伍是一个环,所以以上最终情况的每个还有n-1个衍生的情况,共2n个情况
那么我们对于每个情况进行比较即可,时间复杂度\(O(n^2)\)

优化

之前的情况大多是衍生的,就是由2种原情况旋转来的,所以我们只要计算目标情况与初始情况中每人需要转几次才能转到目标情况
这个转数为x的人中,肯定满足一种情况使得他们刚开始就在目的地上,那我们只需要找出x最大的人数就行了,这样就能\(O(n)\)算k

算旋转的次数时一定要注意如果是负数需要+n来变成正数,等价于向反向转若干次来达成相同结果,但是结果是正数,这样防止一个转数算成两种,会WA60,别问我怎么知道的qaq

Code

#include<bits/stdc++.h>
using namespace std;
int n,a[50005][2];
int idx,b[50005],c[100005],cnt,maxn;
void input(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i][0]>>a[i][1];
}
for(int i=1;i<=n;i++){
if(a[a[i][0]][0]!=i&&a[a[i][0]][1]!=i||a[a[i][1]][0]!=i&&a[a[i][1]][1]!=i){
cout<<-1;
exit(0);
}
}
}
int main(){
input();
cnt=1;
b[1]=1,idx=a[1][0];
while(1){
b[idx]=++cnt;
if(b[a[idx][0]]&&b[a[idx][1]])break;
if(!b[a[idx][0]])idx=a[idx][0];
else idx=a[idx][1];
}
for(int i=1;i<=n;i++){
c[(b[i]-i+n)%n]++;
maxn=max(maxn,c[(b[i]-i+n)%n]);
}
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
cnt=1;
b[1]=1,idx=a[1][1];
while(1){
b[idx]=++cnt;
if(b[a[idx][0]]&&b[a[idx][1]])break;
if(!b[a[idx][0]])idx=a[idx][0];
else idx=a[idx][1];
}
for(int i=1;i<=n;i++){
c[(b[i]-i+n)%n]++;
maxn=max(maxn,c[(b[i]-i+n)%n]);
}
cout<<n-maxn;
return 0;
}

本文作者:一位XXS

本文链接:https://www.cnblogs.com/yiweixxs/p/18592339

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   一位XXS  阅读(22)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起