好题,神题。

题目链接: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)$。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
char ch=getchar();int x=,f=;
while(ch<'' || ch>'') f|=ch=='-',ch=getchar();
while(ch>='' && ch<='') x=x*+ch-'',ch=getchar();
return f?-x:x;
}
inline ll getc(){
char ch=getchar();
while(ch<'' || ch>'') ch=getchar();
return ch-'';
}
int n,m,d,head[maxn],el,to[maxn],nxt[maxn],head2[maxn],el2,to2[maxn],nxt2[maxn];
int dfn[maxn],low[maxn],dfs_clock,stk[maxn],tp,id[maxn],scc,cnt[maxn],tmp[maxn],tl,dp[maxn];
int recstk[maxn],rectp,cur[maxn];
ll vld[];
bool vis[maxn],ins[maxn],fst[maxn],was[maxn];
inline void add(int u,int v){
to[++el]=v;nxt[el]=head[u];head[u]=el;
}
inline void add2(int u,int v){
to2[++el2]=v;nxt2[el2]=head2[u];head2[u]=el2;
}
void dfs(int s){
recstk[rectp=]=s; //recstk模拟系统函数栈
cur[s]=head[s]; //cur[u]表示当前u的边遍历到哪一条
while(rectp){
int u=recstk[rectp];
if(fst[u]){ //fst[u]表示不是第一次入栈(那么就遍历过边)
if(was[u]) low[u]=min(low[u],low[to[cur[u]]]),was[u]=false;
//was[u]表示u上一条遍历到的边是否满足!dfn[v]
//此时就要low[u]=min(low[u],low[v])
//不过由于模拟栈中不能方便回溯,就这么写了
cur[u]=nxt[cur[u]]; //向后推一条边
}
else{ //u第一次进栈
fst[u]=true;
dfn[u]=low[u]=++dfs_clock;
ins[u]=true;
stk[++tp]=u;
}
if(!cur[u]){ //边遍历完了
if(low[u]==dfn[u]){
scc++;tl=; //强连通编号++
do{
ins[stk[tp]]=false;
id[stk[tp]]=scc;
int x=(stk[tp]+d-)/d,y=stk[tp]%d?stk[tp]%d:d;
//这个点是(x,y)
if(!((vld[x]>>y)&)) continue; //(x,y)博物馆不开,不管
tmp[++tl]=x;
if(!vis[x]) vis[x]=true,cnt[scc]++; //如果博物馆x之前没访问过,那么新计入答案
}while(stk[tp--]!=u);
FOR(i,,tl) vis[tmp[i]]=; //vis清空
}
rectp--; //u退栈(相当于回溯)
}
else{
int i=cur[u],v=to[i];
if(!dfn[v]){
recstk[++rectp]=v; //v进栈(相当于递归)
cur[v]=head[v]; //v当前遍历的边是head[v]
was[u]=true; //u这条边满足!dfn[v]
}
else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
}
}
int main(){
n=read();m=read();d=read();
FOR(i,,m){
int u=read(),v=read();
FOR(j,,d) add((u-)*d+j,(v-)*d+j%d+);
}
FOR(i,,n) FOR(j,,d) vld[i]|=getc()<<j; //干脆压下空间……(一开始以为会MLE)
FOR(i,,n*d) if(!dfn[i]) dfs(i);
FOR(u,,n*d) for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(id[u]!=id[v]) add2(id[u],id[v]); //新图连边
}
//根据tarjan的性质, 强连通编号为反向的拓扑序,所以可以从1到n直接遍历一遍
FOR(u,,scc){
for(int i=head2[u];i;i=nxt2[i]) dp[u]=max(dp[u],dp[to2[i]]);
dp[u]+=cnt[u];
}
printf("%d\n",dp[id[]]);
}

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

