2021.10.16CSP模拟赛22 赛后总结

A. 区间

这题似乎用 \(ST\) 表,单调栈等各种方法都可以过。

我用的是无脑线段树(智商不够,数据结构来凑)。

简单来说,维护一下区间最小值及其位置即可,然后递归输出。

直接贴代码吧。

这个 \(pushup\) 好像不能写三目运算符(不知道为什么),大样例一直过不去,然后调了半天 \(QwQ\)。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define ls rt << 1
#define rs rt << 1 | 1

using namespace std;

const int N = 2e5 + 10;
int n;
int a[N], mins[N << 2], pos[N << 2];

inline void pushup(int rt){
	if(mins[ls] <= mins[rs]) pos[rt] = pos[ls], mins[rt] = mins[ls];
	else pos[rt] = pos[rs], mins[rt] = mins[rs];
}

inline void build(int l, int r, int rt){
	if(l == r){
		mins[rt] = a[l];
		pos[rt] = l;
		return;
	}
	int mid = (l + r) >> 1;
	build(l, mid, ls);
	build(mid + 1, r, rs);
	pushup(rt);
}

inline int query(int L, int R, int l, int r, int rt){
	if(l > R || r < L) return 0;
	if(L <= l && r <= R)
		return pos[rt];
	int mid = (l + r) >> 1;
	int res = 0;
	if(L <= mid) res = query(L, R, l, mid, ls);
	if(R > mid){
		int id = query(L, R, mid + 1, r, rs);
		if(a[id] < a[res]) res = id;
	}
	return res;
}

struct node{
	int l, r;
}ans[N];
int cnt;

inline void dfs(int l, int r, int tmp){
	if(l > r) return;
	int id = query(l, r, 1, n, 1);
	int res = a[id] - tmp, d = 0;
	while(res > 0){
		ans[++cnt].l = l, ans[cnt].r = r;
		res--, d++;
	}
	dfs(l, id - 1, tmp + d), dfs(id + 1, r, tmp + d);
}

int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	a[0] = 1e9;
	memset(mins, 0x3f, sizeof(mins));
	build(1, n, 1);
	dfs(1, n, 0);
	printf("%d\n", cnt);
	for(int i = 1; i <= cnt; i++)
		printf("%d %d\n", ans[i].l, ans[i].r);
	return 0;
}

B. 树

事实上我并不是很会算期望,所以就算给我 \(n \leq 10\) 数据的我也做不出来 \(QwQ\)。

考场上当然就直接弃了。

后来听学长讲,发现是一波大力推式子。

由于输入的是一棵树,所以从 \(u\) 到 \(v\) 的路径是先网上走到 \(lca(u, v)\),再往下走到 \(v\)。

所以先推出来从 \(x\) 节点走到 \(fa_x\) 的期望,然后推从 \(fa_x\) 走到 \(x\) 的期望(懒得打了,可以去看别人的题解)。就可以求解了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define ll long long

using namespace std;

inline int read(){
	int x = 0;
	char ch = getchar();
	while(ch < '0' || ch > '9') ch = getchar();
	while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
	return x;
}

const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int n, m;
struct node{
	int v, nxt;
}edge[N << 1];
int head[N], tot;
int ans;
int fa[N][20], dep[N];
ll f[N],g[N];

inline void add(int x, int y){
	edge[++tot] = (node){y, head[x]};
	head[x] = tot;
}

inline void dfs1(int x, int p){
	fa[x][0] = p, dep[x] = dep[p] + 1;
	for(int i = 1; i <= 20; i++)
		fa[x][i] = fa[fa[x][i - 1]][i - 1];
	f[x] = 1;
	for(int i = head[x]; i; i = edge[i].nxt){
		int y = edge[i].v;
		if(y == p) continue;
		dfs1(y, x);
		f[x] = (f[x] + f[y] + 1) % mod;
	}
}

inline void dfs2(int x, int p){
	for(int i = head[x]; i; i = edge[i].nxt){
		int y = edge[i].v;
		if(y == p) continue;
		g[y] = (g[x] + f[x] - f[y]) % mod;
		dfs2(y, x);
	}
}

inline void dfs3(int x, int p){
	f[x] += f[p], g[x] += g[p];
	for(int i = head[x]; i; i = edge[i].nxt)
		if(edge[i].v != p)
			dfs3(edge[i].v, x);
}

