首页 > 其他分享 >P4555 最长双回文串 解题报告

P4555 最长双回文串 解题报告

时间:2022-11-08 20:37:17浏览次数:39  
标签:int P4555 hw 解题 端点 ans fl 回文

看到回文串,于是就想到了马拉车。

马拉车可以帮我们求出每个 \(i\) 的最大扩展距离,容易得出,双回文串就是两个回文串拼一起。当然,两个回文串必须要相交,不然形不成一个字符串。

有的小可爱就会想直接找最大的两个扩展距离然后比一比,但是这是不行的。

因为两个回文串的相交部分必须砍成两个刚好相交的字符串,两个回文串也会因此而缩小。例如如下例子:

aaabaaabaaa

看起来两个回文串扩展距离都有 3 ,但中间是重叠的。最终分出的字符串只能是 aabaaaba ,其中 aabaaaba 是两个回文串。

如果我们按照上面的规则不难发现:两个中心点中间的一段就是实际上回文串的扩展距离,所以只要把中间距离 \(*2\) 就行。

这也告诉我们一件事情,就是在所有满足条件的中心点里面,离当前中心点最远的中心点最优。

不妨将每个点i只向后比较,那么我们要的就是在 log 的时间内快速找到最长回文串左端点在 i-hw[i] 左边(hw[i]为以i为中心的最大扩展距离,就是马拉车求的那个东西)的最大点。

一开始的想法是在平衡树上二分,但是带了两个log。后来想到可以用权值线段树解决。下标为每个点为中心点的最长回文串的左端点,权值为中心点。从最后的字符开始枚举,每次在1~hw[i]中找最大值。之后将 i 的左端点加入线段树。

由于添加了分隔符,所以最终答案要除2。发现前面有个乘2,干脆消掉好了。

但是这还不够。思考一件事情,如果新添加的左端点比原先的任意一个左端点还要大,那么肯定永远不会选到。如果我们把这些点删去且保持下标不变,就会得到一个权值单调上升的序列。每一个点的最优点为序列中下标小于该点右端点的最大值。

这样子可以得到一个常数很小的二分。但是还不够。

我们可以先把最终得到的单调上升的序列跑出来,用类似于并查集的手法处理一下,然后正着跑。这时序列中的点会不断减少(因为我们是倒着统计的),每减少一个点,就参照并查集的写法将两个区间合并。尽管这种做法对应的并查集只能进行路径压缩,但由于不能卡,并且开始可以用线性时间处理完第一波路径压缩,所以时间复杂度接近 \(O(nα(n))\) 按道理来说会比上面两种做法跑的都快,而且常数还很小。

好家伙,别人还在写带字符集大小复杂度的回文自动机呢,你的时间复杂度已经出现了一个逼格满满的α,这不显得很高级?为想出了一种并没有什么用的做法找借口

但我们有线性做法,我们处理出每一个字符串最长回文串的左端点和右端点,之后再跑一遍马拉车,但是这一次我们倒着跑。根据我们在二分做法中得出的结论,我们可以在每一个点进行一个一个更新时寻找那些没被标记过的右端点,如果左端点小于这些右端点则标记。如果有值呢?有值我们不需要管。因为这意味着一个距离更远的点能满足条件。时间复杂度和马拉车复杂度相同,为线性。

对于整个串为回文串的情况要单独处理,因为必须要能分成两个回文串。

注意一件事情,string在本地ubuntu跑的时候啥问题没有,但是在洛谷上对其中单个字符直接赋值就会变成精致小垃圾,私吞你的分数,还留下一两个AC让你不明所以。所以直接赋值最好用char。

