[NOIP2005]篝火晚会


篝火晚会

ps:觉得网上的一些题解比较奇怪,看不懂,自己写一篇来说说这道题。

题面

佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了“小教官”。在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会。一共有 \(n\) 个同学,编号从 \(1\)\(n\) 。一开始,同学们按照 \(1,2,…,n\) 的顺序坐成一圈,而实际上每个人都有两个最希望相邻的同学。如何下命令调整同学的次序,形成新的一个圈,使之符合同学们的意愿,成为摆在佳佳面前的一大难题。

佳佳可向同学们下达命令,每一个命令的形式如下:

\[(b_1,b_2,...b_{m?1},b_m) \]

注意: \(b_1…b_m\) 并不要求为连续数值

这里 \(m\) 的值是由佳佳决定的,每次命令 \(m\) 的值都可以不同。这个命令的作用是移动编号是 $b_1,b_2,…,b_m $的这 \(m\) 个同学的位置。要求 \(b_1\) 换到 \(b_2\) 的位置上, \(b_2\) 换到 \(b_3\) 的位置上,……,要求 \(b_m\) 换到 \(b_1\) 的位置上。执行每个命令都需要一些代价。我们假定如果一个命令要移动 \(m\) 个人的位置,那么这个命令的代价就是 \(m\) 。我们需要佳佳用最少的总代价实现同学们的意愿,你能帮助佳佳吗?

分析

代价

暴力点想,一个数位置不合法,我想让它合法,假设目前这个数的位置是 \(x\) ,在它合法位置上的数是 \(y\) ,显然我们就是要交换 \(x\)\(y\) ,但这样可能会导致 \(y\) 也不合法。

发现其实扩展一下我们将 \(y\) 的后面添上 \(z\) ,满足 \(y\) 的合法位置是 \(z\) ,这样一直扩展,如果当前有解,肯定会扩展回 \(x\)

所以对于 \(m\) 个不合法的数,我们只需要代价为 \(m\) 进行移动。当然 \(m\) 个不合法的数可能不是同一组,但是代价的总和一定是 \(m\)

所以代价就是 \(n-cnt\) ,其中 \(cnt\) 表示与原排列符合的数的个数。

优化

然后我们发现我们现在只需要确定目标环,很容易发现目标环其实只有两种,即 \(1\) 想要相邻的两个数在不同位置,那么我们断环为链就能拿到两个不同的序列。

这个序列我们又可以将它左移得到新的序列。

不难发现其实每个数只会在第 \(k\) 轮左移中与原序列相等,那我们只需要对得到的序列进行预处理,求出 \(dp[i]\) 表示第 \(i\) 轮中有多少个数不用动。

讨论一下序列当前数到原序列 \(1,2,3……4\) 的距离就好了,然后算一下累计一下贡献。

统计答案当然就是 \(min(n-dp[i])\)

无解

无解的情况很容易,如果 \(i\) 想相邻的数不想和 \(i\) 相邻,就无解了。

CODE

#include 
using namespace std;
const int N=5e4+10,INF=1e9;
const int Mxdt=100000; 
inline char gc(){
	static char buf[Mxdt],*p1=buf,*p2=buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
	int t=0,f=0;char v=gc();
	while(v<'0')f|=(v=='-'),v=gc();
	while(v>='0')t=(t<<3)+(t<<1)+v-48,v=gc();
	return f?-t:t;
}
struct node{ int x,y; }arr[N];
int n,ans;
int cnt,line[N];
int dp[N];
bool vis[N];
int tot,v[4*N],nex[4*N],first[4*N];
inline void Add(int x,int y)
{
	nex[++tot]=first[x];
	first[x]=tot,v[tot]=y;
}
inline void Get_line(int u)
{
	vis[u]=true;
	line[++cnt]=u;
	for(register int i=first[u];i;i=nex[i]){
		int to=v[i];
		if(vis[to]) continue;
		Get_line(to);
	}
}
inline void Solve()
{
	memset(dp,0,sizeof(dp));
	for(register int i=1;i<=cnt;i++) dp[(i+(n-line[i]))%cnt]++;
	for(register int i=0;i