洛谷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)\)算法,就是将每条边看成点, 然后将每个点的所有入边和出边暴力连边跑最短路,我们想办法优化这里 ...
随机推荐
- sql函数大全
sql函数大全 一.内部函数 1.内部合计函数 1)COUNT(*) 返回行数 2)COUNT(DISTINCT COLNAME) 返回指定列中唯一值的个数 3)SUM(COLNAME/EXPRESS ...
- 两种方式配置vue全局方法
目录 1,前言 2,第一种方式 3,第二种方式 1,前言 在Vue项目开发中,肯定会有这样一个场景:在不同的组件页面用到同样的方法,比如格式化时间,文件下载,对象深拷贝,返回数据类型,复制文本等等.这 ...
- Identity基于角色的访问授权
详情访问官方文档 例如,以下代码将访问权限限制为属于角色成员的用户的任何操作 AdministrationController Administrator : [Authorize(Roles = & ...
- k8s架构与组件详解
没有那么多花里胡哨,直接进行一个K8s架构与组件的学习. 一.K8s架构 k8s系统在设计是遵循c-s架构的,也就是我们图中apiserver与其余组件的交互.在生产中通常会有多个Master以实现K ...
- jsPlumb开发流程设计器
前言 jsPlumb是一款开源软件,但jsPlumb toolkit是收费的. 本文主要使用jsPlumb实现一些简单的流程设计功能. 基础学习 首先引入jsplumb.min.js. <scr ...
- Vue个人博客关于标题自动打字机效果Typewriter
最近在写个人Blog 中间看过很多个人博客的开发 一大部分用的是Hexo框架或者vuePress框架 导入各种主题样式插件等等 但是看多了就会发现 很多博主的个人博客基本都很相似 并没有什么新东西呈现 ...
- liunx常见指令
linux目录结构 bin:存储普通命令 sbin:存储超级命令 home:存储普通用户 root:存储超级用户 usr /usr/local:下存储数据或软件,通常软件都放在其中 tmp:临时目录 ...
- select后给字段起别名,where和group后不能用,但having后可以
为什么mysql having的条件表达式可以直接使用select后的别名? SQL语句的语法顺序: FROM -> WHERE -> GROUP BY -> HAVING -> ...
- PHP中用+号连接数组的结果是?
我们在开发中,有时候会将两个数组合并连接起来,这个时候要注意了,千万不要偷懒直接使用+号哦,为什么呢?我们看看以下代码: $a = [1, 2]; $b = [4, 5, 6]; $c = $a + ...
- PHP的HTTP验证
在日常开发中,我们进行用户登录的时候,大部分情况下都会使用 session 来保存用户登录信息,并以此为依据判断用户是否已登录.但其实 HTTP 也提供了这种登录验证机制,我们今天就来学习关于 HTT ...