CSP-S模拟14

A. 莓良心

死因答案统计错误,每次删去两个,而我只删一个

发现维护小的上界 \(r\) 和最大的下界 \(l\)

那么所有数都取在 \([r , l]\) 一定最优

所有数的贡献都是 \((l - r)\)

于是就可以继续处理

如果 \(l <= r\) 那么所有数可以取一个值,不会有贡献,直接 \(break\) 即可

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
const int maxn = 300005;
priority_queue<int>ql;
priority_queue<int, vector<int>, greater<int>>qr;

int main(){
	int n = read();
	for(int i = 1; i <= n; ++i){
		ql.push(read());
		qr.push(read());
	}
	ll ans = 0; int l = 1e8, r = 0;
	for(int i = 1; i < n; ++i){
		l = min(l, ql.top()); ql.pop();
		r = max(r, qr.top()); qr.pop();
		if(l <= r)break;
		ans += 1ll * (l - r) * (n - i * 2 + 1);
	}
	printf("%lld\n",ans);
	return 0;
}

B. 尽梨了

比较奇妙

我们要求 \(a\) 列为 \(1\) 的方案数

\(r_i\) 表示某一行的 \(1\) 的个数

如果 \(r_i > a\) 那么该行必然选 \(1\)

如果 \(r_i < a\) 那么该行必然选 \(0\), 因为如果选 \(1\), 那么对应 \(a\) 列选 \(1\) 的位置必然有 \(0\) 存在,状态非法

如果 \(r_i = a\) 的话,那么该行选 \(1 / 0\) 都可以

于是我们可以处理 \(pre_i\) 表示 \(r <= i\)\(1\) 的位置的并

于是我们可以处理 \(nxt_i\) 表示 \(r >= i\)\(0\) 的位置的并

这样,我们每次取到 \(pre\) \(nxt\), 他们分别表示哪些位置一定为 \(1 / 0\)

他们之间不能有交,否则非法

对于 \(r = a\) ,贡献为 \(2^m\) \(m\) 为这样行的个数, 当选择了这样的行时,所有 \(1\) 的位置都确定下来了,故没有额外贡献

