BZOJ 3669: [Noi2014]魔法森林

8 篇文章 0 订阅
7 篇文章 0 订阅

题意就不说了

感觉做了上一题就不觉得这题难啦2333(⊙ ▽ ⊙)

据说kruskal能拿70 黄学长那里有代码 戳这

当然,我们先来说说正解:LCT

根据经验其实就是看别人题解我们可以先限制a,b其中一个量,对a进行排序之后 我们就可以对b来用lct维护,原理跟上一题差不多,可以说是维护最小生成树(你喜欢怎么叫都行),反正就是每次把边加进去之前询问,是否已经联通(也就是要产生环),如果是,我们就要b比较小的那条 min(x y路径中的最大b值与当前边的b值)

最后每次询问一下1 和 N是否联通然后更新一下答案就好啦。。。

代码:(看完代码还有呢。。)

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define inf 1000000000
#define N 180010
using namespace std;
typedef long long LL;
struct edge{int x,y,a,b;}e[100010];
int fa[N],n,m,p,c[N][2],st[N],w[N],mx[N];
bool rev[N];
int Cmp(edge x1,edge x2){return x1.a<x2.a;}
bool Rt(int x)
{
    if(c[fa[x]][0]==x || c[fa[x]][1]==x)return 0;
    return 1;
}
void pushup(int x)
{
    int l=c[x][0],r=c[x][1];
    mx[x]=x;
    if(w[mx[l]]>w[mx[x]])mx[x]=mx[l];
    if(w[mx[r]]>w[mx[x]])mx[x]=mx[r];
}
void pushdown(int x)
{
    int l=c[x][0],r=c[x][1];
    if(rev[x])
    {
        rev[x]=0,rev[l]^=1,rev[r]^=1;
        swap(c[x][0],c[x][1]);
    }
}
void rotate(int x)
{
    int y=fa[x],z=fa[y],a=c[y][1]==x,b=c[z][1]==y,g=c[x][!a];
    if(!Rt(y))c[z][b]=x;
    fa[g]=y,c[y][a]=g;
    fa[y]=x,c[x][!a]=y;
    fa[x]=z;
    pushup(y); pushup(x);
}
void splay(int x)
{
    int top=0,i;
    for(i=x;!Rt(i);i=fa[i])st[++top]=i;
    st[++top]=i;
    for(i=top;i;i--)pushdown(st[i]);
    while(!Rt(x))
    {
        int y=fa[x],z=fa[y],a=c[y][1]==x,b=c[z][1]==y;
        if(!Rt(y))
        {
            if(a==b)rotate(y);
            else rotate(x);
        }
        rotate(x);
    }
}
void access(int x)
{
    int last=0;
    while(x)
    {
        splay(x);
        c[x][1]=last;
        pushup(x);
        last=x,x=fa[x];
    }
}
void make_root(int x)
{
    access(x); splay(x);
    rev[x]^=1;
}
void split(int x,int y)
{
    make_root(x);
    access(y); splay(y);
}
void link(int x,int y)
{
    make_root(x); fa[x]=y;
}
void cut(int x,int y)
{
    split(x,y);
    fa[x]=c[y][0]=0;
}
int findrt(int x)
{
    access(x); splay(x);
    while(c[x][0])x=c[x][0];
    return x;
}
inline int read()
{
    int x=0; char ch=getchar();
    while(ch<'0' || ch>'9')ch=getchar();
    while(ch>='0' && ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x;
}
int main()
{
    int i,x,y,s;
    n=read(),m=read();
    for(i=1;i<=m;i++)
        e[i].x=read(),e[i].y=read(),e[i].a=read(),e[i].b=read();
    sort(e+1,e+1+m,Cmp);
    int ans=inf;
    for(i=1;i<=m;i++)
    {
        x=e[i].x,y=e[i].y;
        w[i+n]=mx[i+n]=e[i].b;
        if(findrt(x)==findrt(y))
        {
            split(x,y);
            s=mx[y];
            if(w[s]>e[i].b)
            {
                cut(e[s-n].x,s),cut(e[s-n].y,s);
                link(x,i+n),link(y,i+n);
            }
        }
        else
            link(x,i+n),link(y,i+n);
        if(findrt(1)==findrt(n))
        {
            split(1,n); ans=min(ans,e[i].a+w[mx[n]]);
        }
    }
    if(ans!=inf)printf("%d\n",ans);
    else printf("-1\n");
    return 0;
}
现在我要告诉你,这道题spfa也能过! 我想骂人微笑

道理一样,排序了a之后我们动态加边(看起来高级而已)

用spfa维护路径上的最大b。 对于d数组不需要清空,每次多一个边就把两个端点放进去跑就好了。

当然了 还有优化我就把A一样的放一起跑罢了 (堆优化不会弄) 其他什么的可以看看这里点击打开链接

实际上不需要m次spfa,虽然不会算时间复杂度可是真的挺快。。(我只加了一个剪枝啊。。。)

下面就是我54行而且时间比lct快的spfa。。。

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define inf 1000000000
using namespace std;
struct edge{int x,y,a,b,next;}a[200100],b[100100];
int Cmp(edge x1,edge x2){return x1.a<x2.a;}
int len,first[50005];
void ins(int x,int y,int bb)
{
	a[++len].x=x,a[len].y=y,a[len].b=bb;
	a[len].next=first[x],first[x]=len;
	a[++len].x=y,a[len].y=x,a[len].b=bb;
	a[len].next=first[y],first[y]=len;
}
queue<int>q; int d[50005];
bool v[50005];
int main()
{
	int n,m,i,x,y;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)scanf("%d%d%d%d",&b[i].x,&b[i].y,&b[i].a,&b[i].b);
	sort(b+1,b+1+m,Cmp);
	for(i=2;i<=n;i++) d[i]=inf;
	int ans=inf;
	for(i=1;i<=m;i++)
	{
		ins(b[i].x,b[i].y,b[i].b);
		q.push(b[i].x); q.push(b[i].y);
		v[b[i].x]=v[b[i].y]=1;
		if(b[i].a==b[i+1].a)continue;
		while(!q.empty())
		{
			x=q.front();
			for(int k=first[x];k;k=a[k].next)
			{
				y=a[k].y;
				if(d[y]>max(d[x],a[k].b))
				{
					d[y]=max(d[x],a[k].b);
					if(!v[y])q.push(y),v[y]=1;
				}
			}
			q.pop();
			v[x]=0;
		}
		if(ans>b[i].a+d[n])ans=b[i].a+d[n];
	}
	if(ans==inf)printf("-1\n");
	else printf("%d\n",ans);
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值