inline int lca(int a, int b){
	if(dep[a] < dep[b]) swap(a, b);
	for(int i = 20; i >= 0; i--)
		if(dep[fa[a][i]] >= dep[b]) a = fa[a][i];
	if(a == b) return a;
	for(int i = 20; i >= 0; i--)
		if(fa[a][i] != fa[b][i])
			a = fa[a][i], b = fa[b][i];
	return fa[a][0];
}

signed main(){
	n = read(), m = read();
	for(int i = 1; i < n; i++){
		int u = read(), v = read();
		add(u, v), add(v, u);
	}
	dfs1(1, 0);
	f[1]--;
	dfs2(1, 0), dfs3(1, 0);
	while(m--){
		int u = read(), v = read();
		int k = lca(u, v);
		// cout <<" dd " <<k << endl;
		printf("%lld\n", ((f[u] - f[k] + g[v] - g[k]) % mod + mod) % mod);
	}
	return 0;
}

C. 做运动

巨水的一道题,但是高一学生们全都写的二分 + \(dij\)(默契十足),复杂度 \(O(nlog_n^2)\),然后全都被卡到 10 ~ 30 分不等……

然鹅正解是并查集 + \(dij\),复杂度 \(O(nlogn)\)。

先按温度排序,然后不停加边,当起点和终点在同一集合中时,最大温度就是那条边的温度,然后把所有合法的边全都加进去,跑个最短路即可。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#define INF 1e18
#define ll long long

using namespace std;

inline ll read(){
	ll x = 0;
	char ch = getchar();
	while(ch < '0' || ch > '9') ch = getchar();
	while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
	return x;
}

const ll N = 5e5 + 10;
const ll M = 1e6 + 10;
ll n, m;
struct node{
	ll u, v, t, c, nxt;
	bool operator < (const node &b) const{
		return t < b.t;
	}
}edge[M << 1], e[M];
ll head[N], tot;
ll temp[M];
ll st, en;
ll dis[N], f[N];
struct Que{
	ll x, dis;
	bool operator < (const Que &b) const{
		return dis > b.dis;
	}
};

inline void add(ll x, ll y, ll c){
	edge[++tot].v = y, edge[tot].c = c, edge[tot].nxt = head[x];
	head[x] = tot;
}

inline void dijkstra(){
	priority_queue <Que> q;
	q.push((Que){st, 0});
	for(int i = 1; i <= n; i++) dis[i] = INF;
	dis[st] = 0;
	while(!q.empty()){
		Que now = q.top();
		q.pop();
		ll x = now.x;
		if(dis[x] < now.dis) continue;
		for(ll i = head[x]; i; i = edge[i].nxt){
			ll y = edge[i].v;
			if(dis[y] > dis[x] + edge[i].c){
				dis[y] = dis[x] + edge[i].c;
				q.push((Que){y, dis[y]});
			}
		}
	}
}

inline int find(int x){
	return f[x] == x ? x : f[x] = find(f[x]);
}

signed main(){
	n = read(), m = read();
	for(ll i = 1; i <= m; i++){
		e[i].u = read(), e[i].v = read(), e[i].t = read(), e[i].c = read();
	}
	st = read(), en = read();
	for(int i = 1; i <= n; i++)
		f[i] = i;
	sort(e + 1, e + 1 + m);
	ll mint, pos;
	for(int i = 1; i <= m; i++){
		int fu = find(e[i].u), fv = find(e[i].v);
		if(fu != fv) f[fu] = fv;
		add(e[i].u, e[i].v, e[i].c * e[i].t), add(e[i].v, e[i].u, e[i].c * e[i].t);
		if(find(st) == find(en)){
			mint = e[i].t, pos = i;
			break;
		}
	}
	for(int i = pos + 1; i <= m; i++){
		if(e[i].t != e[pos].t) break;
		add(e[i].u, e[i].v, e[i].c * e[i].t), add(e[i].v, e[i].u, e[i].c * e[i].t);
	}
	dijkstra();
	printf("%lld %lld\n", mint, dis[en]);
	return 0;
}

D. 手机信号

考场上打 \(O(n^2)\) 暴力,结果没注意写了个 \(c * c\) 直接 \(1e36\) 爆 \(long\ long\) 了,然后 30pts 都没有拿到 \(QwQ\)。

正解似乎是 \(set\) + 乱搞。

然鹅还不太会,先咕了,以后会了在补吧。

上一篇:【精讲】图论算法——并查集入门


下一篇:2021.10.05pm