洛谷 P3783 - [SDOI2017]天才黑客(前后缀优化建图)
神仙题一道。
首先注意到这里的贡献涉及到边的顺序,并且只与相邻的边是什么有关,因此不难想到一个做法——边转点,点转边,具体来说对于每条边 \(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]天才黑客(前后缀优化建图)的更多相关文章
- 洛谷P3783 [SDOI2017]天才黑客(前后缀优化建图+虚树+最短路)
题面 传送门 题解 去看\(shadowice\)巨巨写得前后缀优化建图吧 话说我似乎连线段树优化建图的做法都不会 //minamoto #include<bits/stdc++.h> # ...
- Codeforces 587D - Duff in Mafia(2-SAT+前后缀优化建图)
Codeforces 题面传送门 & 洛谷题面传送门 2-SAT hot tea. 首先一眼二分答案,我们二分答案 \(mid\),那么问题转化为,是否存在一个所有边权都 \(\le mid\ ...
- 【SDOI2017】天才黑客(前后缀优化建图 & 最短路)
Description 给定一张有向图,\(n\) 个点,\(m\) 条边.第 \(i\) 条边上有一个边权 \(c_i\),以及一个字符串 \(s_i\). 其中字符串 \(s_1, s_2, \c ...
- 洛谷3783 SDOI2017 天才黑客(最短路+虚树+边转点+线段树优化建图)
成功又一次自闭了 怕不是猪国杀之后最自闭的一次 一看到最短路径. 我们就能推测这应该是个最短路题 现在考虑怎么建图 根据题目的意思,我们可以发现,在本题中,边与边之间存在一些转换关系,但是点与点之间并 ...
- 洛谷P3588 [POI2015]PUS(线段树优化建图)
题面 传送门 题解 先考虑暴力怎么做,我们把所有\(r-l+1-k\)中的点向\(x\)连有向边,表示\(x\)必须比它们大,那么如果这张图有环显然就无解了,否则的话我们跑一个多源最短路,每个点的\( ...
- 洛谷 P5331 - [SNOI2019]通信(CDQ 分治优化建图+费用流)
题面传送门 首先熟悉网络流的同学应该能一眼看出此题的建模方法: 将每个点拆成两个点 \(in_i,out_i\),连一条 \(S\to in_i\),容量为 \(1\) 费用为 \(0\) 的边 连一 ...
- Luogu P3783 [SDOI2017]天才黑客
题目大意 一道码量直逼猪国杀的图论+数据结构题.我猪国杀也就一百来行 首先我们要看懂鬼畜的题意,发现其实就是在一个带权有向图上,每条边有一个字符串信息.让你找一个点出发到其它点的最短路径.听起来很简单 ...
- [SDOI2017]天才黑客[最短路、前缀优化建图]
题意 一个 \(n\) 点 \(m\) 边的有向图,还有一棵 \(k\) 个节点的 trie ,每条边上有一个字符串,可以用 trie 的根到某个节点的路径来表示.每经过一条边,当前携带的字符串就会变 ...
- 【LG3783】[SDOI2017]天才黑客
[LG3783][SDOI2017]天才黑客 题面 洛谷 题解 首先我们有一个非常显然的\(O(m^2)\)算法,就是将每条边看成点, 然后将每个点的所有入边和出边暴力连边跑最短路,我们想办法优化这里 ...
随机推荐
- FastAPI 学习之路(五十三)根据环境不同连接不同数据库
在实际的开发过程中,我们数据库,可以根据连接的环境不一样,我们会拆分成不一样的数据库,根据我们所要用的环境来选择对应的数据库即可,那么我们应该如何去实现根据选择去选择不一样的数据库呢. 首先,我们找一 ...
- 【数据结构与算法Python版学习笔记】基本数据结构——列表 List,链表实现
无序表链表 定义 一种数据项按照相对位置存放的数据集 抽象数据类型无序列表 UnorderedList 方法 list() 创建一个新的空列表.它不需要参数,而返回一个空列表. add(item) 将 ...
- 新型活跃Mozi样本分析报告
基本信息 对象 值 文件名 Photo.scr 文件类型 PE32 executable for MS Windows (GUI) Intel 80386 32-bit 文件大小 6271259 by ...
- OO_JAVA_表达式求导_单元总结
OO_JAVA_表达式求导_单元总结 这里引用个链接,是我写的另一份博客,讲的是设计层面的问题,下面主要是对自己代码的单元总结. 程序分析 (1)基于度量来分析自己的程序结构 第一次作业 程序结构大致 ...
- Node.js躬行记(13)——MySQL归档
当前我们组管理着一套审核系统,除了数据源是服务端提供的,其余后台管理都是由我们组在维护. 这个系统就是将APP中的各类社交信息送到后台,然后有专门的审核人员来判断信息是否合规,当然在送到后台之前已经让 ...
- 有向路径检查 牛客网 程序员面试金典 C++ Python
有向路径检查 牛客网 程序员面试金典 C++ Python 题目描述 对于一个有向图,请实现一个算法,找出两点之间是否存在一条路径. 给定图中的两个结点的指针DirectedGraphNode* a, ...
- MarkDown学习随笔
MarkDown语法的学习 标题 设置标题方法是在前面加#号,一级标题(最大)是加#+空格 ,二级标题是加##+空格,之后的以此类推. 字体 在文本的前后分别加上一个星号表示斜体字 在文本的前后分 ...
- 使用Visual Studio 2019将ASP.NET Core发布为linux-arm64程序
前言 前段时间入手了一台树莓派4B,一直闲置未使用,最近工作需要,要在上面跑下.NET Core程序,由于树莓派4B使用的是ARM架构,并且支持64位操作系统,为了充分发挥树莓派性能,我的这台树莓派安 ...
- Jmeter 运行结果的csv文件生成报告
把运行结果保存到本地,下次可以直接用结果生成测试报告. 一.首先保证脚本能正常运行 二.本地创建csv文件,用来保存运行结果 三.察看结果树,选择本地文件(上一步创建好的csv文件),保存运行结果,如 ...
- vue+elementUI中单选框el-radio设置默认值和唯一标识某个单选框
vue+elementUI中单选框el-radio设置默认值 如果后台返回的单选框的值是number:单选框的lable需要设置成 :lable='0';如下: <el-form-item la ...