【算法1-1】模拟与高精度

https://www.luogu.com.cn/training/106#problems
P1518 [USACO2.4]两只塔姆沃斯牛 The Tamworth Two
https://www.luogu.com.cn/problem/P1518
原本我是单纯的模拟,没有判断死循环即无解的情况。看了下面的分析,给t设置一个最大值,超过就无解。
“看到题后,我们的第一反应是寻找环判重叠来算次数,但这样也太麻烦了,所以考虑更简洁的直接模拟:

由于只有10x10行的地图,因此牛和农夫最多只有400种情况(每个位置有4次,4方向各一次)由乘法原理可知,无论怎么走,最多都只能出现160000种情况,实际上还要小的多(而且160000也非常小了)”

#include <bits/stdc++.h>

using namespace std;
const int N = 12;

char a[N][N];
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};

int main(){
    int n, sx, sy, ex, ey;
    for(int i = 0; i < 10; i++){
        for(int j = 0; j < 10; j++) {
            cin >> a[i][j]; 
            if(a[i][j] == 'F') sx = i, sy = j;
            if(a[i][j] == 'C') ex = i, ey = j;
        }        
    }

    int t = 0, d1 = 0, d2 = 0;
    while((sx != ex || sy != ey ) && t < 200000){
        t++;
        //对于F 
        int x = sx + dx[d1], y = sy + dy[d1]; 
        if(x >= 0 && x < 10 && y >= 0 && y < 10 && a[x][y] != '*') sx = x, sy = y;
        else d1 = (d1 + 1) % 4;

        //对于C 
        x = ex + dx[d2], y = ey + dy[d2]; 
        if(x >= 0 && x < 10 && y >= 0 && y < 10 && a[x][y] != '*') ex = x, ey = y;
        else d2 = (d2 + 1) % 4;
        //printf("sx = %d sy = %d ex = %d ey = %d\n");
    }
    if(t == 200000) printf("%d\n", 0);
    else printf("%d\n", t);
    //cout << p[pos].w << endl;
    return 0;
}

P1098 [NOIP2007 提高组] 字符串的展开
https://www.luogu.com.cn/problem/P1098
难点在于处理各种特殊情况,要仔细。

#include <bits/stdc++.h>

using namespace std;

int main(){
    int p1, p2, p3;
    cin >> p1 >> p2 >> p3;
    string s, ans = "";
    cin >> s;
    char last;
    for(int i = 0; i < s.size(); i++) {
        if(s[i] != '-') ans += s[i];
        else if((i == 0 || i == s.size() - 1) && s[i] == '-') ans += '-';
        else if(s[i]=='-' && (s[i - 1] == '-' || s[i + 1] == '-')) ans += '-';
        else if(s[i] == '-'){
            if(s[i - 1] >= s[i + 1] || (s[i - 1] < 58 && s[i + 1] > 96)) ans += '-';
            else if(s[i + 1] == s[i - 1] + 1) continue;
            else{
                if(p1 == 3){
                    for(int j = 1; j <= (s[i+1] - s[i-1] - 1)* p2; j++)ans += '*';
                }
                else if(s[i+1] <= '9' && s[i+1] >= '0' && s[i-1] <= '9' && s[i-1] >= '0'){
                    if(p3 == 1){
                        //数字,顺序 
                        for(int j = s[i-1]+1; j <= s[i+1]-1; j++){
                            for(int k = 1; k <= p2; k++)
                                ans = ans + (char)(j);
                        }
                    }else if(p3 == 2){
                        // 数字 ,逆序 
                        for(int j = s[i+1]-1; j >= s[i-1]+1; j--){
                            for(int k = 1; k <= p2; k++)
                                ans = ans + (char)(j);
                        }
                    } 
                }
                else if(p1 == 1){
                    if(p3 == 1){
                        //小写字母,顺序 
                        for(int j = s[i-1]+1; j <= s[i+1]-1; j++){
                            for(int k = 1; k <= p2; k++)
                                ans = ans + (char)(j);
                        }
                    }else if(p3 == 2){
                        // 小写字母,逆序 
                        for(int j = s[i+1]-1; j <= s[i-1]+1; j--){
                            for(int k = 1; k >= p2; k++)
                                ans = ans + (char)(j);
                        }
                    } 
                }
                else if(p1 == 2) {
                    if(p3 == 1){
                        //大写字母,顺序 
                        for(int j = s[i-1]+1; j <= s[i+1]-1; j++){
                            for(int k = 1; k <= p2; k++)
                                ans = ans + (char)(j - 'a' + 'A');
                        }
                    }else if(p3 == 2){
                        // 大写字母,逆序  
                        for(int j = s[i + 1]-1; j >= s[i-1]+1; j--){
                            for(int k = 1; k <= p2; k++)
                                ans = ans + (char)(j - 'a' + 'A');
                        }
                    } 
                }
            }
        }
    }
    //printf("%d\n", t);
    cout << ans << endl;
    return 0;
}

