NOIP2017模拟 玩游戏 最小生成树 树上倍增

5 篇文章 0 订阅
4 篇文章 0 订阅

NOIP2017模拟 玩游戏

题目大意

给出一个n个节点m条边的无向连通图,定义两点间最短路径的长度为所有路径中最长边权值的最小值。现在有一些加边操作和询问操作,共计q次。询问操作是要求判断两组点对间的最短距离是否相等(原题中以最基本的Nim游戏的形式给出)。

数据范围

对于90%的数据,n<=5000,m<=100000,q<=150000,边权<=1e15,加边操作不超过1000次。

另有10%数据,每次加边的权值相同且已知,加边操作不超过5000次,n=1000,m=100000,q=100000。(也就是特殊处理即可)


原本的题目描述不是很容易读,这里简化了。如果能看出来这样的“最短距离”就是最小生成树上两点路径中边权的最大值,那么本题就基本上做完了。

树上讨论两点间的路径果断LCA,至于边权最大值显然可以用倍增处理。建好初始的生成树时间复杂度 O(mlogm) ,预处理出倍增数组的时间复杂度 O(nlogn) ,如果没有加边操作,询问可以在 O(logn) 内完成。

如何处理加边操作?我的方法可能不够优秀,在考场上由于一个优化没加只有70分。加了优化之后勉强能过。

如果暴力重新建树,每次加边操作时间复杂度都是 O(mlogm+nlogn) ,这样显然是过不了的,考虑优化。

首先,Kruskal算法的时间复杂度 O(mlogm) 是因为排序耗了太多时间,如果边已经排好序了,剩下就可以在线性时间内得到答案。每次我们只是加入一个数,而之前的数组都是排好序的,所以插入排序就可以把加边时的Kruskal的时间复杂度降为 O(m)

然而这样还是过不了。有一个显然的优化:如果目前的最小生成树中,最长边的权值都比新加的边要小,那么加这条边显然是没有意义的。加了这个优化似乎就能AC了。

如果用LCT那这道题就是大水题,可惜当时不会。


不优秀的代码:

#include<stdio.h>
#include<algorithm>
#include<cstring>
#define MAXN 5005
#define MAXM 400005
#define ll long long
using namespace std;

int N,M,TOT;
ll MaxLen;

struct edge{int St,En;ll Len;}E[MAXM];
bool operator<(edge a,edge b){return a.Len<b.Len;}

int tot,en[MAXM],nex[MAXM],las[MAXN];
ll len[MAXM];
void Add(int x,int y,ll z)
{
    en[++tot]=y;
    nex[tot]=las[x];
    las[x]=tot;
    len[tot]=z;
}

/*-----------------------MST--------------------------*/

void Ins(int a,int b,ll c)//插入排序
{
    int i;

    TOT++;
    E[TOT].St=a;E[TOT].En=b;E[TOT].Len=c;

    for(i=TOT-1;i;i--)
    {
        if(E[i+1]<E[i])swap(E[i],E[i+1]);
        else return;
    }
}

int Fa[MAXN];
int gf(int x)
{
    if(Fa[x]!=x)Fa[x]=gf(Fa[x]);
    return Fa[x];
}

void Kruskal()
{
    int i,x,y,cnt=0;

    for(i=1;i<=N;i++)Fa[i]=i;

    memset(las,0,sizeof(las));
    tot=0;

    for(i=1;i<=TOT&&cnt!=N-1;i++)
    {
        x=E[i].En;y=E[i].St;
        x=gf(x);y=gf(y);
        if(x==y)continue;

        Add(E[i].En,E[i].St,E[i].Len);
        Add(E[i].St,E[i].En,E[i].Len);
        MaxLen=max(MaxLen,E[i].Len);

        cnt++;
        Fa[x]=y;
    }
}

/*-------------------------LCA-----------------------*/

int fa[MAXN][14],dep[MAXN];
ll v[MAXN][14];

void DFS(int x,int f,ll z)
{
    int i,y;

    fa[x][0]=f;
    dep[x]=dep[f]+1;
    v[x][0]=z;

    for(i=1;i<14;i++)fa[x][i]=fa[fa[x][i-1]][i-1],v[x][i]=max(v[fa[x][i-1]][i-1],v[x][i-1]);
    for(i=las[x];i;i=nex[i])
    {
        y=en[i];
        if(y==f)continue;
        DFS(y,x,len[i]);
    }
}

ll LCA(int x,int y)
{
    int i,d,t;
    ll ans=0;

    if(dep[x]<dep[y])t=x,x=y,y=t;
    d=dep[x]-dep[y];
    for(i=0;i<14;i++)if((d>>i)&1)ans=max(ans,v[x][i]),x=fa[x][i];
    if(x==y)return ans;
    for(i=13;i>=0;i--)
    if(fa[x][i]!=fa[y][i])
    {
        ans=max(ans,v[x][i]);ans=max(ans,v[y][i]);
        x=fa[x][i];y=fa[y][i];
    }
    ans=max(v[x][0],ans);ans=max(ans,v[y][0]);
    return ans;
}

int main()
{
    int i,x,y,cnt=0;
    ll z;
    char op[6];

    scanf("%d%d",&N,&M);
    TOT=M;

    for(i=1;i<=M;i++)
    {
        scanf("%d%d%lld",&x,&y,&z);
        E[i].St=x;E[i].En=y;E[i].Len=z;
    }

    sort(E+1,E+M+1);

    Kruskal();
    DFS(1,0,0);

    int Q,m1,m2,b1,b2;
    ll vb,vm;

    scanf("%d",&Q);

    while(Q--)
    {
        scanf("%s",op);
        if(op[0]=='a')
        {
            scanf("%d%d%lld",&x,&y,&z);

            cnt++;
            if(cnt>1000||MaxLen<z)continue;
            //cnt>1000是为了解决特判的数据点。这里我懒得专门写一种情况了
            Ins(x,y,z);
            Kruskal();
            DFS(1,0,0);
        }
        else
        {
            scanf("%d%d%d%d",&m1,&m2,&b1,&b2);
            vm=LCA(m1,m2);
            vb=LCA(b1,b2);
            if(vb!=vm)puts("madoka");
            else puts("Baozika");
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值