ブルーブリッジカップブラッシング質問のまとめ - コレクション (継続的に更新)

ブルーブリッジカップの質問まとめ

今後の予定:

  • 0. 質問を解く前の注意事項
  • 1. 再帰と再帰
  • 2. 2つのポイント
  • 3. プレフィックスと
  • 4. 数学
  • 5. 列挙
  • 6. シミュレーション
  • 7. 並べ替え
  • 8. ダブルポインタ
  • 9.BFS
  • 10. グラフ理論
  • 11. ツリー配列
  • 12. セグメントツリー
  • 13. 強欲
  • 14. 整数論
  • 15. シンプルDP
  • 16. 複雑なDP
  • 17. その他
  • 18. 第11回ブルーブリッジカップの本当の疑問
  • 19. 第12回ブルーブリッジカップ 本当の問題

BaseOn : https://www.acwing.com

0. 事前のブラッシングの注意事項

1.入力

in.next()in.nextLine()

package com.caopeng.zero;

import java.util.Scanner;

/**
 * @author Crescent_P
 * @date 2021-12-02 19:41
 */
public class InputOutput1 {
    
    
    public static void main(String[] args) {
    
    
        // Scanner类输入
        Scanner in = new Scanner(System.in);
        // 整数
//        int a = in.nextInt();
        // 浮点数
//        double v = in.nextDouble();
        // 长整数
//        long c = in.nextLong();
        // short
//        short d = in.nextShort();
        // 字符串
        String s1 = in.next();          // c++ 中的 cin/scanf 遇到空格结束
        String s2 = in.nextLine();      // c++ 中的 gets      遇到回车结束
        // 输入 abc def g
        System.out.println("s1 :" + s1);    // s1 :abc
        System.out.println("s2 :" + s2);    // s2 : def g
    }
}

hasNext()

package com.caopeng.zero;

import java.util.Scanner;

/**
 * @author Crescent_P
 * @date 2021-12-02 19:41
 */
public class InputOutput2 {
    
    
    public static void main(String[] args) {
    
    
        // Scanner类输入
        Scanner in = new Scanner(System.in);
        // hasNext()
        // hasNext()返回的是bool类型、当缓冲区有数据返回true
        // 当缓冲区没有数据会发生阻塞、等待数据的输入
        // 遇到多组输入,不知道什么时候能结束输入可以使用
        // while(in.hasNext()) 相当于 while(scanf())
        int a,b,c;
        while(in.hasNext()){
    
    
            a = in.nextInt();
            b = in.nextInt();
            c = in.nextInt();
            System.out.printf("%d\n",a+b+c);    // 模式化输出
        }

    }
}

画像-20211202195320323

2. 出力

// 常见的输出
System.out.println();       // 带换行的
System.out.print();         // 不带换行的
System.out.printf();        // 格式化输出,相当于c/c++ 的 printf

3.高速出力出力

BufferedReaderそしてBufferedWriter達成する

4. 回収容器

1. 再帰と再帰

指数列挙の再帰的実装

java

import java.util.*;

public class Main{
    
    
    static Scanner in = new Scanner(System.in);		// 输入
    // 看题目范围 n是1到15,我们稍微把数组开大一点点、防止数组越界。
    static int max = 20,n;							
    // 判断每一位是否被用过
    static boolean[] st = new boolean[max];
    public static void main(String[] args){
    
    
        n = in.nextInt();
        dfs(0);
    }
    // dfs(深度优先搜索)
    private static void dfs(int u){
    
    
        // 搜索到最后一位
        if(u == n){
    
    
            // 打印输出
            for(int i = 0;i < n;i++){
    
    
                if(st[i]) System.out.printf("%d ",i+1);
            }
            System.out.println();
            return;
        }
        // 要第u位
        st[u] = true;
        dfs(u+1);
        // 不要第u位
        st[u] = false;
        dfs(u+1);
    }
}

C/C++

#include <bits/stdc++.h>

using namespace std;

const int N = 20;

bool st[N];

int n;

void dfs(int u){
    // 递归到了叶子节点
    if(u == n){
        for(int i = 0;i < n;i++){
            if(st[i]) cout << i + 1 << " ";
        }
        cout << endl;   // 换行
        return;
    }
    // 选
    st[u] = true;
    dfs(u+1);
    
    // 不选
    st[u] = false;
    dfs(u+1);
}

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

画像

順列列挙の再帰的実装

Java

import java.util.*;
import java.io.*;
public class Main {
    
    
    static Scanner in = new Scanner(System.in);
    static int max = 10, n;
    static int[] path = new int[max];       // 存储路径
    static boolean[] st = new boolean[max]; // 记录每个数字是否被使用了
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    public static void main(String[] args)throws Exception{
    
    
        n = in.nextInt();
        dfs(0);
        out.flush();
    }
    private static void dfs(int u) throws Exception{
    
    
        if(u == n){
    
    
            for(int i = 0;i < n;i++) out.write(path[i] + " ");	// 快速写出
            out.write("\n");       // 换行
            return;
        }
        for(int i = 0;i < n;i++){
    
    
            if(!st[i]){
    
    
                st[i] = true;
                path[u] = i+1;
                dfs(u+1);
                st[i] = false;
                path[u] = 0;
            }
        }
    }
}

