随笔 - 216,  文章 - 0,  评论 - 17,  阅读 - 25482

比赛链接:

https://vjudge.net/contest/510447

A - Theramore

题意:

给定一个 01 字符串,可以选择奇数长度的区间进行翻转,问字符串字典序最小是多少。

思路:

因为选择的是奇数长度,所以每个 01 的奇偶性是不变的,最小就是奇数和偶数位置的 1 分别移动到最后面。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
	string s;
	cin >> s;
	LL n = s.size();
	vector <LL> cnt(2);
	for (int i = 0; i < n; i ++ ){
		cnt[i & 1] += (s[i] == '1');
	}
	for (int i = n - 1; i >= 0; i -- ){
		if (cnt[i & 1]){
			cnt[i & 1] -- ;
			s[i] = '1';
		}
		else{
			s[i] = '0';
		}
	}
	cout << s << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

B - Darkmoon Faire

题意:

对于一个区间,如果它的最大值在奇数位置且最小值在偶数位置,那么称它是 ridiculous 的。
给定一个序列 a,问有多少种分法可以使得每段区间都是 ridiculous 的。

思路:

定义 dp[i] 表示将 [1,i] 划分为每段都是 ridiculous 的方案数。
容易想到暴力做法 dp[i]=dp[j]+check(j+1,i),当 [j+1,i]ridiculous 的,dp[i] 就可以从 dp[j] 转移过来。
找到一个区间的最大值/最小值的位置,可以通过单调栈实现。如果最大值在第 i 位,那么所有满足奇偶性与 i 相同的 j 都是符合最大值的要求的,对于最小值也是一样,当一个位置最大值和最小值的都是符合要求的,它就可以从前面的方案转移过来。
可以通过两棵线段树维护奇数/偶数位置上的信息。用 sum 去记录奇数/偶数位置上的方案数,用 mx 去记录奇数/偶数位置上是否满足最大值和最小值的要求。
最大值的 mx 的更新就直接更新在当前位置对应的奇偶位置,最小值要取反,因为一段区间中最小值要在偶数位置上,与最大值是相反的。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int P = 998244353, N = 3e5 + 10;
LL n, a[N], dp[N], top1, top2, stk_max[N], stk_min[N];
struct SegmentTree{
	struct node{
		LL l, r, sum, add, mx;
	}tr[N << 2];
	void pushup(LL u){
		tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);
		tr[u].sum = 0;
		if (tr[u].mx == tr[u << 1].mx)
			tr[u].sum = (tr[u].sum + tr[u << 1].sum) % P;
		if (tr[u].mx == tr[u << 1 | 1].mx)
			tr[u].sum = (tr[u].sum + tr[u << 1 | 1].sum) % P;
	}
	void pushdown(LL u){
		auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
		left.add = (left.add + root.add) % P;
		left.mx = (left.mx + root.add) % P;
		right.add = (right.add + root.add) % P;
		right.mx = (right.mx + root.add) % P;
		root.add = 0;
	}
	void build(LL u, LL l, LL r){
		if (l == r){
			tr[u] = {l, r, 0, 0, 0};
			return;
		}
		tr[u] = {l, r, 0, 0, 0};
		LL mid = (l + r) >> 1;
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);
		pushup(u);
	}
	void update(LL u, LL p, LL k){
		if (tr[u].l == tr[u].r){
			tr[u].sum = k;
			return;
		}
		LL mid = (tr[u].l + tr[u].r) >> 1;
		pushdown(u);
		if (p <= mid) update(u << 1, p, k);
		else update(u << 1 | 1, p, k);
		pushup(u);
	}
	void update(LL u, LL l, LL r, LL k){
		if (tr[u].l >= l && tr[u].r <= r){
			tr[u].mx = (tr[u].mx + k) % P;
			tr[u].add = (tr[u].add + k) % P;
		}
		else {
			pushdown(u);
			LL mid = (tr[u].l + tr[u].r) >> 1;
			if (l <= mid) update(u << 1, l, r, k);
			if (r > mid) update(u << 1 | 1, l, r, k);
			pushup(u);
		}
	}
}segt[2];
void solve(){
	cin >> n;
	for (int i = 1; i <= n; i ++ )
		cin >> a[i];
	segt[0].build(1, 1, n);
	segt[1].build(1, 1, n);
	top1 = top2 = 0;
	dp[0] = 1;
	for (int i = 1; i <= n; i ++ ){
		segt[i & 1].update(1, i, dp[i - 1]);
		while(top1 && a[stk_max[top1]] < a[i]){
			segt[stk_max[top1] & 1].update(1, stk_max[top1 - 1] + 1, stk_max[top1], -1);
			top1 -- ;
		}
		stk_max[ ++ top1] = i;
		segt[i & 1].update(1, stk_max[top1 - 1] + 1, stk_max[top1], 1);
		while(top2 && a[stk_min[top2]] > a[i]){
			segt[(stk_min[top2] & 1) ^ 1].update(1, stk_min[top2 - 1] + 1, stk_min[top2], -1);
			top2 -- ;
		}
		stk_min[ ++ top2] = i;
		segt[(i & 1) ^ 1].update(1, stk_min[top2 - 1] + 1, stk_min[top2], 1);
		dp[i] = 0;
		if (segt[0].tr[1].mx == 2)
			dp[i] = (dp[i] + segt[0].tr[1].sum) % P;
		if (segt[1].tr[1].mx == 2)
			dp[i] = (dp[i] + segt[1].tr[1].sum) % P;
	}
	cout << dp[n] << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

D - Quel'Thalas

题意:

二维平面上有横纵坐标都是 [0,n] 的整数点,问至少用几条不经过 (0, 0) 的直线能经过所有点。

思路:

就是 2 * n

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
	LL n;
	cin >> n;
	cout << 2 * n << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

E - Ironforge

题意:

n 个点,第 i 个点的值为 ai,第 i 个点和第 i+1 个点之间有边相连,边的权值为一个质数 bi,初始可以选择一个点,可以获得这个点上值的所有质因数,当自己已有的质因数中包含边上的数时,可以走这条边,每走到一个点都可以或者这个点上的数的所有质因子,m 次询问,每次问能不能从第点 x 走到点 y

思路:

记第 i 个点向左最远能走到第 L[i],向右最远能走到 R[i]
对于每个点,先考虑它向右能走到多远的地方,通过二分判断,能不能走这条边。如果能走的区间中包含了这个边的质数,说明这条边能走,所以要预处理出每个质数对应的点有哪些。
接下来往左走,从小到大处理,如果左边的点能够走到现在这个点,那么现在这个点的 L[i] 其实就不用处理了,用之前即可。
如果走不到,那就暴力处理,先向左走,再向右走,直到该点能走的区间不发生变化的时候,结束处理。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e5 + 10;
LL n, m, a[N], b[N], L[N], R[N];
vector <LL> pos[N];
bool check(LL p, LL l, LL r){
	if (!pos[p].size() || pos[p].back() < l) return false;
	LL x = *lower_bound(pos[p].begin(), pos[p].end(), l);
	return x <= r;
}
void solve(){
	cin >> n >> m;
	for (int i = 1; i <= 200000; i ++ )
		pos[i].clear();
	for (int i = 1; i <= n; i ++ )
		cin >> a[i];
	for (int i = 1; i < n; i ++ )
		cin >> b[i];
	for (int i = 1; i <= n; i ++ ){
		LL x = a[i];
		for (int j = 2; j * j <= x; j ++ ){
			if (x % j == 0){
				while(x % j == 0){
					x /= j;
				}
				pos[j].push_back(i);
			}
		}
		if (x > 1) pos[x].push_back(i);
	}
	for (int i = n; i >= 1; i -- ){
		R[i] = i;
		while(R[i] < n && check(b[R[i]], i, R[i])){
			R[i] = R[R[i] + 1];
		}
	}
	for (int i = 1; i <= n; i ++ ){
		L[i] = i;
		if (i > 1 && R[i - 1] >= i){
			if (check(b[i - 1], i, R[i])){
				L[i] = L[i - 1];
				R[i] = R[i - 1];
			}
		}
		else{
			while(1){
				bool flag = false;
				while(L[i] > 1 && check(b[L[i] - 1], L[i], R[i])){
					flag = true;
					L[i] = L[L[i] - 1];
				}
				while(R[i] < n && check(b[R[i]], L[i], R[i])){
					flag = true;
					R[i] = R[R[i] + 1];
				}
				if (!flag) break;
			}
		}
	}
	while(m -- ){
		LL x, y;
		cin >> x >> y;
		if (L[x] <= y && y <= R[x]){
			cout << "Yes\n";
		}
		else{
			cout << "No\n";
		}
	}
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

G - Darnassus

题意:

n 个点,第 i 个点的值为 pi,序列 p 为一个排列,连接点 i 和点 j 的花费为 |ij||pipj|,问连接所有点的最小花费为多少。

思路:

容易想到最小生成树,将所有边排列后跑 Krustral 显然会超时,考虑一个特殊情况,连接第 i 和第 i+1 个点,那么每条边的花费都小于 n
|ij||pipj| < n,所以 |ij|||pipj| 中必有一个小于 n,所以枚举满足条件的边,花费 O(nn) 的时间,接着跑 Krustral
PS:数组开 N450 是因为 50000 为 224,可能插入两次(具体可以看代码),所以要 448 以上。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 5e4 + 10;
int n, fa[N], p[N], pos[N], head[N], tot;
struct Edge{  //要开 int,开 long long 会 MLE
	int u, v, nxt;
}e[N * 450];
void add(int u, int v, int w){  //将边 (u, v) 放到长度 w 中,即所有长度相同的边存在一起
	e[++ tot].u = u;
	e[tot].v = v;
	e[tot].nxt = head[w];
	head[w] = tot;
}
int get(int x){
	return (x == fa[x] ? x : (fa[x] = get(fa[x])));
}
void solve(){
	cin >> n;
	for (int i = 1; i <= n; i ++ ){
		cin >> p[i];
		fa[i] = i;
		pos[p[i]] = i;
		head[i] = 0;
	}
	int k = sqrt(n);
	tot = 0;
	for (int i = 1; i <= n; i ++ ){
		for (int j = i + 1; j <= min(i + k, n); j ++ ){
			LL w = (j - i) * abs(p[i] - p[j]);  //将 i 和 j 相连
			if (w < n){
				add(i, j, w);
			}
			w = abs(pos[i] - pos[j]) * (j - i);  //将 pos[i] 和 pos[j] 相连
			if (w < n){
				add(pos[i], pos[j], w);
			}
		}
	}
	LL ans = 0;
	int cnt = 0;
	for (int i = 1; i < n; i ++ ){  //长度由小到大枚举
		for (int j = head[i]; j; j = e[j].nxt){
			LL u = get(e[j].u), v = get(e[j].v);
			if (u != v){
				fa[u] = v;
				ans += i;
				cnt ++ ;
			}
		}
		if (cnt == n - 1) break;
	}
	cout << ans << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

H - Orgrimmar

题意:

给定一棵树,从中选择若干个点,使得每个点最多连一条边,问最多能选多少个点。

思路:

容易想到树形 dp
定义 dp[u][0/1/2] 表示不选 u,选择 u 作为起点,选择 u 作为终点情况下能选择的最多点的数量。
这里的起点指选择的这个点前面没有边相连,终点指前面有边相连。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
	LL n;
	cin >> n;
	vector < vector<LL> > e(n + 1);
	for (int i = 0; i < n - 1; i ++ ){
		LL u, v;
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	vector < array<LL, 3> > dp(n + 1);
	LL ans = 0;
	function<void(LL, LL)> dfs = [&](LL u, LL fa){
		dp[u][0] = 0;	//不选
		dp[u][1] = 1;	//起点
		dp[u][2] = 0;	//终点
		LL sum0 = 0, sum1 = 0;
		for (auto v : e[u]){
			if (v == fa) continue;
			dfs(v, u);
			sum0 += max(dp[v][0], max(dp[v][1], dp[v][2]));
			sum1 += dp[v][0];
		}
		dp[u][0] = max(dp[u][0], sum0);  //不选 u 的话,所有相连的节点不论什么状态都可以
		dp[u][1] = max(dp[u][1], sum1 + 1);  //选了 u 作为起点,那么所有相连的节点都不能选
		for (auto v : e[u]){  //找最大的方案
			if (v == fa) continue;
			dp[u][2] = max(dp[u][2], sum1 - dp[v][0] + dp[v][1] + 1);  //选 u 作为终点,那么要找一个起点
		}
		for (int i = 0; i < 3; i ++ )
			ans = max(ans, dp[u][i]);
	};
	dfs(1, 0);
	cout << ans << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	int size(512<<20);
	__asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	exit(0);
}

K - Stormwind

题意:

有一个 nm 的矩形,可以沿水平或者竖直方向画线,将矩形分成若干个小矩形,问每块小矩形的面积都 >= k 的情况下最多能划几条线。

思路:

枚举小矩形的长,可以得到宽,然后取画线最多的方案即可。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
	LL n, m, k;
	cin >> n >> m >> k;
	LL ans = 0;
	for (int x = 1; x <= k; x ++ ){
		LL y = (k + x - 1) / x;
		if (x > n || y > m) continue;
		ans = max(ans, n / x + m / y - 2);
	}
	cout << ans << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}
posted on   Hamine  阅读(79)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示