Gym - 101630 部分题解 | 待咕

Gym - 101630 部分题解

前言

有几题没补…但感觉这些题目都是不错的…所以…会抽时间补一下的吧???

不过大概率是咕咕咕的…

正文
Gym-101630A. Archery Tournament
  • 题意
    • 给你一些圆(都与 x x x轴相切)还有一些询问(边询问边插入圆) 问这些点是否在一个圆上…如果是则把这个圆给删除…(保证同一时刻圆不会相交)
    • − 1 e 9 ≤ x , y ≤ 1 e 9 -1e9 \leq x,y \leq 1e9 1e9x,y1e9, n ≤ 1 e 5 n \leq 1e5 n1e5
  • 题解
    • 线段树!(smg)
    • 线段树维护一段区间内的圆的标号…感觉有点像那个K-Dtree…???确定了一个范围…
      • 我们可以知道被一条 x = a x=a x=a的直线切到的圆最多在 l o g log log
    • 虽然是区间修改…但是却不需要什么lazy的标记…而且也不需要区间合并操作…
    • 线段树标记永久化-原理就是: 在路过该节点的时候把修改对答案的影响加上,来省去标记下放的过程
#include <bits/stdc++.h>
#define ls x << 1
#define rs x << 1 | 1
#define ll long long
using namespace std;
const int N = 4e5 + 10;
struct data {
	int tp, x, y;
} q[N];
struct node {
	set<int> id;
} tr[N << 2];
int all[N * 3], tot, ans;

int get(int x) {
	return lower_bound(all + 1, all + 1 + tot, x) - all;
}

void upd(int x, int l, int r, int L, int R, int w) {
	if(L <= l && r <= R) {
		tr[x].id.insert(w);
		return ;
	}
	int mid = (l + r) >> 1;
	if(L <= mid) upd(ls, l, mid, L, R, w);
	if(R > mid) upd(rs, mid + 1, r, L, R, w);
}

void ers(int x, int l, int r, int L, int R, int w) {
	if(L <= l && r <= R) {
		tr[x].id.erase(w);
		return ;
	}
	int mid = (l + r) >> 1;
	if(L <= mid) ers(ls, l, mid, L, R, w);
	if(R > mid) ers(rs, mid + 1, r, L, R, w);
}

ll pf(ll x) {
	return x * x;
}

bool check(int i, int j) {
	return pf(q[i].x - q[j].x) + pf(q[i].y - q[j].y) < pf(q[i].y);
}

void get(int x, int l, int r, int L, int w) {
	set<int> :: iterator sit;
	for(sit = tr[x].id.begin(); sit != tr[x].id.end(); ++sit) {
		int pos = *sit;
		if(check(pos, w)) {
			// printf("q%d %d\n", q[pos].x, q[pos].y);
			// printf("p%d %d\n", q[w].x, q[w].y);
			ans = pos;
		}
	}
	int mid = (l + r) >> 1;
	if(l == r || ans != -1) return ;
	if(L <= mid) get(ls, l, mid, L, w);
	else get(rs, mid + 1, r, L, w);
}

int main() {
	int n, cnt = 0;
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i) {
		scanf("%d%d%d", &q[i].tp, &q[i].x, &q[i].y);
		if(q[i].tp == 1) {
			all[++cnt] = q[i].x - q[i].y, all[++cnt] = q[i].x + q[i].y; 
		} else {
			all[++cnt] = q[i].x;
		}
	}
	sort(all + 1, all + 1 + cnt);
	tot = unique(all + 1, all + 1 + cnt) - all - 1;
	for(int i = 1; i <= n; ++i) {
		if(q[i].tp == 1) {
			int l = get(q[i].x - q[i].y);
			int r = get(q[i].x + q[i].y);
			upd(1, 1, tot, l, r, i);
		} else {
			int x = get(q[i].x);
			ans = -1;
			get(1, 1, tot, x, i);
			printf("%d\n", ans);
			if(ans != -1) {
				int l = get(q[ans].x - q[ans].y);
				int r = get(q[ans].x + q[ans].y);
				ers(1, 1, tot, l, r, ans);
			}
		}
	}
	return 0;
}

