暴力枚举(深度优先遍历)

暴力枚举


4.14

统计方形(数据加强版)

题目描述

有一个 n×m 方格的棋盘,求其方格包含多少正方形、长方形(不包含正方形)。

输入格式

一行,两个正整数 n,mn≤5000,m≤5000)。

输出格式

一行,两个正整数,分别表示方格包含多少正方形、长方形(不包含正方形)。

输入样例

2 3	//n m

输出样例

8 10 //正方形个数  长方形个数

思路

i 和 j 代表长度,i == j 时为正方形,否则为长方形。

代码

#include
using namespace std;
long long  n,m,z,c;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        for(int j=0;j<n;j++){
            if(i!=j) c += (m-i)*(n-j);
            else z += (m-i)*(n-j);
        }
    }
    cout<<z<<' '<<c;
    return 0;
}

4.15

烤鸡

题目描述

猪猪 Hanke 特别喜欢吃烤鸡(本是同畜牲,相煎何太急!)Hanke 吃鸡很特别,为什么特别呢?因为他有 10 种配料(芥末、孜然等),每种配料可以放 1 到 3 克,任意烤鸡的美味程度为所有配料质量之和。

现在, Hanke 想要知道,如果给你一个美味程度 n ,请输出这 10 种配料的所有搭配方案。

输入格式

一个正整数 n,表示美味程度。

输出格式

第一行,方案总数。

第二行至结束,10 个数,表示每种配料所放的质量,按字典序排列。

如果没有符合要求的方法,就只要在第一行输出一个 0。

输入样例

11

输出样例

10
1 1 1 1 1 1 1 1 1 2 
1 1 1 1 1 1 1 1 2 1 
1 1 1 1 1 1 1 2 1 1 
1 1 1 1 1 1 2 1 1 1 
1 1 1 1 1 2 1 1 1 1 
1 1 1 1 2 1 1 1 1 1 
1 1 1 2 1 1 1 1 1 1 
1 1 2 1 1 1 1 1 1 1 
1 2 1 1 1 1 1 1 1 1 
2 1 1 1 1 1 1 1 1 1 

思路

看我的暴力大法。

代码

#include
using namespace std;
int sum;
int ma[10000][11];
int main(){
    int n;
    cin>>n;
    for(int a=1;a<4;a++)
    for(int b=1;b<4;b++)
    for(int c=1;c<4;c++)
    for(int d=1;d<4;d++)
    for(int e=1;e<4;e++)
    for(int f=1;f<4;f++)
    for(int g=1;g<4;g++)
    for(int h=1;h<4;h++)
    for(int i=1;i<4;i++)
    for(int j=1;j<4;j++)
        if(a+b+c+d+e+f+g+h+i+j==n){
            sum++;
            ma[sum][0] = a;
            ma[sum][1] = b;
            ma[sum][2] = c;
            ma[sum][3] = d;
            ma[sum][4] = e;
            ma[sum][5] = f;
            ma[sum][6] = g;
            ma[sum][7] = h;
            ma[sum][8] = i;
            ma[sum][9] = j;
        } 
    cout<<sum<<endl;
    for(int i=1;i<=sum;i++){
        for(int j=0;j<10;j++)
            cout<<ma[i][j]<<' ';
        cout<<endl;
    }
    return 0;
}

First Step (ファーストステップ)

题目描述

我们浦之星女子学院的篮球场是一个R行C列的矩阵,其中堆满了各种学校的杂物 (用"#“表示),空地 (用”."表示) 好像并不多的样子呢……

我们Aqours现在已经一共有K个队员了,要歌唱舞蹈起来的话,我们得排成一条1*K的直线,一个接一个地站在篮球场的空地上呢 (横竖均可)。

我们想知道一共有多少种可行的站位方式呢。

Aqours的真正的粉丝的你,能帮我们算算吗?

输入格式

第一行三个整数 R, C, K。

