zoukankan      html  css  js  c++  java
  • BZOJ 3669 & luogu 2387 魔法森林

    传送门

    前置知识:dijkstra(kruskal,LinkCutTree)dijkstra或(kruskal,Link-Cut-Tree)

    思路:

    因为有两个条件,所以我们可以类似dp那么先固定一个,再跑spfa求瓶颈路。
    同时很明显可以二分优化。(当然,这还不够优秀)
    时间复杂度大致为O(log2mmn)(mn)O(log_2m*m*n)(m*n都会炸了)
    下面提供两种正解:

    1. 动态加点dijkstradijkstra
    2. kruskalkruskal求瓶颈生成树

    1.dijkstradijkstra

    我们以b为第一关键字排序边,然后维护最小生成树。
    对于a呢,因为多加一条边,所以只有边的两个端点可以再贡献1~n的瓶颈路。
    每次加完边后,答案就为刚加边的b值+1~n的瓶颈路长度。

    代码by ZZY大佬:

    // luogu-judger-enable-o2
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #include<algorithm>
    #include<ctype.h>
    using namespace std;
    struct node3
    {
    	int id,d;
    	friend bool operator <(node3 x,node3 y)
    	{
    		return x.d>y.d;//实际上是小根堆
    	}
    };
    priority_queue<node3> f;//堆
    	int n,m,len=0,ans=2147483647;
    	struct node1{int x,y,z1,z2;} d[100010];
    	struct node2{int x,y,z,next;} a[200010];
    	int last[50010],dis[50010];
    	bool bz[50010];
    inline char getc()//fread优化
    {
    	static char buf[1<<18],*fs,*ft;
    	return(fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<18,stdin)),fs==ft)?EOF:*fs++;
    }
    inline int read()
    {
    	char ch=getc(),f=1;
    	int x=0;
    	for(;!isgraph(ch);ch=getc());
    	if(ch=='-') f=-1,ch=getc();
    	for(;isdigit(ch);ch=getc())
    		x=((x+(x<<2))<<1)+(ch^0x30);
    	return x*f;
    }
    bool cmp(node1 x,node1 y)
    {
    	return x.z1<y.z1;
    }
    void ins(int x,int y,int z)
    {
    	a[++len]=(node2){x,y,z,last[x]}; last[x]=len;
    }
    int dijkstra(int st1,int st2)//不完全的dijkstra,同学们可以手打堆来保证堆顶的dis最优。这样每个点就只用进一次堆了
    {
    	f.push((node3){st1,dis[st1]});
    	f.push((node3){st2,dis[st2]});
    	while(!f.empty())
    	{
    		int x=f.top().id;
    		f.pop();
    		for(int i=last[x];i;i=a[i].next)
    		{
    			int y=a[i].y;
    			if(dis[y]>max(dis[x],a[i].z))
    			{
    				dis[y]=max(dis[x],a[i].z);
    				if(!bz[y]) f.push((node3){y,dis[y]}),bz[y]=true;
    			}
    		}
    		bz[x]=false;
    	}
    	return dis[n];
    }
    int main()
    {
    	n=read(),m=read();
    	for(int i=1;i<=m;i++)
    		d[i].x=read(),d[i].y=read(),d[i].z1=read(),d[i].z2=read();
    	sort(d+1,d+m+1,cmp);
    	memset(dis,63,sizeof(dis));
    	dis[1]=0;
    	bool flag=false;
    	for(int i=1;i<=m;i++)
    	{
    		if(d[i].z1>=ans) break;
    		if(d[i].x==d[i].y) continue;
    		ins(d[i].x,d[i].y,d[i].z2);
    		ins(d[i].y,d[i].x,d[i].z2);
    		if(d[i].x==1||d[i].y==1) flag=true;
    		if(flag) ans=min(ans,d[i].z1+dijkstra(d[i].x,d[i].y));//
    	}
    	printf("%d",ans<(int)1e9?ans:-1);
    }
    

    提问: 1. 为什么disdis不用每次都memsetmemset
              ~~~~~~~~~~ 2. 如果加的这题边对瓶颈路没有贡献会不会影响答案。
    回答:
    1.每次memsetmemset整个图都要重跑,复杂度太大。为了保证正确性,加两个端点,使得这条边对图有贡献就行。
    2.分两种情况讨论:
    (1):这条边对瓶颈路有贡献
    这样的话,显然直接加就行。
    (2):这条边对瓶颈路无贡献
    这种情况说明这条边没有之前的边优秀,因为每加一条边就更新一下答案,所以之前就已经更新了较优值,对结果无影响。

    2.我们要用生成树做。

    因为b的上限越大,边越多。(废话)
    所以我们可以按边的b sort,再求a的瓶颈生成树。

    引理:一个无向图的最小生成树一定为瓶颈生成树。(但瓶颈生成树不一定为最小生成树)
    证明:可用反证法。假设最小生成树不为瓶颈生成树,设最小生成树的最大边长度为a,瓶颈路生成树的最大边长度为b,则有a>b,那么瓶颈生成树的每一条边都小于a,我们把长度为a的边(端点为x,y)断开,换成瓶颈生成树中连接x,y所属联通块的边,那么生成树的大小更小,与最小生成树的定义矛盾,故原命题成立。

    所以我们用kruskalkruskal维护a的最小生成树就行。
    本题需要动态加边,所以要用LCT.
    因为我们在找1~n的瓶颈路长度时,不能保证1 ~ n的路径的深度严格递增,所以要换根(makeroot()makeroot()),LCT就是有根树。
    又因为要算边权,但把边权放给子节点的方法,由于换根而不能做。所以我们定义多m个点,把边权压到这些点上,其他点无权。那么我们就把求边权转化为了求点权。

    #include<cstdio>
    #include<cctype>
    #include<cstring>
    #include<algorithm>
    #define g getchar()
    #define lc tr[x].son[0]
    #define rc tr[x].son[1]
    using namespace std;
    const int N=15e4+10,M=1e5+10;//N的范围需注意 
    void qr(int &x)
    {
    	char c=g;bool v=x=0;
    	while(!(isdigit(c)||c=='-'))c=g;//isdigit返回bool值表示是否为十进制中的字符.isdigit(c)同'0'<=c&&c<='9' 
    	if(c=='-')v=1,c=g;
    	while(isdigit(c))x=x*10+c-'0',c=g;
    	if(v)x=-x;
    }
    struct edge{int x,y,a,b;}a[M];//边 
    bool cmp(edge x,edge y){return x.b<y.b;}
    int d[N];
    struct node{int f,son[2],p;bool v;}tr[N];//p为子树内d的最大值的标号 
    void update(int x)
    {
    	tr[x].p=x;
    	if(lc&&d[tr[x].p]<d[tr[lc].p])tr[x].p=tr[lc].p;
    	if(rc&&d[tr[x].p]<d[tr[rc].p])tr[x].p=tr[rc].p;
    }
    void fz(int x)//翻转 
    {
    	tr[x].v=0;swap(lc,rc);
    	tr[lc].v^=1;tr[rc].v^=1;
    }
    bool rt(int x){return tr[tr[x].f].son[0]!=x&&tr[tr[x].f].son[1]!=x;}//是否为splay的根 
    void wh(int x)
    {
    	if(!rt(x))wh(tr[x].f);
    	if(tr[x].v)fz(x);
    }
    void rotate(int x,int w)
    {
    	int f=tr[x].f,ff=tr[f].f,r,R;
    	r=tr[x].son[w];R=f;
    	tr[R].son[1-w]=r;
    	if(r)tr[r].f=R;
    	r=x;R=ff;
    		 if(tr[R].son[0]==f)tr[R].son[0]=r;
    	else if(tr[R].son[1]==f)tr[R].son[1]=r;
    	tr[r].f=R;
    	r=f;R=x;
    	tr[R].son[w]=r;
    	tr[r].f=R;
    	update(f);
    	update(x);
    }
    void splay(int x)
    {
    	wh(x);
    	while(!rt(x))
    	{
    		int f=tr[x].f;
    		if(rt(f))rotate(x,tr[f].son[0]==x);
    		else
    		{
    			int ff=tr[f].f,a=(tr[f].son[0]==x),b=(tr[ff].son[0]==f);
    			if(a^b)rotate(x,a),rotate(x,b);
    			else rotate(f,a),rotate(x,a);
    		}
    	}
    }
    void access(int x){for(int y=0;x;x=tr[y=x].f)splay(x),rc=y,update(x);}
    void makeroot(int x){access(x);splay(x);tr[x].v^=1;}
    int find_root(int x)
    {
    	access(x);splay(x);
    	while(lc)x=lc;
    	return x;
    }
    void split(int x,int y){makeroot(y);access(x);splay(x);}
    void link(int x,int y){makeroot(x);tr[x].f=y;access(x);}
    void cut(int x,int y){split(x,y);rc=tr[y].f=0;update(x);}
    int query(int x,int y){split(x,y);return tr[x].p;}
    int fa[N],s[N];//s为子树大小
    int findfa(int x){return fa[x]==x?x:fa[x]=findfa(fa[x]);} 
    void merge(int x,int y)
    {
    	x=findfa(x);y=findfa(y);
    	if(s[x]<s[y]){fa[x]=y;s[y]+=s[x];}
    	else 		 {fa[y]=x;s[x]+=s[y];}
    }
    int main()
    {
    	int n,m,x,y,z,b;
    	qr(n);qr(m);
    	for(int i=1;i<=n;i++)fa[i]=i,s[i]=1;
    	for(int i=1;i<=m;i++)
    		qr(x),qr(y),qr(z),qr(b),a[i]=(edge){x,y,z,b};
    	sort(a+1,a+m+1,cmp);
    	for(int i=1;i<=m;i++)d[n+i]=a[i].a;
    //因为这是一棵无根树,所以不能把边权压到子节点,我们需要多定义m个点,把边权压到这些点上,其他点的权为0就行 
    	int ans=N;
    	for(int i=1;i<=m;i++)
    	{
    		x=a[i].x;y=a[i].y;bool v=1; 
    		if(findfa(x)==findfa(y))//再加边就会形成回路。如果最大权的边较大,就换下
    		{
    			z=query(x,y);
    			if(d[z]>a[i].a)cut(a[z-n].x,z),cut(z,a[z-n].y);
    			else v=0;
    		}
    		else merge(x,y);
    		if(v)link(a[i].x,i+n),link(i+n,a[i].y);
    		if(findfa(1)==findfa(n))
    			ans=min(ans,a[i].b+d[query(1,n)]);
    	}
    	if(ans==N)puts("-1");
    	else printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    vue-cli
    使用git push命令如何忽略不想提交的文件夹或者文件
    Ajax实现的城市二级联动三
    Ajax实现的城市二级联动二
    Ajax实现的城市二级联动一
    DOM之城市二级联动
    如何把SVG小图片转换为 html字体图表
    vue权威指南笔记01——样式的设置
    2018年前端年末小结
    Array常用方法总结
  • 原文地址:https://www.cnblogs.com/zsyzlzy/p/12373894.html
Copyright © 2011-2022 走看看