2021.09.11 CF1547G How Many Paths & Luogu P5546

CF1547G   How   Many   Paths \color{green}{\texttt{CF1547G How Many Paths}} CF1547G How Many Paths

[Problem] \color{blue}{\texttt{[Problem]}} [Problem]

在这里插入图片描述

[Solution] \color{blue}{\texttt{[Solution]}} [Solution]

首先,如果一个点 u u u 有自环且 1 1 1 能到达 u u u,那么 ans u \text{ans}_{u} ansu 显然就是 − 1 -1 1(因为你可以在自环中跑任意次)。

其次,如果从 u u u 在一个点数大于 1 1 1 的强连通分量上,那么 ans u = − 1 \text{ans}_{u}=-1 ansu=1(因为你可以在强连通分量中跑任意次)。

再次,如果从 1 1 1 x x x 的路径通过了一个点 u u u 满足 ans u = − 1 \text{ans}_{u}=-1 ansu=1,那么 ans x = − 1 \text{ans}_{x}=-1 ansx=1(想一想,为什么)。

所以,本题就用 tarjan 缩点,然后拓扑排序就可以了。

不过,这只是口头 AC 了本题,离程序 AC 还有一定的距离。

本题是细节题,有以下好多点要注意:

  • 初始化拓扑队列时,只能把点 1 1 1 在的强连通分量加入队列。否则会出现本来某个点 u u u 不可达,但却被访问到了而导致答案错误。
  • 因为初始的时候只把点 1 1 1 加入了队列,所以不能当点的入度等于 0 0 0 时才把点加入队列,否则它可能一辈子都不被访问。你应该访问到点 u u u 时就把它加入队列。
  • 因为我们访问到点 u u u 时就把 u u u 加入了队列,所以你应该限制每一个点的入队次数,否则会 TLE。程序实现上 70 70 70 次才能 AC。
  • 初始化数组的时候不能用 memset,因为单次 memset O ( size ) \mathcal{O}(\text{size}) O(size) 的,当输入数据组数很多时,会 TLE。你应该用 for 暴力清空。

其实这里还没有讲完,具体看代码。有注释的地方都要注意。

[code] \color{blue}{\texttt{[code]}} [code]

inline int ckmin(int &a,int b){
	return (a=((a<b)?a:b));
}
inline int ckmax(int &a,int b){
	return (a=((a>b)?a:b));
}

const int N=4e5+100;
const int inf=0x3f3f3f3f;

struct edge{
	int next,to;
}e[N],E[N];int h[N],te,H[N],Te;
inline void add(int u,int v){
	e[++te]=(edge){h[u],v};h[u]=te;
}
inline void Add(int u,int v){
	E[++Te]=(edge){H[u],v};H[u]=Te;
}

int Stack[N],stack_top;
int dfn[N],low[N],dfscnt;
int belong[N],num[N],scc;
void tarjan(int u){
	Stack[++stack_top]=u;
	dfn[u]=low[u]=++dfscnt;
	
	for(int i=h[u];i;i=e[i].next){
		register int v=e[i].to;
		if (!dfn[v]){
			tarjan(v);
			ckmin(low[u],low[v]);
		}
		else if (!belong[v])
			ckmin(low[u],dfn[v]);
	}
	
	if (dfn[u]==low[u]){
		num[belong[u]=++scc]=1;
		
		while (Stack[stack_top]!=u){
			int v=Stack[stack_top--];
			num[belong[v]=scc]++;
		}
		
		--stack_top;
	}
}

int n,m,ans[N],ind[N];
bool UniQue[N];//是否有自环 

inline void init_data(int n){
	te=Te=scc=dfscnt=stack_top=0;
	for(int i=1;i<=n;i++) h[i]=H[i]=0;
	for(int i=1;i<=n;i++) ans[i]=num[i]=0;
	for(int i=1;i<=n;i++) dfn[i]=low[i]=0;
	for(int i=1;i<=n;i++) ind[i]=Stack[i]=0;
	for(int i=1;i<=n;i++) belong[i]=0;
	for(int i=1;i<=n;i++) UniQue[i]=true;
}//不能用 memset 

inline void build_new_graph(){
	for(int u=1;u<=n;u++)
		for(int i=h[u];i;i=e[i].next){
			register int v=e[i].to;
			if (belong[u]!=belong[v]){
				Add(belong[u],belong[v]);
				++ind[belong[v]];
			}
		}
}

