题面传送门

神仙题一道。

首先注意到这里的贡献涉及到边的顺序,并且只与相邻的边是什么有关,因此不难想到一个做法——边转点,点转边,具体来说对于每条边 \(e\),我们将其拆成两个点 \(in_e,out_e\),并连边 \(in_e\to out_e\),权值为 \(c_e\),同时对于所有 \(b_i=a_j\) 的边 \(i,j\),连边 \(out_i\to in_j\),权值为 \(dep[\text{LCA}(d_i,d_j)]\),以及对于所有 \(a_i=1\) 的边连 \(S\to in_i\),权值为 \(0\) 的边,跑最短路,最后 \(ans_i=\min\limits_{b_e=i}dis_{out_e}\)。正确性显然。

然而该做法效率低下,边数最高可达到 \(\mathcal O(m^2)\),一脸过不去的亚子。于是又到了优化建图的时间了,一个 observation 是对于一个点 \(u\) 而言,我们在 \(b_i=a_j=u\) 的边 \(i,j\) 之间连边,这些边边权的种类是不会太多的,因为假设 \(S=\{d_i|b_i=u\lor a_i=u\}\),那么这些边权肯定只能是 \(S\) 当中某两个点的 \(\text{LCA}\) 的深度,根据虚树那一套理论,\(S\) 当中任意两点的 \(\text{LCA}\) 总共只有 \(\mathcal O(|S|)\) 种可能。而对于所有点 \(u\),它们对应 \(|S|\) 的总和为 \(\mathcal O(m)\) 级别,这个是在我们能接受的范围内的。因此考虑从这个性质入手优化这道题,我们将 \(S\) 当中的点按 DFS 序排个序,那么根据虚树中一个定理:对于待建立虚树的 \(m\) 个点 \(a_1,a_2,\cdots,a_m\),假设将其按照 DFS 序排序得到 \(b_1,b_2,\cdots,b_m\),那么记 \(h_i=dep[\text{LCA}(a_i,a_{i+1})]\),有 \(dep[\text{LCA}(b_l,b_r)]=\min\limits_{i=l}^{r-1}h_i\)(和 SA 有点像),因此考虑按照套路求出 \(h_i\),那么问题可以转化为:

  • 有一排 \(t\) 个点,每个点有两种类型 \(0/1\),每两个点中间写有一个数 \(h_i\),我们要从所有 \(0\) 点向 \(1\) 点连边,权值为它们之间数的最小值。

这个有一个显然的做法——对于某个 \(h_x\),满足 \(\min\limits_{i=l}^{r-1}h_i=h_x\) 的 \(l,r\) 肯定分别在一段区间内,单调栈求出对应的区间,线段树优化建图即可,但是这个做法多一个 \(\log\),不知道能不能通过,而且实现起来过于麻烦,因此我们还需考虑更简便做法。考虑最短路的一个性质,就是对于两点间如果有多条边的情况,那么显然只有权值最小的边是有用的,其他边可连可不连——连了也不影响你最短路的大小。因此考虑对于某个 \(h_i\),我们不维护什么单调栈,直接从 \([1,i]\) 中的 \(0\) 点向 \([i+1,t]\) 中的 \(1\) 点连边,\([i+1,t]\) 中的 \(0\) 权点向 \([1,i]\) 中的 \(1\) 点连边,根据上面的结论,对于某个 \(l,r\),只有 \([l,r)\) 中 \(h\) 的最小值(设为 \(h_x\))是有用的,其他边连上也没事,而显然在考虑 \(x\) 的贡献时,已经连了 \(l,r\) 之间的边,因此最短路跑出来依然是对的。考虑优化,注意到每次都是某个前缀向后缀,或者后缀向前缀连边,因此考虑前后缀优化建图,具体来说建立四排点——前缀 in,前缀 out,后缀 in,后缀 out,然后随便瞎连连即可,具体见代码。

时间复杂度 \(\mathcal O(Tm\log m)\),然鹅常数非常大,直接上天……

