整理的算法模板合集: ACM模板
实际上是一个全新的精炼模板整合计划
目录
这场打的头疼,先更一下C题吧,其他的几道题明天醒了再更…
现在脑子不太清醒,先口胡一个最简单的C吧…
来了来了,更一下 A ~ E
(实际上是昨天快两点才睡着,快九点了才起床,十点才吃完饭开始写
比赛链接:https://codeforces.com/contest/1480
A - Yet Another String Game
Problem 1480A - Yet Another String Game
Alice和 Bob 在玩游戏,Alice 先手,给定一个字符串,他们每次可以进行一个操作:让任意一个没有被选过的位置上的字符变成另一个不一样的字符。Alice 想让字符串最后的字典序最小,Bob 想让字符串最后的字典序最大,两位都会进行最优的操作,请问最后字符串会变成什么鬼样子 ~
Solution
签到题
模拟一下就行了,两位都尽量朝着自己的方向去选,所以Alice先选一定选第一个,然后 Bob 会选第二个,为了使得序列的字典序尽可能的打,所以就是奇偶交替进行…
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 50007;
int n, m, t;
string a, b;
int main()
{
scanf("%d", &t);
while(t -- ) {
cin >> a;
int len = a.length();
int cnt = 0, cnt1 = len, num = 0;
for(int i = 0; i < len; ++ i){
if(!(i & 1)) {
if(a[i] == 'a') a[i] = 'b';
else a[i] = 'a';
}
else {
if(a[i] == 'z') a[i] = 'y';
else a[i] = 'z';
}
}
cout << a << endl;
}
return 0;
}
B - The Great Hero
Problem 1480B - The Great Hero
一个简单的贪心。开始还以为是一个模拟,然后立马发现,因为可以同归于尽,所以我们最后一击要用到正确的地方。因为我们打的顺序无所谓,所以可能存在一种情况,我随便打,中间死了,但是我把攻击力最大的哪个怪,放到最后跟他同归于尽,就会最后死,答案从 NO
变成了 YES
。
然后再来分析这么解决。
首先很明显,对于第 i i i 只怪,我们需要 ⌈ b i A ⌉ \lceil \frac{b_i}{A} \rceil ⌈Abi⌉ 次才能打死它,然后每次会掉 a i a_i ai 的血,如果直接打死它的话,会掉 ⌈ b i A ⌉ × a i \lceil \frac{b_i}{A} \rceil\times a_i ⌈Abi⌉×ai 的血。
所以我们就先模拟一遍,打一遍,然后最后的血量加上攻击力最大的那只怪的攻击力,相当于就是把这只放到最后打,要是加上之后的血量大于0,说明成功,否则中间就死了…
因为我特判的是 >0 而不是 >=0 ,我先扣掉了所有的血,再加上最大攻击力,如果我这时候剩余的血量 >0 说明我活到了最后一只攻击力最大的怪 例如这个
1
10 10 2
2 1000
50 10
答案就是NO,我的代码也是NO,我没办法活到1000的那个怪 ~
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <unordered_map>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<ll, ll> PLL;
typedef int itn;
const int N = 5e5 + 7, M = 1e6 + 7, mod = 1e9 + 7;
const ll INF = 1e18 + 7;
ll n, m, t;
ll A, B;
PLL a[N];
bool solve()
{
scanf("%lld%lld%lld", &A, &B, &n);
ll maxx = -INF;
for(int i = 1; i <= n; ++ i) {
scanf("%lld", &a[i].x);
maxx = max(maxx, a[i].x);
}
for(int i = 1; i <= n; ++ i) {
scanf("%lld", &a[i].y);
}
ll sum = 0;
for(int i = 1; i <= n; ++ i) {
ll cnt = ceil(a[i].y * 1.0 / A);
sum += cnt * a[i].x;
}
B -= sum;
B += maxx;
if(B > 0) return true;
else return false;
}
int main()
{
scanf("%lld", &t);
while(t -- ) {
bool ans = solve();
if(ans) puts("YES");
else puts("NO");
}
return 0;
}
C. Searching Local Minimum
Problem 1479A - Searching Local Minimum
交互题,有一个 1 1 1 ~ n n n 的一个排列,只会给你 n n n 的值,求一个为 V 字形的数的下标。其中 V 字形数是指下标为 i i i 的一个数, a i − 1 ≥ a i ≤ a i + 1 a_{i-1}\ge a_i\le a_{i+1} ai−1≥ai≤ai+1 ,想让你找到的这个 i i i。你每次可以询问这个排列的一个下标的值,你最多可以问 100次。
1 ≤ n ≤ 1 0 5 1\le n \le 10^5 1≤n≤105
Solution
因为题目要找的是一个 v 字形的数,也就是 a i − 1 ≥ a i ≤ a i + 1 a_{i-1}\ge a_i\le a_{i+1} ai−1≥ai≤ai+1 的这个 i i i。
数据 1 0 5 10^5 105
如果 a i ≤ a i + 1 a_i\le a_{i+1} ai≤ai+1 那么 一定 a i ≥ a i − 1 a_i\ge a_{i-1} ai≥ai−1 ,不然 i i i 就是答案。同理, a i ≥ a i − 1 a_i\ge a_{i-1} ai≥ai−1,那么 a i − 1 ≥ a i − 2 a_{i-1}\ge a_{i-2} ai−1≥ai−2 ,一次类推,一定是一个单调的
同理若 a i ≥ a i + 1 a_i\ge a_{i+1} ai≥ai+1 也一样单调,所以二分就行了,若 a[mid]
大于右边,则答案在左边…
没了,睡觉睡觉
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <unordered_map>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef int itn;
const int N = 5e3 + 7, M = 1e6 + 7, mod = 1e9 + 7;
const ll INF = 1e18 + 7;
int n, m, t, k, q;
int a[N];
int vis[M];
PII ans[N];
int ask(int x)
{
printf("? %d\n", x);
fflush(stdout);
int res;
scanf("%d", &res);
return res;
}
void solve()
{
int l = 1, r = n;
while(l < r) {
int mid = l + r >> 1;
int rr = ask(mid + 1);
int midd = ask(mid);
if(midd < rr) r = mid;
else l = mid + 1;
}
printf("! %d\n", l);
fflush(stdout);
return ;
}
int main()
{
scanf("%d", &n);
solve();
return 0;
}
D1 - Painting the Array I
Problem 1479B1 - Painting the Array I
给你一个序列 a a a,请你将这个序列撕开分成两个序列 a ( 0 ) a^{(0)} a(0) 和 a ( 1 ) a^{(1)} a(1),使得将 a ( 0 ) a^{(0)} a(0) 和 a ( 1 ) a^{(1)} a(1) 合并所有相邻且相同的元素之后,两个序列剩余的元素个数和最大。
例如:
1 2 3 1
1 2 3 2
答案均为4。
Solution
我们发现直接遇见两个一样的就直接加2什么的暴力算的话不一定对,因为后面可能会出现多算的情况。就是你以为分开就好了,可以加,但是它却和放到的新序列的前一个元素重复,这样就多加了。
例如:
4 4 2 4 4
答案应该是 4 而不是 5 。
所以要考虑贪心,分类讨论后面可能会发生的情况。
我们一步一步分析。
我们首先设 a ( 0 ) a^{(0)} a(0) 序列的最后一个元素为 x x x , a ( 1 ) a^{(1)} a(1) 的最后一个元素为 y y y 。
分类讨论,我们分别考虑什么情况的时候,把当前元素分配给哪一个序列会更优。
对于当前准备去分配的元素 z 1 z1 z1,以及 z 1 z1 z1 后面一位元素 z 2 z2 z2。
(1) 考虑对前面的贡献
- 当
x == y
时,上下两个序列都无所谓。 - 当
x == z1 && y != z1
时,很明显分配给 y y y 会更优。 - 当
y == z1 && x != z1
时,很明显分配给 x x x 会更优。
(2) 考虑对后面的贡献
- 当
x == z2 && y != z2
时,很明显分配给 x x x 会更优,因为可能 z 1 z1 z1 可以填上这个合并的漏洞,填不上的话,就算了,但会更优 - 当
y == z2 && x != z2
时,同理很明显分配给 y y y 会更优。 - 当
x == y == z2
那就无所谓了 ~ 给谁都行
剩余的所有情况应该像D2那样去判断与 x x x , y y y 相同的元素的距离谁最近,就放到谁后面。具体思路 / 证明看下面的 D2 的题解,这里可能是因为数据较弱,所以过掉了,然后我也懒得改了 ~ (往下滑就看到了)
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <unordered_map>
#include <queue>
#include <set>
using namespace std;
typedef long long ll;
typedef int itn;
const int N = 5e6 + 7, M = 6e6 + 7, mod = 1e9 + 7;
const ll INF = 1e18 + 7;
int n, m, t, k;
itn a[N];
int vis[N];
void solve()
{
int ans = 0;
scanf("%d", &n);
for(int i = 1; i <= n; ++ i) {
scanf("%d", &a[i]);
}
int x = -1, y = -1;
for(int i = 1; i <= n; ++ i) {
itn z1 = a[i], z2 = a[i + 1];
if(z1 == x && z1 == y) continue;
if(z1 == x) {
y = z1;
ans ++ ;
}
else if(z1 == y) {
x = z1;
ans ++ ;
}
else {
ans ++ ;
if(z2 == x && z2 != y) {
x = z1;
}
else if(z2 == y && z2 != x) {
y = z1;
}
else x = z1;
}
}
printf("%d\n", ans);
}
int main()
{
solve();
return 0;
}
D2 - Painting the Array II
Problem 1479B2 - Painting the Array II
给你一个序列 a a a,请你将这个序列撕开分成两个序列 a ( 0 ) a^{(0)} a(0) 和 a ( 1 ) a^{(1)} a(1),使得将 a ( 0 ) a^{(0)} a(0) 和 a ( 1 ) a^{(1)} a(1) 合并所有相邻且相同的元素之后,两个序列剩余的元素个数和最小。
Solution
我们先按照上面 D1 的贪心策略分析。
我们首先设 a ( 0 ) a^{(0)} a(0) 序列的最后一个元素为 x x x , a ( 1 ) a^{(1)} a(1) 的最后一个元素为 y y y 。
分类讨论,我们分别考虑什么情况的时候,把当前元素分配给哪一个序列会更优,使得序列最短。
对于当前准备去分配的元素 z 1 z1 z1,以及 z 1 z1 z1 后面一位元素 z 2 z2 z2。
(1) 首先考虑对前面的贡献
- 当
x == y
时,上下两个序列给谁都无所谓 - 当
x == z1 && y != z1
时,很明显分配给 x x x 会更优。 - 当
y == z1 && x != z1
时,很明显分配给 y y y 会更优。
(2) 若上述条件均为达到就考虑对后面的贡献
- 当
x == z2 && y != z2
时,很明显分配给 y y y 会更优 - 当
y == z2 && x != z2
时,同理很明显分配给 x x x 会更优
最后一种情况:若x != z2 && y != z2
,以及其他的所有剩余情况,这时候就有讲究了。
看上去放到哪里都区别不大,但是我们想要最终的答案尽可能地小,也就是让元素尽可能合并,也就是:
a [ i ] a[i] a[i] 以后和 x x x 相同的元素(假设是 x x xx xx)尽量能和 x x x 合并,也就是以后 x x x 后面都不添加数。
a [ i ] a[i] a[i] 以后和 y y y 相同的元素(假设是 y y yy yy)尽量能和 y y y 合并, 也就是以后 y y y 后面都不添加数。
但是我们总归是要在 x x x 或者 y y y 后面选择一个数放进去,假设我们放到了 x x x 后面,这样也就断绝了后面的那个与 x x x 相同的元素 x x xx xx 与 x x x 合并的可能性。
所以我们应该取两个队列末尾元素: x x x 和 y y y 中 与它们相同的数 x x xx xx 或者 y y yy yy 的距离更近的那个队列。
我们假设是 y y y ,这样与 y y y 相同的数 y y yy yy 比与 x x x 相同的数 x x xx xx 距离更近,也就是一个一个来放的话,更先到达两个队列面前,是距离更短的那个数,也就意味着最终可以和 y y y 合并的可能性就更大,因此我们就把 a [ i ] a[i] a[i] 放到 x x x 的后面,让 y y y 去追逐合并的梦想 ~
应该很好理解,非常形象。
我们预处理一下下标 i i i 的最近的下一个相同元素的下标 n e x [ i ] \tt nex[i] nex[i] 就行了,如何实现具体看代码,很好理解。
总结一下就是:
若x != z2 && y != z2
,以及其他的所有剩余情况时:若nex[x] < nex[y]
,我们分配给 y y y 更优
若x != z2 && y != z2
,以及其他的所有剩余情况时:若nex[x] > nex[y]
,我们分配给 x x x 更优
所有条件按照我分析的时候的先后顺序if else
判断即可,因为越往前优先级越大,后面只是有合并的可能性,而前面是直接已经可以合并了。
最后简单实现一下就行了
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <unordered_map>
#include <queue>
#include <set>
using namespace std;
typedef long long ll;
typedef int itn;
const int N = 5e5 + 7, M = 6e6 + 7, mod = 1e9 + 7;
const ll INF = 1e18 + 7;
int n, m, t, k;
itn a[N], b[N];
vector<int> v[N];
int nex[N];
void solve()
{
int ans = 0;
scanf("%d", &n);
for(int i = 1; i <= n; ++ i) {
scanf("%d", &a[i]);
v[a[i]].push_back(i);
}
for(int i = 1; i <= n; ++ i)
nex[i] = n + 1;
for(int i = 1; i <= n; ++ i) {
for(int j = 0; j < (int)v[i].size() - 1; ++ j) {
nex[v[i][j]] = v[i][j + 1];
}
}
int x = 0, y = 0, nex_x = n + 1, nex_y = n + 1;
for(int i = 1; i <= n; ++ i) {
int z1 = a[i], z2 = a[i + 1];
if(z1 == x) {
nex_x = nex[i];
}
else if(z1 == y) {
nex_y = nex[i];
}
else {
ans ++ ;
if(z2 == x && z2 != y) {
y = z1;
nex_y = nex[i];
}
else if(z2 == y && z2 != x) {
x = z1;
nex_x = nex[i];
}
else {
if(nex_x < nex_y) {
y = z1;
nex_y = nex[i];
}
else {
x = z1;
nex_x = nex[i];
}
}
}
}
printf("%d\n", ans);
}
int main()
{
solve();
return 0;
}
E - Continuous City
Problem 1479C - Continuous City
定义中心图为:
有 n n n 个点,编号为 1 1 1 ~ n n n 。以及 m m m 条有向边。每一条有向边上都有一个正整数权值,并且这 m m m 条路都是从编号小的指向编号大的(意味着每次走都会朝着中心 n n n 进发)。并且对于任意两个不同的点,他们之间最多只有一条有向边。
定义 ( L , R ) (L,R) (L,R) 连续图为既满足中心图,又满足下述两个条件的有向图:
- 所有的从 1 1 1 到 n n n 的简单路径的长度 l e n ∈ [ L , R ] len\in[L,R] len∈[L,R]。
- 对于任意的 d d d , L ≤ d ≤ R L\le d\le R L≤d≤R,均恰好存在一条且仅有一条从 1 1 1 到 n n n 的简单路径的长度等于 d d d。
仅给定 L L L 和 R R R,请问你能否构造一个符合题意且点数 n n n 不超过 32 的 ( L , R ) (L,R) (L,R) 连续图,如果不能,输出 NO
,否则输出 YES
并输出该图的 n n n 和 m m m (点数与边数),并输出 m m m 条边的情况(对于每条边,输出 x , y , z x,y,z x,y,z 表示第 i i i 条边从 x x x 到 y y y 边长为 z z z)
Solution
待更…