P1045 [NOIP2003 普及组] 麦森数
https://www.luogu.com.cn/problem/P1045
图片说明

【算法1-2】排序

快排

P1177 【模板】快速排序
https://www.luogu.com.cn/problem/P1177

快排应用:第k个数 练习分治算法

P1923 【深基9.例4】求第 k 小的数
https://www.luogu.com.cn/problem/P1923
方法一:
这里用快排,先排序再输出q[k],不能完全通过,部分会TLE;
图片说明
方法二:
修改一下快排函数,找到第k个就返回它。
而且这里用cin读入仍然会TLE
换成scanf就AC了。
模板的k是从1开始,这里k是从0开始,所以要quick_sort(a, 0, n-1, k + 1)

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 5000010;

int n, k;
int a[N];

int quick_sort(int q[], int l, int r, int k){
    if(l >= r) return q[l];
    int x = q[l + r >> 1];
    int i = l - 1, j = r + 1;
    while(i < j){
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
        if(i < j){
            int tmp = q[i];
            q[i] = q[j];
            q[j] = tmp;
        }
    }
    if(j - l + 1 >= k) quick_sort(q, l, j, k);
    else quick_sort(q, j + 1, r, k - (j - l + 1));
}

int main(){
    int n;
    scanf("%d %d", &n, &k);
    for(int i = 0; i < n; i++){
        scanf("%d", &a[i]);
    }
    //模板的k是从1开始,这里k是从0开始 
    printf("%d ", quick_sort(a, 0, n-1, k + 1));
    return 0;
}

方法三:
nth_element的用法
https://www.cnblogs.com/xenny/p/9424894.html

#include <bits/stdc++.h>

using namespace std;

const int N = 5000010;

int n, k;
int a[N];

int main(){
    scanf("%d%d", &n, &k);
    for(int i = 0; i < n; i++)
        scanf("%d", &a[i]);
    nth_element(a, a + k, a + n); //使第k小整数就位 
    printf("%d", a[k]); //调用第k小整数
    return 0;
}

冒泡排序

P1116 车厢重组
https://www.luogu.com.cn/problem/P1116
方法一: 冒泡排序

#include <bits/stdc++.h>

using namespace std;
const int N = 10010;

int n;
int a[N], cnt;

void bubble(){
    for(int i = 0; i < n - 1; i++){
        for(int j = 0; j < n - 1; j++){
            if(a[j] > a[j + 1]){
                swap(a[j], a[j + 1]);
                cnt++;
            } 
        }
    }
}

int main(){
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i];
    bubble();
    printf("%d\n", cnt);
    return 0;
}

方法二:题目只是问需要多少次移动,没问排序结果
没有做排序,只是迭代去计算每个数字前有几个数字比它大,这意味着它必须要移动几次。

#include <bits/stdc++.h>

using namespace std;
const int N = 10010;

int a[N];

int main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i];
    int cnt = 0;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < i; j++){
            if(a[j] > a[i]) cnt++;
        }
    }
    printf("%d\n", cnt);
    return 0;
}

P1012 [NOIP1998 提高组] 拼数
https://www.luogu.com.cn/problem/P1012
原本我的想法是大的数字放在高位,所以直接比较每个字符串大小就行,但是只能75分。
出错的例子为:
图片说明

#include <bits/stdc++.h>

using namespace std;
const int N = 30;

int n;
string a[N];

bool cmp(string a, string b){
    return a > b;
}