C/C++

#include <bits/stdc++.h>

using namespace std;

const int N = 10;

int path[N];
bool st[N];
int n;

void dfs(int u){
    if(u == n){
        for(int i = 0;i < n;i++) cout << path[i] << " ";
        cout << endl;
        return;
    }
    for(int i = 0;i < n;i++){
        if(!st[i]){
            st[i] = true;
            path[u] = i+1;
            dfs(u+1);
            st[i] = false;
        }
    }
}

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

複合列挙の再帰的実装

Java

import java.util.*;
public class Main{
    
    
    static Scanner in = new Scanner(System.in);
    static int max = 30,n,m;
    static int[] path = new int[max];
    public static void main(String[] args){
    
    
        n = in.nextInt();
        m = in.nextInt();
        dfs(0);
        return;
    }
    public static void dfs(int u){
    
    
        if(u == m){
    
    
            for(int i = 0;i < m;i++) System.out.printf("%d ",path[i]);
            System.out.println();
            return;
        }
        for(int i = 1;i <= n;i++){
    
    
            if(u == 0) path[u] = i;
            else {
    
    
                if(path[u-1] < i) path[u] = i;
                else continue;
            }
            dfs(u+1);
        }
    }
}

C/C++

#include <bits/stdc++.h>

using namespace std;

const int N = 30;

int path[N];

int n,m;

void dfs(int u){
    if(u == m){
        for(int i = 0;i < m;i++) cout << path[i] << " ";
        cout << endl;
        return;
    }
    for(int i = 1;i <= n;i++){
        if(u == 0) path[u] = i;
        else{
            // 前一位要比这一位小
            if(path[u-1] < i) path[u] = i;
            else continue;
        }
        dfs(u+1);
    }
}

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

単純なフィボナッチ数列

java

import java.util.*;
public class Main{
    
    
    static Scanner in = new Scanner(System.in);
    static int n,a = 0,b  = 1;
    public static void main(String[] args){
    
    
        n = in.nextInt();
        for(int i = 0;i < n;i++){
    
    
            System.out.printf("%d ",a);
            int temp = a + b;
            a = b;
            b = temp;
        }
    }
}

C/C++

#include <bits/stdc++.h>

using namespace std;

// 递推公式
// a[i] = a[i-1] + a[i-2]

int n;

int main(){
    int a = 0,b = 1;
    cin >> n;
    for(int i = 0;i < n;i++){
        cout << a << " ";
        int temp = a + b;
        b = a;
        a = temp;
    }
    cout << endl;
    return 0;
}

不可解なスイッチ

このトピックを書くには、まずいくつかの点を分析する必要があります。

  1. 各電球は 1 回しか押すことができません。2 回押しても何も変化せず、さらに多くの手順が必要となるためです。
  2. 押す順序は関係ありません。最初にどれを押し、次にどれを押すかは問題ではありません。

上記の 2 つの結論に基づいて、この質問を見てみましょう。

  1. 最初に最初の行だけを見ることができ、最初の行を自由に押すことができますが、各電球は 1 回しか操作できません。
  2. 最初のラインが操作され、次は 2 番目のラインの番ですが、1 番目のラインがまだ消えている場合は、2 番目のラインを介してのみ消すことができます。ロジックは次のようになり、下の各列の電球が動作するかどうかは、前の列の電球の状態によって決まります。

したがって:

  1. 最初の行にすべての州を列挙します
    1. 列挙方法は、最初の行には合計 5 つの電球があり、各電球にはオンまたはオフの 2 つの状態があり、2^5=32 の状態があります。
  2. 次に、最初の行の現在の状態に従って後続の行を操作します。
  3. 最後の行に移動して開いている部分があるかどうかを確認します。開いている場合は、完全に消すことができないことを意味します。

C/C++

#include <bits/stdc++.h>

using namespace std;

const int N = 6;

char g[N][N],backup[N][N];

// 偏移量,用于遍历(x,y)的上下左右中
int dx[5] = {1,0,-1,0,0},dy[5] = {0,1,0,-1,0};

// 将(x,y)的上下左右摁一遍
void turn(int x,int y){
    for(int i = 0;i < 5;i++){
        int nx = x + dx[i],ny = y + dy[i];
        if(nx < 0 || nx > 5 || ny < 0 || ny > 5) continue;  // 外界不需要考虑
        if(g[nx][ny] == '0') g[nx][ny] = '1';
        else g[nx][ny] = '0';
    }
}

