洛谷3783 SDOI2017 天才黑客(最短路+虚树+边转点+线段树优化建图)
成功又一次自闭了
怕不是猪国杀之后最自闭的一次
一看到最短路径。
我们就能推测这应该是个最短路题
现在考虑怎么建图
根据题目的意思,我们可以发现,在本题中,边与边之间存在一些转换关系,但是点与点之间并不存在。
那么我们考虑 边转点,点转边。
每一条边拆成两个点,之间连边权的边
新建一个起点\(S\),与\(1\)号点的出边所对应的入点连边
然后根据原图中一个点的入度和出度之间的关系建图。(我们可以将\(LCP\)视为\(trie\)树上的\(LCA\))
最后跑一遍\(dijkstra\),那么对于每个原图中的点的最短路,就相当于所有该点的入边的出点的\(dis\)的最小值。
这样朴素建图是\(O(n^2)\)的
显然是接受不了的。
我们考虑怎么优化这个过程
首先,对于一个点来说,我们只需要考虑包含它的边的\(d\),也就是说,我们可以考虑把这些点拎出来,然后建一颗虚树,那么对于当前点,只有这颗虚树上的点才是有用的。
对于一个点\(x\),他作为转折的贡献,当且仅当他成为两条边的\(d\)的\(LCA\)的时候,那么我们可以将它的每个儿子的子树出点向其他儿子的子树入点连边,表示可以他们之间的代价是\(deep[x]-1\)
哎?子树?貌似子树的\(dfs\)序好像是连续的?是不是可以线段树优化建图的\xyx
没错,我们建立两颗线段树,分别表示区间连单点,和 单点连区间。
让当前点的所有入边的对应点连接区间连单点的对应叶子节点。出边也是类似。
然后新建两个点,分别让区间连和连区间。最后再连接这两个点,这两个点之间的边权是\(deep[x]-1\)
这里需要注意的是,每次建立线段树的点,都要留到最后的\(dijkstra\),所以这里建议结构体封装来做,会比较清晰一些
具体就直接看代码吧.......
真的自闭
\(7.5k\)
细节特别的多
尤其是!要把各个结构体分开,不要顺手写错了
虚树记得自杀式遍历
开的数组要够大
上代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk makr_pair
#define ll long long
#define pa pair<long long,int>
#define int long long
#define index inde
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 2e6+1e2;
const int maxm = 8e6+1e2;
int point[maxn],nxt[maxn],to[maxn];
int cnt,n,m;
int index;
int a[maxn],k;
int siz[maxn];
vector<int> in[101010],out[101010];
int deep[101010],f[101010][21];
int dnf[maxn];
int tt;
int pos[maxn];
void addedge(int x,int y,int w)
{
nxt[++cnt]=point[x];
to[cnt]=y;
point[x]=cnt;
}
void dfs(int x,int fa,int dep)
{
dnf[x]=++tt;
deep[x]=dep;
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if (p==fa) continue;
f[p][0]=x;
dfs(p,x,dep+1);
}
}
void init()
{
for (int j=1;j<=20;j++)
for (int i=1;i<=k;i++)
f[i][j]=f[f[i][j-1]][j-1];
}
int go_up(int x,int d)
{
for (int i=0;i<=20;i++)
{
if ((1 << i) & d)
x=f[x][i];
}
return x;
}
int lca(int x,int y)
{
if (deep[x]>deep[y]) x=go_up(x,deep[x]-deep[y]);
else y=go_up(y,deep[y]-deep[x]);
if (x==y) return x;
for (int i=20;i>=0;i--)
{
if (f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
struct result{
int point[maxn],nxt[maxm],to[maxm];
int cnt;
int val[maxm];
int dis[maxn],vis[maxn];
priority_queue<pa,vector<pa>,greater<pa> > q;
void init()
{
cnt=0;
memset(point,0,sizeof(point));
}
void addedge(int x,int y,int w)
{
nxt[++cnt]=point[x];
to[cnt]=y;
val[cnt]=w;
point[x]=cnt;
}
void dijkstra(int s)
{
memset(dis,127/3,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[s]=0;
q.push(make_pair(0,s));
while (!q.empty())
{
int x = q.top().second;
q.pop();
if (vis[x]) continue;
vis[x]=1;
for (int i=point[x];i;i=nxt[i])
{
int p=to[i];
if (dis[p]>dis[x]+val[i])
{
dis[p]=dis[x]+val[i];
q.push(make_pair(dis[p],p));
}
}
}
}
};
result g;
struct segment{
int f[4*maxn],gg[4*maxn];
int point[maxn],nxt[maxm],to[maxm];
int dfn[maxn],back[maxn],leafout[maxn];
int leafin[maxn];
int tot=0;
int cnt=0;
int size[maxn];
void init(){tot=0;cnt=0;}
void addedge(int x,int y)
{
nxt[++cnt]=point[x];
to[cnt]=y;
point[x]=cnt;
}
void dfs(int x,int fa)
{
dfn[x]=++tot;
size[x]=1;
back[tot]=x;
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if (p==fa) continue;
dfs(p,x);
size[x]+=size[p];
}
}
void buildout(int root,int l,int r)
{
f[root]=++index;
if (l==r)
{
leafout[l]=index;
return;
}
int mid = l+r >> 1;
buildout(2*root,l,mid);
buildout(2*root+1,mid+1,r);
g.addedge(f[2*root],f[root],0);
g.addedge(f[2*root+1],f[root],0);
}
void buildin(int root,int l,int r)
{
gg[root]=++index;
if (l==r)
{
leafin[l]=index;
return;
}
int mid = l+r >> 1;
buildin(2*root,l,mid);
buildin(2*root+1,mid+1,r);
g.addedge(gg[root],gg[2*root],0);
g.addedge(gg[root],gg[2*root+1],0);
}
void updateout(int root,int l,int r,int x,int y,int p)
{
if (x<=l && r<=y)
{
g.addedge(f[root],p,0);
return;
}
int mid = l+r >> 1;
if (x<=mid) updateout(2*root,l,mid,x,y,p);
if (y>mid) updateout(2*root+1,mid+1,r,x,y,p);
}
void updatein(int root,int l,int r,int x,int y,int p)
{
if (x<=l && r<=y)
{
g.addedge(p,gg[root],0);
return;
}
int mid = l+r >> 1;
if (x<=mid) updatein(2*root,l,mid,x,y,p);
if (y>mid) updatein(2*root+1,mid+1,r,x,y,p);
}
void link(int x,int y,int xx,int yy,int w)
{
if (x>y || xx>yy) return;
updateout(1,1,tot,x,y,index+1);
updatein(1,1,tot,xx,yy,index+2);
g.addedge(index+1,index+2,w);
index+=2;
}
void solve(int x)
{
dfs(1,0);
buildout(1,1,tot);buildin(1,1,tot);
for (int i=0;i<in[x].size();i++) g.addedge(in[x][i]+m,leafout[dfn[pos[in[x][i]]]],0);
for (int i=0;i<out[x].size();i++) g.addedge(leafin[dfn[pos[out[x][i]]]],out[x][i],0);
for (int i=1;i<=tot;i++)
{
int now = back[i];
link(dfn[now],dfn[now],dfn[now],dfn[now]+size[now]-1,deep[now]-1);
for (int &j=point[now];j;j=nxt[j])
{
int p = to[j];
link(dfn[p],dfn[p]+size[p]-1,dfn[now],dfn[p]-1,deep[now]-1);
link(dfn[p],dfn[p]+size[p]-1,dfn[p]+size[p],dfn[now]+size[now]-1,deep[now]-1);
}
}
}
};
segment tree;
bool cmp(int a,int b)
{
return dnf[a]<dnf[b];
}
struct xvshu{
int cnt;
int top;
int s[maxn];
void solve(int x)
{
tree.init();
k=0;
for (int i=0;i<in[x].size();i++) a[++k]=pos[in[x][i]];
for (int i=0;i<out[x].size();i++) a[++k]=pos[out[x][i]];
sort(a+1,a+1+k,cmp);
cnt=0;
top=1;
s[top]=1;
for (int i=1;i<=k;i++)
{
if (a[i]==a[i-1]) continue;
int l = lca(a[i],s[top]);
if(l!=s[top])
{
while (top>1)
{
if (dnf[s[top-1]]>dnf[l])
{
tree.addedge(s[top-1],s[top]);
top--;
}
else
{
if(dnf[s[top-1]]==dnf[l])
{
tree.addedge(s[top-1],s[top]);
top--;
break;
}
else
{
tree.addedge(l,s[top]);
s[top]=l;
break;
}
}
}
}
if (s[top]!=a[i]) s[++top]=a[i];
}
while (top>1)
{
tree.addedge(s[top-1],s[top]);
top--;
}
}
};
xvshu xv;
void clear()
{
tree.init();
tt=0;
cnt=0;
memset(point,0,sizeof(point));
for (int i=1;i<=n;i++) in[i].clear(),out[i].clear();
g.init();
tree.init();
}
int t;
signed main()
{
cin>>t;
while (t--)
{
clear();
n=read(),m=read(),k=read();
index=2*m;
for (int i=1;i<=m;i++)
{
int x=read(),y=read(),w=read();
pos[i]=read();
g.addedge(i,i+m,w);
out[x].push_back(i);
in[y].push_back(i);
}
for (int i=1;i<k;i++)
{
int u=read(),v=read(),w=read();
addedge(u,v,w);
addedge(v,u,w);
}
dfs(1,0,1);
init();
for (int i=1;i<=n;i++)
{
if (in[i].size()==0 || out[i].size()==0) continue;
xv.solve(i);
tree.solve(i);
}
++index;
for (int i=0;i<out[1].size();i++)
{
g.addedge(index,out[1][i],0);
}
g.dijkstra(index);
for (int i=2;i<=n;i++)
{
int mn = 1e18;
for (int j=0;j<in[i].size();j++)
{
mn=min(mn,g.dis[in[i][j]+m]);
}
cout<<mn<<"\n";
}
}
return 0;
}
洛谷3783 SDOI2017 天才黑客(最短路+虚树+边转点+线段树优化建图)的更多相关文章
- 洛谷 P3783 - [SDOI2017]天才黑客(前后缀优化建图)
题面传送门 神仙题一道. 首先注意到这里的贡献涉及到边的顺序,并且只与相邻的边是什么有关,因此不难想到一个做法--边转点,点转边,具体来说对于每条边 \(e\),我们将其拆成两个点 \(in_e,ou ...
- 洛谷P3783 [SDOI2017]天才黑客(前后缀优化建图+虚树+最短路)
题面 传送门 题解 去看\(shadowice\)巨巨写得前后缀优化建图吧 话说我似乎连线段树优化建图的做法都不会 //minamoto #include<bits/stdc++.h> # ...
- 【洛谷4482】Border的四种求法(后缀自动机_线段树合并_链分治)
这题我写了一天后交了一发就过了我好兴奋啊啊啊啊啊啊 题目 洛谷 4482 分析 这题明明可以在线做的,为什么我见到的所有题解都是离线啊 -- 什么时候有机会出一个在线版本坑人. 题目的要求可以转化为求 ...
- 【洛谷4770/UOJ395】[NOI2018]你的名字(后缀数组_线段树合并)
题目: 洛谷4770 UOJ395 分析: 一个很好的SAM应用题-- 一句话题意:给定一个字符串\(S\).每次询问给定字符串\(T\)和两个整数\(l\).\(r\),求\(T\)有多少个本质不同 ...
- 洛谷 P7879 -「SWTR-07」How to AK NOI?(后缀自动机+线段树维护矩乘)
洛谷题面传送门 orz 一发出题人(话说我 AC 这道题的时候,出题人好像就坐在我的右侧呢/cy/cy) 考虑一个很 naive 的 DP,\(dp_i\) 表示 \([l,i]\) 之间的字符串是否 ...
- [SDOI2017]天才黑客[最短路、前缀优化建图]
题意 一个 \(n\) 点 \(m\) 边的有向图,还有一棵 \(k\) 个节点的 trie ,每条边上有一个字符串,可以用 trie 的根到某个节点的路径来表示.每经过一条边,当前携带的字符串就会变 ...
- 【SDOI2017】天才黑客(前后缀优化建图 & 最短路)
Description 给定一张有向图,\(n\) 个点,\(m\) 条边.第 \(i\) 条边上有一个边权 \(c_i\),以及一个字符串 \(s_i\). 其中字符串 \(s_1, s_2, \c ...
- BZOJ4912 [Sdoi2017]天才黑客 【虚树 + 最短路】
题目链接 BZOJ4912 题解 转移的代价是存在于边和边之间的 所以把边看做点,跑最短路 但是这样做需要把同一个点的所有入边和所有出边之间连边 \(O(m^2)\)的连边无法接受 需要优化建图 膜一 ...
- 【LG3783】[SDOI2017]天才黑客
[LG3783][SDOI2017]天才黑客 题面 洛谷 题解 首先我们有一个非常显然的\(O(m^2)\)算法,就是将每条边看成点, 然后将每个点的所有入边和出边暴力连边跑最短路,我们想办法优化这里 ...
随机推荐
- Node.js开发博客系统
数据库设计 用户表: id phone password nickname head_img personal_sign level_id create_time update_time is_del ...
- 在python3.6环境下使用cxfreeze打包程序
在python3.6环境下使用cxfreeze打包程序 环境:python3.6 打包程序:aliens_invasion 原本想使用pyintaller 进行打包,使用pip的安装过程也没有问题,打 ...
- 图解最长回文子串「Manacher 算法」,基础思路感性上的解析
问题描述: 给你一个字符串 s,找到 s 中最长的回文子串. 链接:https://leetcode-cn.com/problems/longest-palindromic-substring 「Ma ...
- Python之struct模块
面对网络协议,在组包拆包时,python提供了struct模块,它可以帮助我们在python值和C语言的结构体之间相互转换,下面一起来了解struct的具体用法. 假设,我们的网络协议为消息id(un ...
- MyBatis学习总结(六)——Mybatis3.x与Spring4.x整合
一.搭建开发环境 1.1.使用Maven创建Web项目 执行如下命令: mvn archetype:create -DgroupId=me.gacl -DartifactId=spring4-myba ...
- Python - 面向对象编程 - 魔术方法(双下划线方法)
什么是魔术方法 在Python中,所有以 __ 双下划线包起来的方法,都统称为 Magic Method 魔术方法,也叫双下划线方法 有哪些重要的魔术方法? __new__ https://www.c ...
- Python - typing 模块 —— Union
前言 typing 是在 python 3.5 才有的模块 前置学习 Python 类型提示:https://www.cnblogs.com/poloyy/p/15145380.html 常用类型提示 ...
- Flask(3)- Flask 中的 HTTP 方法
查看 app.route() 源代码 def route(self, rule: str, **options: t.Any) -> t.Callable: """ ...
- java 线程状态 详解
线程被创建后,有一个生命周期,下图是线程的生命周期详解. java api java.lang.Thread.State 这个枚举中给出了六种线程状态,分别是: 线程状态 导致状态发生条件 NEW(新 ...
- aes加解密后续问题contentType不是application/json时候后台解析请求对象request
一.post请求的三种content-type 1.application/x-www-form-urlencoded 主要用于如下:1.1: 最常见的POST提交数据方式.1.2:原生form默认的 ...