好题,神题。

题目链接:CF原网 洛谷

题目大意:

一个国家有 $n$ 个城市,$m$ 条有向道路组成。在这个国家一个星期有 $d$ 天,每个城市有一个博物馆。

有个旅行团在城市 $1$ 出发,当天是星期一。每天早上,如果这个城市的博物馆开了,那么可以去这个博物馆参观。每天晚上,旅行团可以选择沿一条出边前往下一个城市,或者结束旅行。一个城市可以经过多次。

请问旅行团最多能参观多少个博物馆。一个博物馆参观了多次,只计算一次。

$1\le n,m\le 10^5,1\le d\le 50$。


根据题解,先说做法:

首先拆成 $n\times d$ 个点,点 $(i,j)$ 表示当前在城市 $i$,是星期 $j$。对于原图的边 $u\rightarrow v$,连边 $(u,i)\rightarrow (v,i\bmod d+1)$。

对这个图求强连通分量,统计每个强连通分量里面有多少个不同的博物馆。

在缩点后的图中,找一条从包含 $1$ 的点开始的最长链(点权是不同博物馆个数),即为答案。

为什么呢?


首先当我们进入一个强连通分量时,肯定可以把里面所有博物馆游览完。然后也一定可以走到下一个强连通分量。

然后为什么答案不会被重复计算呢?不会出现博物馆 $u$ 在这条链里出现多次的情况吗?

我们发现,如果从 $(u,i)$ 能走到 $(u,j)$,那么从 $(u,j)$ 也能走到 $(u,i)$。

因为这代表在原图上可以走一条长 $(j-i)\bmod d$ 的链从 $u$ 走回到 $u$。现在是星期 $j$ 时,只需要走这条链 $d-1$ 次就能到星期 $i$。

那么如果从 $(u,i)$ 能走到 $(u,j)$,两点一定属于一个强连通分量。在统计强连通分量内的答案时可以判掉。于是便不可能有重复统计的情况。

那么这题就做完了……吗?


如果用DFS式的tarjan,那么递归深度可以到 $n\times d$ 即 $5\times 10^6$,爆栈无疑。

虽然可以用pragma指令或者各种其他方式开栈内存,但……假如这是OI考场上呢?

那么就需要用手写栈模拟tarjan过程。具体写着会比较难受,我在代码里写好了注释。

当然也可能是我写复杂了,我的代码真的巨慢无比

