4- noip交流赛 DAY2

本文提供三道算法竞赛题目的详细解答方案,包括接水问题、施肥问题和车站问题。每道题目都给出了针对不同数据规模的优化算法,涵盖枚举、数据结构维护、树形动态规划等技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 接水

题解:

对于30%的数据,可以直接枚举时间,计算每一各单位时间内每个人剩余的接水时间,有离开的话让下一个人接着来接。

对于50%的数据,不难发现,问题可以转化为区间最值的问题,每次从m个人里面找一个用时最少的,让下一个在这里继续排队。

对于100%的数据,可以用数据结构维护区间最值,最终复杂度O(n log m)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, m, t, ans;
ll read(){
	char c;
	bool ok = 0;
	ll num = 0;
	while(c = getchar())
		if(c <= '9' && c >= '0')
			ok = 1, num = num*10 + c-'0';
		else if(ok) return num;
}
priority_queue <ll, vector<ll>, greater<ll> > q1;
int main(){
	freopen("water.in","r",stdin);
	freopen("water.out","w",stdout);
	while(!q1.empty()) q1.pop();
	scanf("%lld%lld", &n, &m);
	for(int i = 1; i <= m; i ++) t = read(), q1.push(t), ans = max(t, ans);
	for(int i = m+1; i <= n; i ++){
		scanf("%lld", &t);
		t += q1.top();
		q1.pop();
		q1.push(t);
		ans = max(t,ans);
	}
	printf("%lld", ans);
	return 0;
} 



2. 施肥

题解:

对于30%的数据,枚举每一个询问的子矩阵,每次找出区间里的最大高度,和现在子矩阵内的元素之和,找出所有子矩阵的情况取最小值。

对于50%的数据,使用二维前缀和优化求和部分,单调队列维护求最值,复杂度O(n m^2 q)。

对于100%的数据,使用二维单调队列优化取最值得部分O(nm)预处理所有小区间的最值,先用单调队列处理每一行的最值,在处理每一列的最值,最终复杂度O(nmq)。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 1e17;
ll a1[1010][1010], sum[1010][1010], mx1[1010][1010], mx2[1010][1010];
ll f[1010];
int n, m, q;
inline ll read(){
	char c;
	bool ok = 0;
	ll num = 0;
	while(c = getchar())
		if(c <= '9' && c >= '0')
			ok = 1, num = num*10+c-'0';
		else if(ok) return num;
}

int main(){
	freopen("fertile.in","r",stdin);
	freopen("fertile.out","w",stdout);
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= m; j ++)
			a1[i][j] = read();
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= m; j ++)
			sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a1[i][j];
	scanf("%d", &q);
	for(int i1 = 1; i1 <= q; i1 ++){
		int a, b;
		scanf("%d%d", &a, &b);
		for(int i = 1; i <= n; i ++){
			int head = 0, last = 1;
			for(int j = 1; j <= m; j ++){
				while(head >= last && a1[i][f[head]] < a1[i][j]) head --;
				f[++head] = j;
				while(f[head] - f[last] >= b) last ++;
				mx1[i][j] = a1[i][f[last]];
			}
		}
		for(int j = 1; j <= m; j ++){
			int head = 0, last = 1;
			for(int i = 1; i <= n; i ++){
				while(head >= last && mx1[f[head]][j] < mx1[i][j]) head --;
				f[++head] = i;
				while(f[head] - f[last] >= a) last ++;
				mx2[i][j] = mx1[f[last]][j];
			}
		}
		ll mn = inf;
		for(int i = a; i <= n; i ++){
			for(int j = b; j <= m; j ++){
				mn = min(mn, mx2[i][j]*a*b-(sum[i][j]-sum[i-a][j]-sum[i][j-b]+sum[i-a][j-b]));
			}
		}
		printf("%lld\n", mn);
	}
	
	return 0;
} 


3. 车站

题解:

对于30%的数据,每次两边bfs求数的直径,在直径上枚举中点的位置。

对于70%的数据,树形动态规划,求出以每个点为根的子树的直径,可以通过向下维护的最长链和次长链,求出直径,并且维护直径端点的位置。

对于100%的数据,从最长链的底部端点的位置,向上倍增,求中点的位置,最终复杂度O(n log n)。


#include <bits/stdc++.h>
using namespace std;
const int maxn = 600010;
const int MX = 20;
int _first[maxn], _next[maxn<<1], _dis[maxn<<1], _to[maxn<<1], _cnt;
int up[maxn][MX+5], end_to[maxn], mx[maxn], d[maxn], r[maxn];
int dis_root[maxn];
void add(int a, int b, int c){
	_next[++_cnt] = _first[a];
	_first[a] = _cnt;
	_to[_cnt] = b;
	_dis[_cnt] = c;
}
void dfs1(int now){
	for(int i = 1; i <= MX; i ++)
		up[now][i] = up[up[now][i-1]][i-1];
	for(int i = _first[now]; i; i = _next[i]){
		if(up[now][0] != _to[i]){
			up[_to[i]][0] = now;
			dis_root[_to[i]] = dis_root[now] + _dis[i];
			dfs1(_to[i]);
		}
	}
}
void dfs2(int now){
	int mx2 = 0;
	end_to[now] = now;
	for(int i = _first[now]; i; i = _next[i]){
		if(_to[i] == up[now][0]) continue;
		dfs2(_to[i]);
		if(d[_to[i]] > d[now]){
			d[now] = d[_to[i]];
			r[now] = r[_to[i]];
		}
		if(mx[_to[i]] + _dis[i] > mx[now]){
			mx2 = mx[now];
			mx[now] = mx[_to[i]] + _dis[i];
			end_to[now] = end_to[_to[i]];
		}else if(mx[_to[i]] + _dis[i] > mx2){
			mx2 = mx[_to[i]] + _dis[i];
		}
	}
	if(mx[now] + mx2 > d[now]){
		d[now] = mx[now] + mx2;
		int z = end_to[now];
		for(int i = MX; i >= 0; i --)
			if(up[z][i] != 0 && 
			d[now]-(dis_root[end_to[now]] - dis_root[up[z][i]]) > d[now]/2)
				z = up[z][i];
		r[now] = min(d[now] - dis_root[end_to[now]] + dis_root[z], 
		dis_root[end_to[now]] - dis_root[up[z][0]]);
	}
}
int main(){ 
	freopen("station.in","r",stdin);
	freopen("station.out","w",stdout);
	int n;
	scanf("%d", &n);
	for(int i = 1; i < n; i ++){
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c);
		add(b, a, c);
	}
	dfs1(1);
	dfs2(1);
	for(int i = 1; i <= n; i ++)
		printf("%d\n", r[i]);
	return 0;
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值