题面传送门

神仙题一道。

首先注意到这里的贡献涉及到边的顺序,并且只与相邻的边是什么有关,因此不难想到一个做法——边转点,点转边,具体来说对于每条边 \(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. 初学python-day2 字符串格式化1

  2. Redis 高阶数据类型重温

    今天这个专题接着上一篇 Redis 的基本数据类型 继续讲解剩下的高阶数据类型:BitMap.HyperLogLog 和 GEO hash.这些数据结构的底层也都是基于我们前面说的 5 种 基本类型, ...

  3. JDK中的SPI机制

    前言 最近学习类加载的过程中,了解到JDK提供给我们的一个可扩展的接口:java.util.ServiceLoader, 之前自己不了解这个机制,甚是惭愧... 什么是SPI SPI全称为(Servi ...

  4. oo第四单元及期末总结

    一.第四单元作业架构总结 第一次UML作业: 在分析各指令所需要的信息后建立了类(class),操作(operation),属性(Attribute)这几个类用来存储分析后的结果,而接口在本次作业中与 ...

  5. 阿里Nacos部署

    Nacos的部署 一.单机部署 **4.修改 Nacos 存储为 Mysql** 二.集群部署 1.机器部署列表 2.修改 `nacos/conf/application.properties`中的端 ...

  6. 设计模式(1-2)-动态代理(newProxyInstance)

    上节设计模式(1-1)-代理模式,讲了代理模式的静态代理与动态代理的写法.本节,会从Proxy.newProxyInstance() 这个方法开始讲,上一节文末的那个class文件怎么一步步的来的. ...

  7. Linux文件IO操作

    来源:微信公众号「编程学习基地」 目录 文件操作 Linux文件类型 Linux文件权限 修改文件权限 Linux error 获取系统调用时的错误描述 打印错误信息 系统IO函数 open/clos ...

  8. SpringBoot整合Easyexcel操作Excel,闲暇之余,让我们学习更多

    关于封面:晚饭后回自习室的路上 Easyexcel 官方文档 Easyexcel | github 前言 最近也是在写的一个小练习中,需要用到这个.趁着这次就将写个整合的Demo给大家. 希望能够让大 ...

  9. linux下软链接文件的拷贝

    最近在编译libnl库准备拷贝到其他机器中使用的时候出现无法拷贝问题,原因是sd卡是fat32文件系统格式,这种文件系统不支持linux下的ln软链接文件, void@void-ThinkPad-E4 ...

  10. 把字符串转换成整数 牛客网 剑指Offer

    把字符串转换成整数 牛客网 剑指Offer 题目描述 将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串 ...