inline void topo_sort(){
	for(int i=1;i<=n;i++)
		if (num[belong[i]]==1&&!UniQue[i])
			num[belong[i]]=inf;
	
	if (num[belong[1]]>1)
		ans[belong[1]]=-1;
	else ans[belong[1]]=1;
	
	queue<int> q;
	q.push(belong[1]);
	
	while (!q.empty()){
		int u=q.front();q.pop();
		for(int i=H[u];i;i=E[i].next){
			register int v=E[i].to;//注意大小写 E 
			if (num[v]>1||ans[u]==-1) ans[v]=-1;
			else if (ans[v]!=-1) ans[v]++;//不能写成 ans[v]+=ans[u],同时注意条件 
			if (ans[v]<=70) q.push(v);//注意限制入队次数 
		}
	}
}

int main(){
	for(int T=1,Q=read();T<=Q;T++){
		n=read();m=read();
		
		init_data(n);
		
		for(int i=1,u,v;i<=m;i++){
			u=read();v=read();
			if (u!=v) add(u,v);
			else UniQue[u]=false;
		}
		
		for(int i=1;i<=n;i++)
			if (!dfn[i]) tarjan(i);//小心全图不连通 
		
		build_new_graph();
		
		topo_sort();
		
		for(int i=1;i<=n;i++)
			if (ans[belong[i]]>=2) printf("2 ");//不能省略掉这里 
			else printf("%d ",ans[belong[i]]);
		printf("\n");
	}
	
	return 0;
}

其实代码不是很长,只是空行很多。

从这道题,我们可以看到,口头 AC 和代码 AC 是有区别的。


Luogu   P5546   [POI2000]公共串 \color{green}{\texttt{Luogu P5546 [POI2000]公共串}} Luogu P5546 [POI2000]公共串

[Problem] \color{blue}{\texttt{[Problem]}} [Problem]

n n n 个字符串的最长公共子串。

[Solution] \color{blue}{\texttt{[Solution]}} [Solution]

首先明白一点,字符串 a , b a,b a,b 的最长公共子串和 c c c 的最长公共子串不是 a , b , c a,b,c a,b,c 的最长公共子串。

有点拗口,看个范例: a a aabbac b b bacabb c c cacacc,则 a , b a,b a,b 的最长公共子串是 abbabb c c c 的最长公共子串是 a。但是 a , b , c a,b,c a,b,c 的最长公共子串是 ac

我们发现,答案只要我们求最长公共子串的长度,而这个东西是有单调性(即可二分性)的。所以我们可以二分。

考虑如何判断 n n n 个字符串是否有一个长度不小于 x x x 的公共子串。

首先算出每个字符串的 hash 值。

对于第 i i i 个字符串,我们提取出它每一个长度为 x x x 的子串,然后用 set 记录第 i i i 个字符串含有这个子串。最后判断是否有一个子串,它被所有的 n n n 的字符串包含即可。

最后的时间复杂度 O ( n l log ⁡ n log ⁡ l ) \mathcal{O}(nl \log n \log l) O(nllognlogl)。其中 l l l 是字符串长度。

不过此题的正解是后缀自动机。当然我太菜了,只会二分 + hash。

[code] \color{blue}{\texttt{[code]}} [code]

const int N=20,M=10010;
const int inf=0x3f3f3f3f;

char s[N][M];int len[N];
long long haSh[N][M],Pow[M];

int n,m,l,r,mid,ans;

inline void init_hash(){
	Pow[0]=1;
	for(int i=1;i<=m;i++)
		Pow[i]=Pow[i-1]*233;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=len[i];j++)
			haSh[i][j]=haSh[i][j-1]*233+(s[i][j]-'a');
}

inline long long Hash(int k,int l,int r){
	return haSh[k][r]-haSh[k][l-1]*Pow[r-l+1];
}

inline bool check(int mid){
	map<int,set<int> > pos;
	
	for(int i=1;i<=n;i++)
		for(int j=mid;j<=len[i];j++)
			if (pos[Hash(i,j-mid+1,j)].find(i)==pos[Hash(i,j-mid+1,j)].end())
				pos[Hash(i,j-mid+1,j)].insert(i);
	
	for(int i=mid;i<=len[1];i++)
		if (pos[Hash(1,i-mid+1,i)].size()==n)
			return true;
	
	return false;
}

int main(){
    scanf("%d",&n);m=inf;
	
	for(int i=1;i<=n;i++){
		scanf("%s",s[i]+1);
		len[i]=strlen(s[i]+1);
		m=min(m,len[i]);
	}
	
	init_hash();
	
	l=1;r=m;ans=0;
	while (l<=r){
		mid=(l+r)>>1;
		if (check(mid)){
			ans=mid;
			l=mid+1;
		}
		else r=mid-1;
	}
	
	printf("%d",ans);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值