int main(){
    int T;
    cin >> T;
    while(T--){
        // 将图读入
        for(int i = 0;i < 5;i++) cin >> g[i];
        // 第一行一共5个按钮,每个按钮开或不开一共2种情况,2^5 = 32
        int ans = 10;
        for(int op = 0;op < 32;op++){
            memcpy(backup,g,sizeof g);  // 将 g的内容放入backup中备份
            int step = 0;   // 操作的步数
            // 第一行所有情况
            for(int i = 0;i < 5;i++){
                // 当前对应了1就操作
                if(op >> i & 1){
                    step++;
                    turn(0,i);
                }
            }
            // 每一行的每一个开关开或不开其实是受上一行的开关影响的
            for(int i = 0;i < 4;i++){
                for(int j = 0;j < 5;j++){
                    // 当前是关的,那么下一行对应的必须操作一次,这个才能打开
                    if(g[i][j] == '0'){
                        step++;
                        turn(i+1,j);
                    }
                }
            }
            // 最后一行是不能有关闭的,否则就是不能实现
            bool dark = false;
            for(int i = 0;i < 5;i++){
                if(g[4][i] == '0'){
                    dark = true;
                    break;
                }
            }
            if(!dark) ans = min(ans,step);
            memcpy(g,backup,sizeof g);  // 将 g的内容放入backup中备份
        }
        if(ans > 6) ans = -1;
        cout << ans << endl;
    }
    return 0;
}

java

import java.util.*;
public class Main{
    
    
    static Scanner in = new Scanner(System.in);
    static int max = 5;
    static char[][] g = new char[max][max];
    static char[][] backup = new char[max][max];
    static int[] dx = {
    
    1,0,-1,0,0},dy = {
    
    0,1,0,-1,0};
    public static void main(String[] args){
    
    
        int T = in.nextInt();
        // T个测试样例
        while(T-- > 0){
    
    
            // 读入图
            int res = 10;
            for(int i = 0;i < 5;i++) g[i] = in.next().toCharArray();
            // 要对图进行一次备份
            for(int i = 0;i < 5;i++){
    
    
                for(int j = 0;j < 5;j++) backup[i][j] = g[i][j];
            }
            // 第一行有5个格子,每个格子开或关有2种可能,一共2^5=32种,我们这里采用二进制来表示
            for(int op = 0;op < 32;op++){
    
    
                int step = 0;
                // 先操作第一行
                for(int i = 0;i < 5;i++){
    
    
                    // 当前位是1就进行一次操作
                    if((op >> i & 1) == 1){
    
    
                        turn(0,i);
                        step++;
                    }
                }
                // 通过上一行操作下一行
                for(int i = 0;i < 4;i++){
    
    
                    for(int j = 0;j < 5;j++){
    
    
                        // 当前是灭的,那么下一行对应的就要开
                        if(g[i][j] == '0'){
    
    
                            turn(i+1,j);
                            step++;
                        }
                    }
                }
                // 判断最后一行有没有灭的
                boolean dark = false;
                for(int i = 0;i < 5;i++){
    
    
                    if(g[4][i] == '0'){
    
    
                        dark = true;
                        break;
                    }
                }
                
                // 全灭的话
                if(!dark) res = Math.min(res,step);
                // 对图进行还原
                for(int i = 0;i < 5;i++){
    
    
                    for(int j = 0;j < 5;j++)  g[i][j] = backup[i][j];
                }
            }
            // 超过步数
            if(res > 6) res = -1;
            // 此时已经遍历了32种情况
            System.out.println(res);
        }
    }
    public static void turn(int x,int y){
    
    
        for(int i = 0;i < 5;i++){
    
    
            int nx = x + dx[i],ny = y + dy[i];
            if(nx < 0 || nx >= 5 || ny < 0 || ny >= 5) continue;    // 出界不需要考虑
            if(g[nx][ny] == '0') g[nx][ny] = '1';
            else g[nx][ny] = '0';
        }
    }
}

コイン投げ

以前の考え方によれば、次のようになります。

  1. 各コインは 1 回だけアクティブに反転されます (その隣のコインはアクティブに反転されないため)。
  2. ひっくり返す順番は関係ありません

この質問について:

左から右に列挙できますが、現在の対応するビットが異なる場合は、それを反転するだけです。

c/c++

#include <bits/stdc++.h>

using namespace std;

string a,b;

int ans = 0;

int main(){
    cin >> a >> b;
    int n = a.length();
    for(int i = 0;i < n - 1;i++){
        // 当前字符不相等,就要翻转,
        // 翻转后一定相等,就不用判断了
        // 但是只要翻转了,下一个字符就一定会改
        if(a[i] != b[i]){
            ans++;
            if(a[i+1] == '*') a[i+1] = 'o';
            else a[i+1] = '*';
        }
    }
    cout << ans << endl;
}

Java

import java.util.*;
import java.io.*;
public class Main{
    
    
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static int N = 110;
    static char[] begin = new char[N],end = new char[N];
    public static void main(String[] args)throws Exception{
    
    
        int ans = 0;
        // 读入
        String a = in.readLine();
        String b = in.readLine();
        begin = a.toCharArray();
        end = b.toCharArray();
        for(int i = 0;i < begin.length-1;i++){
    
    
            if(begin[i] != end[i]){
    
    
                if(begin[i+1] == '*') begin[i+1] = 'o';
                else begin[i+1] = '*';
                ans++;
            }
        }
        in.close();
        System.out.println(ans);
    }
}

