点此看题
真的毒瘤,我 TM 搞了三个小时,调起来太 TM 爽了。
言归正传,这道题很容易想到一个最短路解法,我们把每条边拆成两个点,它们之间的边权值都是原来的边权,然后对于每个点(原图),我们把入点和出点暴力连边,边权为 l c p lcp lcp(字典树上 l c a lca lca的深度 − 1 -1 −1),这个做法可以拿到 0 0 0分的高分,因为入点和出点相连时边数最多是 m 2 m^2 m2,然后时间复杂度就无力回天了。
考虑优化吧,现在的问题在于边数过多,我们能否减少一些边呢?
官方解法用到了权值是 字典树的lca算的 这一性质,我们可以用虚树的思想,我们先用字典树的 d f n dfn dfn将入点和出点排序,然后有一个结论: l c p ( a l , a r ) = min i = l r − 1 l c p ( a i , a i + 1 ) lcp(a_l,a_r)=\min_{i=l}^{r-1} lcp(a_i,a_{i+1}) lcp(al,ar)=mini=lr−1lcp(ai,ai+1)
我们考虑到最短路会选择最短的,那么我们可以用 d f n dfn dfn相邻的 l c p lcp lcp表示区间的 l c p lcp lcp,具体实现如下:
假设我们已经得到了一些入点和出点(这个例子编号的大小代表 d f n dfn dfn的大小),我们先把相邻的入点和出点连边,边权为 0 0 0:
我们要用 d f n dfn dfn相邻的连边代表所有边,上图应该这样连:
总结一下方法,就是先排序,将相邻的点连边,设这两个点为 ( x , y ) (x,y) (x,y),找到 d f n dfn dfn小于等于 x x x并且最右边的, d f n dfn dfn大于等于 y y y的最左边的,将他们连边,边权为 l c p ( x , y ) lcp(x,y) lcp(x,y)
上面举的例子只讲了入点小的向出点大的连的方法,事实上还有处理反向的(比如我们上面能处理 1 − > 2 1->2 1−>2,但是处理不了 6 − > 2 6->2 6−>2),所以我们还有把这个部分扩大两倍,用类似的方法再做一遍。
然后就直接跑 d i j k s t r a dijkstra dijkstra(事实证明我写不来dij,一直调不出来 ),贴个代码,有注释。
#include
#include
#include
#include
#include
#include
using namespace std;
const int N = 20005;
const int M = 200005;
#define LL long long
#define inf (1ll<<60)
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int T,n,m,k,Index,tot,id,t,f[M],tmp[M],pos[M],dfn[N],dep[N],fa[N][20];
vector<int> rr[M],cc[M],g[M];
LL dp[M];
struct edge
{
int v,c,next;
edge(int V=0,int C=0,int N=0) : v(V), c(C), next(N) {}
} e[M*10];
struct node
{
int u;
LL c;
node(int U=0,LL C=0) : u(U), c(C) {}
bool operator < (const node &B) const
{
return c>B.c;
}
};
void add(int u,int v,int c)
{
e[++tot]=edge(v,c,f[u]),f[u]=tot;
}
void ins(int u,int v,int c,int d)
{
//分两部分,要拆成4个点,13出,24入
pos[++id]=d;
add(t+1,t+2,c);
add(t+1,t+4,c);
add(t+3,t+2,c);
add(t+3,t+4,c);
rr[v].push_back(id);
cc[u].push_back(id);
t+=4;
}
int Abs(int x)
{
return x>0?x:-x;
}
bool cmp(int a,int b)
{
//dfn排序,加了abs是因为下面打了负数标记
return dfn[pos[Abs(a)]]<dfn[pos[Abs(b)]];
}
void dfs(int u,int p)//字典树的预处理
{
dfn[u]=++Index;
fa[u][0]=p;
dep[u]=dep[p]+1;
for(int i=1; i<20; i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=0; i<g[u].size(); i++)
if(g[u][i]^p)
dfs(g[u][i],u);
}
int lca(int u,int v)
{
if(dep[u]<=dep[v]) swap(u,v);
for(int i=19; i>=0; i--)
if(dep[fa[u][i]]>=dep[v])
u=fa[u][i];
if(u==v) return u;
for(int i=19; i>=0; i--)
if(fa[u][i]^fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int get(int u,int v)//lcp
{
return dep[lca(u,v)]-1;
}
void build(int x)
{
if(rr[x].empty() || cc[x].empty()) return ;
sort(rr[x].begin(),rr[x].end(),cmp);
sort(cc[x].begin(),cc[x].end(),cmp);
int len=0,tt;
for(int i=rr[x].size()-1; i>0; i--) add(rr[x][i-1]*4-2,rr[x][i]*4-2,0),add(rr[x][i]*4,rr[x][i-1]*4,0); //相邻的接一起
for(int i=cc[x].size()-1; i>0; i--) add(cc[x][i-1]*4-3,cc[x][i]*4-3,0),add(cc[x][i]*4-1,cc[x][i-1]*4-1,0);
for(int i=0; i<cc[x].size(); i++) tmp[++len]=-cc[x][i]; //存下来排序,打负数标记
for(int i=0; i<rr[x].size(); i++) tmp[++len]=rr[x][i];
sort(tmp+1,tmp+len+1,cmp);
for(int t=1,i=0,j=0; t<len; t++)
{
if(tmp[t]<0) j++,tmp[t]*=-1;
else i++;//用打的标记移动下标(i是入,j是出,都是未达到的)
tt=get(pos[tmp[t]],pos[Abs(tmp[t+1])]);
if(i!=0 && j!=cc[x].size()) add(rr[x][i-1]*4-2,cc[x][j]*4-3,tt);//分成两个部分
if(j!=0 && i!=rr[x].size()) add(rr[x][i]*4,cc[x][j-1]*4-1,tt);
}
}
void dijkstra()
{
priority_queue<node> q;
t++;//多建一个出发点,连1的所有出点
for(int i=cc[1].size()-1; i>=0; i--) add(t,cc[1][i]*4-3,0),add(t,cc[1][i]*4-1,0); //一开始没打等号。。。
memset(dp,0x3f,sizeof dp);
dp[t]=0;
q.push(node(t,0));
while(!q.empty())
{
node t=q.top();
q.pop();
if(t.c>dp[t.u]) continue ;
for(int i=f[t.u]; i; i=e[i].next)
{
int v=e[i].v,c=e[i].c;
if(dp[v]>dp[t.u]+c)
{
dp[v]=dp[t.u]+c;
q.push(node(v,dp[v]));//这里写成c了,还得了25分,样例也没被卡。。。
}
}
}
for(int i=2; i<=n; i++)
{
LL ans=inf;
for(int j=0; j<rr[i].size(); j++)
ans=min(ans,dp[rr[i][j]*4]);
printf("%lld\n",ans);
}
}
int main()
{
T=read();
while(T--)
{
n=read();
m=read();
k=read();
tot=id=t=Index=0;
memset(f,0,sizeof f);
for(int i=1; i<=k; i++) g[i].clear();
for(int i=1; i<=n; i++) rr[i].clear(),cc[i].clear();
for(int i=1; i<=m; i++)
{
int u=read(),v=read(),c=read(),d=read();
ins(u,v,c,d);
}
for(int i=1; i<k; i++)
{
int u=read(),v=read();
read();
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0);
for(int i=2; i<=n; i++)
build(i);//不用建1,因为1直接出发即可,它的字串是最好的
dijkstra();
}
}