接下来的R行C列,是浦之星女子学院篮球场。

输出格式

总共的站位方式数量。

输入样例

5 5 2
.###.
##.#.
..#..
#..#.
#.###

输出样例

8

思路

记录每行和每列中连续的.个数j数量x[j] ,比如.....#....#.....一行中,个数)个连续的.2数量)个,四个连续的.有1个。

如果个数 < 成员数k,则不符合条件,

如果个数 = 成员个数k,则结果为其数量

如果个数 > 成员个数k,则结果为 *(个数j-(成员数k-1))数量x[j]

最终结果为三种情况的结果相加,但是如果成员数k为1时,结果要减半

代码

#include
using namespace std;
int r,c,k,sum,ans,maxn,x[110];
char t;
bool a[110][110];
int main(){
    //输入行、列、成员数
    cin>>r>>c>>k;
    //先记录行
    for(int i=1;i<=r;i++){
        for(int j=1;j<=c;j++){
            cin>>t;
            //sum记录点的个数
            if(t=='.') a[i][j]=true, sum++;
            //当点被打断时,x[sum]记录点的个数的数量
            else maxn=sum>maxn?sum:maxn, x[sum]++, sum = 0;
        }
        maxn=sum>maxn?sum:maxn, x[sum]++, sum = 0;
    }
    //再记录列
    for(int i=1;i<=r;i++){
        for(int j=1;j<=c;j++)
            if(a[j][i]) sum++;
            else maxn=sum>maxn?sum:maxn, x[sum]++, sum = 0;
        maxn=sum>maxn?sum:maxn, x[sum]++, sum = 0;
    }
    for(int j=1;j<=maxn;j++)
        if(j==k) ans+=x[j];
        else if(j>k) ans+=(j-(k-1))*x[j];
    k==1?cout<<ans/2:cout<<ans;
    return 0;
}

组合(dfs板子)

题目描述

排列与组合是常用的数学方法,其中组合就是从 n 个元素中抽出 r 个元素(不分顺序且 rn ),我们可以简单地将 n 个元素理解为自然数1,2,…,n,从中任取 r 个数。

现要求你输出所有组合。

例如 n *= 5, r = 3,所有组合为:

12 3 , 1 2 4 , 1 2 5 , 1 3 4 ,1 3 5 , 1 4 5 , 2 3 4 , 2 3 5 , 2 4 5 , 3 4 5

输入格式

一行两个自然数 n , r (1 < n < 21,0 ≤ rn )。

输出格式

所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。

**注意哦!输出时,每个数字需要3个场宽,pascal可以这样:

write(ans:3);

输入样例

5 3 

输出样例

  1  2  3
  1  2  4
  1  2  5
  1  3  4
  1  3  5
  1  4  5
  2  3  4
  2  3  5
  2  4  5
  3  4  5

思路

dfs的组合模板,理解并记住模板即可

模板

#include
#include
using namespace std;
int n,r,a[30];
void dfs(int pos,int cnt){
    if(cnt==r){
        for(int i=0;i<cnt;i++)
            cout<<setw(3)<<a[i];
        cout<<endl;
        return;
    }
    for(int i=pos;i<=n;i++){
        a[cnt] = i;
        dfs(i+1,cnt+1);
    }
}
int main(){
    cin>>n>>r;
    dfs(1,0);
    return 0;
}

[NOIP2002 普及组] 选数

题目描述

已知 n 个整数x1,x2,⋯,xn,以及 1 个整数 kk<n)。从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。例如当 n=4,k=3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:

3+7+12=22

3+7+19=29

7+12+19=38

3+12+19=34

现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数:3+7+19=29。

输入格式

第一行两个空格隔开的整数 n,k(1 ≤ n ≤ 20,k < n)。

第二行 n 个整数,分别为 x1,x2,⋯,xn(1 ≤ xi ≤ 5×106)。

输出格式

