GJOI 2024.4.20 总结

Morning:

T1 小鸟

Statement:

在一个 \(n\) 的二维平面里,\(X\) 轴的正方向是向右的,\(Y\) 轴的正方向是向上的。

在坐标系第一象限里,左下角的点的坐标是 \((0,0)\) ,右上角的点的坐标是 \((n-1,n-1)\)

所以本题我们考虑的整个平面总共有 \(n \times n\)个整点,每个整点都有一只小鸟,除了\((0,0)\)

在整点 \((0,0)\) 处有一把静音的机关枪,你可以选择任意的角度开枪,每开一枪,相当于

\((0,0)\) 处发射出一条射线,凡是在该射线上的小鸟都会被打死。

你最多可以开 \(k\) 枪,请问最多可以打死多少只小鸟?

但是有 \(m\) 只小鸟事先收到风,在你没开枪之前就先飞走了。

\(1\le n \le 4000000, 1\le k\le16000000000000, 1\le m\le \min(n*n-1,2500)\)


Solution:

考场被卡常力(悲

先计算 \(m=0\) 的情况。

注意到一个点至多被一条直线经过,因此分别计算每一条直线的贡献,取最大贡献的 \(k\) 条即可。而且除了 \(y = x\) 都是唯一的,斜率小于 1 的和斜率大于 1 的直线是可以一一对应的,因此接下来只考虑斜率小于 1 的直线。

考虑一条经过 \((0, 0)\) 的直线 \(y = \frac{p}{q}x (p < q, \gcd(p, q) = 1)\) 会经过多少个点。显然是 \(\lfloor \frac{n - 1}{q} \rfloor\) 个。那么分母为 \(q\) 的直线有 \(\phi(q)\) 条,每条贡献 \(\lfloor \frac{n - 1}{q} \rfloor\)。这样就好做了。

那么现在加入 \(m\) 个去除的点。对于一个点 \((x, y)\),将其约分后得到 \((x_0, y_0)\)。这个点就在 \(y = \frac{y0}{x0} x\) 上了。那么就让这条直线的贡献减一就好了。

注意 \(x = 0\)\(y = 0\) 这两条直线。

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#define int long long 
using namespace std;

const int N = 4e6 + 10, M = 5e3 + 10; 

int n, m, k;
int phi[N], prime[N], tot;
struct point{
	int x, y;
}p[M], s[N];
bool isprime[N];
map<int, int> mp; 

void init(){
	for(int i = 1; i < N; i++) isprime[i] = true;
	phi[1] = 1; isprime[1] = false;
	for(int i = 2; i < N; i++){
		if(isprime[i]) phi[i] = i - 1, prime[++tot] = i;
		for(int j = 1; j <= tot && i * prime[j] < N; j++){
			isprime[i * prime[j]] = false;
			if(i % prime[j]) phi[i * prime[j]] = phi[i] * phi[prime[j]];
			else{
				phi[i * prime[j]] = phi[i] * prime[j];
				break;	
			}  
		}
	}
}

bool cmp(struct point p1, struct point p2){
	if(p1.x != p2.x) return p1.x > p2.x;
	return p1.y < p2.y; 
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int T; cin >> T; init();
	while(T--){
		cin >> n >> k >> m; mp.clear();
		mp[n - 1] += 2; int tmp = 0;
		for(int i = 1; i < n; i++) mp[(n - 1) / i] += phi[i] * (i == 1 ? 1 : 2);
		for(int i = 1; i <= m; i++){
			cin >> p[i].x >> p[i].y;
			int g = __gcd(p[i].x, p[i].y);
			if((!p[i].x) || (!p[i].y)) g = 0;
			if(g) p[i].x /= g, p[i].y /= g;
		}
		sort(p + 1, p + m + 1, cmp); 
		int ths = 1;
//		for(int i = 1; i <= m; i++) cout << p[i].x << " " << p[i].y << "\n";
		for(int i = 2; i <= m; i++){ 
			if((p[i].x != p[i - 1].x || p[i].y != p[i - 1].y) && (p[i].x || p[i - 1].x) && (p[i].y || p[i - 1].y)){
				int g = max(p[i - 1].x, p[i - 1].y);
//				cout << p[i - 1].x << " " << p[i - 1].y << " " << g << " " << ths << "\n";  
				if(min(p[i - 1].x, p[i - 1].y) == 0) mp[n - 1]--, mp[n - 1 - ths]++;
				else mp[(n - 1) / g]--, mp[(n - 1) / g - ths]++; 
				ths = 0;
			}
			ths++;
		}
		if(ths){
			int g = max(p[m].x, p[m].y);
			if(min(p[m].x, p[m].y) == 0) mp[n - 1]--, mp[n - 1 - ths]++;
			else mp[(n - 1) / g]--, mp[(n - 1) / g - ths]++; 
		}
		int ans = 0;
		for(map<int, int>::iterator it = mp.begin(); it != mp.end(); it++){
			if(k <= 0) break;
			int val = (*it).first, cnt = (*it).second;
			if(cnt) s[++tmp] ={val, cnt};
		}
		sort(s + 1, s + tmp + 1, cmp);
		for(int i = 1; i <= tmp; i++){
			if(k <= 0) break;
			//cout << s[i].x << " " << s[i].y << "\n";
			ans += s[i].x * min(k, s[i].y); k -= s[i].y;
		}
		cout << ans << "\n";
	}
	
	return 0;
}
/*
1
9 1 7
8 2 
0 1 
7 6 
6 8 
3 3 
0 5 
3 4 
*/

T2 最长连续值域

Statement:

给出一个长度为 \(n\) 的排列 \(P(P1,P2,...Pn)\),以及 \(m\) 个询问。每次询问某个区间 \([l,r]\) 中,最长的值域连续段长度。

1\(\le n,m\le50000\).

Solution:

考虑用莫队求解。

我们令 \(st[x],ed[x]\) 分别是 \(x\) 最远向后可以延申到何处和向前最远延申至何处。

那么添加一个数 \(x\) 就令 \(st[ed[x - 1]] = ed[x + 1], ed[st[x + 1]] = st[x - 1]\),并更新答案 \(res = \max(res, ed[x + 1] - st[x - 1] + 1)\) 即可。

发现不好在删除的时候更新答案,于是使用回滚莫队。

#include<bits/stdc++.h>
#define int long long 
#define bk(x) ((x + unit - 1) / unit)
using namespace std;

const int N = 5e4 + 10;

int n, m, a[N]; 
int unit, st[N], ed[N];
struct query{
	int l, r, id;
}q[N];
int res, tmp, ans[N];

void clr(int x){st[x] = x + 1; ed[x] = x - 1;}

void add(int x){
	res = max(res, ed[x + 1] - st[x - 1] + 1);
	ed[st[x - 1]] = ed[x + 1];
	st[ed[x + 1]] = st[x - 1];
}

void del(int x){
	ed[st[x - 1]] = x - 1;
	st[ed[x + 1]] = x + 1;
}

bool cmp(struct query q1, struct query q2){
	if(bk(q1.l) != bk(q2.l)) return q1.l < q2.l;
	return q1.r < q2.r;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0); 
	cin >> n >> m; unit = sqrt(m);
	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int i = 1; i <= m; i++) cin >> q[i].l >> q[i].r, q[i].id = i;
	sort(q + 1, q + m + 1, cmp);
	int l = 1, r = 0;
	for(int i = 1; i <= m; i++){
		if(bk(q[i].l) != bk(q[i - 1].l)){
			for(int j = 0; j <= n + 1; j++) clr(j);
			r = min(n, bk(q[i].l) * unit); l = min(n, bk(q[i].l) * unit) + 1;  res = 0;
		}
		if(bk(q[i].l) == bk(q[i].r)){
			res = 0;
			for(int j = q[i].l; j <= q[i].r; j++) add(a[j]);
			ans[q[i].id] = res;
			for(int j = q[i].r; j >= q[i].l; --j) del(a[j]);
			res = 0; continue;
		}
		while(r < q[i].r) add(a[++r]);
		int tmp = res, _l = l;
		while(_l > q[i].l) add(a[--_l]);
		ans[q[i].id] = res; res = tmp;
		while(_l < l) del(a[_l++]); 
	}
	for(int i = 1; i <= m; i++) cout << ans[i] << "\n";
	
	return 0;
}