Gym-101630I. Journey from Petersburg to Moscow
  • 题意

    • 1 − n 1-n 1n的最短路…最短路定义为前 k k k大边权的和
    • n ≤ 3000 n \leq 3000 n3000
  • 题解

    神仙题!…看上去现场也没有什么人过的样子…其实这道题也挺可做的呀…qwq

    首先我们需要知道一点…这样定义的最短路一定会比我们原来的最短路要小

    • 如果我们找到了一条路径…它的边数 ≤ k \leq k k 那么这个路径的价值就是所有权值和也就是最短路

    • 如果$k \leq 边 数 , 对 于 这 种 情 况 . 我 们 就 枚 举 这 个 第 k 大 的 边 权 边数,对于这种情况.我们就枚举这个第k大的边权 ,.kc . . 所 有 小 于 ..所有小于 ..c$的边权变为 0 0 0,大于 c c c的边权变为 w − c w-c wc

      跑一遍最短路…用 d i s [ n ] + c ∗ k dis[n]+c*k dis[n]+ck更新答案

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3010;
struct data {
	int nt, to, w;
} a[N << 1];
int cnt = 0;
int head[N];
ll dis[N], v[N];
typedef pair<ll, int> P;
priority_queue<P, vector<P>, greater<P> > q;

void add(int x, int y, int w) {
	a[++cnt].to = y;
	a[cnt].w = w;
	a[cnt].nt = head[x];
	head[x] = cnt;
}

int n, m, k;

void go(ll w) {
	for(int i = 1; i <= n; ++i) {
		dis[i] = 1e13;
	}
	dis[1] = 0;
	q.push(P(0, 1));
	while(!q.empty()) {
		P p = q.top(); q.pop();
		int v = p.second;
		if(dis[v] < p.first) continue;
		for(int i = head[v]; i; i = a[i].nt) {
			int to = a[i].to;
			ll now = max(a[i].w - w, 0ll);
			if(dis[to] > dis[v] + now) {
				dis[to] = dis[v] + now;
				q.push(P(dis[to], to));
			}
		}
	}
}

int main() {
	scanf("%d%d%d", &n, &m, &k);
	for(int i = 1; i <= m; ++i) {
		int x, y, w;
		scanf("%d%d%d", &x, &y, &w);
		add(x, y, w);
		add(y, x, w);
		v[i] = w;
	}
	ll ans = 1e18;
	v[++m] = 0;
	for(int i = 1; i <= m; ++i) {
		go(v[i]);
		// printf("%lld %lld\n", v[i], dis[n]);
		ans = min(ans, dis[n] + v[i] * k);
	}
	printf("%lld\n", ans);
	return 0;
}

Gym101630C. Connections
  • 题意
    • 给你一张 n n n个点 m m m条边的有向图,保证两两之间可以到达…请你删除 m − 2 ∗ n m-2*n m2n条边…使之还可以两两之间到达
    • n , m ≤ 100000 n,m \leq 100000 n,m100000
  • 题解
    • 首先我们肯定是可以构造出来这样的图的!
      • 每加入一个环最坏的情况是只加入一个点…那么最多需要 2 ∗ n − 1 2*n-1 2n1条边
    • 我们只需要保证所有的点都能从1走到且都能走到1
      • 所以正向反向搜一遍dfs树…所有没有被用作树边的边都是可以删除的
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct data {
	int nt, to;
} a[N][2];
int head[N][2], cnt[2], vis[N], vs[N];

void add(int x, int y, int id) {
	a[++cnt[id]][id].to = y;
	a[cnt[id]][id].nt = head[x][id];
	head[x][id] = cnt[id];
}

void dfs(int x, int id) {
	vs[x] = 1;
	for(int i = head[x][id]; i; i = a[i][id].nt) {
		int to = a[i][id].to;
		if(!vs[to]) {
			vis[i]++;
			dfs(to, id);
		}
	}
}

int main() {
	int T;
	scanf("%d", &T);
	for(int o = 1; o <= T; ++o) {
		int n, m;
		cnt[0] = cnt[1] = 0;
		scanf("%d%d", &n, &m);
		for(int i = 1; i <= n; ++i) 
			head[i][0] = head[i][1] = 0;
		for(int i = 1, x, y; i <= m; ++i) {
			scanf("%d%d", &x, &y);
			vis[i] = 0;
			add(x, y, 0);
			add(y, x, 1);
		}
		for(int i = 1; i <= n; ++i)
			vs[i] = 0;
		dfs(1, 0);
		for(int i = 1; i <= n; ++i) 
			vs[i] = 0;
		dfs(1, 1);
		int now = m - 2 * n;
		// for(int i = 1; i <= m; ++i) {
		// 	printf("%d %d\n", i, vis[i]);
		// }
		for(int u = 1; u <= n; ++u) {
			for(int i = head[u][0]; i; i = a[i][0].nt) {
				if(!vis[i] && now) {
					printf("%d %d\n", u, a[i][0].to);
					now--;
				}
			}
		}
	}
	return 0;
}

