2022.4.3模拟赛

本文探讨了算法竞赛中的解题策略,包括0/1背包问题、区间最大平均值查询以及图的最小生成基环树森林的解决方法。通过预处理、动态规划和单调队列等技术,实现高效求解。同时,文章分析了竞赛中常见的思维误区及如何避免。
摘要由CSDN通过智能技术生成

r k 5 rk5 rk5 祭 /dy

T1 sum

  给一个正整数 S ≤ 1000 S \leq 1000 S1000,要求选出若干个互不相同的正整数,是他们的和不大于 S S S,而且每个数的因数(不包含自身)之和最大。

  显然(虽然考试的时候想了很久 qwq)是一个 0 / 1 0/1 0/1 背包。物品就是要选出的正整数,容量是 S S S,然后每个的物品的因数之和。显然因数之和可以暴力 O ( S S ) O(S\sqrt{S}) O(SS ) 预处理出来。所以总的复杂度就是 O ( S 2 + S S ) O(S^2 + S\sqrt{S}) O(S2+SS )

#include<bits/stdc++.h>
using namespace std;
#define MAXN 5050
#define N 1000

int d[MAXN] = { 0 };
int p[MAXN] = { 0 };
int s = 0;
int f[MAXN] = { 0 };

void prework(){
	for(int i = 1; i <= N; i++){
		memset(d, 0, sizeof(d)); int cnt = 0;
		for(int j = 1; j * j <= i; j++)
			if(i % j == 0){
				d[++cnt] = j;
				if(j != i / j) d[++cnt] = i / j;
			}
		sort(d + 1, d + cnt + 1);
		int temp = 0;
		for(int j = 1; j <= cnt; j++) temp += d[j];
		p[i] = temp - i;
	}
}

void dp(){
	for(int i = 1; i <= s; i++)
		for(int j = s; j >= i; j--)
			f[j] = max(f[j], f[j - i] + p[i]);
}

int main(){
	scanf("%d", &s);
	prework(); dp();
	cout << f[s] << '\n';
	return 0;
}

T2 sequence

  给定一个长度为 n n n 的数组 a a a。给定 L , R L, R L,R,求所有满足长度大于等于 L L L 小于等于 R R R a a a 的子区间的平均值的最大。

  显然可以前缀和,然后 n 2 n^2 n2 就能过 60 p t s 60 pts 60pts 然后因为数据太水了,所以又多 A A A 了一个点。就骗了 70 p t s 70 pts 70pts(机房大佬 c m y cmy cmy O ( n 2 ) O(n^2) O(n2) 卡常然后 A A A 掉了 %%%%%)。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 100100

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x ;
}

int n = 0;
int l = 0; int r = 0;
int a[MAXN] = { 0 };
int s[MAXN] = { 0 };

signed main(){
	n = in; l = in; r = in;
	for(int i = 1; i <= n; i++) a[i] = in;
	for(int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
	double ans = 0;
	for(int len = l; len <= r; len++){
		for(int R = n; R - len + 1 >= 1; R--){
			int L = R - len + 1;
			ans = max(ans, double(s[R] - s[L - 1]) / double(R - L + 1));
		}
	}
	printf("%.4lf\n", ans);
	return 0;
}

  然后是正解。先二分答案,也就是二分平均值,然后每个数减去这个平均值,这个时候所有和大于 0 0 0 的区间的平均值就肯定大于二分的值。

  统计减去平均值的后序列的前缀和,问题就变成了前缀和在 [ i − R , i − L ] [i - R, i - L] [iR,iL] 区间内是否存在一个数 s j s_j sj 使得 s i > s j s_i > s_j si>sj,然后就是单调队列优化就能做到 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define INFI 1e6
#define MAXN 100100

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x ;
}

int n = 0;
int L = 0; int R = 0;
int a[MAXN] = { 0 };

bool check(double avr){
	static double s[MAXN]; static double q[MAXN];
	static int head; static int tail; static int pos[MAXN];
	head = 1; tail = 0;
	for(int i = 1; i <= n; i++) s[i] = 1.0 * a[i] - avr + s[i - 1];         // 减后的前缀和 
	for(int i = L; i <= n; i++){                                            // 单调队列 
		while(head <= tail and q[tail] >= s[i - L]) tail--;
		while(head <= tail and pos[head] + R < i) head++;
		q[++tail] = s[i - L]; pos[tail] = i - L;
		if(s[i] >= q[head]) return true;
	}
	return false;
}

