洛谷官服题单刷题日记 2022.2.25

P1618 三连击(升级版)

https://www.luogu.com.cn/problem/P1618

数据量不大,直接暴力dfs就是了,没啥好说的,记得写无解的情况(第一次忘记了)

#include<bits/stdc++.h>
using namespace std;
int a[10] = {0};
int temp[4];
int A, B, C;
int flag;
void dfs(int n){ // 选取到第几个个数字
    if(n == 4){
        if(temp[1] * B == temp[2] * A && temp[2] * C == temp[3] * B){ //判断比例关系
            printf("%d %d %d\n", temp[1], temp[2], temp[3]);
            flag = 1;
        }
        return;
    }
    for(int i = 1; i <= 9; i++)
        for(int j = 1; j <= 9; j++)
            for(int k = 1; k <= 9; k++){
                if(!a[i] && !a[j] && !a[k] && i != j && j != k &&  i != k){
                    temp[n] = i * 100 + j * 10 + k;
                    a[i] = a[j] = a[k] = 1;
                    dfs(n + 1);
                    a[i] = a[j] = a[k] = 0;
                }
            }
}
int main(){
    cin >> A >> B >> C;
    dfs(1);    
    if(flag == 0)
        printf("No!!!");
    return 0;
}

P1036 [NOIP2002 普及组] 选数

https://www.luogu.com.cn/problem/P1036

这题其实就是素数筛 + dfs的问题,我开始一直写不对。。。后面发现是dfs出问题了,这个是组合问题,我写成了排列问题,导致结果大了很多,经过了一段时间的修改才写出来了,总体思路没啥问题,就是细节中间没做好。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e7;
bool is_prime[MAXN];
int a[30];
int vis[30];
int cnt = 0;
int n, k;
void init_prime(){//埃氏筛法
    is_prime[0] = is_prime[1] = true;
    for(int i = 2; i < MAXN / i; i++){
        if(!is_prime[i]){
            for(int j = 2; j < MAXN / i; j ++)
                is_prime[i * j] = true;
        }
    }
}
void dfs(int m, int pos, int sum){//已选了m个,选到pos位置, 当前sum
    if(m == k){
        if(!is_prime[sum])
            cnt++;
        return;
    }
    for(int i = pos; i <= n; i++){
            dfs(m + 1, i + 1, sum + a[i]);
    }
}
int main(){
    init_prime();
    cin >> n >> k;
    for(int i = 1; i <= n; i++)
        cin >> a[i]; 
    dfs(0, 1, 0);
    cout << cnt;
    return 0;
}

P1157 组合的输出

https://www.luogu.com.cn/problem/P1157

又是一个dfs输出组合问题,没什么好说的了,上代码

#include<bits/stdc++.h>
using namespace std;
int n, r;
int temp[3];
void dfs(int m, int pos){
    if(m == r + 1){
        for(int i = 1; i < m; i++)
            printf("%3d", temp[i]);
        printf("\n");
        return;
    }
    for(int i = pos; i <= n; i++){
        temp[m] = i;
        dfs(m + 1, i + 1);
    }
}
int main(){
    cin >> n >> r;
    dfs(1, 1);
    return 0;
}

P1706 全排列问题

https://www.luogu.com.cn/problem/P1706

也是利用dfs的全排列问题, 写法和组合有些不同,还是异曲同工

#include<bits/stdc++.h>
using namespace std;
int n;
int vis[10];
int a[10];
void dfs(int m){ // 选到第m个数字了
    if(m == n + 1){
        for(int i = 1; i < m; i++)
            printf("%5d", a[i]);
        printf("\n");
        return;
    }
    for(int i = 1; i <= n; i++){
        if(!vis[i]){
            vis[i] = 1;
            a[m] = i;
            dfs(m + 1);
            vis[i] = 0;
        }
    }
}
int main(){
    cin >> n;
    dfs(1);
    return 0;
}

P1088 [NOIP2004 普及组] 火星人

https://www.luogu.com.cn/problem/P1088

这题我一开始以为是个双向的dfs,先用dfs强行对应找到外星数字对应的字母,然后相加或得所需求的答案数字再用一次dfs输出回外星数字,结果写了半天一交。。20分。。TLE了全部,所以就只能想着怎么优化一下

20分TLE代码

