专栏文章

题解:P1053 [NOIP 2005 提高组] 篝火晚会

P1053题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mip5crdc
此快照首次捕获于
2025/12/03 06:25
3 个月前
此快照最后确认于
2025/12/03 06:25
3 个月前
查看原文
[Analysis]\color{blue}{\texttt{[Analysis]}}
话说这题和贪心、图论有什么关系。
不过这种题目是我的死穴。
我们首先证明如下结论:如果目标状态和初始状态有 xx 个人位置失配,则一定可以通过 xx 的代价调整回来。
我们考察其中一个失配的同学 α1\alpha_{1},假设 Ta 最终应该在的位置是 β1\beta_{1},现在所在的位置是 ξ\xi
如果有解的情况下,那么 β1\beta_{1} 位置上的同学 α2\alpha_{2} 一定也是失配的(不然就无解了)。假设 α2\alpha_{2} 同学最终应该在 β2\beta_{2} 位置。
  • 如果 β2=ξ\beta_{2}=\xi,即 α1,α2\alpha_{1},\alpha_{2} 两名同学刚好错开,那么我们可以通过一次 m=2m=2 的操作使得 22 个失配的人都回到应该在的位置。
  • 如果 β2ξ\beta_{2} \not = \xi,那么我们继续考察在 β2\beta_{2} 位置的同学 α3\alpha_{3},然后假设 α3\alpha_{3} 最终应该在的位置 β3\beta_{3}。如果 β3=ξ\beta_{3}=\xi,那么我们可以通过一次 m=3m=3 的操作 {α1,α2,α3}\{ \alpha_{1},\alpha_{2},\alpha_{3} \},通过 33 的代价使得 33 个失配的同学回到应该在的位置。
  • 如果还不行,继续按照这个规则考察 α4,β4,α5,β5,,αk,βk\alpha_{4},\beta_{4},\alpha_{5},\beta_{5},\cdots,\alpha_{k},\beta_{k},其中 βk=ξ\beta_{k}=\xi。可以证明 kk 一定是存在的,因为人数是有限的,最多就是 nn 个人全部进来了。可以通过一次 m=km=k 的操作 {α1,α2,,αk}\{ \alpha_{1},\alpha_{2},\cdots,\alpha_{k} \} 使得 kk 个失配的同学回到应该在的位置。
这里的证明倒是有一点图论的味道,最终失配的同学按照上述规则可以形成若干个环,而每个环可以通过一次代价为环大小的操作复原。因此证明了所需的结论。
那么现在问题就变成了,究竟最少有多少个人失配。
根据输入,我们可以确定最后环的形态。
环是没有头尾的,当然可以强制定义一个头尾,即强制第 ii 个同学在序列的开头,检查有多少个人失配,但是这样做是 O(n2)O(n^{2}) 的。
我们还是考虑以 11 作为序列的开头。把初始链和目标链对应位作减法,将会得到一个数列 tmp1n\text{tmp}_{1 \dots n}
旋转目标链和旋转初始链是等价的,我们考虑旋转初始链。
由于初始链是 1,2,3,,n1,2,3,\dots,n,因此旋转之后,除了末尾特殊位置之外,其它位置的 tmp\text{tmp} 值都同样的增大或减小了 11。也就是说,如果两个位置 x1,x2x_{1},x_{2},它们是适配的,即它们无需做操作,则 tmpx1,tmpx2\text{tmp}_{x_{1}},\text{tmp}_{x_{2}} 的差将保持不变。
我们也知道,在初始链旋转到某一个位置时,如果一个位置的 tmpi=0\text{tmp}_{i}^{'}=0,那么这个位置在这个初始链下是适配的,无需做操作的。因此我们可以统计 tmpi\text{tmp}_{i} 里的众数出现的次数,然后我们根据上一段的性质可以通过旋转初始链使得众数都变为 00,那么适配的人数不就最多了吗。
因此,一个 O(n)O(n) 的算法就产生了。
注意链的方向对答案是有影响的,需要顺反做两次。
Code\color{blue}{\text{Code}}
CPP
int Clock[N],Counter[N],ans;
int n,anti[N][2],target[N],init[N];

int main(){
	n=read();
	for(int i=1;i<=n;i++){
		anti[i][0]=read();
		anti[i][1]=read();
		
		init[i]=i;
	}
	
	target[1]=1;
	target[2]=anti[1][0];
	for(int i=2;i<n;i++){
		if (target[i-1]==anti[target[i]][0])
			target[i+1]=anti[target[i]][1];
		else if (target[i-1]==anti[target[i]][1])
			target[i+1]=anti[target[i]][0];
		else{
			printf("-1");
			return 0;
		}//你喜欢人家,人家不喜欢你 
	}//构造目标链
	
	for(int i=1;i<=n;i++){
		Clock[(target[i]-init[i]+n)%n]++;
		Counter[(target[i]-init[n-i+1]+n)%n]++;
	}
	
	for(int i=0;i<n;i++)
		ans=max(ans,max(Clock[i],Counter[i]));
	
	return printf("%d",n-ans)*0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...