2023-12-30 训练总结

发布时间 2023-12-30 14:32:22作者: ZnPdCo

返回 C 组做题,然后发现自己挂分了。

T1 寻找道路

反向建边,跑 dfs 算出能到达终点的点,然后跑 dij 就可以了。

/**
 * @file 寻找道路.cpp 
 * @tag: #GMOJ#最短路
 * @author: ZnPdCo
 * @date: 2023-12-30 14:26:11
 * @problem: https://gmoj.net/senior/#main/show/3934
 **/
#include <cstdio>
#include <queue>
#define ll long long
#define N 10010
#define M 200010
using namespace std;
ll n, m, st, ed;
bool flag[N], go[N];


ll head[N];
ll nxt[2*M];
bool type[2*M];
ll to[2*M], cnt;
void addEdge(ll u, ll v, ll t) {
	cnt++;
	to[cnt] = v;
	nxt[cnt] = head[u];
	type[cnt] = t;
	head[u] = cnt;
}

void dfs(ll u) {
	flag[u] = 1;
	for(ll i = head[u]; i; i = nxt[i]) if(type[i] == 1) {
		ll v = to[i];
		if(!flag[v]) dfs(v);
	}
}

priority_queue<pair<ll, ll> > q;
ll dis[N];
bool vis[N];
void dij() {
	for(ll i = 1; i <= n; i++) dis[i] = 1e15;
	dis[st] = 0;
	q.push(make_pair(0, st));
	while(!q.empty()) {
		ll u = q.top().second;
		q.pop();
		if(vis[u]) continue;
		vis[u] = 1;
		for(ll i = head[u]; i; i = nxt[i]) if(type[i] == 0) {
			ll v = to[i];
			if(!go[v]) continue;
			if(!vis[v]) {
				if(dis[v] > dis[u] + 1) {
					dis[v] = dis[u] + 1;
					q.push(make_pair(-dis[v], v));
				}
			}
		}
	}
}

int main() {
	scanf("%lld %lld", &n, &m);
	for(ll i = 1; i <= m; i++) {
		ll u, v;
		scanf("%lld %lld", &u, &v);
		addEdge(u, v, 0);
		addEdge(v, u, 1);
	}
	scanf("%lld %lld", &st, &ed);
	dfs(ed);
	
	for(ll u = 1; u <= n; u++) {
		go[u] = true;
		for(ll i = head[u]; i; i = nxt[i]) {
			ll v = to[i];
			if(flag[v] == 0) go[u] = false;
		}
	}
	dij();
	if(dis[ed] == 1e15) printf("-1");
	else printf("%lld", dis[ed]);
}
/*
3 3
1 2
2 3
3 1
1 2
*/

T2 联合权值

样例太水了,脑子也抽了,忘记还有兄弟距离了。

题目说得很明白,就是一棵树。对于树上的每一个节点与其相连的两个不同节点距离为 2。

然后就是数学题了。

/**
 * @file 联合权值.cpp 
 * @tag: #GMOJ#树
 * @author: ZnPdCo
 * @date: 2023-12-30 14:26:53
 * @problem: https://gmoj.net/senior/#main/show/3931
 **/
#include <cstdio>
#include <algorithm>
#define ll long long
#define N 200010
#define P 10007
using namespace std;
ll n, w[N], ans1, ans2;
ll fa[N];
ll dep[N];
ll f[N], mx[N];
ll head[N];
ll nxt[2*N];
ll to[2*N], cnt;
void addEdge(ll u, ll v) {
	cnt++;
	to[cnt] = v;
	nxt[cnt] = head[u];
	head[u] = cnt;
}
int main() {
	scanf("%lld", &n);
	for(ll i = 1; i < n; i++) {
		ll u, v;
		scanf("%lld %lld", &u, &v);
		addEdge(u, v);
		addEdge(v, u);
	}
	for(ll i = 1; i <= n; i++) {
		scanf("%lld", &w[i]);
	}
	for(ll u = 1; u <= n; u++) {
		ll sum1 = 0, sum2 = 0, mx1 = 0, mx2 = 0;
		for(ll j = head[u]; j; j = nxt[j]) {
			ll v = to[j];
			(sum1 += w[v]) %= P;
			(sum2 += w[v]*w[v]) %= P;
			if(w[v] > mx1) mx1 = w[v];
			else if(w[v] > mx2) mx2 = w[v];
		}
		
		ans1 = max(ans1, mx1*mx2);
		(ans2 += sum1 * sum1 - sum2) %= P;
	}
	printf("%lld %lld", ans1, ans2);
}

T3 飞扬的小鸟

赛时不知道为什么,直接上线段树,常数炸了。

其实可以直接用背包思想。原题就是一个可重复取的背包。

