2023牛客多校第六场 - B E G

比赛地址:传送门
赛时过了 3 题,继续加油!
B 组合数学题
E 思维签到题
G 思维签到题


B Distance

题意
定义两个多重集的距离 $C(A, B) $ 为利用最少的 \(+ 1\) 操作(选择多重集中的一个元素使其 + 1)使得两个多重集完全相同,如果不可能实现,定义 $C(A, B) = 0 $。
现在给你两个大小为 n 的多重集 S 和 T ,要求求出 S 和 T 的所有子集的距离和 $\displaystyle \sum_{A \subseteq S} \sum_{B \subseteq T} C (A, B) $ 取模 998244353
\(1 \le n \le 2 \times 10^3\)

思路
显然只有长度相等的子集才有可能对答案产生贡献,且一定是在子集元素均升序排序后两两对应改变时的所需的次数最少。则对于一个长度为 k 的子集 A 和 B,其对答案的贡献即为 $\displaystyle \sum_{i = 1}^{k}{abs(A_i - B_i)} $

那如果说枚举所有集合,每个都暴力统计的话,显然不可能这么做,子集个数 \(2^n\) 太多了,而且这样计算里面会有很多重复赘余的地方。哪里赘余呢?其实对于每一个 \(A_i\) 来说,它会和每一个 \(B_j\) 均对答案产生不同程度的贡献,也就是说如果我们能处理出每一对 \(A_i\)\(B_j\) 对于答案的贡献,那么时间复杂度将被大大缩短。

对于一对 \(A_i\)\(B_j\),如果说其对答案产生贡献,那么它俩在各自的集选出的子集中的排名相同,假设都是第 x + 1 名,那么它们各自之前一定有 x 个数小于等于它们,\(A_i\) 还有 i - 1 个比其小的可以选,\(B_j\) 还有 j - 1 个比其小的可以选;如果说子集长度为 x + y,那么它们各自之后一定有 y 个数大于等于它们,\(A_i\) 还有 n - i 个比其大的可以选,\(B_j\) 还有 n - j 个比其大的可以选。那么其对于答案的贡献即为 $abs(A_i - B_j) \displaystyle \sum_{x = 0}^{min(i - 1, j - 1)} \sum_{y = 0}^{min(n - i, n - j)} C_{i - 1}^{x} C_{j - 1}^{x} C_{n - i}^{y} C_{n - j}^{y} $ 。这样遍历所有对,$O(n^4) $ 时间复杂度依旧无法过本题

这里引入范德蒙德卷积公式的推论 - 4来化简这个式子,则每一对的贡献即为 $val = \displaystyle \sum_{x = 0}^{min(i - 1, j - 1)} C_{i - 1}^{x} C_{j - 1}^{x} \sum_{y = 0}^{min(n - i, n - j)} C_{n - i}^{y} C_{n - j}^{y} = C_{i - 1 + j - 1}^{i - 1} C_{n - i + n - j}^{n - i} $ 。此时遍历所有对,\(O(n^2)\) 的时间复杂度完全可以通过本题

参考优秀题解
代码

//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long

using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*

*/
const int maxm = 2e5 + 5, inf = 0x3f3f3f3f;

const int N = 4e3 + 10, mod = 998244353;
ll fact[N], finv[N];
ll qpow(ll a, ll x){//带模快速幂
	ll res = 1;
	while(x){
		if(x & 1) res = res * a % mod;
		x >>= 1;
		a = a * a % mod;
	}
	return res;
}

void pre(){//线性预处理
	fact[0] = 1;
	for(int i = 1; i < N; ++ i){
		fact[i] = fact[i - 1] * i % mod;
	}
	finv[0] = 1;
	finv[N - 1] = qpow(fact[N - 1], mod - 2);
	for(int i = N - 2; i > 0; -- i)
		finv[i] = finv[i + 1] * (i + 1) % mod;
	return ;
}

ll C(int a, int b){//求取组合数
	if(a < 0 || b < 0) return 1;
	return fact[a] * finv[b] % mod * finv[a - b] % mod;
}

