CSP历年复赛题-P1088 [NOIP2004 普及组] 火星人

原题链接:https://www.luogu.com.cn/problem/P1088

题意解读:

火星人的手指可以通过全排列来表示数字,全排列由小到大的顺序即为表示的数字大小,题目可以转化为:

给定按顺序全排列中的某一个排列,求往后数m个排列的内容。

解题思路:

此题与经典全排列问题的差异在于,需要从指定一个排列开始,往后找m个排列。注意由于n值多达10000,如果从头开始全排列,找到指定排列开始,很可能超时,因为全排列时间复杂度是O(n!)。

要从指定排列开始,只需要在经典全排列代码中,加入标记判断,如果还未找到指定起始排列,则赋值为指定排列中的数值,然后继续dfs。

30分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 10005;

int n, m;
int a[N], t[N];
bool flag[N];

bool start; //判断是否已找到起始排列
int cnt; //从起始排列开始计数

void dfs(int k)
{
    if(k > n) //找到一组排列
    {
        if(!start) start = true; //找到的是起始排列,说明可以开始计数了
        else
        {
            if(++cnt == m) //计数,如果是往后数m个排列,输出结果
            {
                for(int i = 1; i <= n; i++) cout << t[i] << " ";
                cout << endl;
            }
        }
        return;
    }
    for(int i = 1; i <= n; i++)
    {
        if(!start) //如果还没有找到起始排列
        {
            i = a[k]; //i从指定的排列数a[k]开始
        }
       
        if(!flag[i])
        {
            flag[i] = true;
            t[k] = i;
            dfs(k + 1);
            flag[i] = false;
        }
    }
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    dfs(1);
    return 0;
}

此代码的问题在于,当找到第m个排列之后,函数没有及时终止,dfs要终止,直接return不行,因为只return了本层递归,上层递归和枚举还在继续,

需要在dfs中return一个标识,true表示已结束,false表示未结束,在所有调用dfs的地方,增加对返回值的判断并进一步return true/false,这样才能在

找到结果后层层返回,起到及时终止程序的作用。当然,也可以在找到第m个排列后,直接exit(0)。

100分代码-DFS:

#include <bits/stdc++.h>
using namespace std;

const int N = 10005;

int n, m;
int a[N], t[N];
bool flag[N];

bool start; //判断是否已找到起始排列
int cnt; //从起始排列开始计数

bool dfs(int k)
{
    if(k > n) //找到一组排列
    {
        if(!start) start = true; //找到的是起始排列,说明可以开始计数了
        else
        {
            if(++cnt == m) //计数,如果是往后数m个排列,输出结果
            {
                for(int i = 1; i <= n; i++) cout << t[i] << " ";
                cout << endl;
                return true; //已经找到,直接返回true,后续不用再继续递归了
            }
        }
        return false;
    }
    for(int i = 1; i <= n; i++)
    {
        if(!start) //如果还没有找到起始排列
        {
            i = a[k]; //i从指定的排列数a[k]开始
        }
       
        if(!flag[i])
        {
            flag[i] = true;
            t[k] = i;
            bool res = dfs(k + 1);
            if(res) return true; //如果已经找到第m个数,不用继续dfs处理了,以免超时
            flag[i] = false;
        }
    }

    return false;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    dfs(1);
    return 0;
}

如果你熟悉STL函数next_permuation(),那么可以得到一个更精简的答案:

100分代码-next_permuation:

#include <bits/stdc++.h>
using namespace std;

const int N = 10005;

int a[N];
int n, m;

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];

    while (m--)
    {
        next_permutation(a + 1, a + n + 1);
    }
    
    for(int i = 1; i <= n; i++) cout << a[i] << " ";

    return 0;
}

 

posted @ 2024-05-23 11:07  五月江城  阅读(39)  评论(0编辑  收藏  举报