signed main(){
	double mid = 0;
	n = in; L = in; R = in;
	for(int i = 1; i <= n; i++) a[i] = in;
	double l = 0; double r = INFI;
	for(int t = 1; t <= 100; t++){                                         // 二分答案 (平均值) 
		mid = (l + r) / 2;
		if(check(mid)) l = mid;
		else r = mid;
	}
	printf("%.4lf\n", mid);
	return 0;
}

T3

  就是给定一个无向图,然后让你保证每个边都有且仅有 1 1 1 的入度。也就是转化成最小生成基环树森林。考场上脑抽了,只打了一个最小生成基环树(就是最小生成树然后再加一条没加进去的最小边),然后就有 50 p t s 50 pts 50pts

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 100100
#define MAXM MAXN * 8

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x ;
}

int ans = 0;
int n = 0; int m = 0;
struct Tedge{
	int x, y;
	int val; bool used;
	bool operator < (const Tedge &rhs) const {
		return val < rhs.val;
	}
}edge[MAXM];

int fa[MAXN] = { 0 };
void init() { for(int i = 1; i <= n; i++) fa[i] = i; }
int find(int x){
	if(x == fa[x]) return x;
	return fa[x] = find(fa[x]);
}

void kruskal(){
	for(int i = 1; i <= m; i++){
		Tedge e = edge[i];
		int fax = find(e.x); int fay = find(e.y);
		if(fax == fay) continue;
		fa[fax] = fay; ans += e.val; edge[i].used = 1; 
	}
}

signed main(){
	n = in; m = in;
	if(m < n) { cout << "No\n"; return 0; }
	for(int i = 1; i <= m; i++){
		edge[i].x = in, edge[i].y = in, edge[i].val = in;
	}
	sort(edge + 1, edge + m + 1);
	init(); kruskal();
	for(int i = 1; i <= n; i++)
		if(!edge[i].used) { ans += edge[i].val; break; }
	cout << ans << '\n';
	return 0;
}

  我们考虑怎样加边来维护基环树森林。判断一条边能否加入的时候,如果这两个端点已经连通了,我们就需要知道这个连通块是否有环,如果没有环就连在一起,并标记他现在有环。如果不连通,那么还是判断两个有没有环。如果都有环就不加边,否则就加边并且标记两边是否有环。

  就用并查集维护连通性,额外维护联通快中是否有环就可以了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 500500
#define MAXM MAXN * 8

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x ;
}

int n = 0; int m = 0;
int cnt = 0; int ans = 0;
struct Tedge{
	int x, y;
	int val;
	bool operator < (const Tedge &rhs) const {
		return val < rhs.val;
	}
}edge[MAXM];

int fa[MAXN] = { 0 };
void init() { for(int i = 0; i <= n; i++) fa[i] = i; }
int find(int x){
	if(x == fa[x]) return x;
	return fa[x] = find(fa[x]);
}

bool isok[MAXN] = { 0 };
void kruskal(){
	for(int i = 1; i <= m; i++){
		int fax = find(edge[i].x); int fay = find(edge[i].y);
		if(fax == fay){
			if(!isok[fax]) { isok[fax] = 1; ans += edge[i].val; cnt++; }
		} else{
			if(isok[fax] and isok[fay]) continue;
			fa[fax] = fay; isok[fay] = isok[fay] | isok[fax];
			ans += edge[i].val; cnt++;
		}
	}
}

signed main(){
	n = in; m = in;
	for(int i = 1; i <= m; i++)
		edge[i].x = in, edge[i].y = in, edge[i].val = in;
	sort(edge + 1, edge + m + 1);
	init(); kruskal();
	if(n != cnt) puts("No");
	else cout << ans << '\n';
	return 0;
}

T4

  一棵树,树上的边是一个一个的字符串。然后每次询问有三个值:点 x , y x, y x,y 和字符串 s s s,求 x x x y y y 的路径上有多少个字符串是以 s s s 为前缀的。

  看到这个题立马想到 T r i e Trie Trie 树。然后再仔细想一想,要维护一棵可持久化 T r i e Trie Trie,从父节点继承信息。假设询问的串为 s s s,这样可以 O ( ∣ s ∣ ) O(|s|) O(s) 求一个串是一个点到根节点的路径上的多少串的前缀,记为 a n s ( x ) ans(x) ans(x)

  答案就是 a n s ( x ) + a n s ( y ) − 2 a n s ( l c a ( x , y ) ) ans(x) + ans(y) - 2ans(lca(x, y)) ans(x)+ans(y)2ans(lca(x,y))。时间复杂度就是 O ( n ∣ s ∣ ) O(n|s|) O(ns)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值