【BZOJ】1576 [Usaco2009 Jan]安全路经Travel
【算法】最短路树+(树链剖分+线段树)||最短路树+并查集
【题解】
两种方法的思想是一样的,首先题目限制了最短路树唯一。
那么建出最短路树后,就是询问对于每个点断掉父边后重新找路径的最小值,其它路径只能是这个点和其子树节点通过非树边到达非子树节点。
这样考虑很难统计,换个角度考虑每条非树边的影响。
一条非树边连接两个端点u,v,它们会有一个LCA,那么这条非树边就可以影响u~LCA和v~LCA两条链上的点。
这样依然不方便统计,因为两条链上每个点的影响各不相同,所以使用差分的思想。
定义一条非树边对两条链上的点的贡献为g[i]=dis[u]+dis[v]+e[i].w,那么对于两条链上的每个点就是ans[x]=min(ans[x],g[i]-dis[x]),因为dis[x]是每个点自身属性,那么就可以统一地对两条链上上的点赋值g[i]。
现在,我们可以明确每条非树边对特定的两条边的贡献,那么显然可以用树链剖分+线段树对两条链上的点进行【区间最小值覆盖+单点查询最小值】,这一操作可以用标记永久化实现。
考虑另一种写法,如果我们把非树边按贡献排序,那么贡献小的覆盖之后,贡献大的就不可能影响到这些被覆盖过的点了,那么可以将覆盖过的点用并查集合并为一个点,遇到直接跳。
复杂度O(m log n)。
<并查集>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cctype>
#include<cstring>
using namespace std;
const int maxn=,inf=0x3f3f3f3f;
struct edge{int u,v,w,from;}e[maxn*];
int n,m,cnt,ans[maxn],first[maxn],tot,fa[maxn],f[maxn],deep[maxn],dis[maxn],d[maxn],c[maxn];
int read()
{
char c;int s=,t=;
while(!isdigit(c=getchar()))if(c=='-')t=-;
do{s=s*+c-'';}while(isdigit(c=getchar()));
return s*t;
}
void insert(int u,int v,int w)
{tot++;e[tot].u=u;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
struct cyc{
int x,d;
bool operator < (const cyc &a)const{
return d>a.d;
}
};
priority_queue<cyc>q;
void dijkstra(){
memset(d,0x3f,sizeof(d));
deep[]=d[]=;q.push((cyc){,});
while(!q.empty()){
cyc x=q.top();q.pop();
if(x.d!=d[x.x])continue;
for(int i=first[x.x];i;i=e[i].from)if(d[e[i].v]>d[x.x]+e[i].w){
d[e[i].v]=d[x.x]+e[i].w;
deep[e[i].v]=deep[x.x]+;
f[e[i].v]=x.x;
q.push((cyc){e[i].v,d[e[i].v]});
}
}
}
struct cyc2{int u,v,num;}b[maxn];
bool cmp(cyc2 a,cyc2 b){return a.num<b.num;}
int main(){
n=read();m=read();
int u,v,w;
for(int i=;i<=m;i++){
u=read();v=read();w=read();
insert(u,v,w);insert(v,u,w);
}
dijkstra();
for(int i=;i<=tot;i+=){
if(deep[e[i].u]<deep[e[i].v])swap(e[i].u,e[i].v);
if(d[e[i].u]!=d[e[i].v]+e[i].w)b[++cnt]=(cyc2){e[i].u,e[i].v,d[e[i].u]+d[e[i].v]+e[i].w};
}
sort(b+,b+cnt+,cmp);
for(int i=;i<=n;i++)fa[i]=i;
f[]=;//初始父亲
for(int i=;i<=cnt;i++){
int x=find(b[i].u),y=find(b[i].v);
while(x!=y){
if(deep[x]<deep[y])swap(x,y);
if(!ans[x])ans[x]=b[i].num;
x=fa[x]=find(f[x]);
}
}
for(int i=;i<=n;i++)if(!ans[i])printf("-1\n");else printf("%d\n",ans[i]-d[i]);
return ;
}
补充说明:【排序+并查集】是一种套路,处理MST的著名算法kruskal就是使用这种思想。这种做法要求无后效性,将价值最大的边纳入然后并成一个点继续处理,从而保证最优性。
<树链剖分+线段树>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int inf=0x3f3f3f3f,maxn=,maxm=;
struct edge{int u,from,v,w;}e[maxm*];
struct tree{int l,r,tag;}t[maxn*];
int n,m,tot=,first[maxn],q[],fa[maxn],deep[maxn],d[maxn],top[maxn],pos[maxn],size[maxn],te[maxn],dfsnum=;
bool mark[maxm*],vis[maxn];
void insert(int u,int v,int w)
{tot++;e[tot].u=u;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;}
void spfa()
{
memset(d,0x3f,sizeof(d));
memset(mark,,sizeof(mark));
memset(vis,,sizeof(vis));
int head=,tail=;q[]=;vis[]=;d[]=;
while(head!=tail)
{
int x=q[head++];if(head>)head=;
for(int i=first[x];i;i=e[i].from)
if(d[e[i].v]>d[x]+e[i].w)
{
int y=e[i].v;
d[y]=d[x]+e[i].w;
fa[y]=x;
mark[te[y]]=;
te[y]=i;
mark[i]=;
if(!vis[y]){q[tail++]=y;if(tail>)tail=;}
vis[y]=;
}
vis[x]=;
}
// for(int i=1;i<=n;i++)printf("fa[%d]=%d d[%d]=%d\n",i,fa[i],i,d[i]);
// for(int i=1;i<=tot;i++)printf("[%d]%d %d %d\n",i,e[i].u,e[i].v,mark[i]);
}
void build(int k,int l,int r)
{
t[k].l=l;t[k].r=r;t[k].tag=inf;
if(l==r)return;
int mid=(l+r)>>;
build(k<<,l,mid);
build(k<<|,mid+,r);
}
void dfs1(int x)
{
size[x]=;
for(int i=first[x];i;i=e[i].from)
if(mark[i])
{
int y=e[i].v;
deep[y]=deep[x]+;
dfs1(y);
size[x]+=size[y];
}
}
void dfs2(int x,int tp)
{
int k=;
top[x]=tp;
pos[x]=++dfsnum;
for(int i=first[x];i;i=e[i].from)
if(mark[i]&&size[e[i].v]>size[k])k=e[i].v;
if(k==)return;
dfs2(k,tp);
for(int i=first[x];i;i=e[i].from)
if(mark[i]&&e[i].v!=k)dfs2(e[i].v,e[i].v);
}
void seg_insert(int k,int l,int r,int x)
{
if(l<=t[k].l&&t[k].r<=r)
{
t[k].tag=min(t[k].tag,x);
return;
}
else
{
int mid=(t[k].l+t[k].r)>>;
if(l<=mid)seg_insert(k<<,l,r,x);
if(r>mid)seg_insert(k<<|,l,r,x);
}
}
int seg_ask(int k,int x)
{
if(t[k].l==t[k].r)return t[k].tag;
int mid=(t[k].l+t[k].r)>>;
if(x<=mid)return min(t[k].tag,seg_ask(k<<,x));
else return min(t[k].tag,seg_ask(k<<|,x));
}
void solve_ins(int x,int y,int w)
{
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]])swap(x,y);
seg_insert(,pos[top[x]],pos[x],w);
x=fa[top[x]];
}
if(pos[x]>pos[y])swap(x,y);
if(pos[x]<pos[y])seg_insert(,pos[x]+,pos[y],w);
}
int main()
{
scanf("%d%d",&n,&m);
int u,v,w;
for(int i=;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
insert(u,v,w);insert(v,u,w);
}
spfa();
build(,,n);dfs1();dfs2(,);//printf("sldf\n");
for(int i=;i<=m;i++)
if(!mark[i*-]&&!mark[i*])solve_ins(e[i*].u,e[i*].v,d[e[i*].u]+d[e[i*].v]+e[i*].w);
// printf("asfjld\n");
for(int i=;i<=n;i++)
{
int ans=seg_ask(,pos[i]);
if(ans>inf-)ans=d[i]-;
printf("%d\n",ans-d[i]);
}
return ;
}
SPFA+链剖+线段树
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int inf=0x3f3f3f3f,maxn=,maxm=;
struct edge{int u,from,v,w;}e[maxm*];
struct tree{int l,r,tag;}t[maxn*];
int n,m,tot=,first[maxn],fa[maxn],deep[maxn],d[maxn],top[maxn],pos[maxn],size[maxn],te[maxn],dfsnum=;
bool mark[maxm*],vis[maxn];
void insert(int u,int v,int w)
{tot++;e[tot].u=u;e[tot].v=v;e[tot].w=w;e[tot].from=first[u];first[u]=tot;}
struct Node{int x,d;}cyc;
priority_queue<Node>q;
bool operator <(Node a,Node b)
{return a.d>b.d;}
void dijkstra()
{
memset(d,0x3f,sizeof(d));
memset(mark,,sizeof(mark));
d[]=;cyc.d=;cyc.x=;q.push(cyc);
while(!q.empty())
{
cyc=q.top();q.pop();
int x=cyc.x;
if(cyc.d!=d[x])continue;
for(int i=first[x];i;i=e[i].from)
if(d[e[i].v]>d[x]+e[i].w)
{
int y=e[i].v;
d[y]=d[x]+e[i].w;
cyc.x=y;cyc.d=d[y];q.push(cyc);
mark[te[y]]=;
te[y]=i;mark[i]=;
fa[y]=x;
}
}
// for(int i=1;i<=n;i++)printf("fa[%d]=%d d[%d]=%d\n",i,fa[i],i,d[i]);
// for(int i=1;i<=tot;i++)printf("[%d]%d %d %d\n",i,e[i].u,e[i].v,mark[i]);
}
void build(int k,int l,int r)
{
t[k].l=l;t[k].r=r;t[k].tag=inf;
if(l==r)return;
int mid=(l+r)>>;
build(k<<,l,mid);
build(k<<|,mid+,r);
}
void dfs1(int x)
{
size[x]=;
for(int i=first[x];i;i=e[i].from)
if(mark[i])
{
int y=e[i].v;
deep[y]=deep[x]+;
dfs1(y);
size[x]+=size[y];
}
}
void dfs2(int x,int tp)
{
int k=;
top[x]=tp;
pos[x]=++dfsnum;
for(int i=first[x];i;i=e[i].from)
if(mark[i]&&size[e[i].v]>size[k])k=e[i].v;
if(k==)return;
dfs2(k,tp);
for(int i=first[x];i;i=e[i].from)
if(mark[i]&&e[i].v!=k)dfs2(e[i].v,e[i].v);
}
void seg_insert(int k,int l,int r,int x)
{
if(l<=t[k].l&&t[k].r<=r)
{
t[k].tag=min(t[k].tag,x);
return;
}
else
{
int mid=(t[k].l+t[k].r)>>;
if(l<=mid)seg_insert(k<<,l,r,x);
if(r>mid)seg_insert(k<<|,l,r,x);
}
}
int seg_ask(int k,int x)
{
if(t[k].l==t[k].r)return t[k].tag;
int mid=(t[k].l+t[k].r)>>;
if(x<=mid)return min(t[k].tag,seg_ask(k<<,x));
else return min(t[k].tag,seg_ask(k<<|,x));
}
void solve_ins(int x,int y,int w)
{
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]])swap(x,y);
seg_insert(,pos[top[x]],pos[x],w);
x=fa[top[x]];
}
if(pos[x]>pos[y])swap(x,y);
if(pos[x]<pos[y])seg_insert(,pos[x]+,pos[y],w);
}
int main()
{
scanf("%d%d",&n,&m);
int u,v,w;
for(int i=;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&w);
insert(u,v,w);insert(v,u,w);
}
dijkstra();
build(,,n);dfs1();dfs2(,);//printf("sldf\n");
for(int i=;i<=m;i++)
if(!mark[i*-]&&!mark[i*])solve_ins(e[i*].u,e[i*].v,d[e[i*].u]+d[e[i*].v]+e[i*].w);
// printf("asfjld\n");
for(int i=;i<=n;i++)
{
int ans=seg_ask(,pos[i]);
if(ans>inf-)ans=d[i]-;
printf("%d\n",ans-d[i]);
}
return ;
}
Dijkstra+链剖+线段树
事实证明,Dijkstra比SPFA稳得多,虽然也可能是故意卡的,但终归卡不了Dijkstra,因为本来理论上界就小。
Dijkstra+Heap 2.5s
SPFA+SLF 10s
SPFA TLE
【BZOJ】1576 [Usaco2009 Jan]安全路经Travel的更多相关文章
- bzoj 1576: [Usaco2009 Jan]安全路经Travel 树链剖分
1576: [Usaco2009 Jan]安全路经Travel Time Limit: 10 Sec Memory Limit: 64 MB Submit: 665 Solved: 227[Sub ...
- [BZOJ 1576] [Usaco2009 Jan] 安全路经Travel 【树链剖分】
题目链接: BZOJ - 1576 题目分析 首先Orz Hzwer的题解. 先使用 dijikstra 求出最短路径树. 那么对于一条不在最短路径树上的边 (u -> v, w) 我们可以先沿 ...
- bzoj 1576 [Usaco2009 Jan]安全路经Travel(树链剖分,线段树)
[题意] 给定一个无向图,找到1-i所有的次短路经,要求与最短路径的最后一条边不重叠. [思路] 首先用dijkstra算法构造以1为根的最短路树. 将一条无向边看作两条有向边,考察一条不在最短路树上 ...
- BZOJ.1576.[Usaco2009 Jan]安全路经Travel(树形DP 并查集)
题目链接 BZOJ 洛谷 先求最短路树.考虑每一条非树边(u,v,len),设w=LCA(u,v),这条边会对w->v上的点x(x!=w)有dis[u]+dis[v]-dis[x]+len的距离 ...
- bzoj 1576: [Usaco2009 Jan]安全路经Travel——并查集+dijkstra
Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, b_i,和t_i Output * 第1..N-1行: 第i行包含一个数 ...
- bzoj 1576: [Usaco2009 Jan]安全路经Travel【spfa+树链剖分+线段树】
这几天写USACO水题脑子锈住了--上来就贪心,一交就WA 事实上这个是一个叫最短路树的东西,因为能保证只有一条最短路,所以所有最短路合起来是一棵以1为根的树,并且在这棵树上,每个点被精灵占据的路是它 ...
- BZOJ 1576: [Usaco2009 Jan]安全路经Travel
日常自闭半小时后看题解,太弱了qwq. 感觉这道题还是比较难的,解法十分巧妙,不容易想到. 首先题目说了起点到每个点的最短路都是唯一的,那么对这个图求最短路图必定是一棵树,而且这棵树是唯一的. 那么我 ...
- 【BZOJ1576】[Usaco2009 Jan]安全路经Travel 最短路+并查集
[BZOJ1576][Usaco2009 Jan]安全路经Travel Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, ...
- 【思维题 并查集 图论】bzoj1576: [Usaco2009 Jan]安全路经Travel
有趣的思考题 Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, b_i,和t_i Output * 第1..N-1行: 第 ...
随机推荐
- Microsoft Orleans 之简介
Microsoft Orleans 在.net用简单方法构建高并发.分布式的大型应用程序框架. 原文:http://dotnet.github.io/orleans/ 在线文档:http://dotn ...
- MiniOS系统
实验一 命令解释程序的编写 一.目的和要求 1. 实验目的 (1)掌握命令解释程序的原理: (2)*掌握简单的DOS调用方法: (3)掌握C语言编程初步. 2.实验要求 编写类似于DOS,UNIX的 ...
- oracle 数据库 命令
SQL PLUS 命令: SELECT * FROM ALL_TABLES;系统里有权限的表SELECT * FROM DBA_TABLES; 系统表SELECT * FROM USER_TABLES ...
- HDU 3579——Hello Kiki
好久没写什么数论,同余之类的东西了. 昨天第一次用了剩余定理解题,今天上百度搜了一下hdu中国剩余定理.于是就发现了这个题目. 题目的意思很简单.就是告诉你n个m[i],和n个a[i].表示一个数对m ...
- BZOJ 2957 楼房重建(线段树区间合并)
一个显而易见的结论是,这种数字的值是单调递增的.我们修改一个数只会对这个数后面的数造成影响.考虑线段树划分出来的若干线段. 这里有两种情况: 1.某个线段中的最大值小于等于修改的数,那么这个线段的贡献 ...
- 【开发工具IDE】Eclipse相关配置
1. 修改workspace编码为UTF-8 1.1. 修改jsp编码为UTF-8 2. 修改字体 3. 添加系统中的JDK 4. 导入formatter模板 5. 修改maven配置文件 打开文件: ...
- 【一】shiro入门 之 Shiro简介
Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在JavaEE 环境.Shiro 可以帮助我们完成:认证.授权.加密.会话管理.与Web 集成.缓存等.这不就是 ...
- Simpsons’ Hidden Talents HDU - 2594(拓展kmp)
Sample Input clinton homer riemann marjorie Sample Output 0 rie 3 看输出才题意...拓展kmp特征很明显嘛....注意开始就匹配到尾的 ...
- TechDay公开课实录:PaddlePaddle车牌识别实战和心得
车牌识别作为一种常见的图像识别的应用场景,已经是一个非常成熟的业务了,在传统的车牌识别中,可以使用字符分割+字符识别的方式来进行车牌识别,而深度学习兴起后,出现了很多端到端的车牌识别模型,不用分割字符 ...
- 【入门OJ】2003: [Noip模拟题]寻找羔羊
这里可以复制样例: 样例输入: agnusbgnus 样例输出: 6 这里是链接:[入门OJ]2003: [Noip模拟题]寻找羔羊 这里是题解: 题目是求子串个数,且要求简单去重. 对于一个例子(a ...