题解: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;
}

posted @ 2025-06-10 22:16  noiiloveyou  阅读(13)  评论(0)    收藏  举报