输出一个整数,表示种类数。

输入样例

4 3
3 7 12 19

输出样例

1

思路

dfs组合模板的变体,组合是按照从1到n去进行组合。

本题是给定数字后进行组成,换汤不换药,我们依然从1到n,但是赋值的时候用a[0]到a[n],每次组合再去相加。

判断是否为素数,素数判断到根号即可。

代码

#include
#include
#include
using namespace std;
int n,r,sum,a[30],b[30];
bool isPrime(int num)
{
     int tmp=sqrt(num);
     for(int i=2;i<=tmp;i++)
        if(num%i==0)
          return 0;
     return 1;
}
void dfs(int pos,int cnt){
    if(cnt==r){
        int t = 0;
        for(int i=0;i<cnt;i++)
            t+=b[i];
        if(isPrime(t)) sum++;
        return;
    }
    for(int i=pos;i<n;i++){
        //根据数组下标,赋值时使用数组的值即可
        b[cnt] = a[i];
        dfs(i+1,cnt+1);
    }
}
int main(){
    cin>>n>>r;
    //给定数字,存入数组
    for(int i=0;i<n;i++) cin>>a[i];
    //按照数组下标深搜
    dfs(0,0);
    cout<<sum;
    return 0;
}

kkksc03考前临时抱佛脚(TLE)

题目描述

详见题目链接。

输入样例

1 2 1 3		
5
4 3
6
2 4 3

输出样例

20

思路

本着套模板的想法,排除全排列和子集的dfs模板,最终还是感觉用组合dfs模板更合适,思路呢,先找出单科习题练习所用最短时间,然后累加即可。

为什么会用到组合的dfs?因为左右脑,不得不说这个名字叫kkksc03的孩子真是天才,还能并发解题,长见识了。

咱们就以输入样例中最后一行为例子,代入组合思维,最后一行有3个数字,我们计算其中2个数字(取中间,数字个数为4时,取2,数字个数为5时,取3)的组合,结果为**{2,4},{2,3},{4,3},那么剩余的数字分别是3,4,2**.

然后再对3种情况一一对比,第一种情况 2+4=6>3 , 那么用时为 6 ,第二种情况 2+3=5>4 , 那么用时为 5 ,第三种情况 4+3=7>2 , 那么用时为 7 ,三种情况中,用时最少为 5 ,那么结果就是 5 了。

之后对每一门课都执行以上操作即可。

代码

#include
using namespace std;
int s[100],a[100],b[100],tmp=-1,ans;
bool f[100];
//计算单科最少用时
void dfs(int pos,int cnt,int n,int r){
    if(cnt==r){
        int x=0,y=0;
        //单科的某个组合用时累加,例如{2,4}
        for(int i=0;i<cnt;i++) x += a[i];
        //单科的某个组合剩余用时累加,例如{3}
        for(int i=1;i<=n;i++) if(!f[i]) y += b[i];
        //单次组合用时,例如6
        if(tmp==-1||tmp>(x>y?x:y)) tmp=x>y?x:y;
        return;
    }
    for(int i=pos;i<=n;i++){
        a[cnt] = b[i];
        f[i] = true;
        dfs(i+1,cnt+1,n,r);
        f[i] = false;
    }
}
int main(){
    for(int i=1;i<=4;i++) cin>>s[i];
    for(int i=1;i<=4;i++){
        for(int j=1;j<=s[i];j++) cin>>b[j];
        dfs(1,0,s[i],1+((s[i]-1)>>1));
        //累计单科最少用时
        ans += tmp;
        tmp=-1;
    }
    cout<<ans;
    return 0;
}

子集(dfs板子)

输入样例

3
1 2 3

输出样例

1 2 3 
1 2 
1 3 
1 
2 3 
2 
3 

思路

找到的求子集dfs板子都是用的vector,本板子是用vector改成普通数组做的。学习视频

模板

