zoukankan      html  css  js  c++  java
  • 某题题解

    题目:

    分析:

      看到题目之后,第一感觉:这不是非常板子的差分约束嘛?直接把边加好之后,然后跑个最长路,当然最长路就不写dij了,搞个spfa,tle了?双端队列优化(如果新加入的元素>=队首元素,直接加前面,否则加在后面,注意判空),然后就这样过了,时间也是很可观.

      当然为了更方便的处理我们建了节点0与所有的点连边权为0的边,且dis[0]=1.直接就符合题意了.

      spfa代码(先别走,还没到正题)

    #include <cstdio>
    #include <string>
    #include <queue>
    #include <cstring>
    using namespace std;
    const int maxn=1e5+10;
    deque<int> qu;
    int rd[maxn];
    struct E{
        int to,next,val;
    }ed[maxn*3];//开出自己虚拟的边 
    int head[maxn],tot;
    void J(int a,int b,int c){
        ed[++tot].to=b;
        ed[tot].val=c;
        ed[tot].next=head[a];
        head[a]=tot;
    }
    int vis[maxn],ha[maxn],dp[maxn];
    int main(){
        int n,m;
        scanf("%d%d",&n,&m);
        int t,js1,js2;
        for(int i=1;i<=n;i++) J(0,i,0);//虚拟的边 
        dp[0]=1;//最小为1 
        for(int i=1;i<=m;i++){//对约束进行处理 
            scanf("%d%d%d",&t,&js1,&js2);
            if(t==1){J(js1,js2,0);J(js2,js1,0);}
            if(t==2) J(js1,js2,1);
            if(t==3) J(js2,js1,0);
            if(t==4) J(js2,js1,1);
            if(t==5) J(js1,js2,0);
        }
        qu.push_front(0);
        while(!qu.empty()){
            int x=qu.front();
            qu.pop_front();
            vis[x]=0;
            for(int i=head[x];i;i=ed[i].next){
                if(dp[ed[i].to]<dp[x]+ed[i].val){
                    dp[ed[i].to]=dp[x]+ed[i].val;
                    if(!vis[ed[i].to]){
                        vis[ed[i].to]=1;
                        ha[ed[i].to]++;
                        if(!qu.empty()&&dp[ed[i].to]>=dp[qu.front()]) qu.push_front(ed[i].to);
                        else qu.push_back(ed[i].to);//双端队列优化 
                    }
                    if(ha[ed[i].to]>=n){printf("-1");return 0;}//正环 
                }
            }
        }
        long long ans=0;
        for(int i=1;i<=n;i++) ans+=(long long)dp[i];
        printf("%lld",ans);
        return 0;
    }

      spfa终究是不稳定的算法,没准那天就开始卡各种优化了,至少裸的spfa已经卡了很多了.所以我们考虑换一种做法.

      首先我们先想如果没有1,3,5,只有2,4这些操作我们怎么办(别再spfa啦),非常简单直接拓扑排序顺便dp一下就完了,如果有环肯定就可以直接输出-1了(毕竟有环就是正环).

      然后我们再考虑加上操作1,这时的环不一定是正环了,直接拓扑排序就会把一些不应该是-1的判断为-1,但是我们可以这样想,1所连的所有的点都是相等的,直接搞个并查集就好了,最后把相等的点缩成一个,这样就很好的避免了0环的干扰.有环就又是正环了,然后就是,拓扑排序了.

      最后还有两个操作3,5,这两个既加的边权为0,有不能保证连接的两点相等,不过你会发现,其实0的边是可以存在的,只要规避一下0的环就好了,0的环能不能直接缩成一个点呢?我们来证明一下.

      首先先说明这里的环指的是强连通分量.

      如果点u,v属于同一个联通分量(由0的边组成的),那么就有u到v有路径,当然0边权的边组成的路径边权和是0,这也就有val[u]<=val[v],同理val[v]<=val[u],于是有val[v]==val[u],也就是说同一由权值为0的边组成的强连通分量里,所有的点权相等.那么就直接缩成一个点就好了,其实操作1的也是类似的,直接用这种缩点就好了.

      这时我们就会得到一个新的图,这个图有什么性质呢?如果有环,必为正环,也就是说这时我们就可以拓扑排序了,排完之后,所有的得到的dp[i]乘上联通分量i的节点的个数的和就是答案了.(当然节点个数要提前处理出来)

      然后一些细节:

        没必要建一个只有0边权的图,tarjan的时候continue掉不合法的边就行了.

        不要建同一强连通分量里0的边,否则就又有0的环了.

        不要忘掉同一强连通分量里为1的边,否则会丢掉一些正环.

    代码:

    #include <cstdio>
    #include <queue>
    using namespace std;
    const int maxn=1e5+10;
    queue<int> qu;
    int rd[maxn];//入度 
    struct E{
        int to,next,val;
    }ed[maxn*2];
    int head[maxn],tot;
    void J(int a,int b,int c){
        ed[++tot].to=b;
        ed[tot].val=c;
        ed[tot].next=head[a];
        head[a]=tot;
    }
    int js,dfn[maxn],low[maxn],q,bel[maxn],sta[maxn],top,gs[maxn];
    //上面变量分别表示dfs的次数记录,时间戳,tarjan追溯值,强连通分量的个数,某个节点属于哪个强连通分量
    //跑tarjan用的栈,栈顶,某个强联通分量的节点个数 
    void tarjan(int x){//tarjan板子 
        low[x]=dfn[x]=++js;sta[++top]=x;
        for(int i=head[x];i;i=ed[i].next){
            if(ed[i].val==1) continue;
            if(!dfn[ed[i].to]){tarjan(ed[i].to);low[x]=min(low[ed[i].to],low[x]);}
            else if(!bel[ed[i].to]) low[x]=min(dfn[ed[i].to],low[x]);
        }
        if(low[x]==dfn[x]){
            q++;int tm;
            do{gs[q]++;tm=sta[top--];bel[tm]=q;}while(tm!=x);
        }
    }
    E ed0[maxn*2];//新图的边 
    int head0[maxn],tot0,dp[maxn];
    void J0(int a,int b,int c){
        ed0[++tot0].to=b;
        ed0[tot0].val=c;
        ed0[tot0].next=head0[a];
        head0[a]=tot0;
    }
    int main(){
        int n,m;
        scanf("%d%d",&n,&m);
        int t,js1,js2;
        for(int i=1;i<=m;i++){//对读入进行处理 
            scanf("%d%d%d",&t,&js1,&js2);
            if(t==1){J(js1,js2,0);J(js2,js1,0);}
            if(t==2) J(js1,js2,1);
            if(t==3) J(js2,js1,0);
            if(t==4) J(js2,js1,1);
            if(t==5) J(js1,js2,0);
        }
        for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);//求强连通分量 
        for(int i=1;i<=n;i++)
            for(int j=head[i];j;j=ed[j].next)
                if(bel[ed[j].to]!=bel[i]||ed[j].val==1){//如果不在同一分量里或者边权为1新图加上此边 
                    J0(bel[i],bel[ed[j].to],ed[j].val);
                    rd[bel[ed[j].to]]++;//统计入度 
                }
        for(int i=1;i<=q;i++) if(rd[i]==0){qu.push(i);dp[i]=1;}//拓扑排序 
        while(!qu.empty()){
            int x=qu.front();qu.pop();
            for(int i=head0[x];i;i=ed0[i].next){
                rd[ed0[i].to]--;
                dp[ed0[i].to]=max(dp[ed0[i].to],dp[x]+ed0[i].val);
                if(rd[ed0[i].to]==0) qu.push(ed0[i].to);
            }
        }
        long long ans=0;
        for(int i=1;i<=q;i++){
            if(rd[i]){printf("-1");return 0;}//有正环(新图有环即为正环) 
            ans+=(long long)gs[i]*(long long)dp[i];
        }
        printf("%lld",ans);
        return 0;
    }

      

  • 相关阅读:
    Ternsorflow 学习:002-Tensorflow 基础知识
    Ternsorflow 学习:001-通过例程,初步了解Tensorflow
    Wpa_supplicant 调试故障原因分析
    Ternsorflow 学习:000-在 Ubuntu 16.04 上安装并使用 TensorFlow_v1.14 (改)
    开发工具类(eclipse、安卓SDK) 镜像站
    Linux镜像源 国内列表
    linux桌面系统 镜像下载
    arm linux 移植 udhcp 与 使用
    Docker三剑客之compose
    Docker基础内容之端口映射
  • 原文地址:https://www.cnblogs.com/wish-all-ac/p/12977115.html
Copyright © 2011-2022 走看看