时间复杂度 $O((n+m)\times d)$。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. const int maxn=;
  5. #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
  6. #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
  7. #define MEM(x,v) memset(x,v,sizeof(x))
  8. inline int read(){
  9. char ch=getchar();int x=,f=;
  10. while(ch<'' || ch>'') f|=ch=='-',ch=getchar();
  11. while(ch>='' && ch<='') x=x*+ch-'',ch=getchar();
  12. return f?-x:x;
  13. }
  14. inline ll getc(){
  15. char ch=getchar();
  16. while(ch<'' || ch>'') ch=getchar();
  17. return ch-'';
  18. }
  19. int n,m,d,head[maxn],el,to[maxn],nxt[maxn],head2[maxn],el2,to2[maxn],nxt2[maxn];
  20. int dfn[maxn],low[maxn],dfs_clock,stk[maxn],tp,id[maxn],scc,cnt[maxn],tmp[maxn],tl,dp[maxn];
  21. int recstk[maxn],rectp,cur[maxn];
  22. ll vld[];
  23. bool vis[maxn],ins[maxn],fst[maxn],was[maxn];
  24. inline void add(int u,int v){
  25. to[++el]=v;nxt[el]=head[u];head[u]=el;
  26. }
  27. inline void add2(int u,int v){
  28. to2[++el2]=v;nxt2[el2]=head2[u];head2[u]=el2;
  29. }
  30. void dfs(int s){
  31. recstk[rectp=]=s; //recstk模拟系统函数栈
  32. cur[s]=head[s]; //cur[u]表示当前u的边遍历到哪一条
  33. while(rectp){
  34. int u=recstk[rectp];
  35. if(fst[u]){ //fst[u]表示不是第一次入栈(那么就遍历过边)
  36. if(was[u]) low[u]=min(low[u],low[to[cur[u]]]),was[u]=false;
  37. //was[u]表示u上一条遍历到的边是否满足!dfn[v]
  38. //此时就要low[u]=min(low[u],low[v])
  39. //不过由于模拟栈中不能方便回溯,就这么写了
  40. cur[u]=nxt[cur[u]]; //向后推一条边
  41. }
  42. else{ //u第一次进栈
  43. fst[u]=true;
  44. dfn[u]=low[u]=++dfs_clock;
  45. ins[u]=true;
  46. stk[++tp]=u;
  47. }
  48. if(!cur[u]){ //边遍历完了
  49. if(low[u]==dfn[u]){
  50. scc++;tl=; //强连通编号++
  51. do{
  52. ins[stk[tp]]=false;
  53. id[stk[tp]]=scc;
  54. int x=(stk[tp]+d-)/d,y=stk[tp]%d?stk[tp]%d:d;
  55. //这个点是(x,y)
  56. if(!((vld[x]>>y)&)) continue; //(x,y)博物馆不开,不管
  57. tmp[++tl]=x;
  58. if(!vis[x]) vis[x]=true,cnt[scc]++; //如果博物馆x之前没访问过,那么新计入答案
  59. }while(stk[tp--]!=u);
  60. FOR(i,,tl) vis[tmp[i]]=; //vis清空
  61. }
  62. rectp--; //u退栈(相当于回溯)
  63. }
  64. else{
  65. int i=cur[u],v=to[i];
  66. if(!dfn[v]){
  67. recstk[++rectp]=v; //v进栈(相当于递归)
  68. cur[v]=head[v]; //v当前遍历的边是head[v]
  69. was[u]=true; //u这条边满足!dfn[v]
  70. }
  71. else if(ins[v]) low[u]=min(low[u],dfn[v]);
  72. }
  73. }
  74. }
  75. int main(){
  76. n=read();m=read();d=read();
  77. FOR(i,,m){
  78. int u=read(),v=read();
  79. FOR(j,,d) add((u-)*d+j,(v-)*d+j%d+);
  80. }
  81. FOR(i,,n) FOR(j,,d) vld[i]|=getc()<<j; //干脆压下空间……(一开始以为会MLE)
  82. FOR(i,,n*d) if(!dfn[i]) dfs(i);
  83. FOR(u,,n*d) for(int i=head[u];i;i=nxt[i]){
  84. int v=to[i];
  85. if(id[u]!=id[v]) add2(id[u],id[v]); //新图连边
  86. }
  87. //根据tarjan的性质, 强连通编号为反向的拓扑序,所以可以从1到n直接遍历一遍
  88. FOR(u,,scc){
  89. for(int i=head2[u];i;i=nxt2[i]) dp[u]=max(dp[u],dp[to2[i]]);
  90. dp[u]+=cnt[u];
  91. }
  92. printf("%d\n",dp[id[]]);
  93. }

upd:貌似可以把tarjan变成inline就没有栈的问题了……