パイロットの兄弟

C/C++

#include <bits/stdc++.h>

#define x first
#define y second

using namespace std;

typedef pair<int,int> PII;

const int N = 5;
char g[N][N],backup[N][N];

// 将二维映射成一维
int get(int x,int y){
    return x*4 + y;
}

void turn_one(int x,int y){
    if(g[x][y] == '-') g[x][y] = '+';
    else g[x][y] = '-';
}

// 将x行,y列的所有操作一遍
void turn_all(int x,int y){
    for(int i = 0;i < 4;i++){
        turn_one(i,y);
        turn_one(x,i);
    }
    // 上面导致(x,y)改变了两次,抵消了
    turn_one(x,y);
}

int main(){
    // 读入图
    for(int i = 0;i < 5;i++) cin >> g[i];
    vector<PII> ans;
    // 枚举所有的情况
    for(int op = 0;op < 1 << 16;op++){
        memcpy(backup,g,sizeof g);  // 备份
        // 这一种情况的操作
        vector<PII> temp;
        // 对每一个点继续操作
        for(int i = 0;i < 4;i++){
            for(int j = 0;j < 4;j++){
                // 点是二维的,我们映射成一维好操作
                if(op >> get(i,j) & 1){
                    temp.push_back({i,j});    // 将操作入队列
                    turn_all(i,j);
                }
            }
        }
        // 判断是否全开的
        bool has_closed = false;
        for(int i = 0;i < 4;i++){
            for(int j = 0;j < 4;j++){
                if(g[i][j] == '+') has_closed = true;
            }
        }
        // 全是开的
        if(!has_closed){
            // ans为空或者ans的操作步骤比temp多
            if(ans.empty() || ans.size() > temp.size()) ans = temp;
        }
        memcpy(g,backup,sizeof g);  // 还原
    }
    // 输出
    cout << ans.size() << endl;
    for(auto op : ans){
        cout << op.x + 1 << " " << op.y+1 << endl;
    }
    return 0;
}

Java

import java.util.*;
public class Main{
    
    
    static int N = 4;
    static char[][] g = new char[N][N],backup = new char[N][N];
    static Scanner in = new Scanner(System.in);
    static List<int[]> ans = new ArrayList<>();
    public static void main(String[] args){
    
    
        // 把图给读进来
        for(int i = 0;i < 4;i++) g[i] = in.next().toCharArray();
        // 一共16个开关,全部枚举,2^16
        for(int op = 0;op < (1 << 16);op++){
    
    
            List<int[]> temp = new ArrayList<>();
            // 备份图
            for(int i = 0;i < 4;i++){
    
    
                for(int j = 0;j < 4;j++){
    
    
                    backup[i][j] = g[i][j];
                }
            }
            // 遍历每一个点
            for(int i = 0;i < 4;i++){
    
    
                for(int j = 0;j < 4;j++){
    
    
                    // 为1就是要操作
                    if(((op >> get(i,j)) & 1) == 1){
    
    
                        turn_all(i,j);
                        temp.add(new int[]{
    
    i,j});
                    }
                }
            }
            // 此时遍历完了,看是否全打开了
            boolean has_closed = false;
            for(int i = 0;i < 4;i++){
    
    
                for(int j = 0;j < 4;j++){
    
    
                    if(g[i][j] == '+') has_closed = true;
                }
            }
            // 没有关闭的
            if(!has_closed){
    
    
                if(ans.isEmpty() || ans.size() > temp.size() ) ans = temp;
            }
            // 复原
            for(int i = 0;i < 4;i++){
    
    
                for(int j = 0;j < 4;j++){
    
    
                    g[i][j] = backup[i][j];
                }
            }
        }
        System.out.println(ans.size());
        for(int[] res : ans){
    
    
            System.out.printf("%d %d\n",res[0]+1,res[1]+1);
        }
    }
    public static int get(int x,int y){
    
    
        return 4 * x + y;
    }
    public static void turn_all(int x,int y){
    
    
        for(int i = 0;i < 4;i++){
    
    
            turn_one(x,i);
            turn_one(i,y);
        }
        turn_one(x,y);
    }
    public static void turn_one(int x,int y){
    
    
        if(g[x][y] == '-') g[x][y] = '+';
        else g[x][y] = '-';
    }
}

スコア付き

アイデア 1:

  1. 分数を使って次のように形式化します: n = a + b / c
    1. ここで、a、b、c は 1 ~ 9 の組み合わせです。
  2. 再度簡略化すると、n * c = a * c + bになります。
    1. したがって、a、b、c をすべて列挙するだけで済みます。
    2. 上式が成り立つだけで十分です
  3. a、b、cをすべて列挙するにはどうすればよいですか?
    1. 前の再帰を通じて、順列列挙を実現でき、完全に並べ替えることもできます。

C/C++

#include <bits/stdc++.h>

using namespace std;

const int N = 10;

int path[N];
bool st[N];

