1. 程式人生 > >nowcoder 提高組模擬賽 最長路 解題報告

nowcoder 提高組模擬賽 最長路 解題報告

最長路

連結:

https://www.nowcoder.com/acm/contest/178/A

來源:牛客網

題目描述

有一張 \(n\) 個點 \(m\) 條邊的有向圖,每條邊上都帶有一個字元,字元用一個數字表示。

求以每個點為起點的最長路,輸出走過的邊的字元構成的字串的字典序最小的方案。

為了方便,你需要這樣輸出方案:

如果最長路無限長,則輸出Infinity

否則假設方案走過的邊的字元依次為 \(w_1,w_2,\cdots,w_k\) ,輸出\((\sum\limits_{i=1}^kw_i\times29^i) \bmod 998244353\)

輸入描述:

第一行兩個整數 \(n\)

,\(m\) ,表示有向圖的結點個數和邊數。
接下來 \(m\) 行,每行三個整數 \(x,y,w\) ,表示有一條從 \(x\) 連向 \(y\) 的邊,上面有字元 \(w\)

輸出描述:

\(n\) 行,第 \(i\) 行表示第 \(i\) 個點所求的方案,輸出方式見題目描述。

備註:

全部的輸入資料滿足:

  • \(1 ≤ n ≤ 1000000\)
  • \(1 ≤ m ≤ 1000000\)
  • \(0 ≤ \text{字元} ≤ 10^9\)

各個測試點的性質如下:(若為空,則表示沒有特殊性質)

測試點標號 n m 特殊性質
\(1,2,3,4\) \(\le 1000\) \(\le 1000\)
\(5,6\) 所有字元=0
\(7,8\) 所有字元相等
\(9,10,11,12,13,14,15,16\) 所有字元互不相等
\(17,18\) \(\le 200000\) \(\le 200000\)
\(19,20\)

如果腦子傻了先縮個點就爆0了。。有向圖有自環時直接tarjan出不來的哦~

直接反向\(topo\)排序進不去就可以判有沒有經過環了。

如果沒有字典序的要求顯然就是一個最長路的簡單題。

考慮如何使字典序最小,題解給出了兩個做法,我就兩個都寫了寫。


方法\(1\),在最短路樹上維護倍增陣列。

我們發現,簡單的判斷字典序是沒法判斷一個正在轉移別人的點之前的路徑的,如果連出去的邊和連出去的點之前的轉移值的那條邊相等,我們就沒得辦法了。

為了可以判斷,我們對路徑的值維護一個\(hash\)值,來判斷兩條路徑是否相等,通過倍增找到路徑不相等的地方並進行比較。

複雜度\(O(nlogn)\)

Code:

#include <cstdio>
#define ll long long
const int N=1e6+10;
const ll mod=998244353ll;
int head[N],to[N],edge[N],Next[N],cnt;
void add(int u,int v,int w)
{
    to[++cnt]=v,edge[cnt]=w,Next[cnt]=head[u],head[u]=cnt;
}
ll po[N];
int n,m,in[N],f[N][21],pre[N],q[N],l=1,r=0;
ll g[N][21];
struct node
{
    int u,las,len;ll ans;
    bool friend operator <(node n1,node n2)
    {
        if(n1.len!=n2.len||n1.las!=n2.las) return n1.len==n2.len?n1.las>n2.las:n1.len<n2.len;
        int u1=n1.u,u2=n2.u;
        for(int i=20;~i;i--)
            if(f[u1][i]&&g[u1][i]==g[u2][i])
                u1=f[u1][i],u2=f[u2][i];
        return pre[u1]>pre[u2];
    }
}dp[N],t;
node max(node n1,node n2){return n1<n2?n2:n1;}
int main()
{
    scanf("%d%d",&n,&m);
    po[0]=1;
    for(int i=1;i<=n;i++) po[i]=po[i-1]*29ll%mod;
    for(int u,v,w,i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(v,u,w),in[u]++;
    }
    for(int i=1;i<=n;i++)
        if(!in[i]) q[++r]=i;
    while(l<=r)
    {
        int now=q[l++];
        for(int i=head[now];i;i=Next[i])
        {
            int v=to[i];
            in[v]--;
            t={now,edge[i],dp[now].len+1,(dp[now].ans+edge[i])*29ll%mod};
            dp[v]=max(dp[v],t);
            if(!in[v])
            {
                f[v][0]=dp[v].u;
                g[v][0]=dp[v].las;
                pre[v]=dp[v].las;
                for(int k=1;f[v][k-1];k++)
                {
                    f[v][k]=f[f[v][k-1]][k-1];
                    g[v][k]=(g[f[v][k-1]][k-1]*po[1<<k-1]+g[v][k-1])%mod;
                }
                q[++r]=v;
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(in[i]) puts("Infinity");
        else printf("%lld\n",dp[i].ans);
    }
    return 0;
}

方法\(2\),根據最長路的長度把點分層。

這個方法更加巧妙,常數也更小。

先把最長路求出來,然後對每個點安排它的層數,排除了一些無用邊。

那麼同層的點就可以按照路徑字典序來判斷這個點之前的路徑了。

具體的,對這層向下層的點連的邊排序,以邊的權值為第一關鍵字,以上層的點的字典序為第二關鍵字就行了。

Code:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define ll long long
const ll mod=998244353ll;
const int N=1e6+10;
int dis[N],edge[N],head[N],to[N],Next[N],cnt;
void add(int u,int v,int w)
{
    to[++cnt]=v,edge[cnt]=w,Next[cnt]=head[u],head[u]=cnt;
}
int q[N],l=1,r=0,n,m,mx,in[N],pre[N];
ll ans[N];
using std::vector;
int max(int x,int y){return x>y?x:y;}
vector <int> pot[N];
struct node
{
    int u,v,w,pre;
    bool friend operator <(node n1,node n2)
    {
        return n1.w==n2.w?n1.pre<n2.pre:n1.w<n2.w;
    }
}e[N];
int main()
{
    scanf("%d%d",&n,&m);
    for(int u,v,w,i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        add(v,u,w),++in[u];
    }
    memset(dis,-1,sizeof(dis));
    for(int i=1;i<=n;i++)
        if(!in[i])
            q[++r]=i,dis[i]=0;
    while(l<=r)
    {
        int now=q[l++];
        for(int i=head[now];i;i=Next[i])
        {
            int v=to[i];
            --in[v];
            dis[v]=max(dis[v],dis[now]+1);
            mx=max(dis[v],mx);
            if(!in[v]) q[++r]=v;
        }
    }
    for(int i=1;i<=n;i++)
        if(~dis[i]) pot[dis[i]].push_back(i);
    int d=0;
    do
    {
        int tot=0,tot0=0;
        for(int i=0;i<pot[d].size();i++)
            for(int j=head[pot[d][i]];j;j=Next[j])
                if(dis[to[j]]==d+1)
                    e[++tot]={pot[d][i],to[j],edge[j],pre[pot[d][i]]};
        std::sort(e+1,e+1+tot);
        for(int i=1;i<=tot;i++)
            if(!pre[e[i].v])
            {
                pre[e[i].v]=++tot0;
                ans[e[i].v]=(ans[e[i].u]+e[i].w)*29ll%mod;
            }
        d++;
    }while(d<mx);
    for(int i=1;i<=n;i++)
    {
        if(in[i]) puts("Infinity");
        else printf("%lld\n",ans[i]);
    }
    return 0;
}

2018.10.21