const int MAXN=5e4;
const int MAXK=2e4;
const int LOG_N=16;
const int MAXV=6e5+1;
const int MAXE=1e7;
int n,m,k,S=6e5+1,ncnt;
int hd[MAXV+5],to[MAXE+5],nxt[MAXE+5],val[MAXE+5],ec=0;
void adde(int u,int v,int w){
// printf("%d %d %d\n",u,v,w);
to[++ec]=v;val[ec]=w;nxt[ec]=hd[u];hd[u]=ec;
}
struct edge{int a,b,c,d;} e[MAXN+5];
vector<int> in[MAXN+5],out[MAXN+5],g[MAXK+5];
int dfn[MAXK+5],fa[MAXK+5][LOG_N+2],dep[MAXK+5],tim=0;
void dfs(int x=1,int f=0){
dfn[x]=++tim;fa[x][0]=f;
for(int y:g[x]) dep[y]=dep[x]+1,dfs(y,x);
}
int getlca(int x,int y){
if(dep[x]<dep[y]) x^=y^=x^=y;
for(int i=LOG_N;~i;i--) if(dep[x]-(1<<i)>=dep[y]) x=fa[x][i];
if(x==y) return x;
for(int i=LOG_N;~i;i--) if(fa[x][i]^fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
bool cmp(pii x,pii y){return dfn[x.fi]<dfn[y.fi];}
ll dis[MAXV+5],ans[MAXN+5];
void clear(){
S=6e5+1;ncnt=0;memset(hd,0,sizeof(hd));ec=0;
memset(dfn,0,sizeof(dfn));memset(fa,0,sizeof(fa));
memset(dep,0,sizeof(dep));tim=0;
memset(dis,63,sizeof(dis));memset(ans,63,sizeof(ans));
for(int i=1;i<=MAXN;i++) in[i].clear(),out[i].clear();
for(int i=1;i<=MAXK;i++) g[i].clear();
}
void solve(){
scanf("%d%d%d",&n,&m,&k);clear();ncnt=m<<1;
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&e[i].a,&e[i].b,&e[i].c,&e[i].d);
out[e[i].a].pb(i);in[e[i].b].pb(i);
}
for(int i=1,u,v;i<k;i++) scanf("%d%d%*d",&u,&v),g[u].pb(v);dfs(1,0);
for(int i=1;i<=LOG_N;i++) for(int j=1;j<=k;j++) fa[j][i]=fa[fa[j][i-1]][i-1];
for(int i=1;i<=m;i++) adde(i,i+m,e[i].c);
for(int i=1;i<=m;i++) if(e[i].a==1) adde(S,i,0);
for(int i=1;i<=n;i++){
vector<pii> ed;
for(int x:in[i]) ed.pb(mp(e[x].d,x+m));
for(int x:out[i]) ed.pb(mp(e[x].d,x));
sort(ed.begin(),ed.end(),cmp);
vector<int> h(ed.size(),0);
vector<int> pre_in(ed.size(),0),pre_out(ed.size(),0);
vector<int> suf_in(ed.size(),0),suf_out(ed.size(),0);
for(int j=0;j+1<ed.size();j++) h[j]=dep[getlca(ed[j].fi,ed[j+1].fi)];
// printf("node %d\n",i);
// for(int j=0;j<ed.size();j++) printf("pts %d %d\n",ed[j].fi,ed[j].se);
// for(int j=0;j+1<ed.size();j++) printf("%d\n",h[j]);
for(int j=0;j<ed.size();j++) pre_in[j]=++ncnt;
for(int j=0;j<ed.size();j++) pre_out[j]=++ncnt;
for(int j=0;j<ed.size();j++) suf_in[j]=++ncnt;
for(int j=0;j<ed.size();j++) suf_out[j]=++ncnt;
for(int j=0;j+1<ed.size();j++) adde(pre_in[j+1],pre_in[j],0);
for(int j=0;j+1<ed.size();j++) adde(pre_out[j],pre_out[j+1],0);
for(int j=0;j+1<ed.size();j++) adde(suf_in[j],suf_in[j+1],0);
for(int j=0;j+1<ed.size();j++) adde(suf_out[j+1],suf_out[j],0);
for(int j=0;j<ed.size();j++){
if(ed[j].se<=m) adde(suf_in[j],ed[j].se,0),adde(pre_in[j],ed[j].se,0);
else adde(ed[j].se,suf_out[j],0),adde(ed[j].se,pre_out[j],0);
}
for(int j=0;j+1<ed.size();j++){
adde(suf_out[j+1],pre_in[j],h[j]);
adde(pre_out[j],suf_in[j+1],h[j]);
}
} memset(dis,63,sizeof(dis));memset(ans,63,sizeof(ans));
priority_queue<pii,vector<pii>,greater<pii> > q;
q.push(mp(dis[S]=0,S));
while(!q.empty()){
pii p=q.top();q.pop();
int sum=p.fi,x=p.se;
if(dis[x]<sum) continue;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=val[e];
if(dis[y]>dis[x]+z) q.push(mp(dis[y]=dis[x]+z,y));
}
}
for(int i=1;i<=m;i++) chkmin(ans[e[i].b],dis[i+m]);
for(int i=2;i<=n;i++) printf("%lld\n",ans[i]);
}
int main(){int qu;scanf("%d",&qu);while(qu--) solve();return 0;}

