东北大学程序设计夏令营试水赛

本次比赛包含题目

难度性 ★:1.IQ Test 2.Theatre Square 3.Broadcasting 4.Digits Sum 5.Equalize Prices Again
难度性 ★★:1.Epic Battle 2.Pink Elephants 3.Tests Preparation 4.Procrastination 5.The Longest Good Substring
难度性 ★★★:1.Innovative Experiment 2.Magic Chains 3.Prohibition 4.Secret Laboratory 5.Make Your Donation Now

Problem A Innovative Experiment

题目链接Innovative Experiment
题目分类:构造,质因数分解,置换
题目大意:一个序列1,2,3…n,经过一次操作后变为p1,p2,p3,…,pn,pi(1 < i <= n)为原序列的另一个排列。序列最小经过k次操作后重新经过回到原序列,给定k,求原序列长度(要求长度小于1e5)和操作方案,若无解,输出No solution(题意也太难概括了吧
题目思路:一个置换表一定可以分成若干轮换,轮换之间互不干扰,比如长度为7的序列操作一轮后变为2,7,5,1,3,4.6,那么这个置换表就有如下两个轮换1->4->6->7->2->1,3->5->3,若一个轮换长度为k,则这个轮换回到原来序列就要k次操作,对于整个置换表来说,需要操作的次数就为所有轮换长度的最大公约数。这题对于序列长度有要求,那么我们就要求长度最小满足要求的方案,故要求轮换的长度两两互素(轮换长度之和为序列长度,而所有轮换的长度的最小公倍数又给定了),所以我们只需将k分解为p1^c1 + p2 ^ c2 + … + pm ^ cm(pi为素数且互不相同),c1 + c2 + … + cm即为所求序列的最小长度,构造的话考虑最简单的情况就好了(某个轮换1,2,3,…,m->2,3,…,m ,1)。细节看代码。
AC代码:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdio>

using namespace std;

vector<long long> a;
const long long b = 100000;

int main() {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    long long k;
    while (~scanf("%lld", &k)) {
        a.clear();
        //质因数分解,注意要有i <= b部分,不然会TLE
        for (long long i = 2; i <= b && i * i <= k; i ++) {
            long long w = 1;
            while (k % i == 0) {
                w *= i;
                k /= i;
            }
            if (w != 1) {
                a.push_back(w);//记录素数i的个数
            }
        }
        if (k != 1) {
            a.push_back(k);
        }
        //a[i]之和即为最小长度
        long long sum = 0;
        for (unsigned i = 0; i < a.size(); i ++) {
            sum += a[i];
            if (sum > b) {
                break;
            }
        }
        if (sum == 0) {
            printf("1\n1\n");
        } else if (sum > b) {
            printf("No solution\n");
        } else {
            bool flag = true;//flag控制格式
            printf("%lld\n", sum);
            long long cnt = 1;//cnt为当前轮换的最小数
            for (unsigned i = 0; i < a.size(); i ++) {
                //先输出cnt + 1 到 cnt + a[i](a[i]为长度)
                for (long long j = 1; j < a[i]; j ++) {
                    if (flag) {
                        printf("%lld", j + cnt);
                        flag = false;
                    } else {
                        printf(" %lld", j + cnt);
                    }
                }
                //再输出cnt
                if (flag) {
                    printf("%lld", cnt);
                    flag = false;
                } else {
                    printf(" %lld", cnt);
                }
                cnt += a[i];//别忘了更新cnt
            }
            putchar(10);
        }
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

快的62ms就跑完了

Problem B Epic Battle

题目链接Epic Battle
题目分类:博弈论
题目描述:有n堆石子,每次操作能将其中一堆石子分为两堆非空石子,两人轮流操作,谁先不能操作谁就赢了,判断获胜的人
题目思路:入门级博弈论问题(我差点没做出来 ),对于一堆数量为n的石子,它必须要n - 1论操作才能把它全部分为1,1,…,1,直接判断石子数减堆数的奇偶性就好了
AC代码

#include <iostream>
#include <cstdio>

using namespace std;

int main() {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int n, x;
    while (~scanf("%d", &n)) {
        int sum = 0;
        for (int i = 0; i < n; i ++) {
            scanf("%d", &x);
            sum += x;
        }
        if ((sum - n) % 2 == 0) {
            printf("Mike\n");
        } else {
            printf("Constantine\n");
        }
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

最快的是60ms

Problem C IQ Test

题目链接IQ Test
题目分类:数学
题目大意:给定四个数字,判断是不是等比数列或等差数列,如果是,输出下一个数,否则输出42
题目思路:用等比中项和等差中项来判断要方便些
AC代码

#include <iostream>

using namespace std;

int a[4];

int main() {
    while (cin >> a[0]) {
        for (int i = 1; i < 4; i ++) {
            cin >> a[i];
        }
        if (2 * a[1] == a[0] + a[2] && 2 * a[2] == a[1] + a[3]) {
            int d = a[1] - a[0];
            printf("%d\n", a[3] + d);
        } else if (a[1] * a[1] == a[0] * a[2] && a[2] * a[2] == a[1] * a[3]) {
            int m = a[3] * a[3], n = a[2];
            if (m % n == 0) {
                printf("%d\n", m / n);
            } else {
                printf("42\n");
            }
        } else {
            printf("42\n");
        }
    }
    return 0;
}
var foo = 'bar';

最快的是30ms

Problem D Pink Elephants

题目链接Pink Elephants
题目分类:动态规划,组合数
题目大意:长度为n的数列,每个数取值为[1,m],要求后一项的值严格大于前一项的值,求满足题意的数列数目
题目思路:其实就相当于从[1,m]中选出n个数,根据c(n, m) = c(n - 1, m - 1) + c(n, m - 1)动态规划求出c(n,m)的值就好了,稍微解释一下吧,第m个数如果选,那就只要从前面的m - 1个数选n - 1个,数目为c(n - 1, m - 1),如果第m个数不选,那就要从前面m - 1个数选n个,数目为c(n, m - 1)
AC代码

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const long long mod = 1000000007;

int ans[201][201];

int main() {
    //freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);
    int n, m;
    //预处理打表
    ans[0][0] = 1;
    for (int i = 1; i <= 200; i ++) {
        ans[0][i] = 1;
        for (int j = 1; j <= i; j ++) {
            ans[j][i] = ans[j - 1][i - 1] + ans[j][i - 1];
            ans[j][i] %= mod;
        }
    }
    while (~scanf("%d %d", &n, &m)) {
        printf("%d\n", ans[n][m]);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

最快的是60ms

Problem E Theatre Square

题目链接Theatre Square
题目分类水题
题目大意:一个n×m的网格,最多能填多少个a×a的块
题目思路:算出横向最多可以填多少个块,纵向最多能填多少个块,将它们乘起来就是答案。
AC代码

#include <iostream>
#include <cstdio>
#include <cmath>

using namespace std;

int main() {
    int m, n, a;
    while (~scanf("%d %d %d", &m, &n, &a)) {
        long long l = ceil(double(m) / a);
        long long h = ceil((double)(n) / a);
        printf("%lld\n", l * h);
    }
    return 0;
}

最快的是15ms

Problem F Broadcasting

题目链接Broadcasting
题目分类:数学,网络流
题目大意:给定n个点(编号1到n)的完全图,1个点1s内最多能将信息传输到k个点,若要将第一个点的信息传输到每一个点,求所需要的时间
题目思路:找找规律就好了,若第k秒最多有an个电脑有信息,则第k + 1秒最多有(k + 1) × an个点有信息(前面每个点都传输到k个不同的点,所以就是k × an + 原来 an个点)
AC代码:注意开long long

#include <iostream>
#include <cstdio>

using namespace std;

int main() {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    long long n, k;
    while (~scanf("%lld %lld", &n, &k)) {
        int ans = 0;
        long long num = 1;
        while (num < n) {
            ans ++;
            num *= (k + 1);
        }
        printf("%d\n", ans);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

最快的是62ms

Problem G Tests Preparation

题目链接Tests Preparation
题目分类:搜索
题目大意:给定m个数n个集合,求至少需要取多少个集合才能包含这m个数
题目思路:由于n<=20,直接暴力搜索就行,也不需要任何优化,算是一道搜索难度稍难的入门搜索题。具体看代码吧,这里介绍一下整数的集合表示,我们用二进制的0/1来表示,那么要按字典序升序枚举所有的子集就是

for (int S = 0; S < 1 << n; S ++) {
	for (int i = 0; i < n; i ++) {
		if (S >> i & 1) {
			//对集合元素的处理
		} else {
 			//对不在集合元素的处理
 		}
	}
}

如果我们要枚举所有长度为k的子集呢,比如说0101110之后是0110011,0111110之后是1001111,不然发现这个变换可以通过以下四个操作实现
(1)求出最低位的1开始的连续的1的区间(0101110->0001110)
(2)将这一区间全部变为0,并将区间左侧的那个0变为1(0101110->0110000)
(3)将第一步取出来的区间右移,直到剩下的1的个数减少了一个(0001110->0000011)
(4)将第(2)步和第(3)步的结果按位取或(0110000 | 0000011 = 0110011)
代码就是

int comb = (1 << k) - 1;
//comb为字典序最小的子集
while (comb < 1 << n) {
	//这里进行争对集合的处理
	int x = comb & -comb, y = comb + x;
	//x为comb中最低位的1,即0000010,y相当于(2)所求即0110000
	comb = ((comb & ~y) / x >> 1) | y;
	//z = comb & ~y 为(1)所求,即0001110
	//z / x 代表先将这个连续的1挪到最右边,即0000111
	//(z / x) >> 1即为(4)所求,即0000011
	//与y按位或即为下一个长度为k的子集
}

AC代码
(1)DFS代码

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>
#include <numeric>

using namespace std;

vector<int> a[20], ans, t;
bool used[60], temp[20][60];
int ma, n, m;

void dfs(int level, int cnt) {
    if (level == n) {
        bool flag = true;
        for (int i = 0; i < m; i ++) {
            if (!used[i]) {
                flag = false;
                break;
            }
        }
        if (flag && ma > cnt) {
            ma = cnt;
            ans = t;
        }
        return ;
    }
    //回溯记录
    for (int i = 0; i < m; i ++) {
        temp[level][i] = used[i];
    }
    t.push_back(level);
    for (unsigned i = 0; i < a[level].size(); i ++) {
        used[a[level][i]] = true;
    }
    dfs(level + 1, cnt + 1);
    /*错误回溯
    for (unsigned i = 0; i < a[i].size(); i ++) {
        used[a[level][i]] = false;
    }
    */
    // 正确回溯
    for (int i = 0; i < m; i ++) {
        used[i] = temp[level][i];
    }
    t.pop_back();
    dfs(level + 1, cnt);
}

int main() {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int k, x;
    while (~scanf("%d %d", &n, &m)) {
        memset(used, false, m * sizeof(bool));
        for (int i = 0; i < n; i ++) {
            a[i].clear();
            scanf("%d", &k);
            while (k --) {
                scanf("%d", &x);
                x --;
                a[i].push_back(x);
            }
        }
        ma = numeric_limits<int>::max();
        ans.clear(); t.clear();
        dfs(0, 0);
        printf("%d\n", ma);
        for (unsigned i = 0; i < ans.size(); i ++) {
            if (i != 0) {
                printf(" %d", ans[i] + 1);
            } else {
                printf("%d", ans[i] + 1);
            }
        }
        putchar(10);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

在这里插入图片描述

(2)集合整数表示代码,有个坑点,如果是long long类型,一定要写(1LL << m),不然默认是int类型。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <iostream>

using namespace std;

const int M = 60, N = 20;
long long st[N];

int main() {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int n, m, l;
    long long x;
    while (~scanf("%d %d", &n, &m)) {
        memset(st, 0, n * sizeof(long long));
        for (int i = 0; i < n; i ++) {
            scanf("%d", &l);
            while (l --) {
                scanf("%lld", &x);
                st[i] |= (1LL << (x - 1));
            }
        }
        int ans = 0, cnt = 0;
        bool flag = false;
        for (int k = 1; k <= n; k ++) {//从小到打枚举所有长度
            int comb = (1 << k) - 1;
            while (comb < 1 << n) {
                long long tmp = 0;
                int num = 0;
                for (int i = 0; i < n; i ++) {
                    if (comb >> i & 1) {
                        tmp |= st[i];
                        num ++;
                    }
                }
                if (tmp == (1LL << m) - 1) {//包含了所有数
                    flag = true;
                    ans = comb, cnt = num;
                    break;
                }
                int x = comb & -comb, y = comb + x;
                comb = ((comb & ~y) / x >> 1) | y;
            }
            if (flag) {
                break;
            }
        }
        printf("%d\n", cnt);
        for (int i = 0; i < n; i ++) {
            if (ans >> i & 1) {
                if (flag) {
                    printf("%d", i + 1);
                    flag = false;
                } else {
                    printf(" %d", i + 1);
                }
            }
        }
        putchar(10);
    }
    fclose(stdin);
    fclose(stdout);
}

在这里插入图片描述

Problem H Magic Chains

题目链接Magic Chains
题目分类:最短路,字符串哈希
题目大意:给定n个单词,若两个单词只相差一个字母,则可以进行一次操作将一个单词变为另一个单词,给定起始单词和目标单词,求最小操作次数,不能则输出FAIL
题目思路:这题很显然是一个无权图最短路的问题,用BFS解决,但这题的难点在与如何建边,这里给出王爷的思路,两个单词要连边,有且只有一个字母不同,其余字母字母都相同,那么hash值也相同。将单词编号放入每个字母不考虑的hash值的vector中,遍历所有的vector,将vector中的点两两连边即可,注意还需要map建立hash,位置和vector的对应关系。具体看代码吧(实在讲不清楚

int len = strlen(s[0]);//先求出单词长度
for (int i = 0; i < n; i ++) {//遍历每个单词
    for (int j = 0; j < len; j ++) {//哪个位置不考虑
        long long res = 0;
        //此部分求hash值
        for (int k = 0; k < len; k ++) {
            if (j != k) {
                res += (s[i][k] - 'a') * pow[k];
            }
        }
        //此部分求hash值
        mp[{res, j}].push_back(i);//必须相同位置不考虑才能连边
    }
}
for(auto &it : mp) {//遍历所有的vector
    vector<int> a = it.second;
    //两两连边
    for(unsigned j = 0; j < a.size(); j ++) {
        int v = a[j];
        for(unsigned k = j + 1; k < a.size(); k ++) {
            int u = a[k];
            G[v].push_back(u);
            G[u].push_back(v);
        }
    }
    //两两连边
}

AC代码

#include <iostream>
#include <cstdio>
#include <map>
#include <queue>
#include <vector>
#include <cstring>

using namespace std;
const int MAX_N = 60000;
map<pair<long long, int>, vector<int> > mp;
vector<int> G[MAX_N];
char s[MAX_N][11];
long long pow[11];
bool visited[MAX_N];
int path[MAX_N], n;
queue<int> que;

void oput(int i, int cnt) {
    if (i == -1) {
        printf("%d\n", cnt);
        return ;
    }
    oput(path[i], cnt + 1);
    printf("%s\n", s[i]);
}

void bfs() {
    memset(path, -1, n * sizeof(int));
    memset(visited, false, n * sizeof(bool));
    que.push(0);
    visited[0] = true;
    while (!que.empty()) {
        int v = que.front(); que.pop();
        for (unsigned i = 0; i < G[v].size(); i ++) {
            int u = G[v][i];
            if (!visited[u]) {
                que.push(u);
                visited[u] = true;
                path[u] = v;
            }
        }
    }
    if (path[n - 1] == -1) {
        printf("FAIL\n");
    } else {
        oput(n - 1, 0);
    }
}

int main() {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    pow[0] = 1;
    for (int i = 1; i <= 10; i ++) {
        pow[i] = pow[i - 1] * 26;
    }
    while (~scanf("%d", &n)) {
        mp.clear();
        for (int i = 0; i < n; i ++) {
            G[i].clear();
            scanf("%s", s[i]);
        }
        int len = strlen(s[0]);
        for (int i = 0; i < n; i ++) {
            for (int j = 0; j < len; j ++) {
                long long res = 0;
                for (int k = 0; k < len; k ++) {
                    if (j != k) {
                        res += (s[i][k] - 'a') * pow[k];
                    }
                }
                mp[{res, j}].push_back(i);
            }
        }
        for (auto &it : mp) {
            vector<int> a = it.second;
            for (unsigned j = 0; j < a.size(); j ++) {
                int v = a[j];
                for (unsigned k = j + 1; k < a.size(); k ++) {
                    int u = a[k];
                    G[v].push_back(u);
                    G[u].push_back(v);
                }
            }
        }
        bfs();
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

在这里插入图片描述

Problem I Digits Sum

题目链接Digits Sum
题目分类:找规律
题目大意:S(x)定义为x各个数字之和,给定n,求1到n有多少个数字满足S(x + 1)< S(x)
题目思路:很显然只有个位数字是9的数字满足题意
AC代码

#include <iostream>
#include <cstdio>

using namespace std;

int main() {
    int t, n;
    while (~scanf("%d", &t)) {
        while (t --) {
            scanf("%d", &n);
            printf("%d\n", (n + 1) / 10);
        }
    }
    return 0;
}

在这里插入图片描述

Problem J Procrastination

题目链接Procrastination
题目分类:贪心
题目大意:有n个题,每个题有ai个知识点,学习一个知识点需要1单位时间,若一个题有x个知识点,学会了y个,则该题得分为y / x,现在有k单位时间,如何安排知识点的学习使得分最高,输出学习方案
题目思路:凭感觉,肯定学习攻克知识点少的题最后分数会高(若一个题知识点有x个,可以看成学这个题一个知识点的得分就是1/x),从知识点少到知识点点多的题目依次学即可,注意排序时要将题目编号放在一起排序
AC代码

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

using namespace std;

pair<int, int> a[2000];
int ans[2000];

int main() {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int k, n;
    while (~scanf("%d %d", &k, &n)) {
        for (int i = 0; i < n; i ++) {
            scanf("%d", &a[i].first);
            a[i].second = i;
            ans[i] = 0;
        }
        sort(a, a + n);
        int rem = k, l = 0;
        while (rem > 0 && l < n) {
            int s = min(a[l].first, rem);
            ans[a[l].second] = s;
            rem -= s;
            l ++;
        }
        for (int i = 0; i < n; i ++) {
            if (i != 0) {
                printf(" %d", ans[i]);
            } else {
                printf("%d", ans[i]);
            }
        }
        putchar(10);
    }
    return 0;
}

在这里插入图片描述

Problem K The Longest Good Substring

题目链接The Longest Good Substring
题目分类:字符串,尺取法
题目大意:给定一个字符串,求一个最长的子串,使得字串中包括的字符种类不超过k
题目思路:很典型的尺取法题,如果满足题意的话就右端点往前爬,否则(左端点往前爬,直到满足题意)具体看代码。尺取法是一种求最小满足条件的区间线性时间复杂度的方法,要能用尺取法必须要“抽象单调”
AC代码

#include <iostream>
#include <cstring>

using namespace std;

string s;
int num[26];//每种字母的当前数量

int main() {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int k;
    while (cin >> k >> s) {
        unsigned l = 0, r = 0, ml = 0, mr = 0, mlen = 0;
        int cnt = 0;//当前字母种类
        memset(num, 0, sizeof(num));
        while (r < s.size()) {
            int w = s[r] - 'a';
            num[w] ++;
            if (num[w] == 1) {//w是新出现的字母
                cnt ++;
                if (cnt > k) { //不满足了,左端点向前爬,直到满足题意
                    do {
                        l ++;
                        int q = s[l - 1] - 'a';
                        num[q] --;
                        if (num[q] == 0) {//q是最后一个字母
                            cnt --;
                            break;
                        }
                    } while (1);
                }
            }
            r ++;
            if (r - l > mlen) {//答案更优
                mlen = r - l;
                ml = l, mr = r;
            }
        }
        printf("%u %u\n", ml + 1, mr);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

在这里插入图片描述

Problem L Prohibition

题目链接Prohibition
题目分类:图论,贪心
题目大意:给定一个n个节点的树,对树的节点进行染色,满足,任何一个节点周围或自己至少有一个点被染色,求最少的染色数目的方案
题目思路:虽然本质上就是一个节点要被染色,则它的子节点至少有一个点未被”控制“(注意不是被染色),但代码一些细节很难考虑清楚。一个点有三种状态:未被控制(-1),不染色(0),染色(1),我们从叶子节点向上考虑,如果它的子节点(随便选个根节点)有未被控制的,那么它就必须染色。如果没有,看它的叶子节点有无被染色的,如果有,那它就是不染色,没有那就是未被控制。注意:输出是根节点-1输出1,其他节点-1输入0。整个染色过程用DFS实现
AC代码

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>

using namespace std;
//一个点被染色当且仅当他的子节点至少有一个不被控制
const int MAX_N = 100;
vector<int> G[MAX_N];
int tag[MAX_N];

void dfs(int v, int fa) {
    bool flag1 = false;//子节点有无染色节点
    bool flag2 = false;//子节点有无未被控制节点
    for (unsigned i = 0; i < G[v].size(); i ++) {
        int w = G[v][i];
        if (w != fa) {
            dfs(w, v);
            if (tag[w] == 1) {
                flag1 = true;
            } else if (tag[w] == -1) {
                flag2 = true;
            }
        }
    }
    if (flag2) {//有未被控制的点
        tag[v] = 1;
    } else if (flag1){ //有染色的点
        tag[v] = 0;
    } else {
        tag[v] = -1;
    }
}

int main() {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    int n;
    while (~scanf("%d", &n)) {
        for (int i = 0; i < n; i ++) {
            G[i].clear();
        }
        int x, y;
        for (int i = 1; i < n; i ++) {
            scanf("%d %d", &x, &y);
            x --, y --;
            G[x].push_back(y);
            G[y].push_back(x);
        }
        memset(tag, -1, n * sizeof(int));
        dfs(0, -1);
        tag[0] = abs(tag[0]);//根节点单独考虑
        for (int i = 0; i < n; i ++) {
            if (i != 0) {
                putchar(' ');
            }
            if (tag[i] == 1) {
                printf("1");
            } else {
                printf("0");
            }
        }
        putchar(10);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

在这里插入图片描述

Problem M Secret Laboratory

题目链接Secret Laboratory
题目分类:最短路,二分答案
题目大意:n个点m条边的无向图,每条边有一定的“安全等级”,只有自身安全等级大于等于这条边才能通过,判断能否在时间t内从1顶点到n顶点,能则还要求出最小的自身安全等级(我这概扩能力,醉了
题目思路:这里谈谈我对二分的理解,二分本质上是把一个求解问题转化为一个验证问题。像这题,显然验证要比求解简单(如果直到的安全等级,对于安全等级小于等于自身安全等级的跑一遍最短路,判断是否小于等于t)。总的来说要用二分要满足两个条件:①验证比求解简单 ②抽象单调。再说说一些代码实现的细节(以搜索最小为例):

int lb = min, ub = max + 1;
//如果搜索最大就是lb = min - 1,ub = max
//为了保险起见可以lb = min - 1,ub = max + 1
while (ub - lb > 1) {
	int mid = (ub + lb) >> 1;
	if (C(mid)) {
		ub = mid;
		//在这部分的ub(或者lb)就是最后答案
		//搜索最大就是lb = mid
	} else {
		lb = mid;
	}
}
if (ub == max + 1) {//到最后ub都没变,也就是没一个满足的
	printf("Not Found");	
} else {
	printf("%d\n", ub);
}

具体这题如何体现二分的,就看代码吧
AC代码

#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 1000;
const int INF = numeric_limits<int>::max();
typedef pair<int, int> P;
struct edge {
    int to, level, cost;
};
int n, m, t, dist[MAX_N];
vector<edge> G[MAX_N];
priority_queue<P, vector<P>, greater<P> > que;

void dijkstra(int sec) {
    fill(dist, dist + n, INF);
    dist[0] = 0;
    que.push({0, 0});
    while (!que.empty()) {
        P p = que.top(); que.pop();
        int v = p.second;
        if (dist[v] < p.first) {
            continue ;
        }
        for (unsigned i = 0; i < G[v].size(); i ++) {
            edge &e = G[v][i];
            if (e.level <= sec && dist[e.to] > e.cost + dist[v]) {
                dist[e.to] = e.cost + dist[v];
                que.push({dist[e.to], e.to});
            }
        }
    }
}

int main() {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    while (~scanf("%d %d", &n, &m)) {
        for (int i = 0; i < n; i ++) {
            G[i].clear();
        }
        while (m --) {
            int x, y, p, s;
            scanf("%d %d %d %d", &x, &y, &p, &s);
            x --, y --;
            G[x].push_back((edge){y, s, p});
            G[y].push_back((edge){x, s, p});
        }
        scanf("%d", &t);
        //其实可以二分所有存在的安全等级
        int lb = 0, ub = 1000001, d = 0;
        while (ub - lb > 1) {
            int md = (ub + lb) >> 1;
            dijkstra(md);
            if (dist[n - 1] <= t) {
                ub = md;
                d = dist[n - 1];
            } else {
                lb = md;
            }
        }
        if (ub == 1000001) {
            printf("NO\n");
        } else {
            printf("YES\n");
            printf("%d %d\n", ub, d);
        }
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

在这里插入图片描述

Problem N Triskaidekaphobia

题目链接Triskaidekaphobia
题目分类思维题
题目大意:给定只含有1和3的字符串,求最少删除多少个字符,使得字符串里没有字串13
题目思路:我的思路太烂了,就不发出来丢人了…这是bwxnQAQ的写法,我听了王爷的解释,将1看成(,将3看成),问题转化为删去最小的括号,使它变为)…).(…(,记录前面多余几个左括号,然后找到一对匹配就删掉就行了,因为)前面不能左括号。
AC代码

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

char s[1001];

int main() {
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    while (~scanf("%s", s)) {
        int cnt = 0, ans = 0;
        int len = strlen(s);
        for (int i = 0; i < len; i ++) {
            if (s[i] == '1') {
                cnt ++;
            } else {
                if (cnt) {
                    ans ++, cnt --;
                }
            }
        }
        printf("%d\n", ans);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

在这里插入图片描述

Problem O Make Your Donation Now

题目链接Make Your Donation Now
题目分类:后缀和,二分
题目大意:给定n个区间,定义区间([l,r])的值函数

if x <= l : f(x) = l
if l < x < r : f(x) = x;
if x >= r : f(x) = 0;

求x使得区间值函数之和S(x)最大
题目思路:这题关键在于发现一个点:x一定是在取某个区间右端点时最大,因为如果x不是区间右端点,那么当x增大时,S(x)一定增大,直到x为某个右端点。这样的话我们就能枚举所有的右端点,找到最大的S(x)。给定自变量x,如何求S(x)呢?①对于所有左端点大于x的区间,S(x)就是它们的左端点之和,如果预先处理出左端点后缀和的话,就可以通过求解数量(假设k1个)来求得S(x),这个通过预先排序(nlogn)+二分查找能在logn时间内实现,②对于x位于区间之中的区间,如果知道比右端点比它小的有多少个区间的话(设为k2个),那么位于中间的区间个数就是(n - k1) - k2(n - k1为左端点小于等于x的数目),那么我们只要预先对右端点排序 + 从小到大枚举就能很好的找到数量,并且与题目所求贴合,想要跟清楚的了解还是看代码吧。
AC代码

#include <iostream>
#include <algorithm>

using namespace std;
//lower_bound和upper_bound的使用
//小心乘积卡long long
int l[100000], r[100000];
long long sum[100001];

int main() {
    int n;
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    while (~scanf("%d", &n)) {
        for (int i = 0; i < n; i ++) {
            scanf("%d %d", &l[i], &r[i]);
        }
        sort(l, l + n);
        sort(r, r + n);
        //求后缀和
        sum[n] = 0;
        for (int i = n - 1; i >= 0; i --) {
            sum[i] = sum[i + 1] + l[i];
        }
        long long ma = -1;
        int val = -1;
        for (int i = 0; i < n; i ++) {
            int k1 = lower_bound(l, l + n, r[i]) - l;
            //一定要写1LL不能写成1
            if (ma <  sum[k1] + 1ll * (k1 - i) * r[i]) {
                ma = sum[k1] + 1ll * (k1 - i) * r[i];
                val = r[i];
            }
        }
        printf("%d %lld\n", val, ma);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

在这里插入图片描述

Problem P Equalize Prices Again

题目链接Equalize Prices Again
题目分类语言基础练习题
题目大意:有n个商品,每个商品要ai元,现在,要将所有商品价格设为一样(假设为x),且商品销售总和不低于原来销售总和,求最小x
题目思路:没啥可写的,很简单的一道题
AC代码

#include <iostream>
#include <cmath>

using namespace std;

int main() {
    int q, n, sum;
    while (~scanf("%d", &q)) {
        while (q --) {
            sum = 0;
            scanf("%d", &n);
            for (int i = 0; i < n; i ++) {
                int x;
                scanf("%d", &x);
                sum += x;
            }
            int ans = ceil((double)(sum) / n);
            printf("%d\n", ans);
        }
    }
    return 0;
}

在这里插入图片描述

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值