但是当所有都取 \(1\) 时, 我们此时只选取了 \(c1 = count(pre_i)\)\(1\) ,而我们确定 \(c2 = count(nxt_i)\) 个位置可以选,所以此时的贡献为 \(C_{c2 - c1}^{i - c1}\)

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
const int maxn = 5005;
const int mod = 998244353;
int n, fac[maxn], inv[maxn], p2[maxn], ans;
bitset<maxn>s[maxn], pre[maxn],nxt[maxn];
vector<int>v[maxn];
char c[maxn];
int qpow(int x, int y){
	int ans = 1;
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
int C(int n, int m){
	if(n < m || m < 0)return 0;
	return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main(){
	n = read();
	for(int i = 1; i <= n; ++i){
		scanf("%s", c + 1);
		for(int j = 1; j <= n; ++j)if(c[j] == '1')s[i].set(j);
		v[s[i].count()].push_back(i);
	}
	inv[0] = fac[0] = 1; for(int i = 1; i <= n; ++i)fac[i] = 1ll * fac[i - 1] * i % mod;
	inv[n] = qpow(fac[n], mod - 2); for(int i = n - 1; i > 0; --i)inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
	p2[0] = 1; for(int i = 1; i <= n; ++i)p2[i] = (p2[i - 1] + p2[i - 1]) % mod;
	for(int i = 1; i <= n; ++i){
		pre[i] = pre[i - 1];
		for(int x : v[i])pre[i] |= s[x];
	}
	for(int i = 1; i <= n; ++i)nxt[n + 1].set(i);
	for(int i = n; i >= 1; --i){
		nxt[i] = nxt[i + 1];
		for(int x : v[i])nxt[i] &= s[x];
	}
	for(int i = 0; i <= n; ++i)if((pre[i] | nxt[i]) == nxt[i]){
		ans += p2[v[i].size()] - 1; ans %= mod;
		int c1 = pre[i].count(), c2 = nxt[i].count();
		ans += C(c2 - c1, i - c1); ans %= mod;
	}
	printf("%d\n",ans);
	return 0;
}

C. 团不过

正难则反,我们求不合法的方案数

\(g_i\) 表示 \(i\) 堆石子的总方案数 \(g_i = (2^{n} - 1)^{i\_}\)

不合法异或为 \(0\)

\(f_i\) 表示 \(i\) 堆石子不合法的方案数

异或为 \(0\)\(f_i += g_{i - 1} - f_{i - 1}\)

就是 \(i - 1\) 堆合法, 第 \(i\) 堆取他们的异或

但是这样会取重复元素,算多了

所以要减去选重的 \(f_{i - 2} \times (i - 1) \times (2^n - i + 1)\)

某两次选取选重,其他满足非法 \(f_{i - 2}\)

\(i - 1\) 个位置可能选重, 选重的有 \(2^n - i + 1\) 种取值

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
const int maxn = 1e7 + 55;
const int mod = 1e9 + 7;
int n;
int f[maxn], g[maxn], pow2;
int qpow(int x, int y){
	int ans = 1;
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
int main(){
	n = read();
	pow2 = qpow(2, n);
	g[0] = 1; for(int i = 1; i <= n; ++i)g[i] = 1ll * g[i - 1] * (pow2 - i) % mod;
	for(int i = 3; i <= n; ++i)
		f[i] = ((g[i - 1] - f[i - 1] - 1ll * (i - 1) * f[i - 2] % mod * (pow2 - i + 1) % mod)  + mod) % mod;
	int ans = (g[n] - f[n] + mod) % mod;
	printf("%d\n",ans);
	return 0;
}

D. 七负我

通过大胆猜测 + 不完全 完全不归纳发现平分一定最优,于是可以有状压暴力了

猜测一定选最大完全子图,于是不会了

解法 \(BK\) 好像就是搜索 + 剪枝,挺离谱

这里使用 \(\text{meet in middle}\)

先跑一半,求出 \(f_s\) 表示集合为 \(s\) 时最大完全子图

求法先跑出完全子图放到对应位置,然后取去掉某个点的 \(f\) 和自己的 \(max\)

跑另外一半,出完全图后,对他们在前一半的边取交集 \(s\), 那么 \(popcount + f_s\) 即为当前最大完全子图

\(max\) 即可

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;
}
int n, m, x, cnt[1 << 21 | 1], mx;
ll r[45];
int  f[1 << 21 | 1];
int main(){
	n = read(), m = read(), x = read();
	for(int i = 1; i <= m; ++i){
		int u = read(), v = read();
		r[u] |= (1ll << (v - 1));
		r[v] |= (1ll << (u - 1));
	}
	int n1 = n >> 1, n2 = n - n1;
	int mx1 = 1ll << n1, mx2 = 1ll << n2;
	for(int i = 1; i <= mx2; ++i) cnt[i] = cnt[i - (i & -i)] + 1;
	for(int i = 1; i < mx1; ++i){
		bool fl = 1;
		for(int j = 1; j <= n1; ++j)
			if(i & (1 << (j - 1)))
				if((r[j] & i) != (i xor (1 << (j - 1)))){fl = 0; break;}
		if(fl)f[i] = cnt[i];
	}
	for(int i = 1; i < mx1; ++i)
		for(int j = 1; j <= n1; ++j)
			if(i & (1 << (j - 1))) f[i] = max(f[i], f[i xor (1 << (j - 1))]);
	for(int i = 0; i < mx2; ++i){
		ll s = mx1 - 1;
		bool fl = 1;
		for(int j = 1; j <= n2; ++j)if(i & (1 << (j - 1))){
			if(((r[j + n1] >> n1) & i) != (i xor (1 << (j - 1)))){
				fl = 0; break;
			}
			s &= r[j + n1];
		}
		if(fl)mx = max(mx, f[s] + cnt[i]);
	}
	double one = (double)x / mx;
	double ans = one * one * (mx  * (mx - 1) / 2);
	printf("%.6lf\n",ans);
	return 0;
}
posted @ 2022-09-29 17:36  Chen_jr  阅读(76)  评论(6编辑  收藏  举报