(代码好写N倍……)

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. const int maxn=;
  5. #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
  6. #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
  7. #define MEM(x,v) memset(x,v,sizeof(x))
  8. inline int read(){
  9. char ch=getchar();int x=,f=;
  10. while(ch<'' || ch>'') f|=ch=='-',ch=getchar();
  11. while(ch>='' && ch<='') x=x*+ch-'',ch=getchar();
  12. return f?-x:x;
  13. }
  14. inline ll getc(){
  15. char ch=getchar();
  16. while(ch<'' || ch>'') ch=getchar();
  17. return ch-'';
  18. }
  19. int n,m,d,head[maxn],el,to[maxn],nxt[maxn],head2[maxn],el2,to2[maxn],nxt2[maxn];
  20. int dfn[maxn],low[maxn],dfs_clock,stk[maxn],tp,id[maxn],scc,cnt[maxn],tmp[maxn],tl,dp[maxn];
  21. ll vld[];
  22. bool vis[maxn],ins[maxn];
  23. inline void add(int u,int v){
  24. to[++el]=v;nxt[el]=head[u];head[u]=el;
  25. }
  26. inline void add2(int u,int v){
  27. to2[++el2]=v;nxt2[el2]=head2[u];head2[u]=el2;
  28. }
  29. inline void dfs(int u){
  30. dfn[u]=low[u]=++dfs_clock;
  31. ins[stk[++tp]=u]=true;
  32. for(int i=head[u];i;i=nxt[i]){
  33. int v=to[i];
  34. if(!dfn[v]) dfs(v),low[u]=min(low[u],low[v]);
  35. else if(ins[v]) low[u]=min(low[u],dfn[v]);
  36. }
  37. if(dfn[u]==low[u]){
  38. scc++;tl=; //强连通编号++
  39. do{
  40. ins[stk[tp]]=false;
  41. id[stk[tp]]=scc;
  42. int x=(stk[tp]+d-)/d,y=stk[tp]%d?stk[tp]%d:d;
  43. //这个点是(x,y)
  44. if(!((vld[x]>>y)&)) continue; //(x,y)博物馆不开,不管
  45. tmp[++tl]=x;
  46. if(!vis[x]) vis[x]=true,cnt[scc]++; //如果博物馆x之前没访问过,那么新计入答案
  47. }while(stk[tp--]!=u);
  48. FOR(i,,tl) vis[tmp[i]]=; //vis清空
  49. }
  50. }
  51. int main(){
  52. n=read();m=read();d=read();
  53. FOR(i,,m){
  54. int u=read(),v=read();
  55. FOR(j,,d) add((u-)*d+j,(v-)*d+j%d+);
  56. }
  57. FOR(i,,n) FOR(j,,d) vld[i]|=getc()<<j; //干脆压下空间……(一开始以为会MLE)
  58. FOR(i,,n*d) if(!dfn[i]) dfs(i);
  59. FOR(u,,n*d) for(int i=head[u];i;i=nxt[i]){
  60. int v=to[i];
  61. if(id[u]!=id[v]) add2(id[u],id[v]); //新图连边
  62. }
  63. //根据tarjan的性质, 强连通编号为反向的拓扑序,所以可以从1到n直接遍历一遍
  64. FOR(u,,scc){
  65. for(int i=head2[u];i;i=nxt2[i]) dp[u]=max(dp[u],dp[to2[i]]);
  66. dp[u]+=cnt[u];
  67. }
  68. printf("%d\n",dp[id[]]);
  69. }

CF1137C Museums Tour(Tarjan,强连通分量)的更多相关文章

  1. Codeforces 1137C Museums Tour (强连通分量, DP)

    题意和思路看这篇博客就行了:https://www.cnblogs.com/cjyyb/p/10507937.html 有个问题需要注意:对于每个scc,只需要考虑进入这个scc的时间即可,其实和从哪 ...

  2. Tarjan 强连通分量 及 双联通分量(求割点,割边)

    Tarjan 强连通分量 及 双联通分量(求割点,割边) 众所周知,Tarjan的三大算法分别为 (1)         有向图的强联通分量 (2)         无向图的双联通分量(求割点,桥) ...

  3. tarjan 强连通分量

    一.强连通分量定义 有向图强连通分量在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly c ...

  4. tarjan强连通分量模板(pascal)

    友好城市 [问题描述]小 w 生活在美丽的 Z 国. Z 国是一个有 n 个城市的大国, 城市之间有 m 条单向公路(连接城市 i. j 的公路只能从 i 连到 j). 城市 i. j 是友好城市当且 ...

  5. 1051: [HAOI2006]受欢迎的牛 (tarjan强连通分量+缩点)

    题目大意:CodeVs2822的简单版本 传送门 $Tarjan$强连通分量+缩点,若连通块的个数等于一则输出n:若缩点后图中出度为0的点个数为1,输出对应连通块内的点数:否则输出0: 代码中注释部分 ...

  6. [poj 2553]The Bottom of a Graph[Tarjan强连通分量]

    题意: 求出度为0的强连通分量. 思路: 缩点 具体有两种实现: 1.遍历所有边, 边的两端点不在同一强连通分量的话, 将出发点所在强连通分量出度+1. #include <cstdio> ...

  7. [poj 1904]King's Quest[Tarjan强连通分量]

    题意:(当时没看懂...) N个王子和N个女孩, 每个王子喜欢若干女孩. 给出每个王子喜欢的女孩编号, 再给出一种王子和女孩的完美匹配. 求每个王子分别可以和那些女孩结婚可以满足最终每个王子都能找到一 ...

  8. 算法模板——Tarjan强连通分量

    功能:输入一个N个点,M条单向边的有向图,求出此图全部的强连通分量 原理:tarjan算法(百度百科传送门),大致思想是时间戳与最近可追溯点 这个玩意不仅仅是求强连通分量那么简单,而且对于一个有环的有 ...

  9. Equivalent Sets HDU - 3836 2011多校I tarjan强连通分量

    题意: 给一些集合 要求证明所有集合是相同的 证明方法是,如果$A∈B$,$B∈A$那么$A=B$成立 每一次证明可以得出一个$X∈Y$ 现在已经证明一些$A∈B$成立 求,最少再证明多少次,就可以完 ...