(代码好写N倍……)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
char ch=getchar();int x=,f=;
while(ch<'' || ch>'') f|=ch=='-',ch=getchar();
while(ch>='' && ch<='') x=x*+ch-'',ch=getchar();
return f?-x:x;
}
inline ll getc(){
char ch=getchar();
while(ch<'' || ch>'') ch=getchar();
return ch-'';
}
int n,m,d,head[maxn],el,to[maxn],nxt[maxn],head2[maxn],el2,to2[maxn],nxt2[maxn];
int dfn[maxn],low[maxn],dfs_clock,stk[maxn],tp,id[maxn],scc,cnt[maxn],tmp[maxn],tl,dp[maxn];
ll vld[];
bool vis[maxn],ins[maxn];
inline void add(int u,int v){
to[++el]=v;nxt[el]=head[u];head[u]=el;
}
inline void add2(int u,int v){
to2[++el2]=v;nxt2[el2]=head2[u];head2[u]=el2;
}
inline void dfs(int u){
dfn[u]=low[u]=++dfs_clock;
ins[stk[++tp]=u]=true;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(!dfn[v]) dfs(v),low[u]=min(low[u],low[v]);
else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
scc++;tl=; //强连通编号++
do{
ins[stk[tp]]=false;
id[stk[tp]]=scc;
int x=(stk[tp]+d-)/d,y=stk[tp]%d?stk[tp]%d:d;
//这个点是(x,y)
if(!((vld[x]>>y)&)) continue; //(x,y)博物馆不开,不管
tmp[++tl]=x;
if(!vis[x]) vis[x]=true,cnt[scc]++; //如果博物馆x之前没访问过,那么新计入答案
}while(stk[tp--]!=u);
FOR(i,,tl) vis[tmp[i]]=; //vis清空
}
}
int main(){
n=read();m=read();d=read();
FOR(i,,m){
int u=read(),v=read();
FOR(j,,d) add((u-)*d+j,(v-)*d+j%d+);
}
FOR(i,,n) FOR(j,,d) vld[i]|=getc()<<j; //干脆压下空间……(一开始以为会MLE)
FOR(i,,n*d) if(!dfn[i]) dfs(i);
FOR(u,,n*d) for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(id[u]!=id[v]) add2(id[u],id[v]); //新图连边
}
//根据tarjan的性质, 强连通编号为反向的拓扑序,所以可以从1到n直接遍历一遍
FOR(u,,scc){
for(int i=head2[u];i;i=nxt2[i]) dp[u]=max(dp[u],dp[to2[i]]);
dp[u]+=cnt[u];
}
printf("%d\n",dp[id[]]);
}

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. 如何使用RSS

    (转载: http://www.ruanyifeng.com/blog/2006/01/rss.html) 一. 自从我发现很多人不知道什么是RSS以后,我就一直想向大家介绍它,因为它太有用了,将来会 ...

  2. vue router 根据不同的id切换链接界面不刷新

    我们一般使用vue的router时候会根据不同的id来切换界面,但是界面没有立刻刷新.下面我们讲下如何解决这个问题. html: <template> <div id="a ...

  3. anaconda + VSCode + 生产环境配置

    1. 修改jupyter notebook 默认路径: 进入anaconda 命令行, jupyter notebook --generate-config   生成配置文件, 该文件在    本机用 ...

  4. Azure系列2.1.4 —— BlobInputStream

    (小弟自学Azure,文中有不正确之处,请路过各位大神指正.) 网上azure的资料较少,尤其是API,全是英文的,中文资料更是少之又少.这次由于公司项目需要使用Azure,所以对Azure的一些学习 ...

  5. [转帖]Docker 清理占用的磁盘空间

    Docker(二十七)-Docker 清理占用的磁盘空间 https://www.cnblogs.com/zhuochong/p/10076599.html docker system docker ...

  6. Windows 激活的简单办法(能上网)

    1. 之前很多机器上面总是提示我  盗版系统看起来挺不high的 2. 还是使用之前的办法来进行激活 slmgr  (之前写过) /ipk <Product Key> 安装产品密钥(替换现 ...

  7. 剑指offer(8)

    题目: 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. 思路: 第一反应想到的是把数右移,每一位与1相与,然后判断个数,但是若输入的为负数,会出现死循环现象. 所以我们设置一个标志量 ...

  8. png8、16、24、32位的区别

    我们都知道一张图片可以保存为很多种不同的格式,比如bmp/png/jpeg/gif等等.这个是从文件格式的角度看,我们抛开文件格式,看图片本身,我们可以分为8位, 16位, 24位, 32位等. 单击 ...

  9. github上测试服出现bug,如何回滚并获得合并之前的分支

    使用场景: 当我们提交了一个pr,但是该pr合并之后,经过在测试测试有问题,需要回滚.这个时候主master代码将会被回滚到提交你的pr之前的代码.而你的pr由于已经被合并过了,所以无法继续提交. 这 ...

  10. 打包一个UWP APP

    Before packaging your app Test your app. Before you package your app for store submission, make sure ...