#include<bits/stdc++.h>
using namespace std;
vector<int> aliennum, v;//aliennum表示
int vis[10100]; //记录那些数字取过了
int cnt = 0;    
int flag = 0;   //函数操作标记
int n, m, res;//一共n个数,要加上的数是m,res3保存aliennum是数值
void dfs(int pos){
    if(flag == 1)
        return;
    if(pos == n){
        cnt++;
        if(flag == 2 && cnt == res){
            for(int i = 0; i < n; i++)
                printf("%d ", v[i]);
            flag = 1;
            return;
        }
        if(v == aliennum && !flag)
            flag = 1;
        return;
    }
    for(int i = 1; i <= n; i++){
        if(!vis[i]){
            vis[i] = 1;
            v.push_back(i);
            dfs(pos + 1);
            vis[i] = 0;
            v.pop_back();
        }
    }
}
int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        int temp;
        cin >> temp;
        aliennum.push_back(temp);
    }
    dfs(0);//flag == 0起始, 代表要计算aliennum的数值
    res = cnt + m;
    cnt = 0;
    flag = 2;
    dfs(0);//flag == 2起始,代表要通过数值写错aliennum
    return 0;
}

然后我就开始想着能不能用空间换时间,直接把每一种情况储存下来这样到时候直接输出就可以了然后用了一个二维数组后发现。从TLE变成了MLE,直接无语了。。。看来这题也没这么暴力啊!!!

20分MLE代码

#include<bits/stdc++.h>
using namespace std;
vector<int> aliennum, temp;//aliennum表示
vector<vector<int> > v;
int vis[10100]; //记录那些数字取过了
int cnt = 0;    
int n, m, res;//一共n个数,要加上的数是m,res3保存aliennum是数值
void dfs(int pos){
    if(pos == n){
        v.push_back(temp);
        return;
    }
    for(int i = 1; i <= n; i++){
        if(!vis[i]){
            vis[i] = 1;
            temp.push_back(i);
            dfs(pos + 1);
            vis[i] = 0;
            temp.pop_back();
        }
    }
}
int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        int t;
        cin >> t;
        aliennum.push_back(t);
    }
    dfs(0);//flag == 0起始, 代表要计算aliennum的数值
    for(int i = 0; i < v.size(); i++){
        if(v[i] == aliennum){
            cnt = i + 1;
            break;
        }
    }
    cnt += m;
    for(int i = 0; i < v[cnt - 1].size(); i++)
        printf("%d ", v[cnt - 1][i]);
    return 0;
}

然后我又想既然是加上一个正整数,那数字肯定是变大了,为什么不能在确定了数以后继续往下走,直接一次dfs结束了,这样既省了时间又省了空间

结果还是20分没有任何用,虽然优化了,但是显然剩下测试的数值都不小,优化这个算法已经很难过了。

#include<bits/stdc++.h>
using namespace std;
vector<int> aliennum, temp;//aliennum表示
int vis[10100]; //记录那些数字取过了
int cnt = 0;    
int flag;
int n, m;//一共n个数,要加上的数是m
void dfs(int pos){
    if(flag == 2)
        return;
    if(pos == n){
        cnt++;
        if(!flag && temp == aliennum){
            flag = 1;
            cnt = 0;
        }
        else if(flag == 1 && cnt == m){
            for(int i = 0; i < n; i++)
                printf("%d ", temp[i]);
            flag = 2;
        }
        return;
    }
    for(int i = 1; i <= n; i++){
        if(!vis[i]){
            vis[i] = 1;
            temp.push_back(i);
            dfs(pos + 1);
            vis[i] = 0;
            temp.pop_back();
        }
    }
}
int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        int t;
        cin >> t;
        aliennum.push_back(t);
    }
    dfs(0);
    return 0;
}

最后无奈之下我只好看了一下大佬们的发言。(普及题都不会惭愧惭愧)看了大佬的方法主要是有3个 dfs的迅速修改查找, stl神器next_permutation(这个其实是我最先想到的,但是题目标签是暴力所以一直想用dfs), 第三是比较难的康托展开

dfs版 主要是能观察到题目中n很大,但是m很小,如果可以直接用到外星人的数组,那不是就不会超时了?顺着这个思路,开始写。果然一次就ac了。。。之前的代码上就缺少一步,就是直接快进的外星人数上。但是这也告诉我题目所设置的数值是有意义的,m的小,n的大正好对应了这种情况,这也是题目的陷阱之一吧。