int n,ans = 0;

// 计算path[l,r]的位数和
int cala(int l,int r){
    
    
    int sum = 0;
    for(int i = l; i <= r;i++){
    
    
        sum *= 10;
        sum += path[i];
    }
    return sum;
}

void dfs(int u){
    
    
    if(u == N){
    
    
        // 此时path中存了1~9的全排列
        for(int i = 1;i <= 7;i++){
    
    
            for(int j = i+1;j <= 8;j++){
    
    
                int a = cala(1,i);
                int b = cala(i+1,j);
                int c = cala(j+1,9);
                if(n*c == a * c + b) ans++;
            }
        }
    }
    for(int i = 1;i < N;i++){
    
    
        if(!st[i]){
    
    
            st[i] = true;
            path[u] = i;
            dfs(u+1);
            st[i] = false;
        }
    }
}

int main(){
    
    
    cin >> n;
    dfs(1);
    cout << ans << endl;
    return 0;
}

JAVA

import java.util.*;
public class Main{
    
    
    static int N = 10;
    static int[] path = new int[N];         // 用于储存全排列
    static boolean[] st = new boolean[N];
    static Scanner in = new Scanner(System.in);
    static int n,ans = 0;
    public static void main(String[] args){
    
    
        n = in.nextInt();
        dfs(1);
        System.out.println(ans);
    }
    private static void dfs(int u){
    
    
        // 此时path以及存好了全排列
        if(u == N){
    
    
            for(int i = 1;i <= 7;i++){
    
    
                for(int j = i+1;j <= 8;j++){
    
    
                    int a = cala(1,i);
                    int b = cala(i+1,j);
                    int c = cala(j+1,9);
                    if(n*c == a*c + b) ans++;
                }
            }
        }
        for(int i = 1;i < N ;i++){
    
    
            if(!st[i]){
    
    
                path[u] = i;
                st[i] = true;
                dfs(u+1);
                st[i] = false;
            }
        }
    }
    public static int cala(int l,int r){
    
    
        int sum = 0;
        for(int i = l;i <= r;i++){
    
    
            sum *= 10;
            sum += path[i];
        }
        return sum;
    }
}

アイデア 2:

  1. 上記によれば、n * c = a * c + b
    1. 方程式には 3 つの位置番号がありますが、そのうちの 2 つだけを知る必要があります。
    2. 次に、a と c を列挙して b を取得します。
    3. b が要件を満たしているかどうかを判断します。要件を満たしている場合は、

c/c++

#include <bits/stdc++.h>

using namespace std;

const int N = 20;

bool st[N],backup[N];

int n,ans;

// 判断等式是否成立
bool check(int a,int c){
    
    
    int b = n*c - a*c;
    // a b c 都得是非0的
    if(!a || !b || !c) return false;
    // st表在递归中还要使用,使用备份表
    memcpy(backup,st,sizeof st);
    // 判断b的每一位有没有和a、c有重合的
    while(b){
    
    
        int x = b % 10;
        b /= 10;
        // 出现位0或者重复使用过
        if(!x || backup[x]) return false;
        backup[x] = true;
    }
    // 判断每一位是否都使用过了
    for(int i = 1;i <= 9;i++){
    
    
        if(!backup[i]) return false;
    }
    return true;
}

void dfs_c(int u,int a,int c){
    
    
    if(u == 9) return;
    if(check(a,c)) ans++;
    for(int i = 1;i <=9;i++){
    
    
        if(!st[i]){
    
    
            st[i] = true;
            dfs_c(u+1,a,c*10+i);
            st[i] = false;
        }
    }
}

// 从第u位枚举,此时的a大小位a
void dfs_a(int u,int a){
    
    
    // 第10位
    if(u == 9) return;
    // 剪枝,因为 n = a + b/c 所有a肯定小于n
    if(a > n) return;
    // 递归枚举c
    if(a) dfs_c(u,a,0);
    for(int i = 1;i <= 9;i++){
    
    
        if(!st[i]){
    
    
            st[i] = true;
            dfs_a(u+1,a*10+i);
            st[i] = false;
        }
    }
}

int main(){
    
    
    cin >> n;
    dfs_a(0,0);
    cout << ans << endl;
    return 0;
}

Java

import java.util.*;
public class Main{
    
    
    static int N = 10;
    static boolean[] st = new boolean[N];       // 每一位只能使用一次
    static Scanner in = new Scanner(System.in);
    static int n,ans = 0;
    public static void main(String[] args){
    
    
        n = in.nextInt();
        // 从1开始搜索,此时a为0
        dfs_a(1,0);
        System.out.println(ans);
    }
    private static void dfs_a(int u,int a){
    
    
        // 第十位
        if(u == N) return;
        // 剪枝,a不可能大于n
        if(a > n) return;
        // 递归遍历c
        if(a > 0) dfs_c(u,a,0);
        for(int i = 1;i < N;i++){
    
    
            if(!st[i]){
    
    
                st[i] = true;
                // 递归a
                dfs_a(u+1,a*10+i);
                st[i] = false;
            }
        }
    }
    private static void dfs_c(int u,int a,int c){
    
    
        if(u == N) return;
        // 判断此时的ac是否满足条件
        if(check(a,c)) ans++;
        for(int i = 1;i < N;i++){
    
    
            if(!st[i]){
    
    
                st[i] = true;
                // 递归c
                dfs_c(u+1,a,c*10+i);
                st[i] = false;
            }
        }
    }
    