#include
using namespace std;
int n,t,a[15],b[15];
void dfs(int k)
{
    if(k == n){
        for(int i=0;i<t;i++) cout<<b[i]<<' ';
        cout<<endl;
        return;
    }
    b[t] = a[k];
    t++;
    dfs(k + 1);
    t--;
    dfs(k + 1);
}
int main()
{
    cin >> n;
    for(int i=0;i<n;i++) cin>>a[i];
    dfs(0);
    return 0;
}

[COCI2008-2009#2] PERKET

题目描述

Perket 是一种流行的美食。为了做好 Perket,厨师必须谨慎选择食材,以在保持传统风味的同时尽可能获得最全面的味道。你有 n 种可支配的配料。对于每一种配料,我们知道它们各自的酸度 s 和苦度 b。当我们添加配料时,总的酸度为每一种配料的酸度总乘积;总的苦度为每一种配料的苦度的总和。

众所周知,美食应该做到口感适中,所以我们希望选取配料,以使得酸度和苦度的绝对差最小。

另外,我们必须添加至少一种配料,因为没有任何食物以水为配料的。

输入格式

第一行一个整数 n,表示可供选用的食材种类数。

接下来 n 行,每行 2 个整数 si 和 bi,表示第 i 种食材的酸度和苦度。

输出格式

一行一个整数,表示可能的总酸度和总苦度的最小绝对差。

输入样例

4
1 7
2 6
3 8
4 9

输出样例

1

思路

dfs求子集板子的变体。

代码

#include
using namespace std;
int n,t,a[15][2],b[15][2],x=1,y,ans=-1;
bool f;
void dfs(int k)
{
    // 每次求出一种子集
    if(k == n){
        // 酸度累乘,苦度累加
        for(int i=0;i<t;i++) f = true, x *= b[i][0], y += b[i][1];
        // 计算绝对差
        int tmp = x>y?x-y:y-x;
        // 判断最小绝对差
        if(f&&(ans==-1||ans>tmp)) ans=tmp;
        f = false, x = 1, y = 0;
        return;
    }
    //选择
    b[t][0] = a[k][0];
    b[t][1] = a[k][1];
    t++;
    dfs(k + 1);
    //不选择
    t--;
    dfs(k + 1);
}
int main()
{
    cin >> n;
    for(int i=0;i<n;i++) cin>>a[i][0]>>a[i][1];
    dfs(0);
    cout<<ans;
    return 0;
}

全排列(dfs板子)

题目描述

按照字典序输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

输入格式

一个整数 n(1≤n≤9)。

输出格式

由 1∼n 组成的所有不重复的数字序列,每行一个序列。

每个数字保留 5 个场宽。

输入样例

3

输出样例

    1    2    3
    1    3    2
    2    1    3
    2    3    1
    3    1    2
    3    2    1

思路

我们知道,这题其实就是 n 个 for 循环嵌套,每个 for 循环的数字范围是 1-n ,然后在 for 循环最里层判断这 n 个数字是否重复即可,不重复则输出,重复就 continue;但是,我们也知道,只有 n 已知的情况下,才能写出 for 循环的嵌套代码,问题是 n 未知,这就无法用for循环去解决,因此,本题需要采用 dfs ,我们甚至可以理解为 dfs 实现了 n 未知的情况下进行 for 循环。

本题的 dfs 题解 参考b站某up主视频,讲的非常清晰,点赞,本代码也是视频原版代码模仿。

模板

const int N = ;//N看题目数据范围

int n;
bool st[N];//存节点是否被访问过
char x[N];//存状态,可能是二维,也可能是多个

void dfs(int u)
{
    if(u == n)//无需搜索
    {
        for(int i = ;i ;i ) 
        {
            cout << ;//输出
        }
        return;
    }
    for(int i = ;i ;i )//搜索
    {
        //下面是上面的那个回溯模板
        st[i] = true;//节点被访问
        /*......*/;//干一些操作
        dfs(u + 1); // 递归下一层
        st[i] = false;
        /*......*/;//还原现场,回溯
    }
}


代码一

#include
#include
using namespace std;
const int N = 15;

int n;
int ans[N];
bool visit[N];

void dfs(int k)
{
    if(k == n)
    {
        for(int i = 0; i < n; i ++) cout << ans[i] << ' ';
        cout << endl;
        return;
    }
    for(int i = 1;i <= n; i ++)
    {
        if(!visit[i])
        {
            visit[i] = true;
            ans[k] = i;
            dfs(k + 1);
            visit[i] = false;
            ans[k] = 0;
        }
    }
}

int main()
{
    cin >> n;
    dfs(0);
    return 0;
}

代码二

#include
using namespace std;
//某一趟是指(1,2,3)结束,开始新的一趟(1,3,2)
//flag数组用来标记n个数字中哪些数字被使用,数字被使用则置为1,否则置为0
//memory数组用来存入使用的数字,当memory长度为n时,输出memory
int n,flag[15],memory[15];
void dfs(int i){
    for(int j=1;j<=n;j++){
        //如果该数字未被使用
        if(flag[j]==0){
            //使用该数字并标记为1
            flag[j] = 1;
            memory[i] = j;
            //当memory数组长度为n时,某一趟排列结束并输出排列顺序
            if(i==n){
                for(int k=1;k<=n;k++){
                    cout<<"    "<<memory[k];
                }
                cout<<endl;
            //当memory数组长度
            }else dfs(i+1);
            //某一趟完成后,开始清零,进入另一趟排列
            flag[j] = 0;
            memory[i] = 0;
        }
    }
}
int main(){
    cin>>n;
    dfs(1);
}

4.16

三连击(升级版)

题目描述

将 1,2,…,9 共 9 个数分成三组,分别组成三个三位数,且使这三个三位数的比例是 A:B:C,试求出所有满足条件的三个三位数,若无解,输出 No!!!

输入格式

三个数,A,B,C

输出格式

若干行,每行 3 个数字。按照每行第一个数字升序排列。

输入样例

1 2 3

输出样例

192 384 576
219 438 657
273 546 819
327 654 981

思路

dfs排列模板变体

首先是1-9的全排列,保障任意数字不重复

其次,三个数字存在比例关系

最后,输出按照从小到大的顺序

本题提供两种解法,实际上一种是 stl 实现全排列,即 next_permutation() 函数,可以实现数组的全排列且按照顺序排列,但是数组初始化时要按照从小到大的顺序,另一种是手写 dfs 实现全排列,代码如下。

代码一(stl)

#include
#include
using namespace std;
int sum;
int main(){
    int n[10] = {0,1,2,3,4,5,6,7,8,9};
    int a,b,c;
    //输入比例
    cin>>a>>b>>c;
    do{
        int x = n[1]*100+n[2]*10+n[3];
        int y = n[4]*100+n[5]*10+n[6];
        int z = n[7]*100+n[8]*10+n[9];
        //成比例则输出
        if(z*a == x*c&&y*a==x*b){
            n[0]++;
            cout<<x<<' '<<y<<' '<<z<<endl;
        }
    }
    //全排列直到结束
    while(next_permutation(n+1,n+10));
    //计数君,默认为零。
    if(n[0]==0){
        cout<<"No!!!";
    }
    return 0;
}

代码二(dfs)

#include
using namespace std;
int sum,a,b,c;
int flag[15],memory[15];
void dfs(int i){
    for(int j=1;j<=9;j++){
        if(flag[j]==0){
            flag[j] = 1;
            memory[i] = j;
            if(i==9){
                int x = memory[1]*100+memory[2]*10+memory[3];
                int y = memory[4]*100+memory[5]*10+memory[6];
                int z = memory[7]*100+memory[8]*10+memory[9];
                if(z*a == x*c&&y*a==x*b){
                    memory[0]++;
                    cout<<x<<' '<<y<<' '<<z<<endl;
                }
            }else dfs(i+1);
            flag[j] = 0;
            memory[i] = 0;
        }
    }
}
int main(){
    cin>>a>>b>>c;
    dfs(1);
    if(memory[0]==0){
        cout<<"No!!!";
    }
    return 0;
}

4.17

[NOIP2004 普及组] 火星人

题目描述

人类终于登上了火星的土地并且见到了神秘的火星人。人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法。这种交流方法是这样的,首先,火星人把一个非常大的数字告诉人类科学家,科学家破解这个数字的含义后,再把一个很小的数字加到这个大数上面,把结果告诉火星人,作为人类的回答。

火星人用一种非常简单的方式来表示数字――掰手指。火星人只有一只手,但这只手上有成千上万的手指,这些手指排成一列,分别编号为 1,2,3,⋯。火星人的任意两根手指都能随意交换位置,他们就是通过这方法计数的。

一个火星人用一个人类的手演示了如何用手指计数。如果把五根手指――拇指、食指、中指、无名指和小指分别编号为 1,2,3,4 和 5,当它们按正常顺序排列时,形成了 5 位数 12345,当你交换无名指和小指的位置时,会形成 5 位数 12354,当你把五个手指的顺序完全颠倒时,会形成 54321,在所有能够形成的 120 个 5 位数中,12345 最小,它表示 1;12354 第二小,它表示 2;54321 最大,它表示 120。下表展示了只有 3 根手指时能够形成的 6 个 3 位数和它们代表的数字:

三进制数 代表的数字
123123 11
132132 22
213213 33
231231 44
312312 55
321321 66

现在你有幸成为了第一个和火星人交流的地球人。一个火星人会让你看他的手指,科学家会告诉你要加上去的很小的数。你的任务是,把火星人用手指表示的数与科学家告诉你的数相加,并根据相加的结果改变火星人手指的排列顺序。输入数据保证这个结果不会超出火星人手指能表示的范围。

输入格式

共三行。
第一行一个正整数 N,表示火星人手指的数目(1≤N≤10000)。
第二行是一个正整数 M,表示要加上去的小整数(1≤M≤100)。
下一行是 1 到 NN 个整数的一个排列,用空格隔开,表示火星人手指的排列顺序。

输出格式

N 个整数,表示改变后的火星人手指的排列顺序。每两个相邻的数中间用一个空格分开,不能有多余的空格。

输入样例

5
3
1 2 3 4 5

输出样例

1 2 4 5 3

思路

dfs排列模板变体

正常思路下,先采用 dfs 模板遍历到12345,接着遍历3次即可,结果可想而知 TLE ,问题就在与遍历到12345太费时间了,必须快速达到12345,具体思路见代码。

代码

#include
using namespace std;
int n, m, amount, ans[10100];
bool flag, visit[10100];
void dfs(int step) {
	if (flag)return;
    //当直达12345之后,就开始正常的dfs了
    //因此要关掉快捷键amount,同时amount也开始担任计数的作用了,每次遍历到下一趟排列,amount都要加一
	if (step > n) {
		amount++;
		if (amount == m+1) {
			flag = true;
			for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
			return;
		}
	}
	for (int i = 1; i <= n; i++)
	{
        //这一句是捷径,快速达到12345,本来每次i都要从1到n,但是这句if判断可以直达。
		if (!amount) i = ans[step];
		if (!visit[i]) {
			visit[i] = true;
			ans[step] = i;
			dfs(step + 1);
			visit[i] = false;
		}
	}
}
int main(void)
{
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++)
		scanf("%d", &ans[i]);
	dfs(1);
	return 0;
}

你可能感兴趣的:(洛谷,暴力枚举,c++,算法,dfs,深度优先遍历,深度优先)