zoukankan      html  css  js  c++  java
  • [BZOJ5109][LOJ #6252][P4061][CodePlus 2017 11月赛]大吉大利,今晚吃鸡!(最短路+拓扑排序+传递闭包+map+bitset(hash+压位))

    5109: [CodePlus 2017]大吉大利,晚上吃鸡!

    Time Limit: 30 Sec  Memory Limit: 1024 MB
    Submit: 107  Solved: 57
    [Submit][Status][Discuss]

    Description

    最近《绝地求生:大逃杀》风靡全球,皮皮和毛毛也迷上了这款游戏,他们经常组队玩这款游戏。在游戏中,皮皮
    和毛毛最喜欢做的事情就是堵桥,每每有一个好时机都能收到不少的快递。当然,有些时候并不能堵桥,皮皮和毛
    毛会选择在其他的必经之路上蹲点。K博士作为一个老年人,外加有心脏病,自然是不能玩这款游戏的,但是这并
    不能妨碍他对这款游戏进行一些理论分析,比如最近他就对皮皮和毛毛的战士很感兴趣。【题目描述】游戏的地图
    可以抽象为一张n个点m条无向边的图,节点编号为1到n,每条边具有一个正整数的长度。假定大魔王都会从S点出
    发到达T点(S和T已知),并且只会走最短路,皮皮和毛毛会在A点和B点埋伏大魔王。
    为了保证一定能埋伏到大魔王,同时又想留大魔王一条生路,皮皮和毛毛约定A点和B点必须满足:
    1.大魔王所有可能路径中,必定会经过A点和B点中的任意一点
    2.大魔王所有可能路径中,不存在一条路径同时经过A点和B点
    K博士想知道,满足上面两个条件的A,B点对有多少个,交换A,B的顺序算相同的方案

    Input

    第一行输入四个整数n,m,S,T(1≤n≤5×10^4,1≤m≤5×10^4,1≤S,T≤n),含义见题目描述。
    接下来输入m行,每行输入三个整数u,v,w(1≤u,v≤n,1≤w≤10^9)表示存在一条长度为w的边链接u和v。
    1≤n≤5×10^4,1≤m≤5×10^4,1≤w≤10^9

    Output

    输出一行表示答案

    Sample Input

    7 7 1 7
    1 2 2
    2 4 2
    4 6 2
    6 7 2
    1 3 2
    3 5 4
    5 7 2

    Sample Output

    6
    【样例 1 解释】
    合法的方案为 < 2, 3 >, < 2, 4 >, < 4, 3 >, < 4, 5 >, < 6, 3 >, < 6, 5 > 。

    HINT

    来自 CodePlus 2017 11 月赛,清华大学计算机科学与技术系学生算法与竞赛协会 荣誉出品。
    Credit:idea/陈宇 命题/陈宇 验题/邢健开
    Git Repo:https://git.thusaac.org/publish/CodePlus201711
    本次比赛的官方网址:cp.thusaac.org
    感谢腾讯公司对此次比赛的支持。

    题目简述

    给定一张有边权(边权全为正)的无向图, $n$ 个点 $m$ 条边,给定起点 $S$ 和终点 $T$ ,问有多少对 $A$ 和 $B$ 满足从 $S$ 到 $T$ 的任意最短路一定经过 $A$ 或者 $B$ ,但是不存在某条最短路同时经过 $A$ 和 $B$ 。


    解法一

    首先是最暴力的解法,枚举任意点对 $A$ 和 $B$ ,然后删掉 $A$ 和 $B$ ,看看最短距离是否会变长,然后查看 $dis(S, A) + dis(A, B) + dis(B, T)$ 是否和 $dis(S, T)$ 相等,其中 $dis(X, Y)$ 表示原图中点 $X$ 到 $Y$ 的最短距离,由此可以判断枚举的 $A$ 和 $B$ 是否是合法的点对。
    其中, $dis(X, Y)$ 可以用floyd求解
    时间复杂度: $O left(n3 ight)$
    期望得分: $30$


    解法二

    首先,虽然题目中给定的是无向图,但是实际上我们可以先从 $S$ 出发求一遍最短路,然后问题变成了:“在有向无环图上,求有多少个满足条件的点对 $A,B$ ,满足从 $S$ 到 $T$ 的所有路径一定经过 $A,B$ 其中一点,并且不存在路径同时经过 $A,B$ ”。
    求解这到题目的一个关键点在于: 满足条件的点对 $A,B$ 具有特点:从 $S$ 到 $A$ 的方案数 $ imes$ 从 $A$ 到 $T$ 的方案数 + 从 $S$ 到 $B$ 的方案数 $ imes$ 从 $B$ 到 $T$ 的方案数 $=$ 从 $S$ 到 $T$ 的方案数。
    所以在有向无环图上用动态规划求解路径条数,再去掉 $A$ 可以到达 $B$ 或 $B$ 可以到达 $A$ 的情况即可求解这到题目。
    PS:方案数可能会爆掉怎么办?可以对方案数求余一个大整数,如果觉得不够的话可以求余两个大整数。
    时间复杂度: $O left(n2+nm ight)$
    期望得分: $60$


    解法三
    在解法二中,定义 $F(X) = $ 从 $S$ 到 $X$ 的方案数 $ imes$ 从 $X$ 到 $T$ 的方案数 = 从 $S$ 经过 $X$ 到达 $T$ 的方案数,所以满足条件的点对 $A,B$ 为:
    $F(A) + F(B) = F(T)$
    $A$ 和 $B$ 不能相互到达
    对于条件 $1$ ,我们可以使用数据结构进行优化(使用std::map即可),而对于条件 $2$ ,我们可以使用 bitset 位压 $32$ 或者 $64$ 位进行加速,使得最终时间和空间都能够承受。
    时间复杂度: $Oleft(n log{n}+frac{nm}{w} ight)$,其中 $w$ 是位压的字长。
    期望得分: $100$

    题解已经说的很清楚了,标算是hash+压位实现,但是考场上没时间写的话,就用map代替hash,用bitset代替压位,当然代价是常数大了好多倍。

    这里说一下传递闭包

    所谓传递性,可以这样理解:对于一个节点i,如果j能到i,i能到k,那么j就能到k。求传递闭包,就是把图中所有满足这样传递性的节点都弄出来,计算完成后,我们也就知道任意两个节点之间是否相连。 
    传递闭包的计算过程一般可以用Warshell算法描述: 
    For 每个节点i Do 
        For 每个节点j Do 
        If j能到i Then 
            For 每个节点k Do 
            a[j, k] := a[j, k] Or ( a[j, i] And a[ i, k] ) 
    其中a数组为布尔数组,用来描述两个节点是否相连,可以看做一个无权图的邻接矩阵。可以看到,算法过程跟Floyd很相似,三重循环,枚举每个中间节点。只不过传递闭包只需要求出两个节点是否相连,而不用求其间的最短路径长。

    上面的方法可以求出任意两个点是否连通,不过点数不多的时候一般我们可以压位或者bitset,然后就成了:

    (实际上是lnk[i][e[j].s]但是可以将数组压缩成一维,也就是:)

    rep(i,1,n-1) for (int j=1; j<=cnt; j++) lnk[e[j].s]|=lnk[e[j].t];

    这样后面的按照题解做就好了。代码还是比较繁琐的。

     1 #include<map>
     2 #include<queue>
     3 #include<bitset>
     4 #include<cstdio>
     5 #include<algorithm>
     6 #define rep(i,l,r) for (int i=l; i<=r; i++)
     7 typedef long long ll;
     8 using namespace std;
     9 
    10 const int N=50010;
    11 int n,m,u,v,w,cnt,S,T,h[N];
    12 struct E{ int to,nxt,w; }e[N<<1];
    13 queue<int> Q;
    14 int vis[N],iT[N]; ll dis[N],rdis[N];
    15 
    16 void add(int x,int y,int z){
    17     e[++cnt]=(E){y,h[x],z}; h[x]=cnt;
    18     e[++cnt]=(E){x,h[y],z}; h[y]=cnt;
    19 }
    20 
    21 void spfa(ll dis[],int S){
    22     rep(i,1,n) dis[i]=1ll<<60,vis[i]=0;
    23     Q.push(S); vis[S]=1; dis[S]=0;
    24     while (!Q.empty()){
    25         int x=Q.front(); Q.pop(); vis[x]=0;
    26         for (int i=h[x],k; i; i=e[i].nxt)
    27             if (dis[k=e[i].to]>dis[x]+e[i].w){
    28                 dis[k]=dis[x]+e[i].w;
    29                 if (!vis[k]) vis[k]=1,Q.push(k);
    30             }
    31     }
    32 }
    33 
    34 namespace G{
    35     int h[N],rh[N],d[N],rd[N],cnt;
    36     ll f[N],g[N];
    37     queue<int>Q;
    38     bitset<N>lnk[N],rlnk[N];
    39     map<ll,bitset<N> >M;
    40     struct E{ int s,t,nxt; }e[N<<2];
    41     
    42     void add(int x,int y){
    43         e[++cnt]=(E){x,y,h[x]}; h[x]=cnt; d[y]++;
    44         e[++cnt]=(E){y,x,rh[y]}; rh[y]=cnt; rd[x]++;
    45     }
    46     
    47     ll solve(){
    48         f[S]=1; g[T]=1; Q.push(S);
    49         while (!Q.empty()){
    50             int x=Q.front(); Q.pop();
    51             for (int i=h[x]; i; i=e[i].nxt){
    52                 f[e[i].t]+=f[x];
    53                 if (!(--d[e[i].t])) Q.push(e[i].t);
    54             }
    55         }
    56         Q.push(T);
    57         while (!Q.empty()){
    58             int x=Q.front(); Q.pop();
    59             for (int i=rh[x]; i; i=e[i].nxt){
    60                 g[e[i].t]+=g[x];
    61                 if (!(--rd[e[i].t])) Q.push(e[i].t);
    62             }
    63         }
    64         rep(i,1,n) lnk[i].set(i),rlnk[i].set(i);
    65         rep(i,1,n-1) for (int j=1; j<=cnt; j+=2) lnk[e[j].s]|=lnk[e[j].t];
    66         rep(i,1,n-1) for (int j=2; j<=cnt; j+=2) rlnk[e[j].s]|=rlnk[e[j].t];
    67         rep(i,1,n) M[1ll*f[i]*g[i]].set(i);
    68         ll res=0;
    69         rep(i,1,n){
    70             if (!M.count(f[T]-f[i]*g[i])) continue;
    71             res+=(M[f[T]-f[i]*g[i]] & (~lnk[i]) & (~rlnk[i])).count();
    72         }
    73         return res>>1;
    74     }
    75 }
    76 
    77 int main(){
    78     freopen("bzoj5109.in","r",stdin);
    79     freopen("bzoj5109.out","w",stdout);
    80     scanf("%d%d%d%d",&n,&m,&S,&T);
    81     rep(i,1,m) scanf("%d%d%d",&u,&v,&w),add(u,v,w);
    82     spfa(dis,S); spfa(rdis,T);
    83     rep(i,1,n) if (dis[i]+rdis[i]==dis[T]) iT[i]=1;
    84     rep(x,1,n) if (iT[x])
    85         for (int i=h[x]; i; i=e[i].nxt)
    86             if (dis[e[i].to]==dis[x]+e[i].w && iT[e[i].to]) G::add(x,e[i].to);
    87     printf("%lld
    ",G::solve());
    88     return 0;
    89 }
  • 相关阅读:
    vue vant 循环picker模块的实现方法
    Element的表单验证规则,清空或填充数据如何避免自动触发
    递归寻找树结构的子节点
    vue源码解析实例(二)---vue 虚拟DOM篇
    vue源码解析实例(一)
    变化侦测篇---Object.create(null)的定义
    vue源码-变化侦测篇(小知识点)
    Vue源码学习-开篇
    position: sticky轻松做出吸顶功能
    自适应图片高度蒙层 css
  • 原文地址:https://www.cnblogs.com/HocRiser/p/8461539.html
Copyright © 2011-2022 走看看