题目描述 Description
佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了“小教官”。在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会。一共有n个同学,编号从1到n。一开始,同学们按照1,2,……,n的顺序坐成一圈,而实际上每个人都有两个最希望相邻的同学。如何下命令调整同学的次序,形成新的一个圈,使之符合同学们的意愿,成为摆在佳佳面前的一大难题。
佳佳可向同学们下达命令,每一个命令的形式如下:
(b1,b2,...bm-1,bm)
这里m的值是由佳佳决定的,每次命令m的值都可以不同。这个命令的作用是移动编号是b1,b2,…… bm –1,bm的这m个同学的位置。要求b1换到b2的位置上,b2换到b3的位置上,……,要求bm换到b1的位置上。
执行每个命令都需要一些代价。我们假定如果一个命令要移动m个人的位置,那么这个命令的代价就是m。我们需要佳佳用最少的总代价实现同学们的意愿,你能帮助佳佳吗?
输入描述 Input Description
输入第一行是一个整数n(3<=n<=50000),表示一共有n个同学。其后n行每行包括两个不同的正整数,以一个空格隔开,分别表示编号是1的同学最希望相邻的两个同学的编号,编号是2的同学最希望相邻的两个同学的编号,……,编号是n的同学最希望相邻的两个同学的编号。
输出描述 Output Description
这一行只包含一个整数,为最小的总代价。如果无论怎么调整都不能符合每个同学的愿望,则输出-1。
样例输入 Sample Input
4
3 4
4 3
1 2
1 2
样例输出 Sample Output
2
数据范围及提示 Data Size & Hint
【数据规模】
对于30%的数据,n<=1000;
对于全部的数据,n<=50000。
题解
大概是我太弱了吧,每篇题解都一头懵哔emmm
首先这道题题面可以说很难懂了
有n个同学,初始时从1~n坐成一圈。每个同学有两个最想坐在边上的同学。
问能否通过交换部分人的座位满足所有人的需求。若能满足,求交换的最小代价。
其中交换的定义:对于某一次交换,让同学a坐到上次交换结束后b坐的位置上,b坐到上次交换结束后c的位置上......让被涉及到的最后一位同学坐到上次结束后a坐的位置上。
代价的定义:对于每次交换,这次的代价为被涉及到的同学数,总代价为每次交换的代价之和。
首先我们拆环成链。
然后我们可以反过来想:给定一个序列,求交换成1~n的序列的最小代价。
可以想到,其实只需要一次交换就可以解决问题:
设同学a的编号为ca,则把a同学放到ca座位;
然后把之前坐在ca号座位上的同学(设为b)赶到cb座位......
这样每个坐错位置的人,都只会挪一次位置。
所以这种拆链方案的代价=坐错位置的同学数
这样就可以用n方的时间解决问题啦!(撒花
在n方的解法中,我们枚举了断点。对于每个断点,用O(n)的时间再扫一遍。
会炸。
想像两个手链,一个手链从1写到n,另一个手链乱序。 枚举断点,实际上只是把乱序手链的某个珠子对准1号珠拿好,然后把每个珠子看一遍,数不同的珠子数;再把乱序手链的下一个珠子对准1号珠...... 如果有一些珠子,它们在第一次数珠子的时候离目标位还有x个珠子,那么下一次离目标就会还有x-1格...... 那么它们要么同时对上,要么同时对不上。 于是问题转化成了: 随便拆一下,求此时的 max(dis[]),也就是一次最多对上的珠子数。 答案就是n=max(dis[])的值,也就是最少有多少对不上。
(撒花~
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> using namespace std; int f[50007][2],a[50007],dis[100007],b[50007]; bool used[50007]; int maxd=0; void make(int pos,int x){ maxd=max(pos,maxd);//记录一下排座位到哪了 a[pos]=x;used[x]=1; if(!used[f[x][0]]){make(pos+1,f[x][0]);} else if(!used[f[x][1]]){make(pos+1,f[x][1]);} return; } int main(){ int n; scanf("%d",&n); //造乱序链 //造链的方法很多啦,随便哪种都可以 for(int i=1;i<=n;++i){ int x,y; scanf("%d%d",&x,&y); if(f[x][0]){//如果x的0号朋友位满了 if(f[x][1]){cout<<-1;return 0;}//如果x已经有了两个朋友 f[x][1]=i; } else f[x][0]=i; if(f[y][0]){ if(f[y][1]){cout<<-1;return 0;} f[y][1]=i; } else f[y][0]=i; } make(1,1);//在1号位放1号同学,然后往下递归 if(maxd!=n){cout<<-1;return 0;}//如果造不出长为n的链,那不可能只坐一个圈 // for(int i=1;i<=n;++i) dis[(a[i]-i+n)%n]++; int ans=0; for(int i=0;i<n;++i) ans=max(ans,dis[i]); //然后还要像硬币翻面一样翻一下乱序手链 b[1]=a[1]; for(int i=2;i<=n;++i) b[i]=a[n+2-i]; //翻了之后再跑一次 memset(dis,0,sizeof(dis)); for(int i=1;i<=n;++i) dis[(b[i]-i+n)%n]++; for(int i=0;i<n;++i) ans=max(ans,dis[i]); // cout<<n-ans; return 0; }
关于翻面:
如果乱序链为 1 4 3 2 ,对比 1 2 3 4需要代价2
但是如果翻一下就不需要代价了,因为读入中只要坐在一起,但我们强行加了他们坐左边还是右边的要求,所以要反过来再跑一遍。