随机推荐

  1. python3 常见的两种文件上传方法

    1.上传页面带input type格式send_keys传值方式上传不能大于60k(具体看开发设置的value)文件大小 fx.find_element_by_id('xx').send_keys(r ...

  2. react 路由4 学习

    表单控件 受控表单组件 非受控的表单组件 demo:收集表单提交的数据 路由(V4) 特点:一切皆是组件 官网:https://reacttraining.com/react-router/ npm ...

  3. 区块链教程(二):比特币、区块链、以太坊、Hyperledger的关系

    不知道大家喜不喜欢音乐! 朋克音乐:诞生于七十年代中期,一种源于六十年代车库摇滚和前朋克摇滚的简单摇滚乐.它由一个简单悦耳的主旋律和三个和弦组成,经过演变,朋克已经逐渐脱离摇滚,成为一种独立的音乐,朋 ...

  4. SQLServer2016 之后增加了索引列数的限制 从 16个列 增加到了 32个列

    创建带有包含列的索引 https://docs.microsoft.com/zh-cn/sql/relational-databases/indexes/create-indexes-with-inc ...

  5. 《Effective C++》设计与声明:条款18-条款25

    条款18:让接口容易被正确使用,不容易被误用 注意使用const,explicit,shared_ptr等来限制接口. 必要时可以创建一些新的类型,限制类型操作,束缚对象等. 注意保持接口的一致性,且 ...

  6. QueryRunner 错误

    QueryRunner qr=new QueryRunner(JDBCUtils.getDataSource()); 写成了 QueryRunner qr = new QueryRunner(); 导 ...

  7. 4.请介绍一下c++和Java的区别

    1.指针 2.c++多重继承,Java只能继承一个父类,但是可以继承多个接口 3.数据类型及类,Java完全面向对象,所有函数和变量都必须是类的一部分.而c++允许将函数和变量定义为全局,Java取消 ...

  8. Django Rest framework 框架之认证使用和源码执行流程

    用这个框架需要先安装: pip3 install djangorestframework 如果写了一个CBV的东西,继承了View. # 继承Django里面View class APIView(Vi ...

  9. Maven 项目 无缘无故报错:版本冲突,其他机器上正常-提交的时候报冲突怎么也解决不掉

    2018年: maven突然之间报错了,显示版本冲突,但是其他的机器是好的, 使用命令:mvn compile -P dev -e; 看看测试环境有没有问题,还是有问题.而且,刚开始只是报错:erro ...

  10. 解决 Redis 只读不可写的问题

    本文转载:https://blog.csdn.net/han_cui/article/details/54767208?tdsourcetag=s_pcqq_aiomsg 解决 Redis 只读不可写 ...