CSP-S模拟14

A. 莓良心

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

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

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

所有数的贡献都是 (lr)

于是就可以继续处理

如果 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 的方案数

ri 表示某一行的 1 的个数

如果 ri>a 那么该行必然选 1

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

如果 ri=a 的话,那么该行选 1/0 都可以

于是我们可以处理 prei 表示 r<=i1 的位置的并

于是我们可以处理 nxti 表示 r>=i0 的位置的并

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

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

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

但是当所有都取 1 时, 我们此时只选取了 c1=count(prei)1 ,而我们确定 c2=count(nxti) 个位置可以选,所以此时的贡献为 Cc2c1ic1

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. 团不过

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

gi 表示 i 堆石子的总方案数 gi=(2n1)i_

不合法异或为 0

fi 表示 i 堆石子不合法的方案数

异或为 0fi+=gi1fi1

就是 i1 堆合法, 第 i 堆取他们的异或

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

所以要减去选重的 fi2×(i1)×(2ni+1)

某两次选取选重,其他满足非法 fi2

i1 个位置可能选重, 选重的有 2ni+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 好像就是搜索 + 剪枝,挺离谱

这里使用 meet in middle

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

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

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

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 @   Chen_jr  阅读(79)  评论(6编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示