CF1137C Museums Tour(Tarjan,强连通分量)
好题,神题。
题目大意:
一个国家有 $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,强连通分量)的更多相关文章
- Codeforces 1137C Museums Tour (强连通分量, DP)
题意和思路看这篇博客就行了:https://www.cnblogs.com/cjyyb/p/10507937.html 有个问题需要注意:对于每个scc,只需要考虑进入这个scc的时间即可,其实和从哪 ...
- Tarjan 强连通分量 及 双联通分量(求割点,割边)
Tarjan 强连通分量 及 双联通分量(求割点,割边) 众所周知,Tarjan的三大算法分别为 (1) 有向图的强联通分量 (2) 无向图的双联通分量(求割点,桥) ...
- tarjan 强连通分量
一.强连通分量定义 有向图强连通分量在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly c ...
- tarjan强连通分量模板(pascal)
友好城市 [问题描述]小 w 生活在美丽的 Z 国. Z 国是一个有 n 个城市的大国, 城市之间有 m 条单向公路(连接城市 i. j 的公路只能从 i 连到 j). 城市 i. j 是友好城市当且 ...
- 1051: [HAOI2006]受欢迎的牛 (tarjan强连通分量+缩点)
题目大意:CodeVs2822的简单版本 传送门 $Tarjan$强连通分量+缩点,若连通块的个数等于一则输出n:若缩点后图中出度为0的点个数为1,输出对应连通块内的点数:否则输出0: 代码中注释部分 ...
- [poj 2553]The Bottom of a Graph[Tarjan强连通分量]
题意: 求出度为0的强连通分量. 思路: 缩点 具体有两种实现: 1.遍历所有边, 边的两端点不在同一强连通分量的话, 将出发点所在强连通分量出度+1. #include <cstdio> ...
- [poj 1904]King's Quest[Tarjan强连通分量]
题意:(当时没看懂...) N个王子和N个女孩, 每个王子喜欢若干女孩. 给出每个王子喜欢的女孩编号, 再给出一种王子和女孩的完美匹配. 求每个王子分别可以和那些女孩结婚可以满足最终每个王子都能找到一 ...
- 算法模板——Tarjan强连通分量
功能:输入一个N个点,M条单向边的有向图,求出此图全部的强连通分量 原理:tarjan算法(百度百科传送门),大致思想是时间戳与最近可追溯点 这个玩意不仅仅是求强连通分量那么简单,而且对于一个有环的有 ...
- Equivalent Sets HDU - 3836 2011多校I tarjan强连通分量
题意: 给一些集合 要求证明所有集合是相同的 证明方法是,如果$A∈B$,$B∈A$那么$A=B$成立 每一次证明可以得出一个$X∈Y$ 现在已经证明一些$A∈B$成立 求,最少再证明多少次,就可以完 ...
随机推荐
- 迁移 VMware 虚拟机到 KVM
虚拟机转换| VMware vCenter Converterhttps://www.vmware.com/cn/products/converter.html 迁移 VMware 虚拟机到 KVMh ...
- JEECG&JWT异常捕获强化处理 | Java: Meaning of catch (final SomeException e)?
//从header中得到token String authHeader = request.getHeader(JwtConstants.AUTHORIZATION); if (authHeader ...
- <c:forEach varStatus="status">中 varStatus的作用
varStatus是<c:forEach>jstl循环标签的一个属性,varStatus属性. varStatus=“status”事实上定义了一个status名的对象作为varStatu ...
- CDH 6.0.1 集群搭建 「Process」
这次搭建我使用的机器 os 是 Centos7.4 RH 系的下面以流的方式纪录搭建过程以及注意事项 Step1: 配置域名相关,因为只有三台机器组集群,所以直接使用了 hosts 的方法: 修改主机 ...
- EXAMPLE FOR PEEWEE 多姿势使用 PEEWEE
使用 PEEWEE 断断续续的差不多已经三个年头了,但是没有像这次使用这么多的特性和功能,所以这次一并记录一下,需要注意的地方和一些使用细节,之后使用起来可能会更方便. 因为是使用的 SQLAched ...
- bpmn.js & BPMN diagram
bpmn.js & BPMN diagram BPMN 2.0 for the web https://github.com/bpmn-io/bpmn-js https://demo.bpmn ...
- python设计模式第十九天【职责链模式】
1.应用场景 (1)将一个任务拆分为具有顺序的多个部分,每个类完成相应的部分,并且顺序执行 (2)软件窗口的消息传播 (3)SERVLET容积的过滤器Filter的实现 2.代码实现 #!/usr/b ...
- Maven最佳实战
Maven中内置的隐藏变量: http://www.cnblogs.com/quanyongan/category/471332.html Maven提供了三个隐式的变量可以用来访问环境变量,POM信 ...
- ES6字符串操作
讨论字符串操作之前,我们先来了解一下Unicode 编码的由来,因为Js中的字符串就是一系列Unicode码的集合. 我们都知道,世界上存在着各种各样的语言,汉语,英语,日语等,相对应的,也就存在各种 ...
- linux用户、文件权限相关命令
root 现代操作系统一般属于多用户的操作系统,也就是说,同一台机器可以为多个用户建立账户,一般这些用户都是为普通用户,这些普通用户能同时登录这台计算机,计算机对这些用户分配一定的资源. 普通用户在所 ...