这里只有权值线段树的做法

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn = 1e5 + 5;
int hw[maxn << 1], n, b[maxn << 1];
char s[maxn << 2];
void change(string S)
{
	s[0] = s[1] = '$';
	for(int i = 0;i < n;i ++)
	{
		s[i * 2 + 2] = S[i];
		s[i * 2 + 3] = '$';
	}
	n = n * 2 + 2;
	s[n] = 0;
	return ;
}
void maracher()
{
    int maxr = 0,mid = 114514;
    for(int i = 1;i < n;i++)
    {
        if(i < maxr) hw[i] = min(hw[(mid << 1) - i],hw[mid] + mid - i);
        else hw[i] = 1;
        for(;s[i + hw[i]] == s[i - hw[i]];++ hw[i]) ;
        if(hw[i] + i > maxr)
        {
            maxr = hw[i] + i;
            mid = i;
        }
    }
	return ;
}
bool ton[maxn << 1];
struct linetree
{
	int tr[maxn << 3];
	#define ls(u) u<<1
	#define rs(u) (u<<1)+1
	void push_up(int u){ tr[u] = max(tr[ls(u)], tr[rs(u)]); }
	void change(int p, int l, int r, int fl, int k)
	{
		if(l > fl || r < fl) return ;
		if(l == r)
		{
			tr[p] = k;
			return ;
		}
		int mid = l + r >> 1;
		change(ls(p), l, mid, fl, k);
		change(rs(p), mid + 1, r, fl, k);
		push_up(p);
		return ;
	}
	int query(int p, int l, int r, int fl, int fr)
	{
		if(l >= fl && r <= fr) 
		{
			return tr[p];
		}
		if(l > fr || r < fl) return 0;
		int mid = l + r >> 1, ans = 0;
		ans = max(ans, query(ls(p), l, mid, fl, fr));
		ans = max(ans, query(rs(p), mid + 1, r, fl, fr));
		return ans;
	}
}T;
char S[maxn];
bool hui()
{
	int n = strlen(S);
	for(int i = 0;i < n - i - 1;i ++) if(S[i] != S[n - i - 1]) return 0;
	return 1;
}
signed main()
{
	scanf("%s", S);
	n = strlen(S);
	int p = n;
	change(S);	
	maracher();
	int ans = 0;
	for(int i = n - 1;i > 0;i --)
	{
		hw[i] --;
		int j = T.query(1, 1, n, 1, i + hw[i]);
		if(j != 0) ans = max(ans, (j - i));
		if(ton[i - hw[i]] == 0)
		{
			ton[i - hw[i]] = 1;
			T.change(1, 1, n, i - hw[i], i);
		}
	}
	if(ans == p && hui()) cout << ans - 1 << endl;
	else cout << ans << endl;
	return 0;
}

标签:int,P4555,hw,解题,端点,ans,fl,回文
From: https://www.cnblogs.com/closureshop/p/16871070.html

相关文章

  • Vulnhub Funbox2靶机解题详细过程
    Funbox2识别目标主机IP地址┌──(kali㉿kali)-[~/Vulnhub/Funbox2]└─$sudonetdiscover-ieth1Currentlyscanning:192.168.116.0/16|ScreenView:Unique......
  • 10.7 解题报告
    T1考场用时:\(40\)min期望得分:\(100\)pts实际得分:\(100\)pts我们都知道,这题是一个并查集的板子题,于是考虑BFS。一开始把擦着底边的推进队列。然后,对于所有的空洞,枚......
  • LeetCode125. 验证回文串
    验证回文串Day:2022-11-7link:https://leetcode.cn/problems/valid-palindromequestion:如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正......
  • 【模板】最长回文串长度 manacher
    \(pa_i\)表示以\(i\)为中心的(原串的)回文串长度#include<cstdio>#include<cstring>#include<algorithm>usingnamespacestd;typedeflonglongLL;intn,m,pa[......
  • Vulnhub Backdoored靶机解题过程
    Backdoored识别目标主机IP地址把目标主机导入VirtualBox后,发现KaliLinux扫描不到目标主机的IP地址,因此需要首先解决没有自动获得IP地址的问题,解决过程见【靶机攻略】相......
  • 11.5 解题报告
    T4考场用时:\(2\)h期望得分:\(50\)pts实际得分:\(0\)pts考场一直在试图推式子,但是没能推出来,最后十分钟匆匆忙忙敲上的暴力还忘了取模,应该先把暴力写好,最后\(10\)min......
  • Vulnhub THM Containme靶机解题过程
    THMContainmev4识别目标主机IP地址VirtualBox中启动THMContainme靶机kaliLinux利用netdiscover识别其IP地址:┌──(kali㉿kali)-[~/Vulnhub/THM_Containme]└─$......
  • Vulnhub NiveK靶机完整解题过程
    NiveK识别目标主机IP地址┌──(kali㉿kali)-[~/Vulnhub/Nivek]└─$sudonetdiscover-ieth1Currentlyscanning:192.168.98.0/16|ScreenView:UniqueHost......
  • 判断回文数
    //前提该数是5位数#include<stdio.h>intmain(){ intnumber; intwan,qian,shi,ge; scanf("%d",&number); wan=number/10000; printf("wan=%d\n",w......
  • Vulnhub Jerome靶机解题过程(未完成)
    Jerome识别目标主机IP地址┌──(kali㉿kali)-[~/Vulnhub/Jerome]└─$sudonetdiscover-ieth1Currentlyscanning:172.16.95.0/16|ScreenView:UniqueHos......