[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