Luogu P3783 [SDOI2017]天才黑客
题目大意
一道码量直逼猪国杀的图论+数据结构题。我猪国杀也就一百来行
首先我们要看懂鬼畜的题意,发现其实就是在一个带权有向图上,每条边有一个字符串信息。让你找一个点出发到其它点的最短路径。听起来很简单,手速码完Dijkstra
然而这题中除了路径上的边权和还要加上去的就是一条路径上所有边代表的字符串的LCP,还有一点就是所有的字符串都是一棵字典树上的路径。
问题有点复杂啊,那我们慢慢分析。
化边为点
考虑到如果我们在跑最短路的时候记录每个点过来的状态就会不可避免的扩出\(O(n^2)\)条边,直接GG
仔细一想这道题最难的部分在哪?其实还是求LCP部分,所以显然边的重要性比点大,因此我们化边为点
具体的说,我们把原先的每条边拆成一个入点一个出点,在这两点之间连边表示原来的边权。
那么现在我们只需要在这些边化为的点直接连接边权为两字符串LCP的边即可(从一个出点连至一个入点)
构建虚树
那么现在我们就要找到原来每个点,将所有与其相连边所需要的节点(不管是进入还是出去)之间的边互相连接。
乍一看这样的点数多的爆炸,但是仔细一想,总点数再怎么多也不会超过\(2m\)啊!
所以我们对这些点构造一颗虚树(不会虚树的左转Luogu P2495 [SDOI2011]消耗战)
此时虚树上的点就是它子树内的点(包括他本身)之间的LCA,所以我们只要在这些点对之间连边,边权为这个点的\(dep-1\)(减不减\(1\)根据个人写法不同)
优化建边
像前面说的那样,直接两两暴力连边边数还是平方级别的。
我们发现这些点都要经过LCA,所以可以考虑以LCA为中转点建虚点连边。(不会建虚点的左转Luogu P1983 车站分级)
然后每次连边的边数是\(O(n)\)的,但是有由于有多个点,所以总的边数还是\(O(n^2)\)。优化了半天却好似放屁
但是我们发现这个时候我们连的边相当于是从某个子树连到某个点,再从这个点连到某个子树中
子树的一个经典性质,相信我不说大家都知道:DFS序连续。
所以问题变成区间向点,点向区间连边。这个由于我比较菜(不会前后缀优化建图),同时这题的数据范围不大,因此我们可以用最经典的线段树优化建图(不会的左转Luogu P3588 [POI2015]PUS)
那么我们只需要同时维护一棵出线段树和一棵入线段树即可
总结&&CODE
最后问题回归到最短路问题,那么这题就被我们轻松艰难的切掉了。
考虑复杂度,由于有线段树的存在总边数是\(O(n\log n)\)的(认为\(n,m\)同阶)
算上Dijkstra的一个\(\log\)复杂度为\(O(n\log^2n)\)
千万注意码的时候要注意自己在干什么,不要不小心把自己码晕了。
附上200+的CODE
// luogu-judger-enable-o2
#include<cstdio>
#include<cctype>
#include<queue>
#include<vector>
#include<cstring>
#include<algorithm>
#define RI register int
#define pb push_back
#define Ms(f,x) memset(f,x,sizeof(f))
#define Tp template <typename T>
using namespace std;
typedef long long LL;
const int N=50005,K=20005;
int t,n,m,k,dep[K],dfn[K],rv[2][K],tot;
class FileInputOutput
{
private:
#define S 1<<21
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
#define pc(ch) (Ftop<S?Fout[Ftop++]=ch:(fwrite(Fout,1,S,stdout),Fout[(Ftop=0)++]=ch))
char Fin[S],Fout[S],*A,*B; int Ftop,pt[25];
public:
Tp inline void read(T &x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
Tp inline void write(T x)
{
if (!x) return (void)(pc('0'),pc('\n')); RI ptop=0;
while (x) pt[++ptop]=x%10,x/=10; while (ptop) pc(pt[ptop--]+48); pc('\n');
}
inline void Fend(void)
{
fwrite(Fout,1,Ftop,stdout);
}
#undef S
#undef tc
#undef pc
}F;
inline bool cmp(int x,int y)
{
return dfn[x]<dfn[y];
}
class LCA_Solver
{
private:
#define P 15
struct edge
{
int to,nxt;
}e[K]; int head[K],cnt,idx,anc[K][P],x,y,z;
inline void add(int x,int y)
{
e[++cnt]=(edge){y,head[x]}; head[x]=cnt;
}
inline void reset(int now)
{
for (RI i=0;i<P-1;++i) anc[now][i+1]=anc[anc[now][i]][i];
}
#define to e[i].to
inline void DFS(int now,int fa)
{
dep[now]=dep[fa]+1; reset(now); dfn[now]=++idx;
for (RI i=head[now];i;i=e[i].nxt) anc[to][0]=now,DFS(to,now);
}
#undef to
inline void swap(int &x,int &y)
{
int t=x; x=y; y=t;
}
public:
inline void init(void)
{
cnt=idx=0; Ms(head,0); for (RI i=1;i<k;++i)
F.read(x),F.read(y),F.read(z),add(x,y); DFS(1,0);
}
inline int query(int x,int y)
{
RI i; if (dep[x]<dep[y]) swap(x,y); for (i=P-1;~i;--i)
if (dep[anc[x][i]]>=dep[y]) x=anc[x][i]; if (x==y) return x;
for (i=P-1;~i;--i) if (anc[x][i]!=anc[y][i])
x=anc[x][i],y=anc[y][i]; return anc[x][0];
}
}L;
class Graph_Solver
{
private:
#define NS 1000005
#define MS 4000005
struct edge
{
int to,nxt,v;
}e[MS]; int head[NS],cnt,pos[NS],st,a,b,d,pcnt,p[N<<1]; bool vis[NS]; LL dis[NS],c;
int num[2][K<<2],stack[K],top,father[K],size[K],ndfn[K];;
struct data
{
LL val; int id;
inline data(LL Val=0,int Id=0) { val=Val; id=Id; }
inline friend bool operator <(data A,data B)
{
return A.val>B.val;
}
}; priority_queue <data> small; vector <int> v[N][2];
inline void add(int x,int y,int z)
{
e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
}
inline void build(int opt,int now,int l,int r)
{
num[opt][now]=++tot; if (l==r) return (void)(rv[opt][p[l]]=tot);
int mid=l+r>>1; build(opt,now<<1,l,mid); build(opt,now<<1|1,mid+1,r);
if (!opt) add(num[opt][now<<1],num[opt][now],0),add(num[opt][now<<1|1],num[opt][now],0);
else add(num[opt][now],num[opt][now<<1],0),add(num[opt][now],num[opt][now<<1|1],0);
}
#define O beg,end,id,val
inline void link(int opt,int now,int l,int r,int beg,int end,int id,int val)
{
if (l>=beg&&r<=end) { if (!opt) add(num[opt][now],id,val); else add(id,num[opt][now],val); return; }
int mid=l+r>>1; if (beg<=mid) link(opt,now<<1,l,mid,O); if (end>mid) link(opt,now<<1|1,mid+1,r,O);
}
#undef O
inline void VT_build(void)
{
RI i; father[stack[top=1]=p[1]]=0;
int t=pcnt; for (i=2;i<=t;++i)
{
int fa=L.query(stack[top],p[i]);
while (top&&dep[stack[top]]>dep[fa])
{
if (top==1||dep[stack[top-1]]<=dep[fa])
father[stack[top]]=fa; --top;
}
if (fa!=stack[top]) father[fa]=stack[top],stack[++top]=fa,p[++pcnt]=fa;
stack[++top]=p[i]; father[p[i]]=fa;
}
sort(p+1,p+pcnt+1,cmp); for (i=1;i<=pcnt;++i) size[p[i]]=1;
for (i=pcnt;i;--i) size[father[p[i]]]+=size[p[i]],ndfn[p[i]]=i;
for (build(0,1,1,pcnt),build(1,1,1,pcnt),i=1;i<=pcnt;++i)
{
int now=p[i]; ++tot; link(0,1,1,pcnt,i,i,tot,dep[now]-1);
link(1,1,1,pcnt,i,i+size[now]-1,tot,0); int fa=father[now]; if (!fa) continue;
++tot; link(0,1,1,pcnt,i,i+size[now]-1,tot,dep[fa]-1);
if (ndfn[fa]<=i-1) link(1,1,1,pcnt,ndfn[fa],i-1,tot,0);
if (i+size[now]<=ndfn[fa]+size[fa]-1) link(1,1,1,pcnt,i+size[now],ndfn[fa]+size[fa]-1,tot,0);
}
}
Tp inline void miner(T &x,T y)
{
if (y<x) x=y;
}
public:
inline void init(void)
{
RI i; for (i=1;i<=n;++i) v[i][0].clear(),v[i][1].clear();
for (cnt=tot=0,Ms(head,0),i=1;i<=m;++i)
{
F.read(a); F.read(b); F.read(c); F.read(d);
pos[tot+1]=d; pos[tot+2]=d; v[a][1].pb(tot+1); v[b][0].pb(tot+2);
add(tot+1,tot+2,c); tot+=2;
}
pos[st=++tot]=1; v[1][0].pb(st);
}
inline void build(void)
{
RI j,k; int lim; for (RI i=1;i<=n;++i)
{
for (pcnt=k=0;k<2;++k)
for (lim=v[i][k].size(),j=0;j<lim;++j)
p[++pcnt]=pos[v[i][k][j]];
sort(p+1,p+pcnt+1); pcnt=unique(p+1,p+pcnt+1)-p-1;
sort(p+1,p+pcnt+1,cmp); VT_build();
for (k=0;k<2;++k) for (lim=v[i][k].size(),j=0;j<lim;++j)
{
int t=v[i][k][j]; if (!k) add(t,rv[k][pos[t]],0); else add(rv[k][pos[t]],t,0);
}
}
}
#define to e[i].to
inline void Dijkstra(void)
{
Ms(dis,63); Ms(vis,0); small.push(data(dis[st]=0,st));
while (!small.empty())
{
int now=small.top().id; small.pop();
if (vis[now]) continue; vis[now]=1;
for (RI i=head[now];i;i=e[i].nxt)
if (dis[to]>dis[now]+e[i].v)
small.push(data(dis[to]=dis[now]+e[i].v,to));
}
}
#undef to
inline void print(void)
{
for (RI i=2;i<=n;++i)
{
int lim=v[i][0].size(); LL ans=1e18;
for (RI j=0;j<lim;++j) miner(ans,dis[v[i][0][j]]);
F.write(ans);
}
}
}G;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (F.read(t);t;--t)
{
F.read(n); F.read(m); F.read(k); G.init();
L.init(); G.build(); G.Dijkstra(); G.print();
}
return F.Fend(),0;
}
Luogu P3783 [SDOI2017]天才黑客的更多相关文章
- 洛谷P3783 [SDOI2017]天才黑客(前后缀优化建图+虚树+最短路)
题面 传送门 题解 去看\(shadowice\)巨巨写得前后缀优化建图吧 话说我似乎连线段树优化建图的做法都不会 //minamoto #include<bits/stdc++.h> # ...
- 洛谷 P3783 - [SDOI2017]天才黑客(前后缀优化建图)
题面传送门 神仙题一道. 首先注意到这里的贡献涉及到边的顺序,并且只与相邻的边是什么有关,因此不难想到一个做法--边转点,点转边,具体来说对于每条边 \(e\),我们将其拆成两个点 \(in_e,ou ...
- [LOJ#2270][BZOJ4912][SDOI2017]天才黑客
[LOJ#2270][BZOJ4912][SDOI2017]天才黑客 试题描述 SD0062 号选手小 Q 同学为了偷到 SDOI7012 的试题,利用高超的黑客技术潜入了 SDOI 出题组的内联网的 ...
- 【LG3783】[SDOI2017]天才黑客
[LG3783][SDOI2017]天才黑客 题面 洛谷 题解 首先我们有一个非常显然的\(O(m^2)\)算法,就是将每条边看成点, 然后将每个点的所有入边和出边暴力连边跑最短路,我们想办法优化这里 ...
- [SDOI2017]天才黑客
题目大意 给一张有向图,再给一颗字典树,有向图上的每条边有一个非负边权还有一个字典树上的字符串,从一条边到另一条边的代价是那条边的边权和这两个字符串的最长公共前缀,问从1到其他点的最短路. 题解 一看 ...
- [SDOI2017]天才黑客[最短路、前缀优化建图]
题意 一个 \(n\) 点 \(m\) 边的有向图,还有一棵 \(k\) 个节点的 trie ,每条边上有一个字符串,可以用 trie 的根到某个节点的路径来表示.每经过一条边,当前携带的字符串就会变 ...
- BZOJ4912 SDOI2017天才黑客(最短路+虚树)
容易想到把边当成点重建图跑最短路.将每条边拆成入边和出边,作为新图中的两个点,由出边向入边连边权为原费用的边.对于原图中的每个点,考虑由其入边向出边连边.直接暴力两两连边当然会被卡掉,注意到其边权是t ...
- BZOJ4912 : [Sdoi2017]天才黑客
建立新图,原图中每条边在新图中是点,点权为$w_i$,边权为两个字符串的LCP. 对字典树进行DFS,将每个点周围一圈边对应的字符串按DFS序从小到大排序. 根据后缀数组利用height数组求LCP的 ...
- BZOJ4912 [Sdoi2017]天才黑客 【虚树 + 最短路】
题目链接 BZOJ4912 题解 转移的代价是存在于边和边之间的 所以把边看做点,跑最短路 但是这样做需要把同一个点的所有入边和所有出边之间连边 \(O(m^2)\)的连边无法接受 需要优化建图 膜一 ...
随机推荐
- Android为TV端助力 布局、绘制、内存泄露、响应速度、listview和bitmap、线程优化以及一些优化的建议!
1.布局优化 首先删除布局中无用的控件和层级,其次有选择地使用性能较低的viewgroup,比如布局中既可以使用RelativeLayout和LinearLayout,那我们就采用LinearLayo ...
- Android为TV端助力 转载弩的博客
Android.mk简介:Android.mk文件用来告知NDK Build 系统关于Source的信息. Android.mk将是GNU Makefile的一部分,且将被Build System解析 ...
- Android IPC机制(四)用ContentProvider进行进程间通信
前言 ContentProvider为存储和获取数据提供统一的接口,它可以在不同的应用程序之间共享数据,本身就是适合进程间通信的.ContentProvider底层实现也是Binder,但是使用起来比 ...
- (python)数据结构---字符串
一.概述 由一个个字符组成的有序序列. 使用单引号.双引号.三引号引住的字符序列. 不可变.线性的数据结构. 二.字符串的相关操作 1.元素访问----下标 字符串是线性的数据结构,可以使用索引去访问 ...
- Keras实现卷积神经网络
# -*- coding: utf-8 -*- """ Created on Sun Jan 20 11:25:29 2019 @author: zhen "& ...
- [20190130]删除tab$记录的恢复2.txt
[20190130]删除tab$记录的恢复2.txt --//前面链接写好了脚本,开始测试删除后的恢复.千万不要在生产系统做这样的测试!!--//参考链接:http://blog.itpub.net/ ...
- javascript 重要属性之prototype(继承)
转载猫猫小屋 http://www.maomao365.com/?p=831 在javascript中每一个函数都拥有 prototype属性,在javascript中使用prototype,可以向已 ...
- Spring MVC 请求映射 (二)
完整的项目案例: springmvc.zip 目录 实例 项目结构: 一.配置web.xml <?xml version="1.0" encoding="UTF-8 ...
- SQL Server 一致性读
我们在Oracle和MySQL数据库中已经对一致性读的概念比较熟悉了,但是在SQL Server中却鲜少提及,但SQL Server自2005版本以来其实也实现了一致性读,几乎所有关系型数据库产品的一 ...
- Linux进程调度器概述--Linux进程的管理与调度(十五)
调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换. 1 背景知识 1.1 什么是调度器 ...