[SDOI2017]天才黑客 (虚树+最短路)

[SDOI2017]天才黑客 (虚树+最短路)

(原谅我写不出简单题意)

可以看到与\(trie\)树上的字母以及\(lcp\)并没有关系。。

以边作为点,可以写出一个非常简单的最短路\(dis_i=min \lbrace dis_j+dep_{LCA(d_i,d_j)}+c_i|v_j=u_i\rbrace\),还可以建立\(u_0=v_0=1,d_0=1,c_0=0\)作为原点

但是恶意卡的话有\(m^2\)条边...

所以要考虑优化连边

对于每个点\(x\),把所有\(u_i=x\)\(v_i=x\)\(d_i\)提出来,建出一棵虚树,然后我们在虚树上连边

对于虚树上的点,我们建立正点和反点,正点向虚树上的父亲连边,反点向儿子连边,边权均为\(0\)

所有\(v_i=x\)的边向虚树上\(d_i\)的正点连一条为\(0\)的边

所有\(u_i=x\)的边,虚树上\(d_i\)的反点连过来一条为\(c_i\)的边

考虑\(LCA\)的三种情况,在走到\(LCA\)时将\(dep_{LCA(d_i,d_j)}\)考虑进来

1.对于\(LCA(d_i,d_j)=d_i\)的情况,直接让\(i\)\(d_i\)的反点连一条权为\(dep_{d_i}\)的边,在反点间转移即可

2.对于\(LCA(d_i,d_j)=d_j\)的情况,同理的,让\(d_j\)的正点向\(j\)连一条权为\(dep_{d_j}\)的边即可

3.最后一种情况比较复杂,因为我们要从\(LCA\)走过去,考虑枚举\(LCA\),让转移直接跳过\(LCA\),在\(LCA\)的儿子之间转移。这样能够保证转移无误,但是如果直接连边依然会被菊花图卡到\(n^2\),如果我们建立一个虚点连边的话,会存在自己向自己转移的非法情况。所以我们考虑前缀后缀连边,把所有儿子打成一个序列\(son_i\),然后建立两个虚点列\(a_i,b_i\),然后让\(a_i\rightarrow a_{i-1},b_i \rightarrow b_{i+1}\)\(son_i \rightarrow a_{i-1},son_i \rightarrow b_{i+1}\),把其中\(son_i\)出发的边边权设为\(dep_{LCA}\)即可

注意类似的连边方式都要避免产生负边,否则不能使用\(Dijkstra\),会因为\(SPFA\)而被卡掉..

#include
using namespace std;

#define pb push_back
#define reg register
typedef long long ll;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
    int s=0;
    while(!isdigit(IO=getchar()));
    do s=(s<<1)+(s<<3)+(IO^'0');
    while(isdigit(IO=getchar()));
    return s;
}

const int N=1e6+10,M=N/20+4;
const ll INF=1e18;

bool be;
int n,m,k;
struct Edge{
    int from,to,pos,w,id;
}e[M];
vector  S[M],T[M];
void AddEdge(int u,int v,int c,int d,int id) {
    S[u].pb(e[id]=(Edge){u,v,d,c,id});
    T[v].pb(e[id]);
}

int fa[M],dep[M],sz[M],top[M],son[M],L[M],R[M],dfn;
vector  G[M];
typedef pair Pii;
vector  E[N];

