zoukankan      html  css  js  c++  java
  • [noip模拟]分组行动

    题目描述

    最近,木木中学要举行一年一度的辩论赛了,我们活泼开朗乐观向上不寂寞不生病不挂科天天回家吃饭的新时代好少年--飞飞,自然是热情参与咯!辩论嘛,就有正方和反方两个组,这是一个传统项目,所以,包括飞飞,木木中学的每一个学生都会加入2个组中的一个,不会有人打酱油的(如果有人打酱油,那么飞飞会义无反顾义不容辞的上前用一翻惊天动地的演说打消他打酱油的念头的)。自然啦,作为有思想有文化能言善辩的好少年,飞飞,其实加入哪个组,从技术角度分析真是无所谓的,不过呢,从另外一些角度来看,就复杂的多了……比如,飞飞是个很不喜欢八卦的正义青年,所以啊,飞飞很不想和年级最PP的女生青菜分在一个组,因为会产生八卦的--为什么会有八卦?首先是他们比较熟(比较熟就会有八卦吗?无限yy中……),最重要的就是因为飞飞是大帅哥啊!人长得帅,有时候真是挺麻烦的,尤其飞飞还如此低调……再比如,飞飞也不想和小邱分在一个组--虽然他们不认识,但是,飞飞听说小邱很懒还很暴力,飞飞不喜欢暴力……当然了,不论如何的分组行动,都会产生一些代价的,比如两个好朋友分在了不同的组,那肯定不是很高兴了。飞飞知道,学校里有M对同学是相互认识的,每对认识的同学间,都有一个要好程度C,自然的,关系越好,要好程度越高,也越不愿意分开。对于一个分组方案,必然会使得有某些认识的同学分开,飞飞认为,一个分组方案的代价就是最不愿意分开的那两个同学之间的要好程度。飞飞在想,和青菜MM分在不同的组最小代价是多少呢?和小邱同学分在不同的组最小代价是多少呢?如果……还要将青菜MM和小邱分在不同的组最小代价是多少呢?……好麻烦啊,飞飞越想越乱,于是来找你帮忙了。一些说明:木木中学有N个学生,木木中学是一所优秀的中学,学生们都是开朗外向的,所以一个学校里任意两个学生间接或者直接肯定认识。显然任意一个辩论组都至少得有一个人。为了方便,我们把木木中学的学生用1到N编号,飞飞有Q个问题,每个问题的形式都是这样的:将编号为a的学生与编号为b的学生分在不同组中,最少的代价是多少呢?关于这道题目本身,只是想检验你能不能帮飞飞,所以你并不需要--具体回答每个问题,只需要最后输出每个问题答案的乘积除以P的余数就可以了。

    输入格式

    输入文件中的第一行为四个正整数N,M,Q,P。

    第二行至第M+1行,每行有三个正整数a,b,c,表示编号为a的同学和编号为b的同学认识,并且要好程度是c。

    保证a和b是不会相同的,也不会有多行描述相同两个学生之间的关系。

    第M+2行至第M+Q+1行,每行有两个正整数a,b,表示飞飞的一个问题,这里保证a和b是不同的。

    N<5000,M<10000,Q<1000,C<1000000,P<1000000。

    输出格式

    输出文件中仅一行为一个整数,所有问题答案的乘积除以P的余数。


    题目翻译过来就是:对于每个询问的a,b,要求将原图分成两个不相连的连通块,并且a,b不在同一个连通块中,答案就是原图中两个连通块之间相连的边的边权最大值。

    进一步,我们可以把题目理解为:删掉一些边使得a,b不连通,删去边的最大值就是答案。

    但是删边的操作太复杂,不如考虑倒过来变成加边的操作。

    显然删边时我们需要从小到大删,那么加边时我们就从大到小加。然后当加上一条边使得a,b连通时,此时加的边的边权就是答案,因为比这条边更大的边不足以使a,b连通,那么我们可以把它们放入连通块中。而此时剩下的边不会大于当前边,无论删哪些边答案都是当前边的边权。

    考虑实现方式:

    先对边从大到小排序。加边可以用并查集实现,每次加边之后枚举每个询问,看是否未被回答过并且现在连通了,并记录答案。忽略掉并查集的复杂度,那么时间复杂度就是O(MlogM+MQ),会T掉。那么考虑优化:

    我们的原本的策略是回答一个询问后打上标记,当再次循环到这个位置时就跳过。那么当询问回答得差不多时就会产生很大的空转。那么我们的优化方法有两个:

    1.Dancing Links,用链表的形式存储询问,回答了当前询问时就从链表删掉这个询问。

    2.队列优化。将未回答的询问加入队列,回答之后将询问出队。

    那么用了上述优化之后可以把复杂度降为O(MlogM+KQ),K是一个比较小的常数。


    但上面的离线做法显然不尽人意,原因是处理问题时不能及时调动存储的询问。所以我们可以考虑在线做法。

    类比着离线做法,用并查集从大到小加边可以看作是求原图的最大生成树,加到询问的两个点连通的边可以看作是询问的两点在最大生成树的路径上的最小边。所以我们在线的做法就是:

    求出最大生成树,然后对于每个询问,求出两点在树上路径上的最小边,这个可以用倍增求。那么复杂度就是:O(MlogM+(N+Q)logN)

    放出在线做法的代码:

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define maxn 5001
    #define maxm 10001
    using namespace std;
     
    int n,m,q,p,ans=1;
    inline int read(){
        register int x(0),f(1); register char c(getchar());
        while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
        while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        return x*f;
    }
    
    struct edge{
        int to,dis,next;
        edge(){}
        edge(const int &_to,const int &_dis,const int &_next){ to=_to,dis=_dis,next=_next; }
    }e[maxn<<1];
    int head[maxn],k;
    inline void add(const int &u,const int &v,const int &w){ e[k]=edge(v,w,head[u]),head[u]=k++; }
    
    struct birm{
        int u,v,w;
        bool operator<(const birm &b)const{ return w>b.w; }
    }b[maxm];
    int fa[maxn],tot;
    int get(int x){ return x==fa[x]?x:fa[x]=get(fa[x]); }
    inline void kruskal(){
        sort(b+1,b+1+m);
        for(register int i=1;i<=n;i++) fa[i]=i;
        for(register int i=1;i<=m;i++){
            int u=b[i].u,v=b[i].v,w=b[i].w;
            if(get(u)==get(v)) continue;
            fa[get(u)]=get(v),tot+=w;
            add(u,v,w),add(v,u,w);
        }
    }
    
    int minedge[maxn][20],f[maxn][20],dep[maxn],maxdep;
    void dfs_getmax(int u,int pre){
        for(register int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(v==pre) continue;
            f[v][0]=u,minedge[v][0]=e[i].dis,dep[v]=dep[u]+1;
            for(register int j=1;j<=maxdep;j++) f[v][j]=f[f[v][j-1]][j-1],minedge[v][j]=min(minedge[v][j-1],minedge[f[v][j-1]][j-1]);
            dfs_getmax(v,u);
        }
    }
    inline int getmin(int u,int v){
        int ans=0x3f3f3f3f;
        if(dep[u]>dep[v]) swap(u,v);
        for(register int i=maxdep;i>=0;i--) if(dep[f[v][i]]>=dep[u]) ans=min(ans,minedge[v][i]),v=f[v][i];
        if(u==v) return ans;
        for(register int i=maxdep;i>=0;i--) if(f[u][i]!=f[v][i]) ans=min(ans,min(minedge[u][i],minedge[v][i])),u=f[u][i],v=f[v][i];
        return min(ans,min(minedge[u][0],minedge[v][0]));
    }
    
    int main(){
        memset(head,-1,sizeof head),memset(minedge,0x3f,sizeof minedge);
        n=read(),m=read(),q=read(),p=read(),maxdep=(int)log(n)/log(2)+1;
        for(register int i=1;i<=m;i++) b[i].u=read(),b[i].v=read(),b[i].w=read();
        kruskal(),dfs_getmax(1,-1);
        for(register int i=1;i<=q;i++){
            int u=read(),v=read();
            ans=(1ll*ans*getmin(u,v))%p;
        }
        printf("%d
    ",ans);
        return 0;
    }
    
  • 相关阅读:
    jquery笔记
    JavaScript 运动框架 Step by step(转)
    js中获取页面元素方法总结
    图片缩放效果IE和火狐兼容模式
    js循环运动效果实现
    网页捕捉错误
    (图文介绍)Virtualbox下实现Ubuntu虚拟机和win7主机文件共享(很简单,亲自试用,按此步骤一般都会成功)
    FFT算法实现(fft算法)快速傅里叶变换算法实现
    $_SESSION跨页面问题
    暂别ACM,转移阵地
  • 原文地址:https://www.cnblogs.com/akura/p/11046350.html
Copyright © 2011-2022 走看看