Gym101630L - Laminar Family
  • 题意

    • 定义 f f f集合合法为 f f f中任意两个集合 A , B A,B A,B A 属 于 B ∣ ∣ B 属 于 A ∣ ∣ A 与 B 无 交 A属于B||B属于A||A与B无交 ABBAAB
    • 给你一个 n n n个节点的树 给你 m m m组路径 ( u , v ) (u,v) (u,v) 问这 m m m组路径是否满足条件
  • 题解

    • 比赛时候 A − E A-E AE都没打出来根本没心情看这种题目啊!…后面真的有几题是可做的呢

    • 首先 u = = v u==v u==v的组合是没有用的

    • 我们可以树上差分记录每个节点被访问到了几次

    • 去除没有访问过的点

      • 很显然…如果有度数>2的点肯定是不行的!

      • 接下来我们要做的就是一个经典问题了对吧…

        有一些线段是否存在交叉关系

        • 这个搞个单调栈什么的就可以了…具体看一下代码吧

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, LOG = 20;
struct Path {
	int len, u, v;
} b[N];
struct data {
	int nt, to;
} a[N << 1];
queue<int> q;
vector<int> G[N];
int cnt = 0, n, m;
int val[N], ys[N], sy[N], vis[N], st[N];
int d[N], head[N], dep[N], g[N][LOG + 1];

void add(int x, int y) {
	a[++cnt].to = y;
	a[cnt].nt = head[x];
	head[x] = cnt;
}

void dfs(int u, int fa) {
	g[u][0] = fa;
	dep[u] = dep[fa] + 1;
	for(int i = head[u]; i; i = a[i].nt) {
		int to = a[i].to;
		if(to == fa) {
			continue;
		}
		dfs(to, u);
	}
}

void Dfs(int u, int fa) {
	for(int i = head[u]; i; i = a[i].nt) {
		int to = a[i].to;
		if(to == fa) {
			continue;
		}
		Dfs(to, u);
		val[u] += val[to];
	}
}

void yyqx() {
	for(int j = 1; j <= LOG; ++j) 
		for(int i = 1; i <= n; ++i)
			g[i][j] = g[g[i][j - 1]][j - 1];
}

int GetLCA(int A, int B) {
	if(dep[A] > dep[B]) swap(A, B);
	for(int i = LOG; i >= 0; --i)
		if(dep[g[B][i]] >= dep[A]) B = g[B][i];
	if(A == B) return A;
	for(int i = LOG; i >= 0; --i) 
		if(g[A][i] != g[B][i])
			A = g[A][i], B = g[B][i];
	return g[A][0];
}

bool cmp(Path A, Path B) {
	return A.len > B.len;
}

bool go(int be) {
	vis[be] = 1;
	q.push(be);
	int now = 0;
	while(!q.empty()) {
		int h = q.front(); q.pop();
		sy[h] = ++now, ys[now] = h;
		for(int i = head[h]; i; i = a[i].nt) {
			int to = a[i].to;
			if(!vis[to] && val[to]) {
				vis[to] = 1;
				q.push(to);
				break;
			}
		}
	}
	int top = 0;
	for(int i = 1; i <= now; ++i) {
		int h = ys[i];
		while(top && st[top] < i) top--;
		for(int j = 0; j < G[h].size(); ++j) {
			int to = G[h][j];
			if(sy[to] <= i) continue;
			if(!top || sy[to] <= st[top]) st[++top] = sy[to];
			else return false;
		}
	}
	return true;
}