    // 判断是否满足条件
    private static boolean check(int a,int c){
    
    
        int b = n * c - a * c;
        // a b c 都是正数
        if(b <= 0 || a <= 0 || c <= 0) return false;
        // 判断是否有重复使用的数
        boolean[] backup = new boolean[N];
        // 使用备份来判断
        for(int i = 1;i < N;i++) backup[i] = st[i];
        while(b > 0){
    
    
            int x = b %10;
            b /= 10;
            if(x == 0 || backup[x]) return false;
            backup[x] = true;
        }
        // 判断1~9是否每一位都使用过
        for(int i = 1;i < N;i++){
    
    
            if(!backup[i]) return false;
        }
        return true;
    }
    
}

2. 2つのポイント

整数バイナリテンプレート

数値の範囲

C/C++

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n,q;
int a[N];

int main(){
    
    
    cin >> n >> q;
    for(int i = 0;i < n;i++) cin >> a[i];
    while(q--){
    
    
        int k;
        cin >> k;
        // 边界
        int l = 0,r = n-1;
        // 找到第一个大于等于k的数
        while(l < r){
    
    
            // 枚举范围内的中点
            int mid = (l + r) >> 1;
            if(a[mid] >= k) r = mid;
            else l = mid + 1;
        }
        // 可以发现,从上面的循环跳出来的时候 l = r
        // 如果二分的结果不为k,表示没有这个数,输出-1 -1 即可
        if(a[l] != k){
    
    
            cout << "-1 -1" << endl;
            continue;
        }
        // 运行到这里就是能找到这个数
        cout << l << " ";
        l = 0,r  = n-1;
        while(l < r){
    
    
            int mid = (l+r+1) >> 1;
            if(a[mid] <= k) l =  mid;
            else r = mid - 1;
        }
        cout << l << endl;
    }
    return 0;
}

Java

ロボットのジャンプ問題

C/C++

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n;
int h[N];

bool check(int mid){
    
    
    // 模拟,走一遍
    for(int i = 1;i <= n;i++){
    
    
        mid = 2 * mid - h[i];
        // 因为h[i] < 1e5,当中途的能量大于1e5的时候,就能直接确认一定能通过
        if(mid >= 1e5) return true;
        else if(mid < 0) return false;
    }
    return true;
}

int main(){
    
    
    scanf("%d",&n);
    for(int i = 1; i<= n;i++) scanf("%d",&h[i]);
    int l = 0 ,r = 100000;
    // 二分初始值
    while(l < r){
    
    
        int mid = (l + r) >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << l << endl;
}

JAVA

サブチョコレート

C/C++

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

typedef long long LL;

int h[N],w[N];

int n,k;

bool check(int mid){
    
    
    LL res = 0;
    for(int i = 0;i < n;i++){
    
    
        res += (h[i] / mid) * (w[i] / mid);
        // 满足条件
        if(res >= k) return true;
    }
    return false;
}

int main(){
    
    
    scanf("%d%d",&n,&k);
    // 读入长、宽
    for(int i = 0;i < n;i++) scanf("%d%d",&h[i],&w[i]);
    int l = 1,r = 1e5;
    // 二分边长
    while(l < r){
    
    
        int mid = (l + r + 1) >> 1;
        if(check(mid)) l = mid;
        else r = mid - 1;
    }
    printf("%d\n",l);
    return 0;
}

数値の立方根

これは浮動小数点の二分法問題です

C/C++

#include <bits/stdc++.h>

using namespace std;

int main(){
    
    
    double n;
    cin >> n;
    // 看题目 n的数据范围 -10000 ~ 10000
	double l = -10000,r = 10000;
	// 精度是 小数点后六位,我们精确的8位一般就没问题
    while(r-l > 1e-8){
    
    
		double mid = (l + r) / 2;
		if(mid * mid * mid > n) r = mid;
		else l = mid;
	}
    printf("%lf",l);
    return 0;
}

4つの平方和

C\C++

暴力、ある点を超えると最終的には TLE

#include<bits/stdc++.h>

using namespace std;

int n;

int main(){
    
    
    scanf("%d",&n);
    for(int a = 0;a*a <= n;a++){
    
    
        for(int b = a;a*a+b*b <= n;b++){
    
    
            for(int c = b;a*a+b*b+c*c<=n;c++){
    
    
                int t = n - a*a - b*b - c*c;
                int d = sqrt(t);
                if(d * d == t){
    
    
                    printf("%d %d %d %d\n",a,b,c,d);
                    return 0;
                }
            }
        }
    }
    return 0;
}

ハッシュ

データが強すぎてハッシュも固まってしまう

STLハッシュテーブル

#include<bits/stdc++.h>

using namespace std;

typedef pair<int,int> PII;

int n;

unordered_map<int,PII> m;

int main(){
    
    
    scanf("%d",&n);
    for(int c = 0;c * c <= n;c++){
    
    
        for(int d = c;d*d + c*c <= n;d++){
    
    
            int t = c*c + d*d;
            if(m.count(t) == 0) m[t] = {
    
    c,d};
        }
    }
    for(int a = 0; a*a <= n;a++){
    
    
        for(int b = 0;b*b +a*a<= n;b++){
    
    
            int sum = n - a*a - b*b;
            if(m.count(sum)){
    
    
                printf("%d %d %d %d\n",a,b,m[sum].first,m[sum].second);
                return 0;
            }
        }
    }
    return 0;
}

手書きのハッシュテーブルは渡せる

#include<bits/stdc++.h>

using namespace std;

typedef pair<int,int> PII;

int n;

const int N = 5000010;

int C[N],D[N];

int main(){
    
    
    scanf("%d",&n);
    // 初始化为-1
    memset(C,-1,sizeof C);
    for(int c = 0;c * c <= n;c++){
    
    
        for(int d = c;d*d + c*c <= n;d++){
    
    
            int s = c*c + d*d;
            if(C[s] == -1){
    
    
                C[s] = c,D[s] = d;
            }
        }
    }
    for(int a = 0; a*a <= n;a++){
    
    
        for(int b = 0;b*b +a*a<= n;b++){
    
    
            int s = n - a*a - b*b;
            if(C[s] != -1){
    
    
                printf("%d %d %d %d",a,b,C[s],D[s]);
                return 0;
            }
        }
    }
    return 0;
}

3. プレフィックスと

プレフィックスと式の導出

プレフィックスと

C/C++

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int a[N],s[N];

int n,m;

int main(){
    
    
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++){
    
    
        scanf("%d",&a[i]);
        // 处理前缀和数组
        s[i] = s[i-1] + a[i];
    }
    while(m--){
    
    
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",s[r] - s[l-1]);
    }
    return 0;
}

