快来踩爆这个蒟蒻吧|

Little_corn

园龄:1年1个月粉丝:10关注:17

2024-04-23 13:38阅读: 54评论: 0推荐: 0

GJOI 2024.4.20 总结

Morning:

T1 小鸟

Statement:

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

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

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

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

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

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

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

1n40000001k160000000000001mmin(nn1,2500)


Solution:

考场被卡常力(悲

先计算 m=0 的情况。

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

考虑一条经过 (0,0) 的直线 y=pqx(p<q,gcd(p,q)=1) 会经过多少个点。显然是 n1q 个。那么分母为 q 的直线有 ϕ(q) 条,每条贡献 n1q。这样就好做了。

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

注意 x=0y=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] 中,最长的值域连续段长度。

1n,m50000.

Solution:

考虑用莫队求解。

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

那么添加一个数 x 就令 st[ed[x1]]=ed[x+1],ed[st[x+1]]=st[x1],并更新答案 res=max(res,ed[x+1]st[x1]+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 的点。

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

显然 fu=max(0,(vsubtree(u)fv)1)+ai。其中 ai=[wi>=x]

判断 f1 是否等于 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 种不同的颜色,编号 1k。现在你要对这 n 个结点染色,每个结点只能染一种颜色。还有一个特殊要求:凡是有边相连的两个结点,它们的颜色编号的和不能等于z。问有多少种不同的染色方案,答案模 1000000007

1n180mmin(18,n×(n1)/2),1k109,2z2109

Solution:

考虑容斥。

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

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

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

  • 该连通块有奇环:如果 z 为偶数,就只能把所有点染成 z2,否则没有染色的可能。

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

  1. k×2<z:0
  2. k>=z1:z1.
  3. otherwise:2×k(z1)
#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;
}

本文作者:Little_corn

本文链接:https://www.cnblogs.com/little-corn/p/18152698

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Little_corn  阅读(54)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起