2019年华南理工大学程序设计竞赛(春季赛) K Parco_Love_String(dp+递推/后缀数组)

链接:https://ac.nowcoder.com/acm/contest/625/K
来源:牛客网
 

时间限制:C/C++ 2秒,其他语言4秒
空间限制:C/C++ 131072K,其他语言262144K
64bit IO Format: %lld

题目描述

众所周知,在算法竞赛中,出题人对他出的题的难度往往存在错误的估计。比如出题人本想出个简单题,没想到却出成了重坑细节题;本想出个中等难度题,结果却变成了防AK题。因此,为了让一场比赛能有良好的体验,有一个靠谱的验题人是非常重要的。

 

CC出好题目后,便拿给小马哥看。不出所料,这些题目小马哥全都是看一眼就会做,但他觉得这对于参赛选手来说还是有点难。为了避免CC被喷成毒瘤出题人,小马哥准备加一道签到题。

 

小马哥有一个只由小写字母组成的字符串S,他会问你一些关于这个字符串的问题。小马哥每次提问时会给你一个整数i,意思是要把字符串S在第i和第i+1个字符之间切断,这样便得到两个新串S[1,i]及S[i+1,|S|]。记A=S[1,i],B=S[i+1,|S|]。现在,先考虑A的所有连续子串,再考虑B的所有连续子串,小马哥问你A,B之间相同子串对有多少个。

 

也就是说,小马哥想让你计算下面这个式子:

 

ans=∑1≤l1≤r1≤|A|∑1≤l2≤r2≤|B|[ A[l1,⋯,r1]==B[l2,⋯,r2] ]ans=∑1≤l1≤r1≤|A|∑1≤l2≤r2≤|B|[ A[l1,⋯,r1]==B[l2,⋯,r2] ]

 

其中,求和式右边的式子是这样一个函数:

 

[x]={1(x=True)0(x=False)[x]={1(x=True)0(x=False)

 

你能解决小马哥的问题,并签到成功吗?

输入描述:

第一行是一个字符串S(1≤|S|≤1000),保证只由小写字母组成。
第二行是一个正整数T(1≤T≤105),表示询问次数。
接下来T行,每行一个整数i(1≤i≤n−1),表示一个询问。

输出描述:

对于每个询问,输出一行一个整数表示对应询问的ans,意义如题目描述中所述。

示例1

输入

复制

ababa
4
1
2
3
4

输出

复制

2
4
4
2

说明

对于i=3这个询问,原串被拆分成 A=aba,B=ba。
此时A的所有子串为:a,a,b,ab,ba,aba;B的所有子串为:a,b,ba。
因此A,B之间相同子串对为:(a,a),(a,a),(b,b),(ba,ba),共计4个。

题意:中文题略。

思路:

考虑n<=1000,先对字符串做一个n方的dp,求出以每个位置j与每个位置i为结尾的最长公共子串长度,再求出以每个位置i与每个位置j为开始的最长公共子串长度。

对于第一个字母,直接统计它在后面的字符串中出现的次数即可。

对于第i(i>1)个字母,答案就是1~i-1的答案,再加上所有j=i+1 ~ n,以i,j为结尾的最长公共子串长度(长度最大为j-i),再减去所有j=1~i-1中与以i为开始有最长公共子串的的答案(长度最大为i-j)。

代码:

#include 
using namespace std;
const int maxn=1010;
 
char s[maxn];
int f[maxn][maxn],g[maxn][maxn];
int ans[maxn];
int n,q;
 
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++) if (s[i]==s[j])
        f[i][j]=f[i-1][j-1]+1;
    for (int i=n;i>=1;i--)
    for (int j=n;j>=1;j--) if (s[i]==s[j])
        g[i][j]=g[i+1][j+1]+1;
    for (int i=2;i<=n;i++) if (s[1]==s[i])
        ans[1]++;
    for (int i=2;i<=n;i++)
    {
        ans[i]=ans[i-1];
        for (int j=i+1;j<=n;j++)
            ans[i]+=min(f[i][j],j-i);
        for (int j=1;j

而我比赛的时候依然蠢得没想到这个做法。

参考POJ 3415 Common Substrings的后缀数组做法,本题是其k=1的特例,并且要手动拆字符串。。。

由于后缀数组的解法是O(n)的,所以只需要暴力枚举前串,再套板子就行了。

代码:

#include
#define ll long long
#define inf 0x3f3f3f3f
#define rep(i,a,b) for(register int i=(a);i<=(b);++i)
using namespace std;
const int maxn = 1e5+10;
int n,lena;
char a[maxn],aa[maxn];
typedef pair pii;
int A[maxn],B[maxn];
int realrank[maxn],k,Len;
pii st[maxn];
int s[maxn][2];
long long h[maxn];
int K;
int C[maxn],D[maxn];
ll anss[maxn];
void init(int start){
    rep(i,1,start)
    a[i]=aa[i];
    lena = start;
    a[lena+1] = '#';
    rep(i,start+1,Len)
    a[i+1]=aa[i];
    n = Len+1;
    //cout << n<= 1; j--){
            C[B[s[j][1]]] = j;
            B[s[j][1]]--;
        }
        for(int j = n ; j >=1; j--){
            D[A[s[C[j]][0]]] = C[j];
            A[s[C[j]][0]]--;
        }
        k = realrank[D[1]] = 1;
        for(int j = 2 ; j <= n ; j++){
            if(s[D[j]][0] != s[D[j-1]][0] ||s[D[j]][1] != s[D[j-1]][1])
                k++;
            realrank[D[j]] = k;
        }
    }
}

void gethigh(){
    int pre = 0,j;
    for(int i = 1; i <= n ; i++){
        if(pre) pre--;
        j = D[realrank[i]-1];
        while(i+pre <= n && j + pre <= n && a[i+pre] == a[j+pre])
            pre++;
        h[realrank[i]] = pre;
    }
}
struct node{
    long long cnt,height;
}stka[maxn],stkb[maxn];

long long ans ,tot;
int topa ,topb,taila,tailb;

ll sov(){
    //cout <<"len = "< topa && stka[taila-1].height >= h[i]){
              //  cout <<"i = "< topa && stka[taila-1].height >= h[i]){

                taila--;
                tot -= stka[taila].cnt*(stka[taila].height-h[i]);

                num += stka[taila].cnt;
            }
            //cout <<"i = "<>=1;
            //cout<

 

你可能感兴趣的:(递推递归,动态规划,经典dp,后缀数组)