int main(){
    cin >> n;
    for(int i = 0; i < n; i++) {
        cin >> a[i];
    }
    sort(a, a + n, cmp);
    string ans = "";
    for(int i = 0; i < n; i++) {
        //cout << a[i] << endl;
        ans += a[i];
    }
    cout << ans << endl;
    //printf("%.3lf\n", ans);
    return 0;
}

所以直接改cmp函数,100分AC

bool cmp(string a, string b){
    return a + b > b + a;
}

【算法1-3】暴力枚举

P1036 [NOIP2002 普及组] 选数
https://www.luogu.com.cn/problem/P1036
但其实下面代码中的st数组也可以不要,因为dfs每次传入了开始扫描的数组下标b就保证了不会重复访问。

#include <bits/stdc++.h>

using namespace std;
const int N = 30;

int n, k;
int a[N], st[N], cnt;

bool isPrime(int x){
    for(int i = 2; i <= x / i; i++){
        if(x % i == 0) return false;
    }
    return true;
}

void dfs(int x, int b, int sum){
    if(x == k){
        if(isPrime(sum)) {
            //cout << sum << endl;
            cnt++;
        }
        return ;
    }

    for(int i = b; i < n; i++){ 
        if(!st[i]){
            st[i] = 1;
            //cout << "sum = " << sum << "ai = " << a[i] << endl;
            dfs(x + 1, i + 1, sum + a[i]);
            st[i] = 0;
        }

    }
}

int main(){
    cin >> n >> k;
    for(int i = 0; i < n; i++) cin >> a[i];
    dfs(0, 0, 0);
    cout << cnt << endl;
    return 0;
}

P1157 组合的输出
都用了dfs,但题目要求的排列不同,所以dfs要相应调整。

P3392 涂国旗
https://www.luogu.com.cn/problem/P3392
先做一下预处理,求出每行每个色块的个数
让后两个嵌套的for循环分别模拟白色行数(顶部)和红色行数(底部)
进而求出用这种方法涂制国旗所需次数,再来个很简单的判断,就可以得出最少次数

#include <bits/stdc++.h>

using namespace std;
const int N = 60;

int n, m;
char a[N][N];
int W[N], B[N], R[N];

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++){
            cin >> a[i][j];
            if(a[i][j] == 'W') W[i]++;
            if(a[i][j] == 'B') B[i]++;
            if(a[i][j] == 'R') R[i]++;
        }
    }
    int ans = 2e9;
    //最上方变为白的行数 
    for(int i = 1; i <= n - 2; i++) {
        //最下方变为红的行数 
        for(int j = 1; j <= n - 2; j++) {
            int tmp = 0;
            if(i + j > n - 1) continue;  //剩余蓝行不足1
            //变白格子需要的次数 
            for(int k = 1; k <= i; k++) tmp += B[k] + R[k];
            //变蓝格子需要的次数 
            for(int k = i + 1; k <= n - j; k++) tmp += W[k] + R[k];
            //变红格子需要的次数 
            for(int k = n - j + 1; k <= n; k++) tmp += W[k] + B[k];
            ans = min(ans, tmp);
        }
    }
    cout << ans << endl;
    return 0;
}

P3654 First Step (ファーストステップ)
https://www.luogu.com.cn/problem/P3654
每次达到k,t--而不是直接变为0,类似滑动窗口。
注意k=1的特殊情况,行列会分别算一遍所以要除以2.

P1149 [NOIP2008 提高组] 火柴棒等式
https://www.luogu.com.cn/problem/P1149
关键是要确定数的范围,这里要是不太能确定的话可以保守一点取大一点。我是看了题解,发现他们的数在2000左右。

P3799 妖梦拼木棒
https://www.luogu.com.cn/problem/P3799
参考第一篇题解
https://www.luogu.com.cn/problem/solution/P3799
图片说明

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 1e5 + 10, mod = 1e9 + 7;

int n, maxl;
int a[N], cnt[N];

//求得从a个数中取出2个数的组合
int C2(int a){
    return a * (a - 1) / 2 % mod;
}