T3 P5306 [COCI2018-2019#5] Transport

一道淀粉质板子题就不讲了。

Afternoon

T1 [ABC246G] Game on Tree 3

首先二分答案 \(x\),将权值大于等于 \(x\) 的置为 \(1\),小于的置为 \(0\)

于是这个时候我们判断 \(B\) 是否可以让 \(A\) 拿不到任何一个权值为 \(1\) 的点。

\(f_u\) 为以 \(u\) 为根的子树中 \(A\) 以最优策略操作,\(B\) 至少要在游戏开始前删除多少个 \(1\) 的点。

显然 \(f_u = \max(0, (\sum_{v \in subtree(u)}f_v) - 1) + a_i\)。其中 \(a_i = [w_i >= x]\)

判断 \(f_1\) 是否等于 \(0\) 即可。

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

const int N = 2e5 + 10;
struct edge{
	int v, next;
}edges[N << 1];
int head[N], idx;

void add_edge(int u, int v){
	edges[++idx] = {v, head[u]};
	head[u] = idx;
}

int n, a[N];
int f[N];

void dfs(int u, int fa){
	int s = 0;
	for(int i = head[u]; i; i = edges[i].next){
		int v = edges[i].v;
		if(v == fa) continue;
		dfs(v, u); s += f[v];
	}
	f[u] += max(0ll, s - 1);
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> n;
	for(int i = 2; i <= n; i++) cin >> a[i];
	for(int i = 1; i < n; i++){
		int x, y; cin >> x >> y;
		add_edge(x, y); add_edge(y, x);
	}
	int l = 1, r = 1e9, ans = 0; 
	while(l <= r){
		int mid = (l + r >> 1);
		for(int i = 1; i <= n; i++) f[i] = (a[i] >= mid);
		dfs(1, 0);
		if(f[1] == 0) r = mid - 1;
		else ans = mid, l = mid + 1;
	}
	cout << ans;

	return 0;
}

T2 无向图染色

Statement:

给出 \(n\) 个结点 \(m\) 条边的无向图。有 \(k\) 种不同的颜色,编号 \(1\)\(k\)。现在你要对这 \(n\) 个结点染色,每个结点只能染一种颜色。还有一个特殊要求:凡是有边相连的两个结点,它们的颜色编号的和不能等于\(z\)。问有多少种不同的染色方案,答案模 \(1000000007\)

\(1 \le n \le 18,0 \le m \le \min(18,n\times(n-1)/2), 1 \le k \le 10^9, 2 \le z \le 2*10^9\)

Solution:

考虑容斥。

定义不可行边为一条连接 \((u, v)\) 的边,而且 \(color_u + color_v = z\),对于一个不可行边集合 \(S\),我们建出这张图。

对于一个孤立点,它染色的可能性就有 \(k\) 种。

接下来我们考虑点数 $ > 1$ 的连通块,有两种情况:

  • 该连通块有奇环:如果 \(z\) 为偶数,就只能把所有点染成 \(\frac{z}{2}\),否则没有染色的可能。

  • 该连通块没有奇环:注意到如果我们确定了一个点的颜色,整个连通块的颜色就可以确定了。那么一个点的可能性就是:

  1. \(k \times 2 < z:0\)
  2. \(k >= z - 1: z - 1\).
  3. \(otherwise: 2 \times k - (z - 1)\)
#include<bits/stdc++.h>
#define int long long 
using namespace std;

const int N = 18 + 3, mod = 1e9 + 7;

int n, m, k, z;
int from[N], to[N], nodecnt;

int qpow(int x, int y){
	int ret = 1;
	while(y){
		if(y & 1) ret = (ret * x) % mod;
		x = (x * x) % mod;
		y >>= 1;
	}
	return ret;
}

struct edge{
	int v, next;
}edges[N << 1];
int head[N], idx;

void add_edge(int u, int v){
	edges[++idx] = {v, head[u]};
	head[u] = idx;
}

int col[N];

int popcnt(int x){if(!x) return 0; return popcnt(x >> 1) + (x & 1);} 
void build(int S){//建图
	idx = 0; nodecnt = 0;
	for(int i = 1; i <= n; i++) head[i] = col[i] = 0;
	for(int i = 0; i < m; i++) if((1 << i) & S) add_edge(from[i + 1], to[i + 1]), add_edge(to[i + 1], from[i + 1]);  
	for(int i = 1; i <= n; i++) if(head[i]) nodecnt++;//自由点的个数
}
bool checker(int u){//检查是否有奇环
	bool flag = true;
	for(int i = head[u]; i; i = edges[i].next){
		int v = edges[i].v;
		if(!col[v]) col[v] = -col[u], flag &= checker(v); //
		else if(col[v] + col[u] != 0) flag = false; //
	}
	return flag;
}
int lsy(){ // 
	if(k >= z - 1) return z - 1;
	if(2 * k < z) return 0;
	return 2 * k - z + 1;
}
int solver(){//算这个图染色的方案数
	int ret = 1;
	for(int i = 1; i <= n; i++){
		if((!col[i]) && head[i]){
			col[i] = 1;//将第 1 个节点染成 1
			if(checker(i) && (z <= 2 * k)) ret = (ret * lsy()) % mod;
			else if((z & 1) || z > 2 * k) ret = 0;
		}
	}
	return ret;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> n >> m >> k >> z;
	for(int i = 1; i <= m; i++) cin >> from[i] >> to[i];
	int ans = 0;
	for(int S = 0; S < (1 << m); S++){
		build(S);
		ans = (ans + ((qpow(k, n - nodecnt) * ((popcnt(S) & 1) ? -1 : 1) * solver()) % mod + mod) % mod) % mod;
		// cout << S << " " << nodecnt << " " << ans << "\n";
	}
	cout << ans;

	return 0;
}
posted @ 2024-04-23 13:38  Little_corn  阅读(69)  评论(0)    收藏  举报