Codeforces Round #625 (Div. 2, based on Technocup 2020 Final Round)
前言
- 第二次打cf,又掉分了
比赛AC
A. Contest for Robots
简明题意
- 一共有n道题,做出第i道题可以得 p i p_i pi分。现在给出数组r[]和b[],分别表示两个人是否做对第i题。
- p i p_i pi是不知道的。现在需要求出r[]这个人获胜的情况下, p i p_i pi的最小值。 p i p_i pi最小为1的正整数
正文
- 两人共同答对的题就没有区分度,不管设置多少分,两人还是平局,因此两人都答对的题设置1分就好了。
- b答对的题r没有答对。这样会使得b的得分增高,想要r分数增高,只能从r答对而b没有答对的题入手。
- 因此要使b答对的题分数尽可能少,就全设为1分。而r答对b每答对的题,用来中和前面那部分分数。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<string>
using namespace std;
const int maxn = 110;
int r[maxn], b[maxn];
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> r[i];
for (int i = 1; i <= n; i++)
cin >> b[i];
bool can_win = 0;
for (int i = 1; i <= n; i++)
{
if (r[i] == 1 && b[i] == 0)
can_win = 1;
}
if (!can_win)
{
cout << "-1";
}
else
{
int num1 = 0, num2 = 0;
for (int i = 1; i <= n; i++)
{
if (r[i] == 0 && b[i] == 1)
num2++;
if (r[i] == 1 && b[i] == 0)
num1++;
}
cout << num2 / num1 + 1;
}
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
B. Journey Planning
简明题意
- 有n个点,每个点有个权w[i],a点走到b点,当且仅当b-a=w[b]-w[a]。现在让你规划一条路线,使得这条路线的权值总和最大。这条路线的点必须是单调增的,
正文
- 一开始想了很多,怎么都想不到。
- 后来突然脑子灵了。直接用w[i]-i,你会发现w[i]-i相等的点可以互相走。那么直接用一个map,统计看哪一种w[i]-i的w[i]之和最大就可以了。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<string>
using namespace std;
const int maxn = 2e5 + 10;
int r[maxn], b[maxn];
map<int, long long> mp;
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
int t;
cin >> t;
mp[i - t] += t;
}
long long ans = -1;
for (auto& it : mp)
ans = max(it.second, ans);
cout << ans;
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
赛后补题
C. Remove Adjacent
简明题意
- 给一个字符串s,如果第i的字母旁边有一个akii码比他小1的字符,那么可以移除第i个字符。选取合适的移除顺序,问最多可以移多少个。
正文
- 贪心,每次移除最大的能移除的字母就可以了。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<string>
#include<vector>
using namespace std;
const int maxn = 2e5 + 10;
vector<char> a;
void solve()
{
int n;
cin >> n;
char las_c;
int las_num = 0;
getchar();
for (int i = 1; i <= n; i++)
{
char t;
scanf("%c", &t);
a.push_back(t);
}
int ans = 0;
for (char i = 'z'; i >= 'a'; i--)
{
for (int m = 1; m <= 100 ; m++)
for (int j = 0; j < a.size(); j++)
if (a[j] == i)
{
if (j - 1 >= 0 && a[j - 1] == i - 1) {
a.erase(a.begin() + j), ans++;
break;
}
else if (j + 1 < a.size() && a[j + 1] == i - 1) {
a.erase(a.begin() + j), ans++;
break;
}
}
}
cout << ans;
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
D. Navigation System
简明题意
- 刚给一个有向图,再给一条行驶路线(一条路径,比如1->4->9>10)。
- 现在有一个导航系统,每走到一个点,导航系统会规划一次最短路。
- 现在问你按照所给的路径,系统最少/最多进行多少次规划。
- 假如145是1-5的最短路径,那么如果题目所给的路径就是145,导航系统可以一次都不重新规划。
- 为什么会有最多、最少的说法呢?同上,145是1-5的最短路,但175也是一条1-5的最短路,加入身处1号节点时,系统的规划为175,那么接下来按照行驶路线,会行驶到4号节点,这是系统会规划成45,所以最终系统会重新规划一次。而如果在1号节点时,系统的规划不是175而是145,那么就不需要系统重新规划了。因此当图不变,行驶路线也不变,重新规划次数还是可能改变。所以有最多、最少的说法。
正文
- 这题读题太难了。
- 先思考,最多、最少可能的重新规划次数。重新规划在什么时候?在到达一个点时。那么假设行驶路径有k个点,最少0次改变,最多,除了第一点,每次都改变,那么最多k-1次重新规划。
- 说上面,就是要搞清楚,重新规划路线,是在每次到达一个点时重新规划。而且到达一个点时,我们有几种选择:1.必须重新规划 2.必须不重新规划 3.可以重新规划也可以不重新规划。
- 如果我们知道了行驶路线上每个点是以上3种选择的哪一种,是不是就可以算出来最多/最少重新规划次数了呢?
- 现在来考虑以上3种情况发生的条件。
1.必须重新规划路线。给出的路径为145,而在1点时,系统的规划路线是15,那么走到4时,一定会重新规划。
2.必须不重新规划。给出的路径为145,在1点时,系统的规划也是145,那么走到4时,只能走4-5,那么就一定不能重新规划。
3.可重新规划也可不重新规划。给出的路径为1345,在1点时系统规划是1345,那么来到3,最短路可以是345或365,那么系统规划可能是这两种,如果选择第一种,那么可以不重新规划,如果选择第二种,那么需要重新规划。 - 现在来计算最少的重新规划次数。只需要统计行驶路线上发生情况1的点的数量。怎么统计?当新的点不在原来点的最短路上时,发生情况1.比如1375这条路,1到5最短路是147,那么新点就是3,原来点就是1,所以说新点3不在原来点的最短路上。
- 计算最多的重新规划次数。统计行驶路线上发生情况3的点的数量。情况3:当走到新的点时,比如从1走到3时,存在135和145两条最短路,那么我们强行使在1点时选择145,然后来到3,这样重新规划次数会增加。也就是说新点在原来点的最短路上且原来点还存在另一条最短路。
- 接下来就是如何判断新点是否在原来点的最短路上。我们直接提前算好每个点到终点的最短路(终点就是规划路线的最后一个点),然后判断两个点的最短路之差是否为1.然后判断原来点还存在另一条最短路,那么直接遍历原来点的所有连接点,看有没有点的最短路和新点的最短路相等,有的话,条件3就成立。
- 至于怎么提前算好每个点到终点的最短路,直接反向建图,以终点为起点bfs一下就可以了。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<string>
#include<vector>
using namespace std;
const int maxn = 2e5 + 10;
int n, m, k, a[maxn];
vector<int> g[maxn];
vector<int> g1[maxn];
int st, ed;
int dis[maxn];
void bfs()
{
queue<int> que;
que.push(ed);
while (que.size())
{
int u = que.front();
que.pop();
for (auto& v : g[u])
if (!dis[v] && v != ed)
{
que.push(v);
dis[v] = dis[u] + 1;
}
}
}
void solve()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int u, v;
scanf("%d%d", &u, &v);
g[v].push_back(u);
g1[u].push_back(v);
}
cin >> k;
for (int i = 1; i <= k; i++)
scanf("%d", &a[i]);
st = a[1], ed = a[k];
bfs();
//最少次数
int ans1 = 0;
for (int i = 2; i <= k; i++)
if (dis[a[i]] != dis[a[i - 1]] - 1)
ans1++;
//最多c次数
int ans2 = 0;
for (int i = 2; i <= k; i++)
{
if (dis[a[i]] != dis[a[i - 1]] - 1)
ans2++;
else
{
for (auto& v: g1[a[i - 1]])
if (v != a[i] && dis[a[i - 1]] - 1 == dis[v])
{
ans2++;
break;
}
}
}
cout << ans1 << " " << ans2;
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
E. World of Darkraft: Battle for Azathoth
简明题意
- 一个人要去打怪。现在又n种武器,m种盾牌,p个怪物。一个人选择1种武器和一种盾牌,问打怪的收益最多是多少。
- 武器有a[i],ca[i],分别表示武器的攻击力和武器的价格。
- 盾牌有b[i],cb[i],分别表示盾牌的防御力和盾牌的价格。
- 怪物有x[i],y[i],z[i],分别表示怪物的防御力、攻击力、击败时掉落的金币。
- 选择一个武器和一个盾牌后,可以得到所有 防御力<武器攻击力,攻击力<盾牌防御力 的怪物的金币。
- 问打怪的收益最多是多少。
正文
- 二维偏序。
- 当盾牌确定时,显然高攻击力越高,盾牌能打的怪收益越多(不计武器的价格)。我们可以先把盾牌的收益设置成他的-他的价格,那么当我们递增这个攻击力时,盾牌的收益就会加上一些。
- 把盾牌列出来,再按照攻击力从小到大枚举武器。显然武器的攻击力越高,这个武器能打的怪物就越多。我们假装有一个怪物集合,那么当武器的高攻击力提高了,就会有一些新的怪物加入了这个怪物集合。当有新的怪物加入怪物集合时,那么对于每个防御力大于加入的怪物的攻击力的盾牌,都会获得一些收益累加。然后在这所有的盾牌中选一个收益值最大的,减去当前武器的价格,就得到选择某个武器的最高收入了。
- 接下来问题就在于,当确定一个武器后,新增了一些怪物,如何给盾牌累加收益。我们可以暴力枚举所有的盾牌,把防御值满足要求的累加收益。然后找最大值,复杂度不能接受。
- 我们也可以按照防御值二分出符合要求的盾牌,然后累加,这样复杂度是会降低,但仍然不稳定。因为可能跟盾牌的防御值都很高,怪物的攻击力都很低,这样的话每次还是得遍历所有的盾牌。
-思考,我们每次是给防御值>怪物攻击力的盾牌累加收益。那么这是不是像在区间加和呢? 所以可以用线段树来维护。如果把盾牌的防御值设置为区间,收益设置为值,可以写线段树。支持区间加以及查询最大值即可。 - 直接从1-1e6建线段树,不存在盾牌的点,收益设置成功-2e18.
代码
#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<string>
#include<vector>
using namespace std;
const int maxn = 1e6 + 10;
int n, m, p;//武器、盾牌、怪物数
int a[(int)1e6 + 10];
int b[(int)1e6 + 10];
struct Node
{
int l, r;
long long max, tag;
};
Node tree[maxn * 4];
struct Mon
{
int x, y, z;
bool operator < (const Mon& a) const
{
return x < a.x;
}
};
Mon mon[(int)2e5 + 10];
void build(int o, int l, int r)
{
tree[o].l = l, tree[o].r = r;
if (l == r)
{
tree[o].max = (b[l] == 0 ? -1e18 : -b[l]);
return;
}
int mid = (l + r) / 2;
build(o * 2, l, mid);
build(o * 2 + 1, mid + 1, r);
tree[o].max = max(tree[o * 2].max, tree[o * 2 + 1].max);
}
void spread(int o)
{
if (tree[o].tag)
{
if (tree[o].l != tree[o].r)
{
tree[o * 2].tag += tree[o].tag;
tree[o * 2 + 1].tag += tree[o].tag;
}
tree[o].max += tree[o].tag;
tree[o].tag = 0;
}
}
void change(int o, int l, int r, int c)
{
spread(o);
if (tree[o].l == l && tree[o].r == r)
{
tree[o].tag = c;
spread(o);
return;
}
int mid = (tree[o].l + tree[o].r) / 2;
if (r <= mid) change(o * 2, l, r, c);
else if (l > mid) change(o * 2 + 1, l, r, c);
else change(o * 2, l, mid, c), change(o * 2 + 1, mid + 1, r, c);
spread(o * 2), spread(o * 2 + 1);
tree[o].max = max(tree[o * 2].max, tree[o * 2 + 1].max);
}
bool x0;
void solve()
{
cin >> n >> m >> p;
int max_dun = 0;
for (int i = 1; i <= n; i++)
{
int x, cx;
scanf("%d%d", &x, &cx);
if (a[x]) a[x] = min(a[x], cx);
else a[x] = cx;
}
for (int i = 1; i <= m; i++)
{
int x, cx;
scanf("%d%d", &x, &cx);
if (b[x]) b[x] = min(b[x], cx);
else b[x] = cx;
max_dun = max(max_dun, x);
}
for (int i = 1; i <= p; i++)
scanf("%d%d%d", &mon[i].x, &mon[i].y, &mon[i].z);
sort(mon + 1, mon + 1 + p);
build(1, 1, max_dun);
int l = 1;
long long ans = -1e18;
for (int i = 1; i <= 1e6; i++)
if (a[i])//存在攻击力为i的武器
{
//攻击力提升了,相应能打的怪物就会增多几个
for (int j = l; j <= p; j++)
if (mon[j].x < i)//枚举能打的怪物
{
if (mon[j].y + 1 <= max_dun)
change(1, mon[j].y + 1, max_dun, mon[j].z);//防御力在[mon[j].y+1, 1e6]之间的盾牌都能多打一些怪物
l++;
}
else break;
ans = max(ans, tree[1].max - a[i]);
}
cout << ans;
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}