/**
 * @file 飞扬的小鸟.cpp 
 * @tag: #GMOJ#dp
 * @author: ZnPdCo
 * @date: 2023-12-30 14:27:53
 * @problem: https://gmoj.net/senior/#main/show/3932
 **/
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define N 10010
using namespace std;
ll n, m, k, ans2;
ll x[N], y[N];
ll f[2][N];
struct node {
	ll p, h, l;
} a[N];
ll pos = 1;
bool cmp(node x, node y) {
	return x.p < y.p;
}
int main() {
	scanf("%lld %lld %lld", &n, &m, &k);
	for(ll i = 1; i <= n; i++) {
		scanf("%lld %lld", &x[i], &y[i]);
	}
	for(ll i = 1; i <= k; i++) {
		scanf("%lld %lld %lld", &a[i].p, &a[i].l, &a[i].h);
	}
	sort(a+1, a+1+k, cmp);
	for(ll i = 1; i <= n; i++) {
		for(ll j = 0; j <= m; j++) f[i%2][j] = 1e15;
		for(ll j = x[i]+1; j < m; j++) {
			f[i%2][j] = min(f[i%2][j], min(f[(i-1)%2][j-x[i]], f[i%2][j-x[i]]) + 1);
		}
		for(ll j = m - x[i]; j <= m; j++) {
			f[i%2][m] = min(f[i%2][m], min(f[(i-1)%2][j], f[i%2][j]) + 1);
		}
		for(ll j = 0; j <= m - y[i]; j++) {
			f[i%2][j] = min(f[i%2][j], f[(i-1)%2][j+y[i]]);
		}
//		for(ll j = 0; j <= m; j++) printf("f[%lld][%lld]=%lld\n", i, j, f[i%2][j]);
		if(pos <= k && a[pos].p == i) {
			for(ll j = 0; j <= a[pos].l; j++) f[i%2][j] = 1e15;
			for(ll j = a[pos].h; j <= m; j++) f[i%2][j] = 1e15;
			for(ll j = 0; j <= m; j++) {
				if(f[i%2][j] != 1e15) {
					ans2 = max(ans2, pos);
				}
			}
			pos++;
		}
	}
	ll ans = 1e15;
	for(ll i = 1; i <= m; i++) {
		ans = min(ans, f[n%2][i]);
	}
	if(ans != 1e15) {
		printf("1\n");
		printf("%lld", ans);
	} else {
		printf("0\n");
		printf("%lld", ans2);
	}
}

T4 解方程

赛时一眼 \(O(nm)\) 正解,然后不知道为什么我竟然每次都求一次 \(x^i\),常数送到 30pts。

因为 \(0\bmod p = 0\),我们可以利用模数的性质,把 \(a_i\) 都取模,然后就是正常暴力。

但是对于 \(p\) 的倍数怎么办呢?没关系,两个模数撞的概率比较小,就可以过了。

我只用了 \(998244353\)

不要每次都求一次 \(x^i\)

然后可以边输入边取模。

/**
 * @file 联合权值.cpp 
 * @tag: #GMOJ#玄学
 * @author: ZnPdCo
 * @date: 2023-12-30 14:27:22
 * @problem: https://gmoj.net/senior/#main/show/3935
 **/
#include <cstdio>
#include <random>
#include <ctime>
#include <algorithm>
using namespace std;
#define ll long long
#define N 110
#define P 998244353
mt19937 rnd(time(0));
ll n, m;
ll a[N];
bool ismx;
ll ans[N], cnt;

ll read() {
	ll x=0, flag = 0;
	char c = '.';
	while((c < '0' || c > '9') && c != '-') c = getchar();
	if(c == '-') flag = 1, c = getchar();
	while(c >= '0' && c <= '9') {
		x = ((x<<1)%P + (x<<3)%P + (c^'0')) % P;
		c = getchar();
	}
	if(flag) x = (-x % P + P) % P;
	return x;
}

void output() {
	printf("%lld\n", cnt);
	for(ll i = 1; i <= cnt; i++) {
		printf("%lld\n", ans[i]);
	}
}
int main() {
	n = read(), m = read();
	for(ll i = 0; i <= n; i++) {
		a[i] = read();
	}
	for(ll i = 1; i <= m; i++) {
		ll sum = 0, xc = 1;
		for(ll j = 0; j <= n; j++) {
			(sum += a[j] * xc % P) %= P;
			(xc *= i) %= P;
		}
		if(sum == 0) ans[++cnt] = i;
	}
	output();
}

比赛赛时发挥不好,很多点都没有想到,特别是 T3,完全没有把背包联系起来(最后其实想到了,但不知道为什么自己丢掉了)