void solve(){
	int n;
	cin >> n;
	vector<int> s(n + 1), t(n + 1);
	for(int i = 1; i <= n; ++ i) cin >> s[i];
	for(int i = 1; i <= n; ++ i) cin >> t[i];
	sort(s.begin(), s.end());
	sort(t.begin(), t.end());
	ll ans = 0;
	for(int i = 1; i <= n; ++ i){
		for(int j = 1; j <= n; ++ j){
			(ans += abs(s[i] - t[j]) * C(i + j - 2, i - 1) % mod * C(n * 2 - i - j, n - i) % mod ) %= mod;
		}
	}
	cout << ans << '\n';
	return ;
}

signed main(){
	pre();
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	int _ = 1;
	// cin >> _;
	while(_ --){
		solve();
	}
	return 0;
}

E Sequence

题意
给你一个长为 n 的数组 a,一共进行 q 次询问,每次询问区间 \([l, r]\) 是否能被完整划分为 k 个独立的子区间,满足每个子区间的和均是 2 的倍数?

思路
讲讲赛时代码的思路

首先输入时对 2 取余,则奇数为 1,偶数为 0

对于每个区间询问,我们首先需要判断该区间中的 1 的个数是否为偶数

当个数为奇数时,不可能存在这种分割;当个数为奇数时,我们则需要两两匹配 1,每两个 1 及其当中的 0 可以构成一个合法子区间,剩下的 0 都可以单独构成合法子区间,所以我们只需要统计指定区间最多可以构成的合法子区间的个数是否大于等于 k 即可。那么基于以上的描述,我们就是需要一个较快的方法取找到合法子区间的个数,唯一的难点就在于数字 1,每两个之间的 0 均无效,而且给定询问区间的划分又是随意的,那么怎么处理呢?

赛时想到,无效的 0 肯定是两个 1 之间出现的,而且肯定是交替无效。什么意思?比如说数组 10 : 2 4 3 2 2 1 4 2 3 2,当询问区间 [3, 6] 时,其内的偶数均被包含了,这个区间最多只能被划分为一个合法区间;当询问区间 [4, 9]时,区间 [4, 6]的偶数又可以被划分了。所以如果说我们可以预先处理出每个位置出现的有效 0 的个数,那么询问时就十分简单了。而且基于有效的 0 是间隔出现的,我们开两个数组,从前往后统计 0 的个数,在遇到 1 的时候翻转状态,这里给出的样例即可构造如下的两个数组:
sum [0] :1 1 x 0 0 x 1 1 x 0
sum [1] :0 0 x 1 1 x 0 0 x 1

再统计这两个数组的前缀和,x 的地方填入 0 即可,这样就获得了去除 无效 0 的前缀和。
那么询问的时候我们怎么知道到底是该用哪个数组呢?我们对于从前往后遇到的 1 打一个循环标记:1 2 1 2 1 2 .... 再后面询问的时候,找到区间里面的第一个 1 ,其标记就可以告诉自己选哪个数组,选择原则就是让 1 后为无效 0 ,1 前为有效 0 。之后,整个询问区间内最大合法子区间数 = 区间内 1 的个数 / 2 + 有效 0 的个数

感觉其实大概应该肯定没有这么复杂哈哈,这个思路就当自己的一次瞎整吧~

讲讲牛客题解代码
整体感觉其实和我上面的思路差不多,但是题解代码我的思路的升华简化版,但是其实有效 0 和无效 0 的个数在一个指定的区间里面是存在关联的,,,,这个关联还没有搞懂,但估计就是这样的。详见代码吧。。。。

代码
赛时代码

//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long

using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*

*/
const int maxm = 1e5 + 5, inf = 0x3f3f3f3f, mod = 998244353;
ll n, q, a[maxm];

