• 【uoj3】 NOI2014—魔法森林


    http://uoj.ac/problem/3 (题目链接)

    题意

      给出一张带权图,每条边有两个权值A和B,一条路径的花费为路径中的最大的A和最大的B之和。求从1走到n的最小花费。

    Solution

      枚举A,SPFA松弛。

      不得不说UOJ的hack还是很强力的,仔细想了想,数据好像也并不是特别难构。只要使每次加边都必须遍历整张图,之前n都未连通,最后一条A最大的边连向n即可。

    代码

    // uoj3
    #include<algorithm>
    #include<iostream>
    #include<cstdlib>
    #include<cstring>
    #include<cstdio>
    #include<vector>
    #include<cmath>
    #include<queue>
    #include<ctime>
    #define LL long long
    #define inf (1ll<<30)
    #define Pi acos(-1.0)
    #define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
    using namespace std;
    
    const int maxn=50010,maxm=100010;
    int head[maxn],dis[maxn],fa[maxn],vis[maxn],n,m,cnt;
    struct data {int u,v,wa,wb;}a[maxm];
    struct edge {int to,next,wa,wb;}e[maxm<<1];
    
    void link(int u,int v,int wa,int wb) {
    	e[++cnt]=(edge){v,head[u],wa,wb};head[u]=cnt;
    	e[++cnt]=(edge){u,head[v],wa,wb};head[v]=cnt;
    }
    int find(int x) {
    	return x==fa[x] ? x : fa[x]=find(fa[x]);
    }
    bool cmp(data a,data b) {
    	return a.wa<b.wa;
    }
    int main() {
    	scanf("%d%d",&n,&m);
    	for (int i=1;i<=n;i++) fa[i]=i;
    	for (int i=1;i<=m;i++) {
    		scanf("%d%d%d%d",&a[i].u,&a[i].v,&a[i].wa,&a[i].wb);
    		link(a[i].u,a[i].v,a[i].wa,a[i].wb);
    		if (find(a[i].u)!=find(a[i].v)) fa[find(a[i].u)]=find(a[i].v);
    	}
    	if (find(1)!=find(n)) {puts("-1");return 0;}
    	sort(a+1,a+1+m,cmp);
    	for (int i=1;i<=n;i++) dis[i]=inf;dis[1]=0;
    	int ans=inf;vis[n]=1;
    	for (int i=1;i<=m;i++) {
    		queue<int> q;
    		if (!vis[a[i].u]) q.push(a[i].u),vis[a[i].u]=1;
    		if (!vis[a[i].v]) q.push(a[i].v),vis[a[i].v]=1;
    		while (i<m && a[i+1].wa==a[i].wa) {
    			i++;
    			if (!vis[a[i].u]) q.push(a[i].u),vis[a[i].u]=1;
    			if (!vis[a[i].v]) q.push(a[i].v),vis[a[i].v]=1;
    		}
    		while (!q.empty()) {
    			int x=q.front();q.pop();
    			vis[x]=0;
    			for (int j=head[x];j;j=e[j].next)
    				if (e[j].wa<=a[i].wa && dis[e[j].to]>max(dis[x],e[j].wb)) {
    					dis[e[j].to]=max(dis[x],e[j].wb);
    					if (!vis[e[j].to]) vis[e[j].to]=1,q.push(e[j].to);
    				}
    		}
    		ans=min(ans,a[i].wa+dis[n]);
    		if ((double)clock()/CLOCKS_PER_SEC>=2.8) break;
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    Solution

      link cut tree维护最小生成树。把边权转为点权表示。每次加边时,如果${u,v}$在一个连通块内,查询连通块中点权最大值,如果大于当前边的边权,就把这个点删掉。每次加完一条边后,如果起点和终点在同一连通块中,就更新下答案。

    代码

    // uoj2
    #include<algorithm>
    #include<iostream>
    #include<cstdlib>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define LL long long
    #define inf (1ll<<30)
    #define Pi acos(-1.0)
    #define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
    using namespace std;
    
    const int maxn=200010;
    int a[maxn],tr[maxn][2],rev[maxn],fa[maxn],mx[maxn];
    int n,m;
    
    struct edge {
    	int u,v,wa,wb;
    	friend bool operator < (const edge a,const edge b) {
    		return a.wa<b.wa;
    	}
    }e[maxn];
    
    void pushup(int x) {
    	if (a[mx[tr[x][0]]]>a[mx[tr[x][1]]]) mx[x]=mx[tr[x][0]];
    	else mx[x]=mx[tr[x][1]];
    	if (a[mx[x]]<a[x]) mx[x]=x;
    }
    void pushdown(int x) {
    	if (tr[fa[x]][0]==x || tr[fa[x]][1]==x) pushdown(fa[x]);
    	if (rev[x]) {
    		swap(tr[x][0],tr[x][1]);
    		rev[tr[x][0]]^=1;rev[tr[x][1]]^=1;rev[x]^=1;
    	}
    }
    void rotate(int x) {
    	int y=fa[x],z=fa[y],l,r;
    	l=tr[y][1]==x;r=l^1;
    	if (tr[z][0]==y || tr[z][1]==y) tr[z][tr[z][1]==y]=x;
    	fa[y]=x;fa[x]=z;fa[tr[x][r]]=y;
    	tr[y][l]=tr[x][r];tr[x][r]=y;
    	pushup(y);pushup(x);
    }
    void splay(int x) {
    	pushdown(x);
    	while (tr[fa[x]][0]==x || tr[fa[x]][1]==x) {
    		int y=fa[x],z=fa[y];
    		if (tr[z][0]==y || tr[z][1]==y) {
    			if (tr[z][0]==y ^ tr[y][0]==x) rotate(x);
    			else rotate(y);
    		}
    		rotate(x);
    	}
    }
    void access(int x) {
    	for (int y=0;x;y=x,x=fa[x])
    		splay(x),tr[x][1]=y,pushup(x);
    }
    void makeroot(int x) {
    	access(x);splay(x);rev[x]^=1;
    }
    void link(int x,int y) {
    	makeroot(x);fa[x]=y;
    }
    void cut(int x,int y) {
    	makeroot(x);access(y);splay(y);
    	tr[y][0]=fa[x]=0;pushup(y);
    }
    int find(int x) {
    	while (fa[x]) x=fa[x];
    	return x;
    }
    int query(int x,int y) {
    	makeroot(x);access(y);splay(y);
    	return mx[y];
    }
    int main() {
    	scanf("%d%d",&n,&m);
    	for (int i=1;i<=m;i++) scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].wa,&e[i].wb);
    	sort(e+1,e+1+m);
    	int ans=inf;
    	for (int i=1;i<=n;i++) a[i]=0;
    	for (int i=1;i<=m;i++) {
    		a[i+n]=e[i].wb;
    		if (find(e[i].u)!=find(e[i].v)) link(e[i].u,i+n),link(e[i].v,i+n);
    		else {
    			int tmp=query(e[i].u,e[i].v);
    			if (a[tmp]>a[i+n]) {
    				cut(e[tmp-n].u,tmp),cut(e[tmp-n].v,tmp);
    				link(e[i].u,i+n),link(e[i].v,i+n);
    			}
    		}
    		if (find(1)==find(n)) ans=min(ans,e[i].wa+a[query(1,n)]);
    	}
    	printf("%d",ans==inf ? -1 : ans);
    	return 0;
    }
    

      

  • 相关阅读:
    Java 多个线程之间共享数据
    Mysql索引为什么要采用B+Tree而非B-Tree
    MyBatis常见面试题:通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
    CentOS 8.1 基于二进制安装docker
    shell实现一键证书申请和颁发脚本
    配置DNS的主从以及实现域名反向解析
    利用Dockerfile实现nginx的部署
    编译安装Mariadb-10.5.5
    登录mysql出错:mysql: error while loading shared libraries: libtinfo.so.5: cannot open share
    一键安装MySQL5.7脚本
  • 原文地址:https://www.cnblogs.com/MashiroSky/p/6378250.html
Copyright © 2020-2023  润新知