P3682 [CERC2016]自由的套娃 Free Figurines 题解
呜呜呜我是个只会模拟的蒟蒻~~~
大体思路:
模拟。
怎么模拟?
遍历每个套娃,如果该套娃不符合结局要求,那我们就移动他一次!
可以发现,直接正推条件很多,很难使用代码实现(其实是我不会),所以,我们换一种方法实现:倒推。
实现倒推:
倒推的思路与正推刚好相反,我们在输入的时候直接累计次数,最后遍历寻找不必要的操作,并将其减去。
为了使寻找无用操作变得简单,我们在累计次数时尽可能多的累计。如下:
for(int i=1;i<=n;i++){
ans+=(a[i]!=0)+(b[i]!=0);
}
现在的重点便是如何找到不必要的操作并减去。
请看 1 号列表:
-
初始与结局相同即可不用操作。
-
查找时按照从小到大(从里到外)的顺序查找。
-
由于在累计时两者都是 0 的情况没有累计上,所以我们在减少时自然也不用减少 0 的情况了。
综上所述,给出寻找并减去这一片段的代码:
for(int i=1;i<=n;i++){
if(f[i]==0) { //稍后解释
int j;
j=i;
while(a[j]==b[j]&&a[j]!=0){
ans=ans-2;
j=a[j]; //继续寻找套在他外面的套娃,即他的父亲。
}
}
}
其中,出现了 f 数组。
基于 1 号列表第 2 条,查找时的顺序要从内到外,所以我们用 fi 来存储第 i 个点肚子里是否有套娃,即他是不是父亲。如果他不是父亲,也就是说他是最里面的套娃,那我们就可以开始将不必要的操作减去了。
至于怎么存储嘛,有很多种方法,这里给出一种最通俗易懂的方法:
for(int i=1;i<=n;i++) f[i]=false; //初始化
for(int i=1;i<=n;i++){
cin>>a[i];
f[a[i]]=true; //如果点 i 有父亲,那他的父亲肚子里就有套娃(那个套娃就是他),所以我们将他的父亲打标记。
}
for(int j=1;j<=n;j++){
cin>>b[j]; //同上。
f[b[j]]=true;
}
至此,代码讲解完毕。
完整代码见下文。
代码:
#include<iostream>
using namespace std;
int n;
int a[100001]={0};
int b[100001]={0};
bool f[100001]={0};
int main(){
cin>>n;
long long ans=0;
for(int i=1;i<=n;i++) f[i]=false; //f 数组初始化。
for(int i=1;i<=n;i++){
cin>>a[i];
f[a[i]]=true; //使用 f 数组记录。
}
for(int j=1;j<=n;j++){
cin>>b[j];
f[b[j]]=true; //同上。
}
for(int i=1;i<=n;i++){
ans+=(a[i]!=0)+(b[i]!=0); //累计答案。
}
for(int i=1;i<=n;i++){
if(f[i]==0) { //寻找最里面的套娃。
int j;
j=i;
while(a[j]==b[j]&&a[j]!=0){ //寻找不必要的操作。
ans=ans-2;
j=a[j];
}
}
}
cout<<ans<<endl;
return 0;
}
总结:
只要你的思路清楚,跟题目走,就能做对!