• [NOIP2017]逛公园


    2018-05-24

    题目大意:

    给一张由非负边组成的图,(可能有零边),设从出发点1到终点n的路径的最短路为d,求出所有到达n的路径中,路程长度为d~d+k的方案总数。(取模)

    若有无数条,输出-1;

    分析:

    暴力:

    1.30分k=0,最短路计数暴力妥妥30分

    2.70分,因为,k<=50,比较小,而n<=100000,所以是NK复杂度。

    每个点的偏移量k可以由之前能到达这个点的小于等于k的值转过来,

    所以设f[i][j]表示,在第i号点,距离到i号点最短路的偏移量为j的方案总数。也就是说,到i的路径长度为dis[i]+j的方案数。(dis[i]为从1到i最短路长度)

    其中,j>=0&&j<=k

    可以知道,从1-u-v和从1-v(路径都是最短路)相比较,偏移量就是:dis[u]+val[u,v]+j-dis[x]这么长

    发现,同一层之间可能有转移。

    不能有后效性,那么转移顺序怎么处理?

    发现,同一层之间有转移的话,必然是从dis小的到dis大的。

    dis大的不可能转移到dis小的,并且偏移量不变。

    所以对dis排序。

    至于dis相同的,也不可能之间产生转移。随便排序就好了。

    100分

    但是刚才这个办法显然有坑。剩下的数据点,有0边。

    先判断-1,发现,有0环,并且走0环可以是一条符合长度偏移量<=k的方案。

    (但是我蒟蒻,没想这么多,直接判的零环,没想到过了。看来没有“有零环但不在方案数中”的测试点)

    (所以代码其实有错,正解应该是判断0环上的是否存在一点x符合 dis1[x]+disn[x]<=dis[n]+k 这里,dis1表示从1出发最短路,disn表示从n出发最短路)

    (这样才能保证走到0环后,随便走零边都是一种合法方案。而不是还没到0环就超过了k)

    对于0链,a->b->c,w均为0,dis值是一样的,不能按dis值随便排了,必须先更新a,再b,再c。

    所以,拓扑排序。专为0边准备。

    把所有0边揪出来,判断-1的时候,也就进行了拓扑排序。

    所以,现在,结点排序的优先级定义为:dis不同,dis小的优,否则拓扑序小的优。

    spfa最短路。(有人用线段树,我太弱不会)

    这个题,不开O2,洛谷还是要T两个点。很差。

    但是换了LibreOJ就2000ms一个点AC了。看来luogu评测机还是比较慢。

    估计CCF老爷机要卡爆了。就算考试想出正解,估计也就卡到70分了。

    总结:spfa+拓扑+dp+卡常

    代码:

    #include<bits/stdc++.h>
    #define num ch-'0'
    using namespace std;
    typedef long long ll;
    const int N=100000+10;
    const int M=200000+10;
    const int inf=(0x3f3f3f3f)*2;
    int n,m,K,p;
    int rd()
    {
        int x=0;char ch;
        while(!isdigit(ch=getchar()));
        for(x=num;isdigit(ch=getchar());x=x*10+num);
        return x;
    }
    struct node{
        int nxt,to,val;
    }bian[M];
    struct zero{
        int nxt,to;
    }e0[M];
    int has[N],tot;
    int hd[N],cnt1;//真正边 
    int pp[N],cnt3;//0边 
    int du[N];//度数 
    int dis[N];//最短路 
    int da[N];//最终的循环顺序da[i]表示排名为i的节点 
    int q[N*2],l,r;
    bool vis[N];
    bool flag=true;//零环bool 
    ll f[N][52];//dp数组 
    int id[N];//拓扑序,主要是针对0边处理 
    void add1(int x,int y,int z)
    {
        bian[++cnt1].nxt=hd[x];
        bian[cnt1].to=y;
        bian[cnt1].val=z;
        hd[x]=cnt1;
    }
    void add3(int x,int y)//建0边 
    {
        e0[++cnt3].nxt=pp[x];
        e0[cnt3].to=y;
        pp[x]=cnt3;
    }
    bool jud()//判零环,这其实是错的。
    {
        memset(vis,0,sizeof vis);
        l=1;r=0;
        for(int i=1;i<=n;i++)
        { 
            if(du[i]==0) q[++r]=i;
        }
        while(l<=r)
        {
            int x=q[l++];
            vis[x]=1;
            for(int i=pp[x];i;i=e0[i].nxt)
            {
                int y=e0[i].to;
                du[y]--;
                if(du[y]==0) q[++r]=y;
            }
        }
        for(int i=1;i<=n;i++)
          id[q[i]]=i;
        for(int i=1;i<=tot;i++)
        {
            if(vis[has[i]]!=1) return true;
        }
        return false;
    }
    void spfa()//最短路 
    {
        memset(vis,0,sizeof vis);
        for(int i=1;i<=n;i++) dis[i]=inf;
        dis[1]=0;
        vis[1]=1;
        l=1;r=0;
        q[++r]=1;
        while(l<=r)
        {
            int now=q[l++];
            vis[now]=0;
            for(int i=hd[now];i;i=bian[i].nxt)
            {
                int y=bian[i].to;
                if(dis[y]>dis[now]+bian[i].val)
                {
                    dis[y]=dis[now]+bian[i].val;
                    if(!vis[y])
                    {
                        vis[y]=1;
                        q[++r]=y;
                    }
                }
            }
        }
    }
    bool cmp(int x,int y){//排序,决定循环顺序 
        return dis[x]==dis[y]?id[x]<id[y]:dis[x]<dis[y];
    }
    void work()//最终循环 
    {
        l=1;r=0;
        for(int i=1;i<=n;i++) da[i]=i;
        sort(da+1,da+n+1,cmp);
        f[1][0]=1;
        for(int k=0;k<=K;k++)//注意,k从小到大枚举,并且放在外面。 
         for(int j=1;j<=n;j++)
         {
             int x=da[j];
             for(int i=hd[x];i;i=bian[i].nxt)
             {
                 int y=bian[i].to;
                if(dis[x]+bian[i].val-dis[y]+k<=K) 
                 f[y][dis[x]+bian[i].val-dis[y]+k]=(f[y][dis[x]+bian[i].val-dis[y]+k]+f[x][k])%p;
             }
         }
    }
    void clear()//数组清空
    {
        cnt1=0;
        cnt3=0;
        flag=true;
        memset(f,0,sizeof f);
        memset(du,0,sizeof du);
        memset(hd,0,sizeof hd);
        memset(pp,0,sizeof pp);
        tot=0;
    }
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T)
        {
            clear();    
            n=rd(),m=rd(),K=rd(),p=rd();
            int x,y,z;
            for(int i=1;i<=m;i++) {
                x=rd(),y=rd(),z=rd(),add1(x,y,z);
                if(z==0){//将所有的0边的两端的度数都记录下来,便于之后判断零环和拓扑排序 
                    has[++tot]=x,has[++tot]=y;
                    add3(x,y);du[y]++;
                }
            }
            if(jud()){
                printf("-1
    ");
            }
            else{
            spfa();
            work();
            ll ans=0;
            for(int i=0;i<=K;i++)
            {
                ans=(ans+f[n][i])%p;
            }
            printf("%lld
    ",ans);
            }
            T--;
        }
        return 0;
    }
    
     
    View Code

     upda:2018.11.5

    之前太菜了。

    这个代码可以不卡常直接AC:

    #include<bits/stdc++.h>
    #define reg register int
    #define il inline
    #define numb (ch^'0')
    using namespace std;
    typedef long long ll;
    const int N=100000+5;
    const int M=200000+5;
    int n,m,t;
    int k,mod;
    void rd(int &x){
        char ch;x=0;
        while(!isdigit(ch=getchar()));
        for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    }
    struct node{
        int nxt,to;
        int val;
    }e[2*M],bian[2*M];
    int hd[N],cnt1;
    int pre[N],cnt2;
    bool in[N];
    int du[N];
    int f[N][55];
    int d[N];
    int po[N];
    bool zero[N];
    bool vis[N];
    void add(int x,int y,int z){
        e[++cnt1].nxt=hd[x];
        e[cnt1].to=y;
        e[cnt1].val=z;
        hd[x]=cnt1;
    }
    void add_c(int x,int y){
        bian[++cnt2].nxt=pre[x];
        bian[cnt2].to=y;
        pre[x]=cnt2;
    }
    int dis[N];
    int q[10*N],l,r;
    void spfa(){
        l=1,r=0;
        memset(vis,0,sizeof vis);
        memset(dis,0x3f,sizeof dis);
        dis[1]=0;
    //    f[1][0]=1;
        q[++r]=1;
        while(l<=r){
            int x=q[l++];vis[x]=0;
        //    cout<<" x "<<x<<" dis "<<dis[x]<<endl;
            for(int i=hd[x];i;i=e[i].nxt){
                int y=e[i].to;
        //        cout<<" yy "<<y<<endl;
                if(dis[y]>dis[x]+e[i].val){
                    dis[y]=dis[x]+e[i].val;
                    //f[y][0]=f[x][0];
                    if(!vis[y]){
                        vis[y]=1;
                        q[++r]=y;
                    }
                }
            }
        }
    }
    bool topo(){
        l=1,r=0;
        memset(po,0x3f,sizeof po);
        memset(in,0,sizeof in);
        for(reg i=1;i<=n;++i){
            d[i]=i;
            if(zero[i]&&du[i]==0){
                q[++r]=i;
            }
        }
        int lp=0;
        while(l<=r){
            int x=q[l++];
            po[x]=++lp;
        //    cout<<" xxx "<<x<<endl;
            in[x]=1;
            for(int i=pre[x];i;i=bian[i].nxt){
                int y=bian[i].to;
                du[y]--;
                if(du[y]==0){
                    q[++r]=y;
                }
            }
        }
        for(reg i=1;i<=n;++i){
            if(zero[i]&&!in[i]) return false;
        }
        return true;
    }
    bool cmp(int a,int b){
        if(dis[a]==dis[b]) return po[a]<po[b];
        return dis[a]<dis[b];
    }
    void clear(){
        memset(f,0,sizeof f);
        memset(zero,0,sizeof zero);
        memset(du,0,sizeof du);
        memset(hd,0,sizeof hd);
        memset(pre,0,sizeof pre);
        cnt1=cnt2=0;
    }
    int main(){
        scanf("%d",&t);
        while(t--){
            scanf("%d%d%d%d",&n,&m,&k,&mod);
            clear();
            int x,y,z;
            for(reg i=1;i<=m;++i){
                rd(x);rd(y);rd(z);
                add(x,y,z);
                if(!z){
                    add_c(x,y);
                    du[y]++;
                    zero[x]=1;zero[y]=1;
                }
            }
            spfa();
            bool fl=topo();
            if(!fl){
                puts("-1");
                continue;
            }
            sort(d+1,d+n+1,cmp);
            f[1][0]=1;
            for(reg o=0;o<=k;++o){
                for(reg i=1;i<=n;++i){
                    int x=d[i];
                //    cout<<" now "<<x<<endl;
                    for(reg i=hd[x];i;i=e[i].nxt){
                        int y=e[i].to;
                    //    cout<<" goto "<<y<<endl;
                        int to=dis[x]+o+e[i].val-dis[y];
                        if(to<=k){
                            f[y][to]=(f[y][to]+f[x][o]>=mod)?f[y][to]+f[x][o]-mod:f[y][to]+f[x][o];
                        }
                    }
                }
            }
            ll ans=0;
            for(reg i=0;i<=k;++i){
                ans=ans+f[n][i];
                if(ans>=mod) ans-=mod;
            }
            printf("%lld
    ",ans);
        }
        return 0;
    }
  • 相关阅读:
    MySQL学习(十二)
    MySQL学习(十一)
    MySQL学习(十)
    MySQL学习(九)
    MySQL学习(八)
    hlg1600线性代数中的矩阵问题【区间dp】
    HDU1556Color the ball【标号法||树状数组】
    hlg1481 Attack of the Giant n-pus【二分+二分图】
    0918
    20140913
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9079401.html
Copyright © 2020-2023  润新知