洛谷 P3783 - [SDOI2017]天才黑客(前后缀优化建图)的更多相关文章

  1. 洛谷P3783 [SDOI2017]天才黑客(前后缀优化建图+虚树+最短路)

    题面 传送门 题解 去看\(shadowice\)巨巨写得前后缀优化建图吧 话说我似乎连线段树优化建图的做法都不会 //minamoto #include<bits/stdc++.h> # ...

  2. Codeforces 587D - Duff in Mafia(2-SAT+前后缀优化建图)

    Codeforces 题面传送门 & 洛谷题面传送门 2-SAT hot tea. 首先一眼二分答案,我们二分答案 \(mid\),那么问题转化为,是否存在一个所有边权都 \(\le mid\ ...

  3. 【SDOI2017】天才黑客(前后缀优化建图 & 最短路)

    Description 给定一张有向图,\(n\) 个点,\(m\) 条边.第 \(i\) 条边上有一个边权 \(c_i\),以及一个字符串 \(s_i\). 其中字符串 \(s_1, s_2, \c ...

  4. 洛谷3783 SDOI2017 天才黑客(最短路+虚树+边转点+线段树优化建图)

    成功又一次自闭了 怕不是猪国杀之后最自闭的一次 一看到最短路径. 我们就能推测这应该是个最短路题 现在考虑怎么建图 根据题目的意思,我们可以发现,在本题中,边与边之间存在一些转换关系,但是点与点之间并 ...

  5. 洛谷P3588 [POI2015]PUS(线段树优化建图)

    题面 传送门 题解 先考虑暴力怎么做,我们把所有\(r-l+1-k\)中的点向\(x\)连有向边,表示\(x\)必须比它们大,那么如果这张图有环显然就无解了,否则的话我们跑一个多源最短路,每个点的\( ...

  6. 洛谷 P5331 - [SNOI2019]通信(CDQ 分治优化建图+费用流)

    题面传送门 首先熟悉网络流的同学应该能一眼看出此题的建模方法: 将每个点拆成两个点 \(in_i,out_i\),连一条 \(S\to in_i\),容量为 \(1\) 费用为 \(0\) 的边 连一 ...

  7. Luogu P3783 [SDOI2017]天才黑客

    题目大意 一道码量直逼猪国杀的图论+数据结构题.我猪国杀也就一百来行 首先我们要看懂鬼畜的题意,发现其实就是在一个带权有向图上,每条边有一个字符串信息.让你找一个点出发到其它点的最短路径.听起来很简单 ...

  8. [SDOI2017]天才黑客[最短路、前缀优化建图]

    题意 一个 \(n\) 点 \(m\) 边的有向图,还有一棵 \(k\) 个节点的 trie ,每条边上有一个字符串,可以用 trie 的根到某个节点的路径来表示.每经过一条边,当前携带的字符串就会变 ...

  9. 【LG3783】[SDOI2017]天才黑客

    [LG3783][SDOI2017]天才黑客 题面 洛谷 题解 首先我们有一个非常显然的\(O(m^2)\)算法,就是将每条边看成点, 然后将每个点的所有入边和出边暴力连边跑最短路,我们想办法优化这里 ...

随机推荐

  1. .Net Core微信服务商二次进件

    最近商城进行微信服务商二次进件的开发,大致有几个点 一,服务商签名 二,服务商证书获取 三,图片上传 四,敏感信息加密 五,查询进件状态 除此之外,就是进件信息的拼装 电商二级商户进件申请单-状态流转 ...

  2. vue.$set实现原理

    上源码: export function set (target: Array<any> | Object, key: any, val: any): any { if (process. ...

  3. Spring Cloud Alibaba整合Sentinel

    Spring Cloud Alibaba 整合 Sentinel 一.需求 二.实现步骤 1.下载 sentinel dashboard 2.服务提供者和消费者引入sentinel依赖 3.配置控制台 ...

  4. netty中使用protobuf实现多协议的消息

    在我们使用 netty 的过程中,有时候为了高效的传输数据,经常使用 protobuf 进行数据的传输,netty默认情况下为我们实现的 protobuf 的编解码,但是默认的只能实现单个对象的编解码 ...

  5. Noip模拟37 2021.8.12

    T1 数列 真是考场上不是数学的乱推柿子,想定理,是数学的没想出来.. 比较悲伤... 列柿子不用动脑子,就是没有想出来$EXgcd$解不定方程,淦.. 解处一组解后利用比较显然的性质: $x+\fr ...

  6. 常见SOC启动流程分析

    本文以s5pv210这款SOC为例,分析了其启动流程 在s5pv210的SOC内部,存在着一个内部的ROM和一个内部的RAM 这个内部的ROM叫做 IROM,它是norflash的一种.其不同于板子上 ...

  7. WPF PropertyChanged实现子属性通知

    今天用WPF的View绑定了ViewModel的一个属性类,结果在属性类的子属性修改时,没有通知到UI. 如有要显示一个学生信息,采用WPF MVVM的模式,则前端代码 <StackPanel& ...

  8. Python中的括号()、[]、{}

    长时间不用容易混淆,仅记! 在Python语言中最常见的括号有三种,分别是:小括号().中括号[].花括号{} . Python中的小括号(): 代表tuple元祖数据类型,元祖是一种不可变序列.大多 ...

  9. Huffman算法

    一.Huffman算法介绍 霍夫曼编码(英语:Huffman Coding),又译为哈夫曼编码.赫夫曼编码,是一种用于无损数据压缩的熵编码(权编码)算法.在计算机数据处理中,霍夫曼编码使用变长编码表对 ...

  10. shell调用另一个脚本的三种方式fork/exec/source

    exec和source都属于bash内部命令(builtins commands),在bash下输入man exec或man source可以查看所有的内部命令信息. bash shell的命令分为两 ...