NOI2014 魔法森林 [LCT+贪心]

题目大意:给出一个无向图,每条边i有两个值ai、bi。要求出一条从1到n的路,使得路上经过的a的最大值(设为A)与b的最大值(设为B)的和尽量小。

思路:容易想到逐步往图中加入边的做法,但是这一题存在两个关键值,所以我们还需要利用一点点贪心的思想来权衡答案。

            我们考虑按a值从小到大加入每条边i,每一次求出A不超过ai的情况下,1的n的路径上B的最小值。

            那么我们只需要维护以b值为权值的一棵包含最优解的树就可以了:

            当前加入边i,如果没有形成环,那么直接连接xi,yi。如果形成环,找到环上b值最大的边,将它删去即可。

            这样,我们每一次询问1到n在树上的路径上的B值就可以了。

            这棵不断删边、连边的树可以用LCT来维护,大功告成。

  注意:1.LCT有一些需要注意的地方,比如updata不能少,比如rotate有一点点区别于splay等;

            2.这题有重边、自环!

            这些注意点都已在程序中用attention标出。

#include <cstdio>
#include <algorithm>
#include <cstring>
#define lf ch[x][0]
#define rg ch[x][1]
#define rep(i,j,k) for (i=j;i<=k;i++)
using namespace std;
const int N=2e5+5,M=1e5+5;
int n,m,i,u,v,fx,fy,wh,tmp,ans;
struct arr{
	int x,y,a,b;
}e[M];
bool cmp(arr A,arr B) {
	return A.a<B.a;
}
struct lct{
	int i,tp,cs,w[N],ms[N],ch[N][2],stk[N],fa[N],rev[N];
	inline bool isroot(int x) { return fa[x]==0 || ((ch[fa[x]][0]!=x)&&(ch[fa[x]][1]!=x));}
	inline bool side(int x) { return ch[fa[x]][1]==x; }
	inline void updata(int x) {
		ms[x]=x;
		if (lf && w[ms[lf]]>w[ms[x]]) ms[x]=ms[lf];
		if (rg && w[ms[rg]]>w[ms[x]]) ms[x]=ms[rg];
	}
	void push(int x) { if (!rev[x]) return;
		rev[x]=0; swap(ch[x][0],ch[x][1]);
		rev[lf]^=1; rev[rg]^=1;
	}
	void rotate(int x)
	{
		int f=fa[x],gf=fa[fa[x]],sd=side(x),fsd=side(f),son=ch[x][sd^1],flg=isroot(fa[x]);
		fa[f]=x; fa[x]=gf; if (!flg) ch[gf][fsd]=x; //attention >_<
		fa[son]=f; ch[f][sd]=son; ch[x][sd^1]=f;
		updata(f); updata(x);
	}
	void splay(int x)
	{
		int y=x;
		for(tp=0;!isroot(y);y=fa[y]) stk[++tp]=y;
		for (stk[++tp]=y;tp;tp--) push(stk[tp]); //attention ._.
		while (!isroot(x))
		{
			if (isroot(fa[x])) rotate(x);
			else {
				if (side(x)==side(fa[x])) rotate(fa[x]),rotate(x);
				else rotate(x),
				rotate(x);
			}
		}
	}
	void access(int x)
	{
		int y=0;
		while (1) {
			splay(x);
			ch[x][1]=y; updata(x); //attention ._.
			y=x; x=fa[x];
			if (!x) return ;
		}
	}
	void makeroot(int x) {
		access(x); splay(x); rev[x]^=1;
	}
	void link(int u,int v) {
		makeroot(u); fa[u]=v;
	}
	void cut(int u,int v) {
		makeroot(u); access(v); splay(v);
		ch[v][0]=0; fa[u]=0; updata(v); //attention ._.
	}
	int query(int u,int v) {
		makeroot(v); access(u); splay(v); return ms[v];
	}
	bool smt(int u,int v) {
		makeroot(u); makeroot(v);
		return !isroot(u);
	}
}tr;
void read(int &ret)
{
	char ch; ret=0;
	for (ch=getchar();ch<'0' || ch>'9';ch=getchar());
	for (;ch>='0' && ch<='9';ch=getchar()) ret=ret*10+ch-'0';
}
int main()
{
	read(n); read(m);
	rep(i,1,m) read(e[i].x),read(e[i].y),read(e[i].a),read(e[i].b);
	sort(e+1,e+1+m,cmp);
	rep(i,1,n+m) tr.ms[i]=i;
	rep(i,n+1,n+m) tr.w[i]=e[i-n].b;
	ans=1e9;
	rep(i,1,m)
	{
		tmp=e[i].a; u=e[i].x; v=e[i].y; if (u==v) continue; //attention-_-
		if (!tr.smt(u,v)) {
			tr.link(u,n+i); tr.link(n+i,v);
		}
		else {
			wh=tr.query(u,v);
			if (tr.w[wh]>e[i].b)
			{
				tr.cut(e[wh-n].x,wh); tr.cut(e[wh-n].y,wh);
				tr.link(u,n+i);       tr.link(n+i,v);
			}
		}
		if (tr.smt(1,n))
		{
			tmp+=tr.w[tr.query(1,n)];
			ans=min(ans,tmp);
		}
	}
	if (ans==1e9) ans=-1;
	printf("%d\n",ans);
	return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
对于计算机专业的学生而言,参加各类比赛能够带来多方面的益处,具体包括但不限于以下几点: 技能提升: 参与比赛促使学生深入学习和掌握计算机领域的专业知识与技能,如编程语言、算法设计、软件工程、网络安全等。 比赛通常涉及实际问题的解决,有助于将理论知识应用于实践中,增强问题解决能力。 实践经验: 大多数比赛都要求参赛者设计并实现解决方案,这提供了宝贵的动手操作机会,有助于积累项目经验。 实践经验对于计算机专业的学生尤为重要,因为雇主往往更青睐有实际项目背景的候选人。 团队合作: 许多比赛鼓励团队协作,这有助于培养学生的团队精神、沟通技巧和领导能力。 团队合作还能促进学生之间的知识共享和思维碰撞,有助于形成更全面的解决方案。 职业发展: 获奖经历可以显著增强简历的吸引力,为求职或继续深造提供有力支持。 某些比赛可能直接与企业合作,提供实习、工作机会或奖学金,为学生的职业生涯打开更多门路。 网络拓展: 比赛是结识同行业人才的好机会,可以帮助学生建立行业联系,这对于未来的职业发展非常重要。 奖金与荣誉: 许多比赛提供奖金或奖品,这不仅能给予学生经济上的奖励,还能增强其成就感和自信心。 荣誉证书或奖状可以证明学生的成就,对个人品牌建设有积极作用。 创新与研究: 参加比赛可以激发学生的创新思维,推动科研项目的开展,有时甚至能促成学术论文的发表。 个人成长: 在准备和参加比赛的过程中,学生将面临压力与挑战,这有助于培养良好的心理素质和抗压能力。 自我挑战和克服困难的经历对个人成长有着深远的影响。 综上所述,参加计算机领域的比赛对于学生来说是一个全面发展的平台,不仅可以提升专业技能,还能增强团队协作、沟通、解决问题的能力,并为未来的职业生涯奠定坚实的基础。
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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值