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}\),否则没有染色的可能。
-
该连通块没有奇环:注意到如果我们确定了一个点的颜色,整个连通块的颜色就可以确定了。那么一个点的可能性就是:
- \(k \times 2 < z:0\)
- \(k >= z - 1: z - 1\).
- \(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;
}
标签:le,2024.4,int,ed,st,++,res,20,GJOI
From: https://www.cnblogs.com/little-corn/p/18152698