Codeforces Round 893 (Div. 2)个人题解(A - D)

A
链接:https://codeforces.com/contest/1858/problem/A
题意:三个按钮a,b,c,alice可以摁a和c,bob可以摁b和c,每个按钮只能被摁一次,给出a,b,c的数量,谁先不能摁按钮谁就输了,alice先手,问赢家是谁。
思路:优先摁c肯定是最优的,判断即可
代码:

void solve()
{
	cin >> a >> b >> c;
	if((a + (c+1)/2) > b + (c/2))
		cout << "First" << "\n";
	else
		cout << "Second" << "\n";

}	

B

链接:https://codeforces.com/contest/1858/problem/B
题意:n个点,m个饼干商人,现在alice要从1走到n,她只会在以下三种情况吃饼干 1:当前位置为1,2:距离上一次吃饼干已经走了d个点,3:当前位置有饼干商人。alice可以移除任意一个饼干商人,问alice最少吃几个饼干以及移除饼干商人使alice会吃到最少饼干的方案数。
思路:枚举每一个饼干商人,考虑移除他会影响哪些状态(不会影响之前的状态,不会影响遇到下一个饼干商人之后的状态)设区间左端点l为上一次吃饼干的点,右端点r为下一个饼干商人的位置,比较在这个区间内移除前和移除后的吃饼干数,如果移除后更优,则更新答案。
代码:

void solve()
{
	ll n,m,d;
	cin >> n >> m >> d;
	for (int i = 1;i <= m;i++)
		cin >> arr[i];
	arr[m+1] = n+1;
	ll l = 1;
	ll ans = 0;
	ll sum = m;
	for (int i = 1;i <= m;i++)
	{
		ll sum1 = (arr[i] - arr[i-1])/d;
		if(i == 1)
		{
			sum1 = (arr[1] - 1) / d;
			l = 1+sum1 * d;
		}
		else
			l = arr[i-1] + sum1 * d;
		ll r = arr[i+1];
		ll tmp1 = 1 + (r-arr[i]-1)/d;
		sum += tmp1 - 1;
		ll tmp2 = (r-l-1)/d;
		if(l == arr[i])
			tmp2++;
		if(tmp2 < tmp1)
			ans++;
	}
	sum++;
	sum += ((arr[1]-1) / d);
	if((arr[1] - 1)%d == 0)
		sum--;
	if(ans != 0)
		sum--;
	if(ans == 0)
		ans = m;
	cout << sum << ' ' << ans << "\n";
}	

C

链接:https://codeforces.com/contest/1858/problem/C
题意:构造一个长度为n的排列,使得gcd( a i a_i ai, a ( i   m o d   n ) + 1 a_{(i~mod~n)+1} a(i mod n)+1)的种类最多
思路:只要按照 i ,2i,3i…这样构造即可,注意判断当前点走没走过

void solve()
{
	int n;
	cin >> n;
	for (int i = 1;i <= n;i++)
		vis[i] = 0;
	for (int i = 1;i <= n;i++)
	{
		if(vis[i])
			continue;
		int j = i;
		while(j <= n)
		{
			cout << j << ' ';
			vis[j] = 1;
			j <<= 1;
		}
	}
	cout << "\n";

}	

D
链接:https://codeforces.com/contest/1858/problem/D
题意:给定一个长度为n的01字符串s,可以进行k次操作,每次操作可以选择一位翻转,定义 l 0 l_0 l0为最长连续0段, l 1 l_1 l1为最长连续1段,对于每一个i(1 <= i <= n) 输出i* l 0 l_0 l0 + l 1 l_1 l1的最大值。
思路:不是很懂,看了一下dalao的题解,先考虑一个暴力的做法,我们枚举每一个区间全为0/1,在剩的区间中找到最长的连续1/0段,但这些写显然是会超时的,考虑优化,首先修改过后的 l 0 l_0 l0 l 1 l_1 l1一定是分开的,我们可以设 l 0 l_0 l0在左 l 1 l_1 l1在右,相反的情况翻转字符串重新计算一次即可。
我们用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示从i到n进行j次操作能获得的最大 l 1 l_1 l1,对于一个i,他的值一定是大于等于后面的值的,所以我们从后往前更新, d p [ i ] [ j ] dp[i][j] dp[i][j]的初始值由 d p [ i + 1 ] [ j ] dp[i+1][j] dp[i+1][j]获得,然后定义一个cost值,每次都计算从i到n连续1段的cost值,当cost <= k 时更新 d p [ i ] [ c o s t ] dp[i][cost] dp[i][cost] cost更新完之后当前的 d p [ i ] [ j ] dp[i][j] dp[i][j] = max( d p [ i ] [ j ] dp[i][j] dp[i][j], d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1])因为小花费能得到的状态大花费一定能得到。
然后考虑前缀的 l 0 l_0 l0,对于每一个点,我们向前枚举区间,用 h [ i ] h[i] h[i]记录当前长度为i的区间大小全为0的最小花费,计算cost并更新 h [ i ] h[i] h[i],算完之后,我们就有了从该点往前长度为j(0 <= j <= i)的0段花费,从该点往后的最长1段 d p [ j ] [ k − h [ j ] ] dp[j][k - h[j]] dp[j][kh[j]] ,用 g [ i ] g[i] g[i]表示长度为i的0段能得到的最长1段,每次算完 h [ i ] h[i] h[i]更新 g [ i ] g[i] g[i]最大值即可
最后对于每个a,枚举可能的0段长度i,答案为 a ∗ i + g [ i ] a * i + g[i] ai+g[i],更新最大值即可
代码:

void work()
{
	for (int i = 0;i <= n;i++)
	{
		g[i] = -1;
		for (int j = 0;j <= n;j++)
			dp[i][j] = 0;
	}
	for (int i = n-1;i >= 0;i--) // 预处理dp数组
	{
		for (int j = 0;j <= k;j++)
			dp[i][j] = dp[i+1][j]; //继承上一个状态
		int cost = 0;
		for (int j = i;j < n;j++)
		{
			cost += (s[j] == '0');
			if(cost <= k)
				dp[i][cost] = max(dp[i][cost],j-i+1); //更新
		}
		for (int j = 1;j <= k;j++)
			dp[i][j] = max(dp[i][j],dp[i][j-1]); //小花费能得到,大花费一定能得到
	}
	for (int i = 1;i <= n;i++)
		h[i] = k+1;
	h[0] = 0;
	for (int i = 0;i <= n;i++)
	{
		int cost = 0;
		for (int j = i-1;j >= 0;j--) // 区间长度为i-j+1全部是0的最小次数
		{
			cost += (s[j] == '1');
			h[i - j] = min(h[i - j],cost);
		}
		
		for(int j = 0;j <= n;j++)
		{
			if(h[j] <= k)
				g[j] = max(g[j],dp[i][k - h[j]]);
		}
	}
	for (int a = 1;a <= n;a++)
	{
		for (int i = 0;i <= n;i++)
		{
			if(g[i] != -1)
				ans[a] = max(ans[a],i * a + g[i]);
		}
	}
}
void solve()
{
	cin >> n >> k;
	for (int i = 1;i <= n;i++)
		ans[i] = 0;
	cin >> s;
	work();
	reverse(s.begin(),s.end());
	work();
	for (int i = 1;i <= n;i++)
		cout << ans[i] << ' ';
	cout << "\n";
}	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值