[COCI2019-2020 #3]Lampice

lampice

题目描述

Mirko 准备用 NN 个 LED 灯来装饰圣诞树。这 NN 个灯通过了 N-1N−1 根电线连接在一起,任意两个灯之间都能通过电线互相到达。并且我们知道所有灯的颜色。

装饰结束后,Mirko 发现了很多有趣的图案,其中他最感兴趣的是 palindromic segments。一个 palindromic segments 是一条灯 uu 和灯 vv 之间的路径,满足从 uu 到 vv 经过的灯的颜色序列和从从 vv 到 uu 经过的灯的颜色序列相同。

Mirko 想要知道最长的 palindromic segments 有多长。一个 palindromic segments 长度定义为这条路径上灯的个数。

输入格式

第一行包含一个整数 N,表示灯的个数。

第二行包含一个长度为 NN 的字符串,仅由小写字母组成,其中第 ii 个字符表示第 ii 个灯的颜色。

接下里 N-1N−1 行,每行包含两个整数 AA 和 BB (A \neq BA\=B),表示灯 AA 和 灯 BB 之间有一条电线直接相连。

输出格式

仅一行,包含一个整数表示最长的 palindromic segments 的长度。

输入输出样例

输入 #1

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

输出 #1

3

输入 #2

4
aabb
1 2
1 3
3 4

输出 #2

2

输入 #3

8
acdbabcd
1 6
6 7
6 3
3 4
4 5
5 2
8 5

输出 #3

5

前置芝士:点分治,二分,哈希

且不说我不会淀粉质,就算学了再写这道也炒鸡毒瘤QWQ

快读帮我狗过洛谷的时限

首先这个结果是存在单调性的(要分奇偶),如果存在 l e n + 2 len +2 len+2的回文串,那么一定存在 l e n len len的回文串,所以我们可以二分回文串长度,(在我的代码中,先二分偶数长度的,再二分奇数长度的).

然后分治部分,计算对于一个"根" r o o t root root来说,是否有经过它的长度为 l e n len len的回文串,找到了就直接返回节约时间,没有就继续分治直到结束.(分治部分套模板函数就可以了)

比较难的是 c a l c ( 计 算 是 否 存 在 的 函 数 部 分 ) calc(计算是否存在的函数部分) calc()

它包含三个函数 d f s 1 , d f s , d f s 2 dfs1,dfs,dfs2 dfs1,dfs,dfs2,下面会写细一点
M A : 计 算 子 树 中 最 深 的 深 度 , 防 止 越 界 h s 1 : 从 高 位 往 低 位 − > 从 子 节 点 往 根 方 向 的 哈 希 值 ( 未 包 括 根 节 点 ) h s 2 : 从 高 位 往 低 位 − > 从 根 节 点 往 子 节 点 方 向 的 哈 希 值 ( 包 括 根 节 点 ) m a : 哈 希 表 , 本 来 应 该 储 存 h a s h 值 , 这 里 储 存 h a s h 的 差 值 , 具 体 怎 么 存 看 代 码 MA:计算子树中最深的深度,防止越界 \\ hs1:从高位往低位 -> 从子节点往根方向的哈希值(未包括根节点)\\ hs2:从高位往低位 -> 从根节点往子节点方向的哈希值(包括根节点)\\ ma:哈希表,本来应该储存hash值,这里储存hash的差值,具体怎么存看代码\\ MA:,hs1:>()hs2:>()ma:,hash,hash,
阅读顺序最好是:dfs->dfs2->dfs1

inline void calc(int x,int len)
{
	MA = 0;//统计子树中最深的,防止搜索越界
	dfs1(x,0,0,1);
	hs1[x] = 0;
	for(int i = 0;i <= MA + 1;++ i) ma[i].clear();
	for(int i = h[x];i;i = ne[i])
	{
		int y = to[i];
		if(vis[y]) continue;
		dfs(y,x,1,len);
		if(flag) return ;
		dfs2(y,x,1,len);
		if(flag == 1)
			return ;
	}
}
inline void dfs1(int x,int fa,int ha,int dep)
{
	ha = 1ll * ha * P % mod + s[x];
	ha %= mod;
	MA = max(MA,dep);
	hs2[x] = ha;
	//sz[x] = 1;
	for(int i = h[x];i;i = ne[i])
	{
		int y = to[i];
		if(vis[y] || y == fa) continue;
		dfs1(y,x,ha,dep + 1);
		//sz[x] += sz[y];
	}
}//这一部分就是求出hs2的值 + MA

inline void dfs(int x,int fa,int dep,int len)
{
	hs1[x] = hs1[fa] + 1ll * p[dep - 1] * s[x] % mod;
	
	hs1[x] %= mod;
//如果这个长度合适(当前不超过,且剩下的不超过最长深度)	
	if(dep < len && len - dep <= MA)
		if(dep != len - 1 && ma[len - dep].count((1ll * p[len - dep] * hs1[x] % mod - hs2[x] + mod)% mod))//这里讨论的情况是根节点在回文串中间,并不在端点处.如果hash表中已经存在长度补齐且和当前差值相等的,说明有回文串
			flag = 1;
	if(flag) return;
	for(int i = h[x];i;i = ne[i])
	{
		int y = to[i];
		if(vis[y] || y == fa) continue;
		dfs(y,x,dep + 1,len);
		if(flag) return ;
	}
}//这一部分是判断有无回文串并且求hs1

之前说存哈希的差值,仔细说一下:如果一个串是回文串(假设根是 R R R,两端是 a , b a,b a,b),那么从 a − > R − > b = b − > R − > a a->R->b = b->R->a a>R>b=b>R>a,也就是:
h s 1 [ a ] ∗ p [ l e n a ] + h s 2 [ b ] = h s 1 [ b ] ∗ p [ l e n b ] + h s 2 [ a ] hs1[a] * p[lena] + hs2[b] = hs1[b] * p[lenb] + hs2[a] hs1[a]p[lena]+hs2[b]=hs1[b]p[lenb]+hs2[a]
交换一下位置:
h s 1 [ a ] ∗ p [ l e n a ] − h s 2 [ a ] = h s 1 [ b ] ∗ p [ l e n b ] − h s 2 [ b ] hs1[a] * p[lena] - hs2[a] = hs1[b] * p[lenb] - hs2[b] hs1[a]p[lena]hs2[a]=hs1[b]p[lenb]hs2[b]
所以我们查找就是查这个,存也是存这个(区分一下hs1和hs2一些细节)

inline void dfs2(int x,int fa,int dep,int len)
{
	if(dep < len - 1)
		ma[dep + 1][(1ll * p[len - dep] * hs1[x] % mod - hs2[x] + mod) % mod] = 1;
	if(dep == len - 1 && hs2[x] == (1ll * p[1] * hs1[x] % mod + s[root]) % mod)//此处特判根节点是某一端的情况,因为hs2包括了根节点,hs1不包括,所以处理一下
		flag = 1;
	if(flag) return ;
	for(int i = h[x];i;i = ne[i])
	{
		int y = to[i];
		if(y == fa || vis[y]) continue;
		dfs2(y,x,dep + 1,len);
		if(flag == 1) return ; 
	}
}

所以这三个函数一起大概就是这么一个思路:

1.先把子树中的hs2处理出来

2.对每一棵子树分别搜索得出hs1,比较之前加入的子树中的子串是否有相等的

3.将这一个子树的hs2 - hs1的值加入哈希表

4.重复2~3操作

完整代码:

#include<bits/stdc++.h>
#include <tr1/unordered_map>
using namespace std;
using namespace tr1;
const int N = 5e4 + 10;
const int mod = 1e9 + 7,P = 131;
int total,n,sz[N],maxson[N];
int hs1[N],hs2[N];
int root;
bool flag;
int MA;
int p[N];
int h[N],ne[N << 1],to[N << 1],cnt;
bool vis[N];
inline void add(int u,int v)
{
	ne[++ cnt] = h[u];
	to[cnt] = v;
	h[u] = cnt;
} 
char s[N];
tr1 :: unordered_map<int ,int >ma[N];

inline int read()//inline 加速读入
{
	int x=0;char c=getchar();//x代表返回值,c代表读取的字符
	while (c<'0'||c>'9') c=getchar();//读取所有非数部分
	while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();//如果读取的字符为数,加入返回值
	return x;
}

inline void get_root(int fa,int x)
{
	sz[x] = 1;
	maxson[x] = 0;
	for(int i = h[x];i;i = ne[i])
	{
		int y = to[i];
		if(y == fa || vis[y]) continue;
		get_root(x,y);
		sz[x] += sz[y];
		maxson[x] = max(maxson[x],sz[y]);
	}
	maxson[x] = max(maxson[x],total - sz[x]);
	if(maxson[x] < maxson[root]) root = x;
}
inline void dfs1(int x,int fa,int ha,int dep)
{
	ha = 1ll * ha * P % mod + s[x];
	ha %= mod;
	MA = max(MA,dep);
	hs2[x] = ha;
	sz[x] = 1;
	for(int i = h[x];i;i = ne[i])
	{
		int y = to[i];
		if(vis[y] || y == fa) continue;
		dfs1(y,x,ha,dep + 1);
		sz[x] += sz[y];
	}
}
inline void dfs(int x,int fa,int dep,int len)
{
	hs1[x] = hs1[fa] + 1ll * p[dep - 1] * s[x] % mod;
	
	hs1[x] %= mod;
	
	if(dep < len && len - dep <= MA)
		if(dep != len - 1 && ma[len - dep].count((1ll * p[len - dep] * hs1[x] % mod - hs2[x] + mod)% mod))
			flag = 1;
	if(flag) return;
	for(int i = h[x];i;i = ne[i])
	{
		int y = to[i];
		if(vis[y] || y == fa) continue;
		dfs(y,x,dep + 1,len);
		if(flag) return ;
	}
}

inline void dfs2(int x,int fa,int dep,int len)
{
	if(dep < len - 1)
		ma[dep + 1][(1ll * p[len - dep] * hs1[x] % mod - hs2[x] + mod) % mod] = 1;
	if(dep == len - 1 && hs2[x] == (1ll * p[1] * hs1[x] % mod + s[root]) % mod)
		flag = 1;
	if(flag) return ;
	for(int i = h[x];i;i = ne[i])
	{
		int y = to[i];
		if(y == fa || vis[y]) continue;
		dfs2(y,x,dep + 1,len);
		if(flag == 1) return ; 
	}
}

inline void calc(int x,int len)
{
	MA = 0;
	dfs1(x,0,0,1);
	hs1[x] = 0;
	for(int i = 0;i <= MA + 1;++ i) ma[i].clear();
	for(int i = h[x];i;i = ne[i])
	{
		int y = to[i];
		if(vis[y]) continue;
		dfs(y,x,1,len);
		if(flag) return ;
		dfs2(y,x,1,len);
		if(flag == 1)
			return ;
	}
}

inline void solve(int x,int len)
{
	vis[x] = 1;
	calc(x,len);
	for(int i = h[x];i;i = ne[i])
	{
		int y = to[i];
		if(vis[y]) continue;
		maxson[0] = total = sz[y];
		root = 0;
		get_root(x,y);
		solve(root,len);
		if(flag == 1) return ;
	}
}

inline bool check(int x)
{
	flag = 0;
	root = 0;
	total = maxson[0] = n;
	get_root(0,1);
	solve(root,x);
	memset(vis,0,sizeof vis);
	return flag; 
}

int main()
{
//	freopen("lampice.in","r",stdin);
	scanf("%d",&n);
	scanf("%s",s + 1);
	p[0] = 1;
	for(int i = 1;i <= n;++ i) p[i] = 1ll * p[i - 1] * P % mod;
	for(int i = 1,u,v;i < n;++ i)
	{
		u = read();
		v = read();
		add(u,v);
		add(v,u);
	}
	int l = 1, r = n / 2;
    int ans = 1;
    while (l <= r) { 
        int mid = (l + r) >> 1;
        if (check(mid * 2))
            l = mid + 1, ans = max(ans, mid * 2);
        else
            r = mid - 1;
    }
    l = 1, r = (n - 1) / 2;
    while (l <= r) { 
        int mid = (l + r) >> 1;
        if (check(mid * 2 + 1))
            l = mid + 1, ans = max(ans, mid * 2 + 1);
        else
            r = mid - 1;
    }
    cout << ans;
//	cout << ans;
}

完结撒花

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值