int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1, x, y; i < n; ++i) {
		scanf("%d%d", &x, &y);
		add(x, y), add(y, x);
	}
	dfs(1, 0), yyqx();
	int tot = 0;
	for(int i = 1, x, y; i <= m; ++i) {
		scanf("%d%d", &x, &y);
		if(x == y) continue;
		int lca = GetLCA(x, y);
		// printf("%d %d %d\n", x, y, lca);
		b[++tot].u = x, b[tot].v = y, b[tot].len = dep[x] + dep[y] - 2 * dep[lca];
		val[x]++, val[y]++;
		val[lca] -= 1;
		val[g[lca][0]] -= 1;
	}
	Dfs(1, 0);
	for(int u = 1; u <= n; ++u) {
		if(val[u]) {
			for(int i = head[u]; i; i = a[i].nt) {
				int to = a[i].to;
				if(val[to]) {
					d[u]++;
				}
			}
		}
	}
	for(int i = 1; i <= n; ++i) {
		if(d[i] > 2) return printf("No\n"), 0;
	}
	sort(b + 1, b + 1 + tot, cmp);
	for(int i = 1; i <= tot; ++i) {
		G[b[i].u].push_back(b[i].v);
		G[b[i].v].push_back(b[i].u);
	}
	for(int i = 1; i <= n; ++i) {
		if(!vis[i] && d[i] == 1) {
			if(!go(i)) return printf("No\n"), 0;
		}
	}
	printf("Yes\n");
	return 0;
}

Gym101630 - I . Interactive Sort
  • 题意

    • 一个长度 n ≤ 10000 n \leq 10000 n10000的一个排列…分成偶数和奇数两个数组(保证随机)
    • 每次询问可以得到 e v e n [ i ] 和 o d d [ j ] even[i]和odd[j] even[i]odd[j]的大小关系(询问次数 ≤ 300000 \leq 300000 300000
  • 题解

    • 我们可以利用奇数将偶数分成若干段
    • 每次求奇数的值的时候直接二分出在偶数的哪一个段
    • 然后直接暴力拆解就行了
    • 由于是随机的所以 O ( n l o g n ) O(nlog_n) O(nlogn)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
vector<int> g[N], u, v;
char lx[5];
int e[N], o[N];
int tot = 0;

bool Ask(int x, int y) {
	printf("? %d %d\n", x, y);
	fflush(stdout);
	scanf("%s", lx + 1);
	return lx[1] == '<';
}

void split(int now, int x) {
	u.clear(), v.clear();
	for(int i = 0; i < g[now].size(); ++i) {
		if(Ask(g[now][i], x)) 
			u.push_back(g[now][i]);
		else 
			v.push_back(g[now][i]);
	}
}

int insert(int x) {
	for(int i = tot; i > x; --i)
		g[i + 1] = g[i];
	g[x] = u, g[x + 1] = v, tot++;
	int ans = 0;
	for(int i = 1; i <= x; ++i)
		ans += (int)g[i].size();
	return ans * 2 + 1;
}

int main() {
	int n;
	scanf("%d", &n);
	if(n == 1) {
		printf("! 1\n");
		fflush(stdout);
		return 0;
	}
	tot = 1;
	for(int i = 1; i <= n / 2; ++i) {
		g[1].push_back(i);
	}
	// printf("%d\n", (int)g[1].size());
	for(int i = 1; i <= (n + 1) / 2; ++i) {
		int l = 1, r = tot, ans = 0;
		while(l <= r) {
			int mid = (l + r) >> 1;
			if(Ask(g[mid][0], i)) {
				ans = mid, l = mid + 1;
			} else {
				r = mid - 1;
			}
		}
		// printf("%d\n", ans);
		if(!ans) {
			// printf("???\n");
			split(1, i);
			if(!u.size()) {
				o[i] = 1;
			} else if(!v.size()) {
				o[i] = u.size() * 2 + 1;
			} else {
				o[i] = insert(1);
			}
			// printf(">??\n");
		} else {
			int now = 0;
			for(int i = 1; i < ans; ++i)
				now += (int)g[i].size();
			split(ans, i);
			if(!v.size()) {
				if(ans == tot) {
					o[i] = n;
				} else {
					now += (int)g[ans].size();
					split(ans + 1, i);
					if(!u.size()) {
						o[i] = now * 2 + 1;
					} else {
						o[i] = insert(ans + 1);
					}
				}
			} else {
				o[i] = insert(ans);
			}
		}
	}
	int cur = 0;
	for(int i = 1; i <= tot; ++i)
		for(int j = 0; j < g[i].size(); ++j)
			cur++, e[g[i][j]] = cur * 2;
	printf("! ");
	for(int i = 1; i <= n / 2; ++i) {
		printf("%d", e[i]);
		if(i != n / 2) printf(" ");
	}
	for(int i = 1; i <= (n + 1) / 2; ++i)
		printf(" %d", o[i]);
	puts("");
	fflush(stdout);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值