【CCPC2021 A】So Many Lucky Strings

传送门

除夕晚上写妙妙题,写完题解就去看 K-ON(雾)。

可能是因为前几天教练发过 UVA1010 的缘故,这题没有卡很久,感兴趣的可以做做。

闲话少说。

对于这类问题,有一个常见的自然想法。我们先来考虑给定一些串,怎么搞出一个回文串出来。

那自然的想法是从回文中心开始构造,如果左边部分长度 \(l\) 大于右边长度 \(r\),那么我们就往右边加入一个串,否则往左边加串。就是说,哪边短就往哪边加入。那么我们发现一个回文串可以用这种方式唯一的确定,所以求能凑出的回文串个数就等价于求用这种方式搞出多少个左右相等的串。

那么我们可以枚举一个区间 \([i,j]\),意义是当前回文串最左边由 \(s_i\) 构成,最右边由 \(s_j\) 构成,这样,然后转移就是左边界左移或者右边界右移。为了确定到底往哪边移动,我们的 \(dp\) 应该还得记录一个差值,代表左边长度减去右边长度,然后构成回文就是这个差值为 \(0\)。(当然,转移的时候你要判断是否还满足回文的性质)。

感觉 \(dp\) 状态出现负数不太自然,那就可以这样:设 \(f(i,j,d)\) 是回文串最左边 \(s_i\),最右边 \(s_j\),且左边比右边长 \(d\) 个字符;同理 \(g(i,j,d)\) 的意义就是右边比左边长 \(d\) 个字符,然后钦定 \(f\) 中的 \(d\ge 0\),\(g\) 中的 \(d\gt 0\),最后 \(\sum f(i,j,0)\) 就是答案。

然后发现有个事情,就是回文中心可能在一个字符串的某个字符上,甚至在一个字符串的两个字符之间,但是不用怕,可以把 \(f,g\) 中的 \(i,j\) 限制设为 \(i\le j\),这样 \(i=j\) 的时候就代表我们只选了 \(s_i\) 这一个串,这部分区间 \(dp\) 初始化一下就好。

这个时候遇到一个问题,就是这个 \(dp\) 的状态数看上去是 \(n^2\sum |S|\) 的应该会爆炸,但是其实它的总状态数是 \(n\sum |S|\) 的,这是本题关键的一点:

因为我们使用了文中一开始提到的构造方式,那么左边最多比右边多 \(|S_i|\) 个,最多比右边少 \(|S_j|\) 个,所以求和一波就发现是 \(O(n\sum |S|)\) 级别的状态数,那么实现的时候就对每个 \(i,j\) 开两个 vector 代表 \(f,g\) 就可以了。

那么转移就好想了,比如以 \(f\) 的转移为例,首先按区间长度枚举区间 \([i,j]\) 然后枚举差值 \(d\)。此时注意到我们要分类讨论,分讨最后一步加入的串是 \(s_i\) 还是 \(s_j\),这个时候发现要求两个形如 \(\sum_{k\lt j}f_{i,k,d}\) 和 \(\sum_{k\gt i}g_{k,j,d}\) 的东西,记为 \(sumf_{i,j,d},sumg_{i,j,d}\) 然后同样区间 \(dp\) 就完事了。至于判断合法的过程可以对每个串正反哈希一遍(如果你想要保证正确的算法可以SA...)然后直接判断就行了,最后发现转移其实可以 \(O(1)\) 实现。