int main(){
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        maxl = max(maxl, a[i]);
        cnt[a[i]] ++;
    }
    int ans = 0;
    //枚举a,b
    for(int i = 1; i <= maxl; i++) {
        for(int j = i; j <= maxl; j++) {
            if(i == j) ans = (ans + C2(cnt[i]) * C2(cnt[i + j])) % mod;
            else ans = (ans + cnt[i] * cnt[j] % mod * C2(cnt[i + j]) % mod) % mod;
        }
    }
    cout << ans << endl;
    return 0;
}

【算法1-4】递推与递归

P1255 数楼梯
https://www.luogu.com.cn/problem/P1255
这里要用到高精度,数值比较大。
vector<vector<int> >的各种用法和易错
https://www.cnblogs.com/superjn/p/10730541.html</int>

#include <bits/stdc++.h>

using namespace std;

int n;
vector<vector<int> > f;

vector<int> add(vector<int> &a, vector<int> &b){
    if(a.size() < b.size()) return add(b, a);
    vector<int> c;
    int t = 0;
    for(int i = 0; i < a.size(); i++){
        t += a[i];
        if(i < b.size()) t += b[i];
        c.push_back(t % 10);
        t /= 10;
    }
    if(t) c.push_back(t);
    return c;
}

int main(){
    cin >> n;
    vector<int> t;
    t.push_back(0); f.push_back(t); 
    t.pop_back(); t.push_back(1); f.push_back(t);
    t.pop_back(); t.push_back(2); f.push_back(t);
    for(int i = 3; i <= n; i++) {
        vector<int> tmp = add(f[i - 1], f[i - 2]);
        f.push_back(tmp);
    }
    for(int i = f[n].size() - 1; i >= 0; i--)
        cout << f[n][i];
    return 0;
}

P1002 [NOIP2002 普及组] 过河卒
https://www.luogu.com.cn/problem/P1002
这里需要知道马是怎么走的,且马能一步走到的地方是不能经过的。

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 30;

int n, m, mx, my;
int dx[9] = {0, -2, -1, 1, 2, 2, 1, -1, -2};
int dy[9] = {0, 1, 2, 2, 1, -1, -2, -2, -1};
LL f[N][N];
bool st[N][N]; //是否被马拦住 

int main(){
    cin >> n >> m >> mx >> my;
    n++, m++, mx++, my++;

    //标记不能经过的位置 
    for(int i = 0; i < 9; i++) st[mx + dx[i]][my + dy[i]] = 1;
    f[1][1] = 1; 
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            if((i != 1 || j != 1) && !st[i][j]) f[i][j] = f[i - 1][j] + f[i][j - 1];
        }
    }
    cout << f[n][m] << endl;
    return 0;
}

P1044 [NOIP2003 普及组] 栈
https://www.luogu.com.cn/problem/P1044
https://www.luogu.com.cn/problem/solution/P1044
有多种方法。

P1928 外星密码
https://www.luogu.com.cn/problem/P1928
这个题解法很神奇,再看看。

P1164 小A点菜
https://www.luogu.com.cn/problem/P1164

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 110;

int m, n, cnt, t;
int a[N];

void dfs(int k, int sum){
    t++;
    if(sum == m) {
        cnt++;
        return;
    }
    if(k > n) return;
    dfs(k + 1, sum);
    dfs(k + 1, sum + a[k]);
}

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

dfs会部分超时
图片说明
改用dp,AC

#include <bits/stdc++.h>

using namespace std;
const int N = 110, M = 10010;

int m, n;
int a[N], f[M];

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    f[0] = 1;
    for(int i = 1; i <= n; i++){
        for(int j = m; j >= a[i]; j--){
            f[j] = f[j] + f[j - a[i]];
        }
    }
    cout << f[m] << endl;
    return 0;
}

P1990 覆盖墙壁
https://www.luogu.com.cn/problem/P1990
再看看

P3612 [USACO17JAN]Secret Cow Code S
重新写写!是参考题解AC的。

P1228 地毯填补问题
https://www.luogu.com.cn/problem/P1228
分治,重新写写!是参考题解AC的。

【算法1-5】贪心

