万径人踪灭(FFT+manacher)

传送门

这题……我觉得像我这样的菜鸡选手难以想出来……

题目要求求出一些子序列,使得其关于某个位置是对称的,而且不能是连续一段,求这样的子序列的个数。这个直接求很困难,但是我们可以先求出所有关于某个位置对称的子序列,最后减去子串的个数。

子串个数可以用\(manacher\)求,至于子序列的话,我们假设以第\(i\)位为中心,那么如果两边有\(x\)对相同的字符,那么这个位置对答案的贡献就是\(2^x-1\)或者\(2^(x+1)-1\)。(因为有可能回文串的长度是偶数,也就是不存在中间点)

考虑怎么求\(x_i\)。\(x_i\)的形式可以写成如下的形式:

\[\sum_{j=0}^ic[i-j] == c[i+j]\]

发现这个式子非常像卷积的形式。那么我们先初始化两个序列,第一个序列是原字符串为‘a’,对应位置为1,第二个是原字符串为‘b‘,对应位置是1,剩下都是0。这样结果就转化为如下形式:

\[\sum_{j=0}^i a(i-j) * a(i+j) + b(i-j) * b(i+j) \]

然后让他们自己和自己乘起来,结果相加一下,然后因为卷积会重复把元素计算两遍,所以要+1再/2.

这样得到的各项系数就是各项\(x_i\),我们就可以用快速幂计算。算完之后减去\(manacher\)求出的子串个数即可。

看一下代码。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<set>
#include<vector>
#include<map>
#include<queue>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar(‘\n‘)
#define fr friend inline
#define y1 poj
#define mp make_pair
#define pr pair<int,int>
#define fi first
#define sc second
#define pb push_back
#define I puts("bug")

using namespace std;
typedef long long ll;
const int M = 200005;
const int INF = 1000000009;
const double eps = 1e-7;
const double pi = acos(-1);
const ll mod = 1e9+7;

int read()
{
    int ans = 0,op = 1;char ch = getchar();
    while(ch < ‘0‘ || ch > ‘9‘) {if(ch == ‘-‘) op = -1;ch = getchar();}
    while(ch >= ‘0‘ && ch <= ‘9‘) ans = ans * 10 + ch - ‘0‘,ch = getchar();
    return ans * op;
}

struct Comp
{
   double x,y;
   Comp(){}
   Comp(double kx,double ky){x = kx,y = ky;}
   fr Comp operator + (const Comp &a,const Comp &b) {return (Comp){a.x + b.x,a.y + b.y};}
   fr Comp operator - (const Comp &a,const Comp &b) {return (Comp){a.x - b.x,a.y - b.y};}
   fr Comp operator * (const Comp &a,const Comp &b) {return (Comp){a.x * b.x - a.y * b.y,a.y * b.x + a.x * b.y};}
}a[M<<1],b[M<<1],kx,ky;

int n,len = 1,L,p[M<<1],rev[M<<1];
char s[M<<1],c[M];
ll tot,d[M<<1],ans;

int change()
{
   int l = strlen(c),j = 2;
   s[0] = ‘!‘,s[1] = ‘#‘;
   rep(i,0,l-1) s[j++] = c[i],s[j++] = ‘#‘;
   s[j] = ‘&‘;
   return j;
}

void manacher()
{
   int l = change(),mx = 1,mid = 1;
   rep(i,1,l-1)
   {
      if(i < mx) p[i] = min(mx - i,p[(mid<<1) - i]);
      else p[i] = 1;
      while(s[i-p[i]] == s[i+p[i]]) p[i]++;
      if(mx < i + p[i]) mid = i,mx = i + p[i];
      tot += (p[i] >> 1),tot %= mod;
   }
}

void fft(Comp *a,int f)
{
   rep(i,0,len-1) if(i < rev[i]) swap(a[i],a[rev[i]]);
   for(int i = 1;i < len;i <<= 1)
   {
      Comp omg1(cos(pi / i),f * sin(pi / i));
      for(int j = 0;j < len;j += (i << 1))
      {
     Comp omg(1,0);
     rep(k,0,i-1)
     {
        kx = a[k+j],ky = omg * a[k+j+i];
        a[k+j] = kx + ky,a[k+j+i] = kx - ky,omg = omg * omg1;
     }
      }
   }
}

ll qpow(ll a,ll b)
{
   ll p = 1;
   while(b)
   {
      if(b & 1) p *= a,p %= mod;
      a *= a,a %= mod;
      b >>= 1;
   }
   return p;
}

int main()
{
   scanf("%s",c);
   n = strlen(c);
   rep(i,0,n-1)
   {
      if(c[i] == ‘a‘) a[i].x = 1;
      else b[i].x = 1;
   }
   while(len <= n << 1) len <<= 1,L++;
   //I;
   rep(i,0,len-1) rev[i] = (rev[i>>1] >> 1) | ((i&1) << (L-1));
   fft(a,1),fft(b,1);
   rep(i,0,len-1) a[i] = a[i] * a[i] + b[i] * b[i];
   fft(a,-1);
   rep(i,0,len-1) d[i] = ((ll)floor(a[i].x / len + 0.5) + 1) >> 1;
   rep(i,0,len-1) ans += (qpow(2,d[i]) - 1),ans %= mod;
   manacher(),ans -= tot,ans %= mod;
   while(ans < 0) ans += mod;
   printf("%lld\n",ans);
   return 0;
}

