BZOJ4912 SDOI2017天才黑客(最短路+虚树)
容易想到把边当成点重建图跑最短路。将每条边拆成入边和出边,作为新图中的两个点,由出边向入边连边权为原费用的边。对于原图中的每个点,考虑由其入边向出边连边。直接暴力两两连边当然会被卡掉,注意到其边权是trie上lca的深度,由lca转rmq的做法可知,两点lca即为欧拉序区间中它们之间深度最小的点,于是跑出欧拉序后对入边出边的前后缀建虚点连边即可。当然每次连边时都需要将trie上有用的点提取出来,建虚树即可。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define N 50010
#define inf 2000000000
#define in(i) (i*2+n)
#define out(i) (i*2+n-1)
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
int read()
{
int x=0,f=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
int T,n,m,k,p[N],t;
struct data{int to,nxt,len,s;
}edge[N];
vector<int> in_edge[N];
namespace trie
{
int p[N],t,fa[N][18],deep[N],dfn[N],cnt;
struct data{int to,nxt;}edge[N];
void clear(){memset(p,0,sizeof(p));cnt=t=0;}
void addedge(int x,int y){t++;edge[t].to=y,edge[t].nxt=p[x],p[x]=t;}
void dfs(int k)
{
dfn[k]=++cnt;
for (int i=p[k];i;i=edge[i].nxt)
{
deep[edge[i].to]=deep[k]+1;
fa[edge[i].to][0]=k;
dfs(edge[i].to);
}
}
void build()
{
fa[1][0]=1;dfs(1);
for (int j=1;j<18;j++)
for (int i=1;i<=k;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
}
int lca(int x,int y)
{
if (deep[x]<deep[y]) swap(x,y);
for (int j=17;~j;j--) if (deep[fa[x][j]]>=deep[y]) x=fa[x][j];
if (x==y) return x;
for (int j=17;~j;j--) if (fa[x][j]!=fa[y][j]) x=fa[x][j],y=fa[y][j];
return fa[x][0];
}
}
namespace graph
{
int p[N<<6],t,cnt,dis[N<<6];
bool flag[N<<6];
struct data{int to,nxt,len;}edge[N<<7];
struct data2
{
int x,d;
bool operator <(const data2&a) const
{
return d>a.d;
}
};
priority_queue<data2> q;
void addedge(int x,int y,int z){t++;edge[t].to=y,edge[t].nxt=p[x],edge[t].len=z,p[x]=t;}
void clear(){cnt=n+m*2;t=0;memset(p,0,sizeof(p));}
void dijkstra()
{
for (int i=1;i<=cnt;i++) dis[i]=inf;dis[1]=0;
memset(flag,0,sizeof(flag));
q.push((data2){1,0});
for (;;)
{
while (!q.empty()&&flag[q.top().x]) q.pop();
if (q.empty()) break;
data2 x=q.top();q.pop();
flag[x.x]=1;
for (int i=p[x.x];i;i=edge[i].nxt)
if (dis[x.x]+edge[i].len<dis[edge[i].to])
{
dis[edge[i].to]=dis[x.x]+edge[i].len;
q.push((data2){edge[i].to,dis[edge[i].to]});
}
}
}
}
namespace virtual_tree
{
int a[N],tot,stk[N],id[N<<1],top,p[N],x[N],y[N],idin[N<<1],idout[N<<1],pre[N<<1],suf[N<<1],t,cnt;
struct data{int to,nxt;}edge[N<<1];
void addedge(int u,int v){t++;x[t]=u,y[t]=v;}
void clear(){tot=top=t=cnt=0;}
void push(int x){a[++tot]=x;}
bool cmp(const int&a,const int&b)
{
return trie::dfn[a]<trie::dfn[b];
}
void dfs(int k)
{
id[++cnt]=k;idin[k]=graph::cnt+cnt;
for (int i=p[k];i;i=edge[i].nxt)
{
dfs(edge[i].to);
id[++cnt]=k;
}
}
void build()
{
if (tot==0) return;
sort(a+1,a+tot+1,cmp);
tot=unique(a+1,a+tot+1)-a-1;
stk[++top]=1;
for (int i=1+(a[1]==1);i<=tot;i++)
{
int l=trie::lca(a[i],stk[top]);
if (stk[top]!=l)
{
while (top>1&&trie::deep[stk[top-1]]>=trie::deep[l]) addedge(stk[top-1],stk[top]),top--;
if (stk[top]!=l) addedge(l,stk[top]);
stk[top]=l;
}
stk[++top]=a[i];
}
while (top>1) addedge(stk[top-1],stk[top]),top--;
for (int i=1;i<=t;i++) p[x[i]]=p[y[i]]=0;
for (int i=1;i<=t;i++) edge[i].to=y[i],edge[i].nxt=p[x[i]],p[x[i]]=i;
dfs(1);for (int i=1;i<=cnt;i++) idout[id[i]]=idin[id[i]]+cnt;graph::cnt+=cnt<<1;
for (int i=1;i<=cnt;i++)
{
pre[i]=++graph::cnt;
graph::addedge(idin[id[i]],pre[i],0);
if (i>1) graph::addedge(pre[i-1],pre[i],0);
}
for (int i=cnt;i>=1;i--)
{
suf[i]=++graph::cnt;
graph::addedge(suf[i],idout[id[i]],0);
if (i<cnt) graph::addedge(suf[i],suf[i+1],0);
}
for (int i=1;i<=cnt;i++) graph::addedge(pre[i],suf[i],trie::deep[id[i]]);
for (int i=1;i<=cnt;i++)
{
pre[i]=++graph::cnt;
graph::addedge(pre[i],idout[id[i]],0);
if (i>1) graph::addedge(pre[i],pre[i-1],0);
}
for (int i=cnt;i>=1;i--)
{
suf[i]=++graph::cnt;
graph::addedge(idin[id[i]],suf[i],0);
if (i<cnt) graph::addedge(suf[i+1],suf[i],0);
}
for (int i=1;i<=cnt;i++) graph::addedge(suf[i],pre[i],trie::deep[id[i]]);
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("bzoj4912.in","r",stdin);
freopen("bzoj4912.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
T=read();
while (T--)
{
n=read(),m=read(),k=read();
memset(p,0,sizeof(p));t=0;
for (int i=1;i<=n;i++) in_edge[i].clear();
for (int i=1;i<=m;i++)
{
int x=read(),y=read(),len=read(),s=read();
t++;edge[t].to=y,edge[t].nxt=p[x],edge[t].len=len,edge[t].s=s,p[x]=t;
}
trie::clear();
for (int i=1;i<k;i++)
{
int x=read(),y=read(),z=read();
trie::addedge(x,y);
}
trie::build();
graph::clear();
for (int i=p[1];i;i=edge[i].nxt)
graph::addedge(1,out(i),0);
for (int i=1;i<=m;i++) if (edge[i].to!=1) graph::addedge(in(i),edge[i].to,0);
for (int i=1;i<=m;i++) graph::addedge(out(i),in(i),edge[i].len);
for (int i=1;i<=m;i++) in_edge[edge[i].to].push_back(i);
for (int i=1;i<=n;i++)
{
virtual_tree::clear();
for (int j=0;j<in_edge[i].size();j++) virtual_tree::push(edge[in_edge[i][j]].s);
for (int j=p[i];j;j=edge[j].nxt) virtual_tree::push(edge[j].s);
virtual_tree::build();
for (int j=0;j<in_edge[i].size();j++) graph::addedge(in(in_edge[i][j]),virtual_tree::idin[edge[in_edge[i][j]].s],0);
for (int j=p[i];j;j=edge[j].nxt) graph::addedge(virtual_tree::idout[edge[j].s],out(j),0);
}
graph::dijkstra();
for (int i=2;i<=n;i++) printf("%d\n",graph::dis[i]);
}
return 0;
}
BZOJ4912 SDOI2017天才黑客(最短路+虚树)的更多相关文章
- [LOJ#2270][BZOJ4912][SDOI2017]天才黑客
[LOJ#2270][BZOJ4912][SDOI2017]天才黑客 试题描述 SD0062 号选手小 Q 同学为了偷到 SDOI7012 的试题,利用高超的黑客技术潜入了 SDOI 出题组的内联网的 ...
- BZOJ4912 [Sdoi2017]天才黑客 【虚树 + 最短路】
题目链接 BZOJ4912 题解 转移的代价是存在于边和边之间的 所以把边看做点,跑最短路 但是这样做需要把同一个点的所有入边和所有出边之间连边 \(O(m^2)\)的连边无法接受 需要优化建图 膜一 ...
- BZOJ4912 : [Sdoi2017]天才黑客
建立新图,原图中每条边在新图中是点,点权为$w_i$,边权为两个字符串的LCP. 对字典树进行DFS,将每个点周围一圈边对应的字符串按DFS序从小到大排序. 根据后缀数组利用height数组求LCP的 ...
- [SDOI2017]天才黑客[最短路、前缀优化建图]
题意 一个 \(n\) 点 \(m\) 边的有向图,还有一棵 \(k\) 个节点的 trie ,每条边上有一个字符串,可以用 trie 的根到某个节点的路径来表示.每经过一条边,当前携带的字符串就会变 ...
- 【BZOJ4912】天才黑客(最短路,虚树)
[BZOJ4912]天才黑客(最短路,虚树) 题面 BZOJ 洛谷 题解 \(Anson\)爷讲过的题目,然而我还是不会做 只有照着\(zsy\)的程序打我才会做....果然太弱了. 这道题目显然是把 ...
- 【LG3783】[SDOI2017]天才黑客
[LG3783][SDOI2017]天才黑客 题面 洛谷 题解 首先我们有一个非常显然的\(O(m^2)\)算法,就是将每条边看成点, 然后将每个点的所有入边和出边暴力连边跑最短路,我们想办法优化这里 ...
- 洛谷3783 SDOI2017 天才黑客(最短路+虚树+边转点+线段树优化建图)
成功又一次自闭了 怕不是猪国杀之后最自闭的一次 一看到最短路径. 我们就能推测这应该是个最短路题 现在考虑怎么建图 根据题目的意思,我们可以发现,在本题中,边与边之间存在一些转换关系,但是点与点之间并 ...
- [SDOI2017]天才黑客
题目大意 给一张有向图,再给一颗字典树,有向图上的每条边有一个非负边权还有一个字典树上的字符串,从一条边到另一条边的代价是那条边的边权和这两个字符串的最长公共前缀,问从1到其他点的最短路. 题解 一看 ...
- Luogu P3783 [SDOI2017]天才黑客
题目大意 一道码量直逼猪国杀的图论+数据结构题.我猪国杀也就一百来行 首先我们要看懂鬼畜的题意,发现其实就是在一个带权有向图上,每条边有一个字符串信息.让你找一个点出发到其它点的最短路径.听起来很简单 ...
随机推荐
- [翻译] ASP.NET Core 2.1.0 发布
原文: ASP.NET Core 2.1.0 now available 今天,我们很高兴可以发布 ASP.NET Core 2.1.0!这是我们 .NET平台下开源的.跨平台的 Web 框架的最新版 ...
- Python—re模块
re模块 正则表达式就是字符串的匹配规则,在多数编程语言里都有相应的支持,python里对应的模块是re 常用的表达式规则 '.' 默认匹配除\n之外的任意一个字符,若指定flag DOTALL,则匹 ...
- java中的代码块是什么意思,怎么用
代码块是一种常见的代码形式.他用大括号“{}”将多行代码封装在一起,形成一个独立的代码区,这就构成了代码块.代码块的格式如下: 方法/步骤 普通代码块:是最常见的代码块,在方法里用一对“{ ...
- Linux之磁盘挂载
1.查看磁盘分区情况: fdisk -l 可以看到,红框中的硬盘没有分区. 2.开始分区: fdisk /dev/vdb 3.格式化分区: mkfs.xfs 分区名 4.挂载磁盘 挂载方式1: 手动挂 ...
- 【学习总结】Git学习-参考廖雪峰老师教程四-时光机穿梭
学习总结之Git学习-总 目录: 一.Git简介 二.安装Git 三.创建版本库 四.时光机穿梭 五.远程仓库 六.分支管理 七.标签管理 八.使用GitHub 九.使用码云 十.自定义Git 期末总 ...
- this is incompatible with sql_mode=only_full_group_by
mysql命令gruop by报错this is incompatible with sql_mode=only_full_group_by - Jim_.NET - 博客园 http://www.c ...
- jmeter的jtl日志转html报告常见报错笔记
问题:生成的jmeter文件可以放任意位置 输入命令转换hmtl报告 PS D:\user\80003288\桌面\Ques> jmeter -g .\test1.jtl -e -o .\rep ...
- js-其他跨域技术(JSONP`Comet)
###1. JSONP JSONP由两部分组成:回调函数和数据 JSONP是通过动态<script>元素来使用的,使用时可以为src属性指定一个跨域URL eg: function ha ...
- vue实现双向数据绑定之Object.defineProperty()篇
前言 vue.js中使用ES5的Object.defineProperty()实现数据的双向绑定 Object.defineProperty()原理 Object.defineProperty()可以 ...
- [转帖]IP地址、子网掩码、网络号、主机号、网络地址、主机地址以及ip段/数字-如192.168.0.1/24是什么意思?
IP地址.子网掩码.网络号.主机号.网络地址.主机地址以及ip段/数字-如192.168.0.1/24是什么意思? 2016年03月26日 23:38:50 JeanCheng 阅读数:105674 ...