一、题目
二、解法
真的毒瘤,我 TM 搞了三个小时,调起来太 TM 爽了。
言归正传,这道题很容易想到一个最短路解法,我们把每条边拆成两个点,它们之间的边权值都是原来的边权,然后对于每个点(原图),我们把入点和出点暴力连边,边权为lcp(字典树上lca的深度−1),这个做法可以拿到0分的高分,因为入点和出点相连时边数最多是m2,然后时间复杂度就无力回天了。
考虑优化吧,现在的问题在于边数过多,我们能否减少一些边呢?
官方解法用到了权值是 字典树的lca算的 这一性质,我们可以用虚树的思想,我们先用字典树的dfn将入点和出点排序,然后有一个结论:lcp(al,ar)=mini=lr−1lcp(ai,ai+1)
我们考虑到最短路会选择最短的,那么我们可以用dfn相邻的lcp表示区间的lcp,具体实现如下:
假设我们已经得到了一些入点和出点(这个例子编号的大小代表dfn的大小),我们先把相邻的入点和出点连边,边权为0:
我们要用dfn相邻的连边代表所有边,上图应该这样连:
总结一下方法,就是先排序,将相邻的点连边,设这两个点为(x,y),找到dfn小于等于x并且最右边的,dfn大于等于y的最左边的,将他们连边,边权为lcp(x,y)
上面举的例子只讲了入点小的向出点大的连的方法,事实上还有处理反向的(比如我们上面能处理1−>2,但是处理不了6−>2),所以我们还有把这个部分扩大两倍,用类似的方法再做一遍。
然后就直接跑dijkstra(事实证明我写不来dij,一直调不出来 ),贴个代码,有注释。
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
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();
}
}
C202044zxy
发布了192 篇原创文章 · 获赞 12 · 访问量 3247
私信
关注