[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\n");
        }
        else{
        spfa();
        work();
        ll ans=0;
        for(int i=0;i<=K;i++)
        {
            ans=(ans+f[n][i])%p;
        }
        printf("%lld\n",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\n",ans);
    }
    return 0;
}

 

posted @ 2018-05-23 21:11  *Miracle*  阅读(224)  评论(2编辑  收藏  举报