P1803 凌乱的yyy / 线段覆盖
https://www.luogu.com.cn/problem/P1803
本来想的是按开始时间从小到大排,但WA
实际上应该按结束时间从小到大排,越早结束越应该选。
类似问题:
在一个数轴上有n条线段,现要选取其中k条线段使得这k条线段两两没有重合部分,问最大的k为多少。
最左边的线段放什么最好?
显然放右端点最靠左的线段最好,从左向右放,右端点越小妨碍越少
其他线段放置按右端点排序,贪心放置线段,即能放就放

本来直接用数组排序,每次计算两个数的值,还用到了前缀和
但只通过了一个,仔细思考后发现,把最小的两个相加后的值不一定是当前最小的,而这个题始终要选最小的两个相加。
所以要用小根堆维护,c++里由优先队列实现。
priority_queue<int, vector<int>, greater<int> > heap;</int></int>

P5019 [NOIP2018 提高组] 铺设道路
https://www.luogu.com.cn/problem/P5019
用模拟会超时。用贪心的思路求解。
再写一遍!

【算法1-6】二分查找与二分答案

P1102 A-B 数对
https://www.luogu.com.cn/problem/P1102
自己写的,本来只用了一个简单二分,但是数组可能有重复值,所以要先排序,然后再找出现的第一个位置和最后一个位置。
题解还有更简单的方法可以看看。

#include <bits/stdc++.h>

using namespace std;
typedef pair<int, int> PII; 
typedef long long LL;
const int N = 1000010, mod = 10000;

int n, c;
int a[N];

//找等于x的最小的位置 
int search1(int x){
    int l = 1, r = n;
    while(l < r){
        int mid = (l + r) / 2;
        if(a[mid] >= x) r = mid;
        else l = mid + 1; 
    }
    if(a[l] == x) return l;
    else return -1;
}

//找等于x的最大的位置 
int search2(int x){
    int l = 1, r = n;
    while(l < r){
        int mid = (l + r + 1) / 2;
        if(a[mid] <= x) l = mid;
        else r = mid - 1; 
    }
    if(a[l] == x) return l;
    else return -1;
}

int main(){
    cin >> n >> c;
    for(int i = 1; i <= n; i++) cin >> a[i];
    sort(a + 1, a + 1 + n);//用二分数组要有序
    LL cnt = 0;
    //枚举b,a = b + c
    for(int i = 1; i <= n; i++){
        if(search2(a[i] + c) != -1 && search1(a[i] + c) != -1 && search2(a[i] + c) != i && search1(a[i] + c) != i)
            cnt += search2(a[i] + c) - search1(a[i] + c) + 1;
    } 
    cout << cnt << endl; 
    return 0;
}

P1678 烦恼的高考志愿
https://www.luogu.com.cn/problem/P1678
再看看

P2678 [NOIP2015 提高组] 跳石头
P3853 [TJOI2007]路标设置
P1182 数列分段 Section II
P1163 银行贷款
P3743 kotori的设备
类似思路,思考什么样的问题可以用二分。

图片说明

【算法1-7】搜索

P2392 kkksc03考前临时抱佛脚
重重重!

P1433 吃奶酪
常规的dfs+剪枝 最后一个测试点会TLE
查看题解
图片说明
这里我没有像他那样先按距离排序,而是直接在源代码上加了一个超时弹出的条件,最后一个也能AC了。

void dfs(int now, int cnt, double d){
    l++;
    if(l >= 30000000) {
        int t = clock();
        if(t >= 940) return;
    }
    if(d >= ans) return;
    if(cnt == n){
        ans = min(ans, d);
        return;
    }

    for(int i = 1; i <= n; i++){
        if(now != i && !vis[i]){
            vis[i] = 1;
            //cout << dis[now][i]<< endl;
            dfs(i, cnt + 1, d + dis[now][i]);
            vis[i] = 0;
        }
    }
}

P1101 单词方阵
和一般的dfs套路不一样,要多一步处理

P1162 填涂颜***r>https://www.luogu.com.cn/problem/P1162
本来用bfs找1围成的形状的上下左右四条边,但是只得了32分。因为围城的图形不是矩形,是不规则的,所以会有在围墙外的0被改为2.
图片说明
可以转为思考哪些0应该被涂,哪些不应该被涂。
边界上的一定不会被涂,和它相连的也不应该被涂,剩下的就是被1围起来的。