复杂度 \(O(n\sum |S|)\)。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define op(x) ((x&1)?x+1:x-1)
#define odd(x) (x&1)
#define even(x) (!odd(x))
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define lowbit(x) (x&-x)
#define Max(a,b) (a>b?a:b)
#define Min(a,b) (a<b?a:b)
#define next Cry_For_theMoon
#define il inline
#define pb(x) push_back(x)
#define is(x) insert(x)
#define sit set<int>::iterator
#define mapit map<int,int>::iterator
#define pi pair<int,int>
#define ppi pair<int,pi>
#define pp pair<pi,pi>
#define fr first
#define se second
#define vit vector<int>::iterator
#define mp(x,y) make_pair(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef double db;
using namespace std;
const int MAXN=110,MAXM=1e5+10,mod=1e9+7,base=13331;
int n,len[MAXN];
string s[MAXN],rs[MAXN];
vector<ull>h[MAXN],rh[MAXN];
ull power[MAXM];
vector<ll>f[MAXN][MAXN],g[MAXN][MAXN],sumf[MAXN][MAXN],sumg[MAXN][MAXN];
ll ans;
ull gethash(const vector<ull>& h,int i,int j){return h[j]-h[i-1]*power[j-i+1];}
int valid(int x,int y,int l1,int r1,int l2,int r2){return gethash(h[x],l1,r1)==gethash(rh[y],len[y]-r2+1,len[y]-l2+1);}
void add(ll& x,ll y){x=(x+y)%mod;}
int main(){
    power[0]=1;rep(i,1,1e5)power[i]=power[i-1]*base;
    cin>>n;
    rep(i,1,n){
        cin>>s[i];len[i]=s[i].length();rs[i]=s[i];reverse(rs[i].begin(),rs[i].end());
        s[i]=" "+s[i];rs[i]=" "+rs[i];
        h[i].resize(len[i]+10);rh[i].resize(len[i]+10);
        rep(j,1,len[i])h[i][j]=h[i][j-1]*base+(s[i][j]-'a'+1);
        rep(j,1,len[i])rh[i][j]=rh[i][j-1]*base+(rs[i][j]-'a'+1);
    }
    rep(i,1,n)rep(j,i,n){
        f[i][j].resize(len[i]+10);g[i][j].resize(len[j]+10);
        fill(f[i][j].begin(),f[i][j].end(),0);
        fill(f[i][j].begin(),f[i][j].end(),0);
        sumf[i][j].resize(len[i]+10);sumg[i][j].resize(len[j]+10);
        fill(sumf[i][j].begin(),sumf[i][j].end(),0);
        fill(sumg[i][j].begin(),sumg[i][j].end(),0);
    }
    //初始化
    //两个串的
    rep(i,1,n)rep(j,i+1,n){
        int l=min(len[i],len[j]);
        if(!valid(i,j,len[i]-l+1,len[i],1,l))continue;
        if(len[i]>=len[j])add(f[i][j][len[i]-len[j]],1);
        else add(g[i][j][len[j]-len[i]],1);
    }
    
    //一个串的
    rep(i,1,n){
        rep(j,1,len[i]-1){
            int l=min(j,len[i]-j);
            if(!valid(i,i,j-l+1,j,j+1,j+l))continue;
            if(j>=len[i]-j)add(f[i][i][j-len[i]+j],1);
            else add(g[i][i][len[i]-j-j],1);
        }
        //以某个字符为回文中心的
        if(len[i]==1){
            add(f[i][i][0],1);
            continue;
        }
        add(f[i][i][len[i]-1],1);add(g[i][i][len[i]-1],1);
        rep(j,2,len[i]-1){
            int l=min(j-1,len[i]-j);
            if(!valid(i,i,j-l,j-1,j+1,j+l))continue;
            if(j-1 >= len[i]-j)add(f[i][i][j-1-len[i]+j],1);
            else add(g[i][i][len[i]-j-j+1],1);
        }
    }
    
    //转移
    rep(l,2,n){
        rep(i,1,n){
            int j=i+l-1;if(j>n)break;
            rep(d,0,len[i]){
                sumf[i][j][d]=sumf[i][j-1][d];
                add(sumf[i][j][d],f[i][j-1][d]);
            }
            rep(d,0,len[j]){
                sumg[i][j][d]=sumg[i+1][j][d];
                add(sumg[i][j][d],g[i+1][j][d]);
            }
            rep(d,0,len[i]){
                if(len[j]<len[i]-d){
                    //删除右边的j  
                    if(valid(i,j,d+1,d+len[j],1,len[j])){
                        add(f[i][j][d],sumf[i][j][d+len[j]]);
                    }  
                }else{
                    //删除左边的i
                    if(valid(i,j,d+1,len[i],len[j]-(len[i]-d)+1,len[j])){
                        add(f[i][j][d],sumg[i][j][len[i]-d]);
                    }
                }
            }
            rep(d,1,len[j]){
                if(len[i]<=len[j]-d){
                    //删除左边的i
                    if(valid(i,j,1,len[i],len[j]-d-len[i]+1,len[j]-d)){
                        add(g[i][j][d],sumg[i][j][d+len[i]]);
                    }
                }else{
                    //删除左边的j
                    if(valid(i,j,1,len[j]-d,1,len[j]-d)){
                        add(g[i][j][d],sumf[i][j][len[j]-d]);
                    }
                }
            }
        }
    }
    //统计
    rep(i,1,n)rep(j,i,n){
        add(ans,f[i][j][0]);
    }
    printf("%lld\n",ans);
    
    return 0;
}
上一篇:luogu1510:精卫填海


下一篇:Codeforces Round #765 (Div. 2)