Codeforces Round #625 (ABCDE)


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;
}

总结

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值