2021.07.06【NOIP提高B组】模拟 总结

本文总结了2021年NOIP提高B组模拟赛的四道算法题解。第一题利用并查集解决区间问题;第二题采用动态规划结合单调队列或线段树优化;第三题通过最小生成树找到最优解;第四题通过贪心策略求解。文章详细介绍了每道题的思路和代码实现。

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

2021.07.06【NOIP提高B组】模拟 总结

第一题:发现答案是一段区间,每次暴力加边,用并查集判断一下。

第二题:显然是动态规划,然后单调队列或线段树优化。

第三题:考虑把组合起来的数算出来,如果这个数为000,计算一下最小代价ccc,就是一个最小生成树。接着我们设fif_ifi表示选了状态为iii的答案。转移就是fi∣j=fi+cjf_{i|j}=f_{i}+c_jfij=fi+cj

第四题:考虑把车站算成一个点n+1n+1n+1,然后能走的边一定是在这个图的最大生成树上。贪心选择做这个题,不要管买不能丢钱的限制,每次选完所有的钱,然后贪心做一下,看一看树上的两点上最小的权值是多少,去一下min⁡\minmin

#include<bits/stdc++.h>
using namespace std;
long long n,m,q,ord[100005],lim[100005];
struct edge{
	long long x,y,z;
}e[200005];
long long fa[100005],tra[100005];
bool cmp(edge e1,edge e2){
	return e1.z>e2.z;
}
const long long inf=1000000000000000000;
long long cnt=0,la[100005],to[400005],we[400005],ne[400005],f[100005][17],g[100005][17],dep[100005];
void add(long long x,long long y,long long z){
	++cnt;
	to[cnt]=y;
	we[cnt]=z;
	ne[cnt]=la[x];
	la[x]=cnt;
}
long long find(long long x){
	if (x==fa[x]) return x;
	else return fa[x]=find(fa[x]);
}
void dfs(long long x,long long y,long long w){
	f[x][0]=y;
	g[x][0]=w;
	for (long long i=1;i<=16;i++){
		f[x][i]=f[f[x][i-1]][i-1];
		g[x][i]=min(g[x][i-1],g[f[x][i-1]][i-1]);
	}
	dep[x]=dep[y]+1;
	for (long long i=la[x];i;i=ne[i]){
		long long z=to[i];
		if (z!=y)
			dfs(z,x,we[i]);
	}
}
void query(long long &ans1,long long &ans2,long long x,long long y){
	if (x==y){
		ans1=inf;
		ans2=0;
		return;
	}
	if (dep[x]<dep[y]) swap(x,y);
	ans1=ans2=inf;
	long long k=dep[x]-dep[y],s=0;
	while (k){
		if (k&1) ans1=min(ans1,g[x][s]),x=f[x][s];
		s++;
		k>>=1;
	}
	if (x==y){
		ans2=x;
		return;
	}
	for (long long i=16;i>=0;i--){
		if (f[x][i]!=f[y][i]){
			ans1=min(ans1,min(g[x][i],g[y][i]));
			x=f[x][i];
			y=f[y][i];
		}
	}
	ans1=min(ans1,min(g[x][0],g[y][0]));
	ans2=f[x][0];
	return;
}
int main(){
	freopen("motorcycle.in","r",stdin);
	freopen("motorcycle.out","w",stdout);
	scanf("%lld%lld%lld",&n,&m,&q);
	for (long long i=1;i<=n;i++) scanf("%lld",&ord[i]);
	for (long long i=1;i<=n;i++) scanf("%lld",&lim[i]);
	for (long long i=1;i<=m;i++) scanf("%lld%lld%lld",&e[i].x,&e[i].y,&e[i].z);
	while (q--){
		long long x;
		scanf("%lld",&x);
		tra[x]=1;
	}
	for (long long i=1;i<=m;i++){
		if (tra[e[i].x]) e[i].x=n+1;
		if (tra[e[i].y]) e[i].y=n+1;
		if (e[i].x==e[i].y) e[i].z=0;
	}
	sort(e+1,e+m+1,cmp);
	int tot=0;
	for (long long i=1;i<=n+1;i++) fa[i]=i;
	for (long long i=1;i<=m;i++){
		long long x=find(e[i].x);
		long long y=find(e[i].y);
		if (x==y) continue;
		else{
			add(e[i].x,e[i].y,e[i].z);
			add(e[i].y,e[i].x,e[i].z);
			fa[x]=y;
		}
	}
	long long root=0;
	for (long long i=1;i<=n+1;i++)
		if (la[i])
			root=i;
	dfs(root,0,inf);
	long long now=ord[1],nsum=lim[ord[1]];
	if (nsum<=0){
		printf("%lld\n",0);
		nsum=max(nsum,(long long)0);
	}
	for (long long i=2;i<=n;i++){
		long long x=(tra[ord[i-1]]?(n+1):ord[i-1]);
		long long y=(tra[ord[i]]?(n+1):ord[i]);
		long long lca,dis;
		query(dis,lca,x,y);
		nsum=min(nsum,dis);
		if (lim[ord[i]]<0){
			printf("%lld\n",min(nsum,-lim[ord[i]]));
		}
		nsum+=lim[ord[i]];
		nsum=max(nsum,(long long)0);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值