题解:P1088 [NOIP 2004 普及组] 火星人
很明显,这题考查全排列。但是,用朴素的 DFS 算法做,时间复杂度为 \(O(n!)\),只能得 30 分,因此要进行优化。
先来复习一下全排列的核心代码。
for(int i=1;i<=n;++i){
if(!vis[i]){
vis[i]=1;
f[k]=i;
dfs(k+1);
vis[i]=0;
}
}
这道题要求加上一个数字,所以并不需要枚举比原数小的数字,对吗?
for(int i=a[k];i<=n;++i){
if(!vis[i]){
vis[i]=1;
f[k]=i;
dfs(k+1);
vis[i]=0;
}
}
不对!例如,1 2 3 5 4 的下一个排列是 1 2 4 3 5,而上面的代码产生 1 2 4 后就卡壳了。因此,只有第一次遍历时能从原数开始,其余的就得从一开始。
还有一个小问题:最初火星人手指的排列顺序也会被计算。那么,我们在判断程序是否结束时,就要使用 if(sum==m+1) 而不是 if(sum==m)。
分析完毕!看看代码吧:
#include<bits/stdc++.h>
using namespace std;
int n,m,a[10005],f[10005],sum=0;
bitset<10005> vis;//相当于 bool vis[10005],但前者更省空间。
void dfs(int k){
if(k==n+1){//如果遍历完毕一个排列
++sum;
if(sum==m+1){
for(int i=1;i<=n;++i){
cout<<f[i]<<' ';
}
exit(0);//直接退出程序
}
return;
}
if(sum==0){//如果是第一次遍历
for(int i=a[k];i<=n;++i){
if(!vis[i]){
vis[i]=1;
f[k]=i;
dfs(k+1);
vis[i]=0;
}
}
}
else{
for(int i=1;i<=n;++i){
if(!vis[i]){
vis[i]=1;
f[k]=i;
dfs(k+1);
vis[i]=0;
}
}
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
dfs(1);
return 0;
}

浙公网安备 33010602011771号