CF - B. Combinatorics Homework、C. Zero Path(思维,答案区间)

本文探讨了两道关于动态规划的题目,分别是字符排列问题和矩阵路径权值和。第一题要求判断给定数量的字符'A'、'B'、'C'能否组成字符串,使得相邻重复字符的数量为特定值。第二题涉及n*m矩阵,询问是否存在一条路径,使得路径上的数字和为0。两题均通过求解最大值和最小值确定问题可行性。动态规划在解决这类问题中起到关键作用。
摘要由CSDN通过智能技术生成

https://codeforces.com/contest/1574/problem/B

题意
给定 a 个字符 ‘A’,b 个字符 ‘B’,c 个字符 ‘C’。
问,所有字符能否组成一个字符串,存在且只存在 m 个位置满足 s[i] = s[i+1]。

思路
求出能满足的 s[i] = s[i+1] 的位置个数的最大值和最小值,然后判断 m 是否在这个区间中。

证明:
最小值的情况是:a b a b a c a c a a,如果想让满足的位置个数+1,只需要调换其中某个 a,b 或者 a,c 的顺序,就是只要想让满足位置数+1就可以调换两个位置使其满足,直到加到最大值。
所以最小值到最大值这个区间中的所有数都可以满足。

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];

signed main(){
	Ios;
	cin >> T;
	while(T--)
	{
		int a, b, c;
		cin >> a >> b >> c >> m;
		int maxa = a + b + c - 3;
		int mina = 1e9;
		
		if(a > b + c) mina = a - (b + c) - 1;
		else if(b > a + c) mina = b - (a + c) - 1;
		else if(c > a + b) mina = c - (a + b) - 1;
		else mina = 0;
		
		if(m >= mina && m <= maxa) cout << "YES\n";
		else cout << "NO\n";
	}
	
	return 0;
}

今天又碰见一道这样的题目:
https://codeforces.com/contest/1695/problem/C

C. Zero Path

题意
给定 n*m 的矩阵,每个位置有 1 或者 -1 两种数。
每次操作只能向右或者向下走一格,问能否存在一条从 (1, 1) 到 (n, m) 的路径满足走过的格子权值之和为 0?

思路
和上一题一样,判断性问题。

特判 n+m 为偶数的情况,一共走过 n+m-1 个格子,格子数都为奇数,最后的总和肯定不会为偶数 0。
然后和上一题所用的方法是一样的,求出从起点到终点所走路径中权值和的最大值、最小值,判断 0 是否在这个区间内。

为什么这样是正确的呢?
一共 n+m-2 个方向抉择,形成 R D 两种字符组成的字符串。如果对调两个位置,对其他位置没有影响,考虑对总权值的影响。
如果走到的两个位置权值不变则没有影响 +0,而如果原来是 +1 变成了 -1 或者 -1 变成了 +1,这样产生的效果是原来权值之和 -2 或者 +2,发现都是变的偶数。
每变一次都使得最后答案+2或者-2,那么最小值最大值区间中的所有偶数都能得到,那么 0 在这个区间中的话也能得到了。
很神奇。

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 1010, mod = 1e9+7;
int T, n, m;
int a[N][N], maxa[N][N], mina[N][N];

signed main(){
	Ios;
	cin >> T;
	while(T--)
	{
		cin >> n >> m;
		
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++)
			{
				int x; cin >> x;
//				maxa[i][j] = max(maxa[i-1][j], maxa[i][j-1]) + x; 这样更新不可以
//				mina[i][j] = min(mina[i-1][j], mina[i][j-1]) + x;
				if (i == 1)
	            {
	                maxa[i][j] = x + maxa[i][j - 1];
	                mina[i][j] = x + mina[i][j - 1];
	            }
	            if (j == 1)
	            {
	                maxa[i][j] = x + maxa[i - 1][j];
	                mina[i][j] = x + mina[i - 1][j];
	            }
	            if (i != 1 && j != 1)
	            {
	                maxa[i][j] = x + max(maxa[i][j - 1], maxa[i - 1][j]);
	                mina[i][j] = x + min(mina[i][j - 1], mina[i - 1][j]);
	            }
			}
		}
		if((n + m) % 2 == 0) cout << "NO\n";
		else if(maxa[n][m] >= 0 && mina[n][m] <= 0) cout << "YES\n";
		else cout << "NO\n";
	}
	
	return 0;
}

要额外注意更新到第一行和第一列位置的最大值和最小值的时候,不要越界了!
这个题和之前直接越界的题目不同的地方在于,之前越界可以求方案数:越界的地方方案数初始为0;可以求最大值:保证维护的值为正数了,而越界的地方初始为0…
而这个题就不能越界:因为维护的值可能为正数,这样的话维护最小值,和越界位置的0取最小值就变成 0 了,而越界的地方是不应该去更新的。同样维护的值可能为负数,这样的话维护最大值的时候和越界位置取最大值也变成 0,出现错误!


然后这道题也可以用 bitset 过掉,直接 f[i, j] 表示成一个二进制数,第 k 为是否为 1 表示到达 (i, j) 点时,能否满足的权值和为 k
然后在更新的时候,当前位置直接从上一位置的二进制数 左移 1 位或者右移 1 位来更新,O(1)。
然后 n*m 个 bitset,每个 bitset 长度为 2000,空间复杂度为 O(n*m*2000/32 = 6e7),刚刚好。(长度为 n 的 bitset 所占空间为 n/32,很优秀!)
注意开的时候不要开太多,用多少开多少。

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 1010, mod = 1e9+7;
int T, n, m;
int a[N];

signed main(){
	Ios;
	cin >> T;
	
	while(T--)
	{
		cin >> n >> m;
		bitset<2010> f[n+1][m+1]; //不要开太多,建立耗时
		f[0][1][1000] = f[1][0][1000] = 1;
		
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				int x; cin >> x;
				if(x == 1) f[i][j] |= f[i-1][j] << 1, f[i][j] |= f[i][j-1] << 1;
				else f[i][j] |= f[i-1][j] >> 1, f[i][j] |= f[i][j-1] >> 1;
			}
		}
		if(f[n][m][1000] == 1) cout << "YES\n";
		else cout << "NO\n";
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值