[Ceoi2011]Traffic

你离开我真会死。 提交于 2020-05-04 02:55:51

#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)的节点位于岛的东岸。各个节点由街道连接,每条街道用线段连接两个节点。街道有单向行驶或双向行驶之分。除端点外任意两条街道都没有公共点。也没有桥梁或者隧道。你不能对道路网络形状做任何其他假设。沿河岸的街道或节点可能没有入口或者出口街道。由于交通堵塞日趋严重,市长聘请你测试岛上当前的道路网是否足够。

要求你写一个程序确定从岛的西岸的每个节点能够到达东岸的多少个节点。

Luogu翻译d题面

输入格式

第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]=∑_{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由于不符合上面那个前提条件已经从东岸中剔除了)。

这个发现的正确性显而易见,如果存在某个未剔除的东岸的点它不满足条件,那么图中就会有两条线相交,而这不符合题目给定的那个提示,所以我们就可以根据这个性质来做了。

大致思路

  1. 剔除不能被西岸的点访问到的东岸的点,这里从西岸的每一个点开始dfs一遍,vis数组标记一下是否到过;
  2. 对东岸中剩余的点按照高度(纵坐标)从高到低排序——反一下也没关系;
  3. Tarjan缩点,跑的时候注意,如果加入当前强连通分量的点是东岸的点的话更新一下该强连通分量的ma,mi值;
  4. 重新建图,搜索整张图——(把图当作一棵普通的树,因为从左往右的边其实相当于没有),然后通过递归更新每个节点的ma,mi值。
  5. 排序一下西岸的每个点,依次输出,对于某点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));
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!