K回間隔

C/C++

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int a[N];
LL sum[N],cnt[N];
int n,k;

int main(){
    
    
    scanf("%d%d",&n,&k);
    for(int i = 1;i <= n;i++){
    
    
        scanf("%d",&a[i]);
        sum[i] += sum[i-1] + a[i];  // 前缀和数组
    }
    LL ans = 0;
    // 余数为i的个数
    cnt[0] = 1;
    for(int i = 1;i <= n;i++){
    
      // 枚举右端点
        ans += cnt[sum[i] % k];
        cnt[sum[i]%k]++;
    }
    printf("%lld",ans);
}

2次元のプレフィックスと式の導出

部分行列の合計

C/C++

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int a[N][N],s[N][N];

int n,m,q;

int main(){
    
    
    scanf("%d%d%d",&n,&m,&q);
    for(int i = 1;i <= n;i++){
    
    
        for(int j = 1;j <= m;j++) {
    
    
            scanf("%d",&a[i][j]);
            s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
        }
    }
    while(q--){
    
    
        int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        printf("%d\n",s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]);
    }
    return 0;
}

Java

レーザー爆弾

4. 数学

買えない金額

#include <bits/stdc++.h>

using namespace std;

int main(){
    
    
    int a,b;
    cin >> a >> b;
    cout << a * b - a - b << endl;
    return 0;
}

アリの風邪

#include <bits/stdc++.h>

using namespace std;

const int N = 55;

int n;
int x[N];

int main(){
    
    
    cin >> n;
    for(int i = 0;i < n;i++) cin >> x[i];
    // 左边向右走和右边向左走
    int left = 0,right = 0;
    for(int i = 0;i < n;i++){
    
    
        // 右边向左走
        if(abs(x[0]) > abs(x[i]) && x[i] > 0) left++;
        else if(abs(x[0]) < abs(x[i]) && x[i] < 0) right++;
    }
    // 感冒蚂蚁向左走,并且没有左边蚂蚁向右走的 或者 感冒蚂蚁向右走,并且没有向右边左走的蚂蚁
    if((x[0] > 0 && right == 0) || ( x[0] < 0 && left == 0)) cout << 1 << endl;
    else cout << left + right + 1 << endl;
    return 0;
}

ドリンク引き換え

#include <bits/stdc++.h>

using namespace std;

int getDrink(int n){
    
    
    // sum 喝的饮料数, count 瓶盖数
    int sum = n,count = n;
    while(count >= 3){
    
    
        sum += count / 3;               // 3个瓶盖兑换成一个瓶子
        count = count / 3 + count % 3;  // 剩余的盖子数量
    }
    return sum;
}

int main(){
    
    
    int n;
    scanf("%d",&n);
    printf("%d",getDrink(n));
    return 0;
}

15. シンプルDP

動的計画法解析手法

01 バックパック問題

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int weight[N],value[N];

int n,bag;

