【NOI2014】魔法森林

本文介绍了一种解决双关键字最小生成树问题的算法。通过将边按第一个关键字排序并加入图中,利用LCT(Link-Cut Tree)数据结构进行路径最大值的维护和更新,实现了对边的选择优化。在遇到同一连通块内的顶点时,比较路径上的最大第二个关键字,若当前边的第二个关键字更优,则替换原有边,确保生成树的最优性。

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

双关键字最小生成树,我们的大体思路就是,先将a从小到大排序,然后加边,如果两个端点不在同一连通块,直接连,因为lct只能维护点权,所以我们将每条边拆成点,在这道题里,我们边的编号不变,点的编号都加上m。如果在同一个连通块,那么看一下这两个点路径上最大的b值,如果路径上最大的b要大于我们当前这条边的b值就用当前边替换一下,每次判断1,n是否连通,如果能则更新答案。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define lc c[x][0]
#define rc c[x][1]
using namespace std;
const int N=5e4+5;
const int M=1e5+5;
int c[M*5][2],f[M*5],mx[M*5],r[M*5];
int n,m,fa[5*M],u,v,f1,f2,ans=100001,x,y,z;
struct node{
	int u,v,a,b;
};
node e[M*5];//数组起码要开大点,不然会锅
void R(int &x){
	int t=0; char ch;
	for (ch=getchar();!('0'<=ch&&ch<='9');ch=getchar());
	for (;('0'<=ch&&ch<='9');ch=getchar()) t=t*10+ch-'0';
	x=t;
}
bool cmb(node a,node b){
	return a.a<b.a;
}
int find(int x){
	if (x==fa[x]) return x;
	fa[x]=find(fa[x]);
	return fa[x];
}
bool wh(int x){
	return x==c[f[x]][1];
}
bool nroot(int x){
	return x==c[f[x]][0]||x==c[f[x]][1];
}
void pushr(int x){
	swap(lc,rc); r[x]^=1;
}
void pushdown(int x){
	if (r[x]){
		if (lc) pushr(lc);
		if (rc) pushr(rc);
		r[x]=0;
	}
}
void update(int x){
	mx[x]=x;
	if ( e[mx[x]].b < e[mx[lc]].b) mx[x]=mx[lc];
	if ( e[mx[x]].b < e[mx[rc]].b) mx[x]=mx[rc];
}
void rotate(int x){
	int y=f[x],z=f[y],k=wh(x),w=c[x][1^k];
	if (nroot(y)) c[z][wh(y)]=x; f[x]=z;
	c[y][k]=w; if (w) f[w]=y;
	c[x][1^k]=y; f[y]=x;
	update(y); update(x);
}
void pd(int x){
	if (nroot(x)) pd(f[x]);
	pushdown(x);
}
void splay(int x){
	pd(x);
	for (;nroot(x);rotate(x))
		if (nroot(f[x])) 
			rotate(wh(f[x])==wh(x)?f[x]:x);
}
void access(int x){
	for (int y=0;x;x=f[y=x]) 
		splay(x),rc=y,update(x);
}
void make(int x){
	access(x); splay(x); pushr(x);
}
void split(int x,int y){
	make(x); access(y); splay(y);
}
void link(int x,int y){
	make(x); f[x]=y;
}
void cut(int x,int y){ // 如果保证原来有边,这样打常数更小,link也一样
	split(x,y); f[x]=c[y][0]=0;
	update(y);
} 
int main(){
	//freopen("jzoj3754.in","r",stdin);
	//freopen("jzoj3754.out","w",stdout);
	R(n);R(m);
	fo(i,1,m){
		R(e[i].u); R(e[i].v); R(e[i].a); R(e[i].b);
		e[i].u+=m; e[i].v+=m;
	}
	sort(e+1,e+m+1,cmb);
//	fo(i,1,m) printf("%d %d %d %d\n",e[i].u,e[i].v,e[i].a,e[i].b);	
//	return 0;
	fo(i,1,n) fa[i+m]=i+m;
	fo(i,1,m){
		x=e[i].u; y=e[i].v;
		if (x==y) continue;
		f1=find(x); f2=find(y);
		if (f1!=f2){
			fa[f1]=f2;
			link(x,i); link(i,y);
		}
		else{
			split(x,y); z=mx[y]; // 第一次cut之后mx[y]可能会变,所以要记录下来,否则第二次cut就会出锅
			if (e[z].b>e[i].b) {
				cut(e[z].u,z); 
				cut(e[z].v,z);
				link(x,i); link(i,y);
			}
		}
		if (find(1+m)==find(n+m)){
			split(1+m,n+m);
			ans=min(ans,e[i].a+e[mx[n+m]].b);
		}
	}
	if (ans==100001) printf("%d",-1);
	else printf("%d",ans);
	return 0;
}

P2375 [NOI2014] 动物园是一道经典的动态规划题目,以下是该题的详细题意和解题思路。 【题意描述】 有两个长度为 $n$ 的整数序列 $a$ 和 $b$,你需要从这两个序列中各选出一些数,使得这些数构成一个新的序列 $c$。其中,$c$ 序列中的元素必须在原序列中严格递增。每个元素都有一个价值,你的任务是选出的元素的总价值最大。 【解题思路】 这是一道经典的动态规划题目,可以采用记忆化搜索的方法解决,也可以采用递推的方法解决。 记忆化搜索的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int dfs(int x, int y) { if (dp[x][y] != -1) return dp[x][y]; if (x == n || y == n) return 0; int res = max(dfs(x + 1, y), dfs(x + 1, y + 1)); if (a[x] > b[y]) { res = max(res, dfs(x, y + 1) + b[y]); } return dp[x][y] = res; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); memset(dp, -1, sizeof(dp)); printf("%d\n", dfs(0, 0)); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值,-1表示未计算过。dfs(x,y)表示选到a数组中第x个元素和b数组中第y个元素时的最大价值,如果dp[x][y]已经计算过,则直接返回dp[x][y]的值。如果x==n或者y==n,表示已经遍历完一个数组,直接返回0。然后就是状态转移方程了,如果a[x] > b[y],则可以尝试选b[y],递归调用dfs(x, y+1)计算以后的最大价值。否则,只能继续遍历数组a,递归调用dfs(x+1, y)计算最大价值。最后,返回dp[0][0]的值即可。 递推的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); for (int i = n - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]); if (a[i] > b[j]) { dp[i][j] = max(dp[i][j], dp[i][j + 1] + b[j]); } } } printf("%d\n", dp[0][0]); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值。从后往前遍历数组a和数组b,依次计算dp[i][j]的值。状态转移方程和记忆化搜索的方法是一样的。 【参考链接】 P2375 [NOI2014] 动物园:https://www.luogu.com.cn/problem/P2375
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值