东北大学程序设计夏令营试水赛
- 本次比赛包含题目
- Problem A Innovative Experiment
- Problem B Epic Battle
- Problem C IQ Test
- Problem D Pink Elephants
- Problem E Theatre Square
- Problem F Broadcasting
- Problem G Tests Preparation
- Problem H Magic Chains
- Problem I Digits Sum
- Problem J Procrastination
- Problem K The Longest Good Substring
- Problem L Prohibition
- Problem M Secret Laboratory
- Problem N Triskaidekaphobia
- Problem O Make Your Donation Now
- Problem P Equalize Prices Again
本次比赛包含题目
难度性 ★: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;
}
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;
}
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';
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;
}
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;
}
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;
}
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;
}