// 二维01背包
void m1();
// 一维01背包
void m2();

int main(){
    
    
    scanf("%d%d",&n,&bag);
    for(int i = 0;i < n;i++) scanf("%d%d",&weight[i],&value[i]);
    // m1();
    m2();
    return 0;
}

// 二维dp
void m1(){
    
    
    // dp[i][j] 在0~i件物品中任选、背包容量为j,能装下的最大价值为dp[i][j]
    // 递推公式:
    // 对于第i件物品来说,可以分为选和不选两个状态
    // 不选第i件物品 dp[i][j] = dp[i-1][j]
    //   选第i件物品 dp[i][j] = dp[i-1][j-weight[i]] + value[i]
    // 那么dp[i][j]就是这两种情况中最大的
    // 初始化
    // 看递推公式 会用到dp[0][j] 和 dp[i][0]
    // dp[0][j] 当能放入第0件物品的时候,dp[0][j] = value[0]
    // dp[i][0] 容量为0,价值也为0,那么  dp[i][0] = 0
    // 遍历顺序,从左上遍历到左下
    
    vector<vector<int>> dp(n,vector<int>(bag+1,0));
    // 初始化
    // for(int i = 0;i < n;i++) dp[i][0] = 0;   // 不用初始化也为0
    for(int j = weight[0];j <= bag;j++) dp[0][j] = value[0];
    // 状态转移
    for(int i = 1;i < n;i++){
    
               // 第0件物品已经初始化了,遍历物品
        for(int j = 0;j <= bag;j++){
    
        // 遍历背包
            if(j < weight[i]) dp[i][j] = dp[i-1][j];   // 放不下,就不要第i件物品
            else dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
        }
    }
    printf("%d\n",dp[n-1][bag]);
}

// 一维dp
void m2(){
    
    
    // dp[j] 容量为j,背包最大能装下dp[j]价值的物品
    // 状态转移方程 和一维的一样,分为要第i件物品和不要第i件物品
    // dp[j] = max(dp[j],dp[j-weight[i]]+value[i])
    // 初始化
    // dp[0] = 0
    // 遍历顺序
    // 一维dp其实是二维dp的压缩,就是当前这一行,复制拷贝了上面一行
    // 二维dp遍历是从左上往右下遍历,就是对于一个dp[i][j]其实依赖于左上方的值
    // 那么一维更新的时候,就不能覆盖到左边的值,应该用左边的值去更新右边的值
    // 因此对于内层循环,应该从右往左
    vector<int> dp (bag+1,0);
    dp[0] = 0;
    for(int i = 0;i < n;i++){
    
    
        for(int j = bag;j >= weight[i];j--) dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);
    }
    printf("%d\n",dp[bag]);
}

ピーナッツを摘む

#include <bits/stdc++.h>

using namespace std;

const int N = 110;

// dp[i][j] 到(i,j)位置采摘到的最多的花生
// 到(i,j)只有两种办法 (i-1,j)或者(i,j-1)
// 所以 dp[i][j] = max(dp[i-1][j],dp[i][j-1])+a[i][j]
// 可以看到需要用到 dp[i][0] 以及 dp[0][j]
// dp[i][0] 就是 第一列从上面走到下面 dp[0][j] 就是第一行,从左边走到右边
int a[N][N],dp[N][N];

int r,c,ans,T;

int main(){
    
    
    scanf("%d",&T);
    while(T--){
    
    
        scanf("%d%d",&r,&c);
        for(int i = 0;i < r;i++){
    
    
            for(int j = 0;j < c;j++) scanf("%d",&a[i][j]);
        }
        dp[0][0] = a[0][0];
        for(int i = 1;i < r;i++) dp[i][0] = a[i][0] + dp[i-1][0];
        for(int j = 1;j < c;j++) dp[0][j] = a[0][j] + dp[0][j-1];
        for(int i = 1;i < r;i++){
    
    
            for(int j = 1;j < c;j++) dp[i][j] = max(dp[i-1][j],dp[i][j-1])+a[i][j];
        }
        printf("%d\n",dp[r-1][c-1]);
    }
    return 0;
}

最長の昇順サブシーケンス

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

// dp[i] i以及i之前的最长上升子序列的长度为dp[i]
// if(a[i] > a[j] ) dp[i] = max(dp[i],dp[j]+1);
// dp[0] = 1
int a[N],dp[N];

int n,ans = 0;

int main(){
    
    
    scanf("%d",&n);
    // 读入数组
    for(int i = 0;i < n;i++) scanf("%d",&a[i]);
    for(int i = 0;i < n;i++) dp[i] = 1;
    for(int i = 1;i < n;i++){
    
    
        for(int j = 0;j < i;j++){
    
    
            if(a[i] > a[j]) dp[i] = max(dp[i],dp[j]+1);
            ans = max(dp[i],ans);
        }
    }
    printf("%d\n",ans);
}

地下宮殿からの宝の取り出し

ボラティリティシリーズ

おすすめ

転載: blog.csdn.net/Destiny_159/article/details/121801786