#include<bits/stdc++.h>
using namespace std;
vector<int> aliennum, temp;//aliennum表示
int vis[10100]; //记录那些数字取过了
int cnt = 0;    
int flag;
int n, m;//一共n个数,要加上的数是m
void dfs(int pos){
    if(flag == 2)
        return;
    if(pos == n){
        cnt++;
        if(!flag){
            flag = 1;
            cnt = 0;
        }
        else if(flag == 1 && cnt == m){
            for(int i = 0; i < n; i++)
                printf("%d ", temp[i]);
            flag = 2;
        }
        return;
    }
    for(int i = 1; i <= n; i++){
        if(!flag)//通过标记进行, 并且直接速度快进到外星人数上
            i = aliennum[pos];
        if(!vis[i]){
            vis[i] = 1;
            temp.push_back(i);
            dfs(pos + 1);
            vis[i] = 0;
            temp.pop_back();
        }
    }
}
int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        int t;
        cin >> t;
        aliennum.push_back(t);
    }
    dfs(0);
    return 0;
}

next_permutation版(stl大法好啊)

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

int main(){
    int n, m;
    cin >> n >> m;
    int a[10100];
    for(int i = 0; i < n; i++)
        cin >> a[i];
    for(int i = 1; i <= m; i++)
        next_permutation(a, a + n);
    for(int i = 0; i < n; i++)
        cout << a[i] << " ";
    return 0;
}

康托展开版 —— 变进制数

(以下解释转载)https://www.luogu.com.cn/problem/solution/P1088

我们的目标是把全排列转化成一个变进制数,以方便我们进行加法。
对于第i根手指,它有n-i+1种选择,也就是说,这一位数是n-i+1进制的。

那么,整个过程分为3步:

  1. 将火星数变成变进制数
  2. 将变进制数加上m
  3. 将变进制数变成火星数

我们来看一个实例: 将 1,4,5,2,3变成变进制数

  • 首位1是5种选择{1,2,3,4,5}的第1种,故变为0(从0开始)
  • 次位4是4种选择{2,3,4,5}的第3种,故变为2
  • 中间位5是3种选择{2,3,5}的第3种,故变为2
  • 次低位2是2种选择{2,3}的第1种,故变为0
  • 末位3是1种选择的{3}第1种,故变为0
  • 最后,1,4,5,2,3变成了(02200) (变进制数)

然后将它加上3,得(03010) (变进制数)

最后将它变回火星数。

  • 首位0表示这位应选择{1,2,3,4,5}第1种,即1
  • 次位3表示这位应选择{2,3,4,5}第4种(1被选过了),即5
  • 中间位0表示这位应选择{2,3,4}第1种,即2
  • 次低位1表示这位应选择{3,4}第2种,即4
  • 末位0表示这位应选择{3}第1种,即3
  • 所以本题答案为“14523”+3=“15243”
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 10010;
int vis[MAXN], a[MAXN];//vis记录使用过的数字,a是外星人数
int main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++){//外星人数转变进制数
        cin >> a[i];
        int x = a[i];
        for(int j = 1; j <= a[i]; j++)//把x减去1 - a[i]中用过数的个数
            x -= vis[j];
        vis[a[i]] = 1;//记录使用
        a[i] = x - 1;//第一种排序是代表0,第二种排序代表1,这个不能用错
    }
    a[n] += m;
    for(int i = n; i >= 1; i--){ //处理变进制数
        a[i - 1] += a[i] / (n - i + 1);
        a[i] %= (n - i + 1);
    }
    // for(int i = 1; i <= n; i++)
    //     cout << a[i] << " ";
    // cout << endl;
    memset(vis, 0, sizeof(vis));//重置vis数组
    for(int i = 1; i <= n; i++){//变进制数转回外星人数
        int j = 0, k = 1;//取到第j种,k是a的(指针)
        a[i]++;
        while(j < a[i]){//剩余数种的第a[i]种,也就是j要取到j == a[i]
            if(!vis[k])//碰到没取过的计为一种
                j++;
            k++;
        }
        k--;
        cout << k << " ";
        vis[k] = 1;
    }
    return 0;
}

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:深蓝海洋 设计师:CSDN官方博客 返回首页
评论

打赏作者

NaHCOOO_

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值