[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<bits/stdc++.h>
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 <Edge> 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 <int> G[M];
typedef pair<int,int> Pii;
vector <Pii> 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]]<sz[v]) son[u]=v;
}
R[u]=dfn;
}
void dfs2(int u,int t) {
top[u]=t;
if(son[u]) dfs2(son[u],t);
for(int v:G[u]) if(v!=son[u]) dfs2(v,v);
}
int LCA(int x,int y) {
while(top[x]!=top[y]) {
if(dep[top[x]]<dep[top[y]]) y=fa[top[y]];
else x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
int line[M],le[M],lc,cnt;
int id[M],to[N],stk[M],Top;
void Insert(int x){
int lca=LCA(stk[Top],x);
if(stk[Top]==lca) {
stk[++Top]=x,id[x]=++cnt;to[cnt]=x;
cnt++;to[cnt]=x;
return;
}
while(Top>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]<L[y];});
lc=unique(line+1,line+lc+1)-line-1;
int tcnt=cnt;
stk[Top=1]=1,id[1]=++cnt;to[cnt]=to[cnt+1]=1;
cnt++;
rep(i,1,lc) if(line[i]!=1) Insert(line[i]); //建立虚树
while(Top>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 <Node> 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]<d) continue;
rep(i,0,E[u].size()-1) {
int v=E[u][i].first,w=E[u][i].second;
if(dp[v]>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();
}
}