void dfs(int u,int f) {
    fa[u]=f,L[u]=++dfn,sz[u]=1,son[u]=0;
    for(int v:G[u]) {
        dep[v]=dep[u]+1;
        dfs(v,u);
        sz[u]+=sz[v];
        if(sz[son[u]]1 && dep[stk[Top-1]]>=dep[lca]) {
        E[id[stk[Top]]].pb((Pii){id[stk[Top-1]],0});
        E[id[stk[Top-1]]+1].pb((Pii){id[stk[Top]]+1,0});
        Top--;
    }
    if(stk[Top]==lca) {
        stk[++Top]=x,id[x]=++cnt;to[cnt]=to[cnt+1]=x;
        cnt++;
        return;
    }
    if(dep[stk[Top]]>dep[lca]) {
        id[lca]=++cnt;to[cnt]=to[cnt+1]=lca;
        cnt++;

        E[id[stk[Top]]].pb((Pii){id[lca],0});
        E[id[lca]+1].pb((Pii){id[stk[Top]]+1,0});
        Top--;
        stk[++Top]=lca; 
        stk[++Top]=x;
        id[x]=++cnt;to[cnt]=to[cnt+1]=x;
        cnt++;
    }
}


void PreMake() {
    rep(u,1,n) {
        if(!T[u].size() || !S[u].size()) continue;
        lc=0;
        rep(i,0,T[u].size()-1) line[++lc]=T[u][i].pos;
        rep(i,0,S[u].size()-1) line[++lc]=S[u][i].pos;
        sort(line+1,line+lc+1,[&](int x,int y){return L[x]1) {
            E[id[stk[Top]]].pb((Pii){id[stk[Top-1]],0});
            E[id[stk[Top-1]]+1].pb((Pii){id[stk[Top]]+1,0});
            Top--;
        }
        rep(i,0,T[u].size()-1) {  
            E[T[u][i].id].pb((Pii){id[T[u][i].pos],0}); // 向正点连边
            E[T[u][i].id].pb((Pii){id[T[u][i].pos]+1,dep[T[u][i].pos]}); //直接向反点连边,考虑dep
        }
        rep(i,0,S[u].size()-1) {
            E[id[S[u][i].pos]+1].pb((Pii){S[u][i].id,S[u][i].w}); //反点连过来
            E[id[S[u][i].pos]].pb((Pii){S[u][i].id,dep[S[u][i].pos]+S[u][i].w});//直接从正点连过来
        }
        rep(i,tcnt+1,cnt) if(~(i-tcnt)&1) {
            lc=0;
            rep(j,0,E[i].size()-1) 
                if(E[i][j].first>tcnt && E[i][j].first<=iend) 
                    line[++lc]=E[i][j].first; //提出儿子的序列
            if(lc<=1) continue;
            int lst=-1; 
            rep(j,1,lc) {
                cnt++;
                E[cnt].pb((Pii){line[j],0});
                if(~lst) {
                    E[line[j]-1].pb((Pii){lst,dep[to[i]]});
                    E[cnt].pb((Pii){lst,0});
                }
                lst=cnt;//连接ai
            }
            lst=-1;
            drep(j,lc,1) {
                cnt++;
                E[cnt].pb((Pii){line[j],0});
                if(~lst) {
                    E[line[j]-1].pb((Pii){lst,dep[to[i]]});
                    E[cnt].pb((Pii){lst,0});
                }
                lst=cnt; //连接bi
            }
        }
    }
}

struct Node{
    int x;
    ll d;
    bool operator < (const Node __) const{
        return d>__.d;
    }
};
priority_queue  que;
ll dp[N],Ans[M];
void Dijkstra(int st) {
    rep(i,0,cnt) dp[i]=INF;
    rep(i,1,n) Ans[i]=INF;
    dp[0]=0;
    que.push((Node){st,0});
    while(!que.empty()) {
        int u=que.top().x;
        ll d=que.top().d;que.pop();
        if(dp[u]dp[u]+w) dp[v]=dp[u]+w,que.push((Node){v,dp[v]});
        }
    }
    rep(i,1,m) Ans[e[i].to]=min(Ans[e[i].to],dp[i]);
}

bool ed;
int main(){
    rep(kase,1,rd()) {
        n=rd(),m=rd(),k=rd();
        AddEdge(1,1,0,1,0);
        rep(i,1,m) {
            int u=rd(),v=rd(),c=rd(),d=rd();
            AddEdge(u,v,c,d,i);
        }
        rep(i,2,k) {
            int u=rd(),v=rd(); rd();
            G[u].pb(v);
        }
        dfn=0,dfs(1,0),dfs2(1,1);
        cnt=m;
        PreMake();
        Dijkstra(0);
        fprintf(stderr,"%d\n",cnt);
        rep(i,2,n) printf("%lld\n",Ans[i]);
        rep(i,0,cnt) E[i].clear();
        rep(i,0,n) S[i].clear(),T[i].clear();
        rep(i,0,k) G[i].clear();
    }
}

你可能感兴趣的:([SDOI2017]天才黑客 (虚树+最短路))