某题题解

题目:

分析:

  看到题目之后,第一感觉:这不是非常板子的差分约束嘛?直接把边加好之后,然后跑个最长路,当然最长路就不写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;
}

  

猜你喜欢

转载自www.cnblogs.com/wish-all-ac/p/12977115.html