[NOI2014]魔法森林

魔法森林

题解

看到这道题应该是很容易想到贪心,我们可以先将所有边根据b值排序,再当前基础上找到所需a值最小的一条路径。

但是由于边会产生改变,于是每次都要跑一遍最短路,明显是会T飞的。

很容易发现,当前加入的一条边只对部分的路径产生影响,而这些路径都是在已有路径的最小生成树上构成一个环的。由于当前到每个点的最优路径都会对以后的答案产生影响,所以我们要实时维护当前最小生成树的形态。

每次加入边时如果这个环上最劣的边比加入的这条边a值更大,就将其删去,再加入当前边。于是我们就需要用LCT来维护这棵最小生成树。

但是于此同时我们如何维护兩(两)点之间连得边呢?我们可以将这些边建成虚点,每次连接就将两个端点连到虚点上,这样就不需要每次更改后都很麻烦的维护点权了,因为每次修改后点的父子关系都是会变的。

这样每次加入边后都维护出当前最优值,答案就是它们中最小的。

时间复杂度O\left( (m+n)log_{n+m}\right ),可以过的。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 200005
typedef long long LL;
typedef pair<int,int> pii;
const int INF=0x7f7f7f7f;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int val[MAXN],fa[MAXN],n,m,ans;
struct edge{int u,v,a,b;}e[MAXN];
bool cmp(edge x,edge y){return x.b<y.b;}
void makeSet(int x){for(int i=1;i<=x;i++)fa[i]=i;}
int findSet(int x){return fa[x]==x?x:fa[x]=findSet(fa[x]);}
void unionSet(int a,int b){
	int u=findSet(a),v=findSet(b);
	if(u==v)return ;fa[u]=v;
}
class Link_Cut_Tree{
	private:
		int rev[MAXN],lzy[MAXN],maxp[MAXN];
		int father[MAXN],ch[MAXN][2],sta[MAXN],stak,siz[MAXN];
		void updata(int p){
			siz[p]=1+siz[ch[p][0]]+siz[ch[p][1]];maxp[p]=p;
			if(ch[p][0]&&val[maxp[ch[p][0]]]>val[maxp[p]])
				maxp[p]=maxp[ch[p][0]];
			if(ch[p][1]&&val[maxp[ch[p][1]]]>val[maxp[p]])
				maxp[p]=maxp[ch[p][1]];
		}
		void downdata(int p){
			if(rev[p]){
				swap(ch[p][0],ch[p][1]);rev[p]^=1;
				rev[ch[p][0]]^=1;rev[ch[p][1]]^=1;
			}	
		}
		bool identify(int x){return ch[father[x]][1]==x;}
		bool isRoot(int x){return ch[father[x]][0]^x&&ch[father[x]][1]^x;}
		void connect(int x,int fa,int d){father[x]=fa;ch[fa][d]=x;}
		void rotate(int x){
			int y=father[x],z=father[y];
			int d1=identify(y),d2=identify(x);
			int B=ch[x][d2^1];father[x]=z;
			if(!isRoot(y))connect(x,z,d1);
			connect(B,y,d2);connect(y,x,d2^1);
			updata(y);updata(x);
		}
		void splay(int x){
			stak=0;sta[++stak]=x;int y=x;
			while(!isRoot(y))sta[++stak]=y=father[y];
			while(stak)downdata(sta[stak--]);
			for(y=father[x];!isRoot(x);rotate(x),y=father[x])
				if(!isRoot(y))rotate(identify(x)==identify(y)?y:x);
			updata(x);
		}
		void access(int x){
			for(int y=0;x;x=father[y=x])
				splay(x),ch[x][1]=y,updata(x);
		}
		void makeRoot(int x){access(x);splay(x);rev[x]^=1;}
		int findRoot(int x){
			access(x);splay(x);downdata(x);
			while(ch[x][0])downdata(x=ch[x][0]);
			return x;
		}
	public:
		int split(int x,int y){makeRoot(x);access(y);splay(y);return maxp[y];}
		void linkTree(int u,int v){makeRoot(u);if(findRoot(v)!=u)father[u]=v;}
		void cutTree(int u,int v){
			makeRoot(u);if(findRoot(v)^u)return ;
			access(v);splay(v);ch[v][0]=father[u]=0;updata(v);
		}
}Tree;
signed main(){
	read(n);read(m);ans=INF;
	for(int i=1;i<=m;i++)read(e[i].u),read(e[i].v),read(e[i].a),read(e[i].b);
	sort(e+1,e+m+1,cmp);makeSet(n+m);for(int i=n+1;i<=n+m;i++)val[i]=e[i-n].a;
	for(int i=1;i<=m;i++){
		int u=e[i].u,v=e[i].v;bool fg=1;
		if(findSet(u)==findSet(v)){
			int w=Tree.split(u,v);
			if(val[w]>e[i].a)
				Tree.cutTree(e[w-n].u,w),Tree.cutTree(w,e[w-n].v);
			else fg=0;
		}
		else unionSet(u,v);if(fg)Tree.linkTree(u,i+n),Tree.linkTree(i+n,v);
		if(findSet(1)==findSet(n))ans=min(ans,e[i].b+val[Tree.split(1,n)]);
	}
	if(ans<INF-1)printf("%d\n",ans);else puts("-1");
	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、付费专栏及课程。

余额充值