原文地址:https://www.cnblogs.com/captain1/p/10112041.html

时间: 2024-10-09 15:29:55

万径人踪灭(FFT+manacher)的相关文章

BZOJ 3160 万径人踪灭 FFT+Manacher

题意:链接 方法: FFT+Manacher 解析: 对于一个序列,求以任意位置(可以为间隙)为轴对称的不连续回文序列. 我们不妨举一个栗子. 然后我们发现,如果以图中的红线为对称轴的话,那么他的最长回文长度为3,也就是可非连续回文半径为3. 所以大情况下这个对答案的贡献是 C13+C23+C33=23?1 但是其中如图所示有一些不合法的选取. 也就是对于轴来说我们选取了连续,无断点的一段. 我们要把这部分减掉. 然而这部分恰好是以红线为轴的最长连续回文串的半径. 捋一下思路. 首先我们求出所有

BZOJ3160 万径人踪灭 【fft + manacher】

题解 此题略神QAQ orz po神牛 由题我们知道我们要求出: 回文子序列数 - 连续回文子串数 我们记为ans1和ans2 ans2可以用马拉车轻松解出,这里就不赘述了 问题是ans1 我们设\(f[i]\)表示以i位置为中心的对称的字符对数,那么i位置产生的回文子序列数 = \(2^{f[i]} - 1\) 如何求? 由对称的性质,以i为对称中心的两点\(a,b\)满足\(a+b=2*i\) 我们可以设一个这样的序列: \(c[n]\)表示以\(n/2\)位置为对称点的对称点对数[n/2若

【bzoj3160】万径人踪灭 FFT

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=3160 我是一个傻叉 微笑脸 1 #include<bits/stdc++.h> 2 #define inf 1000000000 3 #define ll long long 4 #define N 200005 5 #define mod 1000000007 6 using namespace std; 7 int read(){ 8 int x=0,f=1;char ch=getch

【BZOJ3160】万径人踪灭 Manacher+FFT

[BZOJ3160]万径人踪灭 Description Input Output Sample Input Sample Output HINT 题解:自己想出来1A,先撒花~(其实FFT部分挺裸的) 做这道题,第一思路很重要,显然看到这题的第一想法就是ans=总数-不合法(不要问我为什么显然).因为向这种用补集法的题一般都会给一些很奇葩的限制条件,但是一旦换个角度去想就很水了,好了不多说废话了. 显然,不合法的情况,也就是连续的回文区间的方案数,我们直接上Manacher就搞定了嘛!答案就是所

【manacher+FFT】BZOJ3160-万径人踪灭

[题目大意] 在一个仅仅含有a,b的字符串里选取一个子序列,使得: 1.位置和字符都关于某条对称轴对称: 2.不能是连续的一段. [思路] 不连续的回文串的个数=总的回文串个数-连续回文串的个数. 后者可以用manacher在O(n)时间里面求出.求的是个数不是最长串,和之前写的几道不怎么一样,注意一下. 求总的回文串个数稍微复杂一些.我们用f[i]表示以i为对称中心,两边有多少个对称的字符.对于每个中心i我们有(2^f[i])-1种方案 答案即Σ[1<=i<=n*2+1]((2^f[i])-

【BZOJ 3160】 3160: 万径人踪灭 (FFT)

3160: 万径人踪灭 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 1440  Solved: 799 Description Input Output Sample Input Sample Output HINT Source 2013湖北互测week1 [分析] 看题目被吓死,其实很多废话.. 还是自己想出来了..FFT好强啊..可以加速很多东西的说. 然后就是,枚举对称轴吧? 假设a[i]==a[j],那么用i+j表示它的对称轴. 共有

bzoj 3160: 万径人踪灭 manachar + FFT

3160: 万径人踪灭 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 133  Solved: 80[Submit][Status][Discuss] Description Input Output Sample Input Sample Output HINT 以每一个位置为中心,分别处理连续一块的回文串,回文序列个数. 比较容易看出是FFT+manachar,但是FFT还是不太熟悉,特别要注意三层for语句中i,j,k不能写错,这东西很难调

BZOJ 3160 万径人踪灭 Manacher算法+快速傅里叶变换

题目大意:给定一个由'a'和'b'构成的字符串,求不连续回文子序列的个数 首先回文一定是将字符串倍增 由于求的是不连续回文子序列的个数 因此我们可以求出总回文子序列的个数,然后减掉连续的 连续的就是回文子串 用Manacher算法可以O(n)求解 不连续的就有些难搞了 首先我们令f[i]表示以i为中心的对称字符对个数 比如s[]=$#a#b#a 那么s[4]='b' f[4]=2 那么对于每个中心i我们有(2^f[i])-1种方案 答案即Σ[1<=i<=n*2+1]((2^f[i])-1) 问

BZOJ3160 万径人踪灭 字符串 多项式 Manachar FFT

原文链接http://www.cnblogs.com/zhouzhendong/p/8810140.html 题目传送门 - BZOJ3160 题意 给你一个只含$a,b$的字符串,让你选择一个子序列,使得: $1.$位置和字符都关于某一条对称轴对称. $2.$不能是连续的一段. 问原来的字符串中能找出多少个这样的子序列.答案对$10^9+7$取模. 串长$\leq 10^5$. 题解 下面的讨论都在满足条件$1$的情况下进行. 首先,我们先不考虑条件$2$.然后再减掉不满足条件$2$的就可以了