ZR19CSP-S赛前冲刺day5

为了保护ZR的版权,这里不提供题目QWQ

http://zhengruioi.com/contest/442 (你进得去吗/xyx)

A 染色

发现对于每个连通块最多剩下一棵树

答案就是 ∑ 边 数 − 点 数 + 1 \sum 边数 - 点数 + 1 +1
然后就没了

code:

#include<bits/stdc++.h>
#define N 2000005
using namespace std;
struct edge {
	int v, nxt;
}e[N << 1];
int p[N], eid;
void init() {
	memset(p, -1, sizeof p);
	eid = 0;
}
void insert(int u, int v) {
	e[eid].v = v;
	e[eid].nxt = p[u];
	p[u] = eid ++;
}
int n, m, vis[N], ds, bs, in[N];
void dfs(int u) {
	vis[u] = 1;
	bs += in[u], ds ++;
	for(int i = p[u]; i + 1; i = e[i].nxt) {
		int v = e[i].v;
		if(!vis[v]) dfs(v);
	}
}
int main() {
	init();
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; i ++) {
		int u, v;
		scanf("%d%d", &u, &v);
		insert(u, v); in[u] ++;
		insert(v, u); in[v] ++;
	}
	int ans = 0;
	for(int i = 1; i <= n; i ++) if(!vis[i]) {
		bs = 0;
		ds = 0;
		dfs(i);
		bs /= 2;
		ans += bs - ds + 1;
	}
	printf("%d ", ans);
	return 0;
}

B 乘方

首先可以二分答案

然后考虑容斥

对于已选来的集合 ( − 1 ) k ∩ S ( n i )       k 个 (-1)^k \cap S(n_i) \ \ \ \ \ k个 (1)kS(ni)     k
考虑如何计算
发现就是 S ( l c m ( n i ) ) S(lcm(n_i)) S(lcm(ni))

S ( x ) S(x) S(x) 可以直接用二分 + 快速幂求出来

这样只能拿部分分

发现只和 l c m lcm lcm有关
再考虑DP, f [ i ] 表 示 l c m = i 的 容 斥 系 数 之 和 f[i]表示lcm=i的容斥系数之和 f[i]lcm=i
最后答案就是 ∑ f [ i ] S ( i ) \sum f[i]S(i) f[i]S(i)
1要特殊处理一下
然后就没了

code:


#include<bits/stdc++.h>
#define N 65
#define int long long
#define double long double 
using namespace std;
int gcd(int x, int y) {
	return y? gcd(y, x % y) : x;
}
int lcm(int x, int y) {
	return x * y / gcd(x, y);
}
int n, m, a[N], f[N], q;
int qpow(int x, int y) {
	int ret = 1;
	for(; y; y >>= 1, x = x * x) if(y & 1) ret = ret * x;
	return ret;
}
int find(int x, int y) {
	int l = 1, r = pow(1e18, 1.0 / (double)y) + 1;
	while(l + 1 < r) {
		int mid = (l + r) >> 1;
		if(qpow(mid, y) <= x) l = mid;
		else r = mid;
	}
	return l;
}
int calc(int X) {
	int ans = 1;
	for(int i = 1; i <= 60; i ++) ans += (find(X, i) - 1) * f[i];
	return ans;
}
signed main() {
	scanf("%lld\n", &q);
	while(q --) {
		memset(f, 0, sizeof f);
		scanf("%lld%lld", &m, &n);
		for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
		sort(a + 1, a + 1 + n);
		for(int i = 1; i <= n; i ++) {
			for(int j = 60; j >= 1; j --) 
				if(lcm(j, a[i]) <= 60) f[lcm(j, a[i])] -= f[j];
			f[a[i]] ++;
		}
			
		int l = 0, r = 1e17 + 1;
		while(l + 1 < r) {
			int mid = (l + r) >> 1;
			if(calc(mid) >= m) r = mid;
			else l = mid;
		}
		printf("%lld\n", r);	
	}
	
	return 0;
}

C 位运算

大力FWT

注意常数

最好用常数 / 2的那种写法

code:


#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = (1 << 23) + 5;
void fwt_or(ll *a, int len, int opt){
	for(int i = 1; i < len; i <<= 1)
		for(int j = 0; j < len; j += i << 1)
			for(register int k = j; k < j + i; k ++)
				a[i + k] += (ll)opt * a[k];
}
void fwt_and(ll *a, int len, int opt){
	for(int i = 1; i < len; i <<= 1)
		for(int j = 0; j < len; j += i << 1)
			for(register int k = j; k < j + i; k ++)
				a[k] += (ll)opt * a[i + k];
}
 
void fwt_xor(ll *a, int len, int opt){
	for(int i = 1; i < len; i <<= 1)
		for(int j = 0; j < len; j += i << 1)
			for(register int k = j; k < j + i; k ++){
				ll X = a[k], Y = a[k + i];
				a[k] = X + Y, a[k + i] = X - Y;
				if(opt == -1) a[k] = (X + Y) >> 1, a[k + i] = (X - Y) >> 1;
			}
}

int n, opt, cnt[N];
ll f[N], g[N];
int main(){
	scanf("%d%d", &n, &opt);
	int ma = 0;
	for(int i = 1; i <= n; i ++) {
		int x;
		scanf("%d", &x);
		cnt[x] ++;
		f[x] ++;
		ma = max(ma, x);
	}
	int len = (1 << 23);
	//	printf("%d\n", len);
	if(opt == 3) {
		fwt_or(f, len, 1);
		for(int i = 0; i < len; i ++) f[i] = f[i] * f[i];
		fwt_or(f, len, -1);
		for(int i = len; i >= 0; i --) if(f[i] - cnt[i] > 0) {printf("%d %lld", i, (f[i] - cnt[i]) / 2); return 0;}			
	}
	
	if(opt == 1) {
		fwt_and(f, len, 1);
		for(int i = 0; i < len; i ++) f[i] = f[i] * f[i];
		fwt_and(f, len, -1);
		for(int i = len; i >= 0; i --) if(f[i] - cnt[i] > 0) {printf("%d %lld", i, (f[i] - cnt[i]) / 2); return 0;}	
	}
	
	if(opt == 2) {
		fwt_xor(f, len, 1);
		for(int i = 0; i < len; i ++) f[i] = f[i] * f[i];
		fwt_xor(f, len, -1);
		for(int i = len; i >= 1; i --) if(f[i]) {printf("%d %lld", i, f[i] / 2); return 0;} 
		printf("0 %lld", (f[0] - n) / 2);
	}	
	
	return 0;
}

总结

速度要提上去

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值