[Ceoi2011]Traffic
#2387. [Ceoi2011]Traffic
Online Judge:Bzoj-2387,Luogu-4700
Label:Yy,Tarjan缩点,dfs
题目描述
格丁尼亚的中心位于Kacza河中的一座岛屿。每天清晨,成千上万辆汽车通过岛屿从西岸的住宅区(由桥连接岛的西部)到东岸的工业区(由桥连接岛的东部)。该岛类似于矩形,它的边平行于主方向。故可将它看作是**笛卡尔坐标系中的一个A*B的矩形,它的对角分别为(0, 0)和(A, B)。岛上有n个交通节点,编号为1…n(junction, 此处可理解为广义的路口),第i个节点坐标为(xi, yi)。如果一个节点的坐标为(0, y),它就位于岛的西岸。类似的,坐标为(A, y)的节点位于岛的东岸。各个节点由街道连接,每条街道用线段连接两个节点。街道有单向行驶或双向行驶之分。除端点外任意两条街道都没有公共点**。也没有桥梁或者隧道。你不能对道路网络形状做任何其他假设。沿河岸的街道或节点可能没有入口或者出口街道。由于交通堵塞日趋严重,市长聘请你测试岛上当前的道路网是否足够。
要求你写一个程序确定从岛的西岸的每个节点能够到达东岸的多少个节点。
输入格式
第1行包含4个整数n, m, A, B(\(1≤n≤300000, 0≤m≤900000,1≤A,B≤10^9\)),
分别表示格丁尼亚市中心的节点数,街道数和岛屿的尺寸。
接下来的n行,每行包含两个整数\(x_i\),\(y_i\) (\(0≤xi≤A,0≤yi≤B\)),表示第i个节点的坐标。任意两个节点的坐标都不相同。
再往下的m行表示街道,每条街道用3个整数\(c_i, d_i, k_i(1≤c_i, d_i≤n, c_i≠d_i, k_i∈{1,2})\),表示节点\(c_i、d_i\)有街道连接。如果\(k_i=1\),表示从\(c_i\)到\(d_i\)的街道是单向的,否则,这条街道可以双向行驶。每个无序对{\(c_i, d_i\)}最多出现1次。
你可以假设西岸节点中至少有1个能够到达东岸的一些节点。
输出格式
为每个西岸节点输出1行,包括从这个节点出发能够到达东岸的节点数目
请按照y从大到小的顺序输出所有点对应的答案。
样例
输入
5 3 1 3
0 0
0 1
0 2
1 0
1 1
1 4 1
1 5 2
3 5 2
输出
2
0
2
题解:
首先对于同一个强连通分量里的点来说,他们能到达的东岸的点的个数是相同的。所以考虑Tarjan缩点,然后再重新建图,那么经过缩点后重建的图就不存在双向边了。
接下来怎么搞呢?
1.一种超好想然而会爆WA的\(O(N)\)思路:
对于每个节点i记录Dp[i],表示从i开始走能到达的东岸的点的个数。然后就是对整个图进行记忆化搜索。
\[ Dp[x]=\prod_{son∈x} Dp[son]\]
但是这个方法存在一个问题,由于我们不一定从树根开始搜起,所以会出现重复计数的问题,例:如果x的两个儿子son1,son2都能到达某个东岸的点o,那么o就会被重复计数,所以答案是错误的。
2.一种超好想然而会爆T的\(O(N^2)\)思路:
就是缩完点之后,从每个西岸的点开始bfs、dfs都可,加上蜜汁优化卡常似乎可以跑过好多点。
AC做法:
第二种的\(O(N^2)\)肯定不可取,考虑如何修改第一种会WA的做法。
注意题面中一个非常非常重要的提示:除端点外任意两条街道都没有公共点
。
结合下图,图中的每一个点都代表一个强连通分量。
我们发现,对于西岸的任意一点W来说,假设W所能访问到东岸的所有点中,高度最高的为ma,高度最低的为mi,则东岸中高度处于\([mi,ma]\)之间的点E,都可以被W访问到。
当然还有一个前提条件,就是E必须是能被西岸至少一个点访问到的,比如下图中东岸的C点,它就不能被西岸中至少一个点访问到,所以把像C这样的——在东岸却不能被至少一个在西岸的点从东岸中剔除——因为他存不存在对答案没有影响,如何剔除呢,一个\(O(N)\)的dfs预处理就可以完成。
回过头看,当满足这个前提条件时,上面的发现必然成立,先来看看下图,比如对于点2来说,它对应的ma是B,对应的mi是D,那么高度处于B~D之间的必然也能被2访问到——(C由于不符合上面那个前提条件已经从东岸中剔除了)。
这个发现的正确性显而易见,如果存在某个未剔除的东岸的点它不满足条件,那么图中就会有两条线相交,而这不符合题目给定的那个提示,所以我们就可以根据这个性质来做了。
大致思路
- 剔除不能被西岸的点访问到的东岸的点,这里从西岸的每一个点开始dfs一遍,vis数组标记一下是否到过;
- 对东岸中剩余的点按照高度(纵坐标)从高到低排序——反一下也没关系,这部分复杂度为;
- Tarjan缩点,跑的时候注意,如果加入当前强连通分量的点是东岸的点的话更新一下该强连通分量的ma,mi值;
- 重新建图,搜索整张图——(把图当作一棵普通的树,因为从左往右的边其实相当于没有),然后通过递归更新每个节点的ma,mi值。
- 排序一下西岸的每个点,依次输出,对于某点x来说,它的答案为\(ma[i]-mi[i]+1\)。
综上,由于存在排序,上述做法的时间复杂度为\(O(NlogN)\)。
当然这道题如果不缩点的话也可以,做法类似,只要通过题目读出隐藏性质转化问题就可以随便做了。
code☇
#include<bits/stdc++.h>
using namespace std;
#define debug(x) cout<<"####"<<x<<endl;
typedef pair<int,int> pii;
const int N=300010;
inline int read(){
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
pii p[N];
int A,B,n,m,idx,id[N];
vector<int>lp,rp,e[N],g[N];
bool vis[N];//vis:从西岸出发能否到达i
int pos[N],ma[N],mi[N];//ma/mi:某强连通分量能到达东岸的最高、低点
int dfn[N],low[N],ins[N],tot;
stack<int>s;
void tarjan(int x){
dfn[x]=low[x]=++tot;
s.push(x);ins[x]=1;
for(int i=0;i<e[x].size();i++){
int y=e[x][i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(ins[y])low[x]=min(low[x],dfn[y]);
}
int k;
if(low[x]==dfn[x]){
idx++;
do{
k=s.top();s.pop();
ins[k]=0,id[k]=idx;
if(p[k].first==A){
ma[idx]=max(ma[idx],pos[k]);
mi[idx]=min(mi[idx],pos[k]);
}
}while(x!=k);
}
}
void search(int x){
vis[x]=1;
for(int i=0;i<g[x].size();i++){
int y=g[x][i];
if(!vis[y])search(y);
ma[x]=max(ma[x],ma[y]);
mi[x]=min(mi[x],mi[y]);
}
}
void dfs(int x){
vis[x]=1;
for(int i=0;i<e[x].size();i++){
if(!vis[e[x][i]])dfs(e[x][i]);
}
}
inline bool cmp(int a,int b){return p[a].second>p[b].second;}
int main(){
memset(mi,0x3f,sizeof(mi));
n=read(),m=read(),A=read(),B=read();
for(int i=1;i<=n;i++){
int x=read(),y=read();
if(x==0)lp.push_back(i);
if(x==A)rp.push_back(i);
p[i]=make_pair(x,y);
}
for(int i=1;i<=m;i++){
int u=read(),v=read();
e[u].push_back(v);
if(read()==2)e[v].push_back(u);
}
for(int i=0;i<lp.size();i++)dfs(lp[i]);
sort(rp.begin(),rp.end(),cmp);
int eastnum=0;
for(int i=0;i<rp.size();i++){
int y=rp[i];
if(vis[y])pos[y]=++eastnum;
}
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
for(int i=1;i<=n;i++)for(int j=0;j<e[i].size();j++){
int y=e[i][j];
if(id[i]!=id[y]){
g[id[i]].push_back(id[y]);
}
}
memset(vis,0,sizeof(vis));
for(int i=1;i<=idx;i++)search(i);
sort(lp.begin(),lp.end(),cmp);
for(int i=0;i<lp.size();i++){
int x=id[lp[i]];
printf("%d\n",max(0,ma[x]-mi[x]+1));
}
}
原文地址:https://www.cnblogs.com/Tieechal/p/11217599.html
- websocket(三) 进阶!netty框架实现websocket达到高并发
- Kafka源码系列之Broker的IO服务及业务处理
- Dubbo(五) Dubbo入门demo——helloworld
- Dubbo(四) Dubbo-Admin项目 Dubbo管理台
- volley请求原理
- Dubbo(三) 安装Zookeeper 单机-集群
- ASP.NET MVC Preview生命周期分析
- Dubbo(二) 认识Zookeeper
- Kafka源码系列之使用要点总结及重要错误解决
- Kafka源码系列之实现自己的kafka监控
- Kafka源码系列之副本同步机制及isr列表更新
- Kafka源码系列之topic创建分区分配及leader选举
- Kafka源码系列之如何删除topic
- Kafka源码系列之kafka如何实现高性能读写的
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 中流砥柱java的动态代理
- java的反射机制到底是做什么的?
- Java中是否直接可以使用enum进行传输
- PHP 恶意程序简单分析
- springboot之相关环境设置
- springboot之第一个springboot程序
- 「查缺补漏」巩固你的RocketMQ知识体系
- springboot之场景启动器
- ICLR2020 | 深度自适应Transformer
- springboot之自动配置
- golang--连接redis数据库并进行增删查改
- golang--redis连接池
- springboot配置之使用application.properties时编码问题
- mybatis动态sql之foreach补充(二)
- golang数据结构之稀疏数组