void solve(){
	cin >> n >> q;
	vector<int> v(n + 1, 0);
	vector<vector<int>> sum(2, vector<int>(n + 1, 0));
	for(int i = 1; i <= n; ++ i){
		cin >> a[i];
		a[i] %= 2ll;
	}
	int f = 0, c = 2;
	vector<pii> p;
	for(int i = 1; i <= n; ++ i){
		if(a[i]){
			c = 3 - c;
			f = 1 - f;
			p.push_back({i, c});
			sum[f][i] += sum[f][i - 1];
			sum[1 - f][i] += sum[1 - f][i - 1];
		}else{
			sum[f][i] = 1 + sum[f][i - 1];
			sum[1 - f][i] = 0 + sum[1 - f][i - 1];
		}
	}
	c = 3 - c;
	p.push_back({n + 1, c});		//末尾守门员
	for(int i = 0; i < q; ++ i){
		int l, r, k;
		cin >> l >> r >> k;
		auto st = lower_bound(p.begin(), p.end(), make_pair(l, 0)) - p.begin();
		auto ed = lower_bound(p.begin(), p.end(), make_pair(r + 1, 0)) - p.begin();
		if(p[st].first > r){		//当前区间没有 1
			if(r - l + 1 >= k) cout << "YES\n";
			else cout << "NO\n";
			continue;
		}
		if((ed - st) % 2) cout << "NO\n";
		else{
			int ans = (ed - st) / 2;
			if(p[st].second == 1){
				ans += sum[0][r] - sum[0][l - 1];
			}else ans += sum[1][r] - sum[1][l - 1];
			if(ans >= k) cout << "YES\n";
			else cout << "NO\n";
		}
	}
	return ;
}

signed main(){
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	int _ = 1;
	cin >> _;
	while(_ --){
		solve();
	}
	return 0;
}

题解代码

//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long

using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*

*/
const int maxm = 1e5 + 5, inf = 0x3f3f3f3f, mod = 998244353;
ll n, q, a[maxm], sum[maxm], sum2[maxm];

void solve(){
	cin >> n >> q;
	for(int i = 1; i <= n; ++ i){
		cin >> a[i];
		sum[i] = (sum[i - 1] + a[i]) % 2;           //a数组前缀和的奇偶性
	}
	for(int i = 1; i <= n; ++ i){
		sum2[i] = sum2[i - 1] + (sum[i] ^ 1);   //a数组前缀和奇偶性的前缀和
	}
	for(int i = 0; i < q; ++ i){
		int l, r, k, t;
		cin >> l >> r >> k;
		t = sum2[r] - sum2[l - 1];
		if(sum[l - 1] != 0) t = (r - l + 1) - t;
		if(t >= k && (sum[r] - sum[l - 1]) % 2 == 0) cout << "YES\n";
		else cout << "NO\n";
	}
	return ;
}

signed main(){
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	int _ = 1;
	cin >> _;
	while(_ --){
		solve();
	}
	return 0;
}

G Gcd

题意
给你一个集合 S,初始包含两个数 \(x\) 和 $y $ \((x \ne y)\),你可以进行两种操作:

  • 操作 1:选择集合里面不相等的两个数 \(a\)\(b\),将数 \(a - b\) 插入集合中
  • 操作 2:选择集合里面不相等的两个数 \(a\)\(b\),将数 \(gcd(a, b)\) 插入集合中

问你是否可能在若干次操作后将数 z 放入集合中?

思路

  • 当数 \(z = 0\) 时,显然只有当 $ x = 0 $ 或者 $ y = 0 $ 才有可能成立
  • 当数 $z \ne 0 $ 时,判断其是否为 $gcd(x, y) $ 的整数倍即可

为什么呢?当数 \(z \ne 0\) 时,我们令 \(c = gcd(x, y)\),则 \(x = ag, y = bg\),再进行操作 1,可以得到 $(a - 1)g、(b - 1)g、(a - 2)g 、... $。最后我们一定可以得到 \(2g\),再得到 \(-g = g - 2g\),所以至此,所有的 \(g\) 的正整数倍均可以被表示
至于其余的数,我们可以发现,无论你取哪两个数进行减或者取最大公因数,一定包含因子 \(g\),即最后得到的数一定是 \(g\) 的倍数(好像还可以通过裴蜀定理来理解 但我不太懂 qwq

后记:其实题目的操作就是辗转相减法,又因为可以得到负数,所以可以得到 gcd 的所有倍数
代码

int gcd(int a, int b){ if(b == 0) return a; else return gcd(b, a % b); }

void solve(){
	int x, y, z;
	cin >> x >> y >> z;
	if(z == 0){
		if(x == 0 || y == 0)
			cout << "YES\n";
		else cout << "NO\n";
	}else{
		int b = gcd(x, y);
		if(z % b == 0){
			cout << "YES\n";
		}else cout << "NO\n";
	}
	return ;
}
posted on 2023-08-04 20:14  Qiansui  阅读(16)  评论(0编辑  收藏  举报