中超7 hdu 7055 Yiwen with Sqc (加法卷积化简,优化常数,O(n)优化 )

53 篇文章 0 订阅
10 篇文章 0 订阅

2021“MINIEYE杯”中国大学生算法设计超级联赛(7)

hdu 7055 Yiwen with Sqc

题意:

​ 字符串的子串当中每个字母的出现个数(用 s [ ′ a ′ ] [ l , r ] s['a'] [l, r] s[a][l,r] 来表示),要求的就是每个字母的所有 s [ l , r ] s[l, r] s[l,r] 的平方和

分析:

​ 现考虑一个字母对 a n s ans ans 的贡献值,即
a n s = ∑ i = 1 n ∑ j = i n ( s [ j ] − s [ i − 1 ] ) 2 ans = \sum^n_{i=1} \sum^n_{j=i}(s[j]-s[i-1])^2 ans=i=1nj=in(s[j]s[i1])2
s [ i ] s[i] s[i] 表示到字符串第i位某个字母个数的前缀和

若直接对这个式子操作的话,那必然是 n 2 n^2 n2 的算法,所以还要进一步化简:
a n s = ∑ i = 1 n ∑ j = i n s [ j ] 2 + ∑ i = 1 n ∑ j = i n s [ i − 1 ] 2 − 2 ∑ i = 1 n ∑ j = i n s [ j ] ∗ s [ i − 1 ] = n ∑ i = 1 n s [ i ] 2 − ( ∑ i = 1 n s [ i ] ) 2 + ∑ i = 1 n s [ i ] 2 = ( n + 1 ) ∑ i = 1 n s [ i ] 2 − ( ∑ i = 1 n s [ i ] ) 2 \begin{aligned} ans &=\sum^n_{i=1} \sum^n_{j=i}s[j]^2 + \sum^n_{i=1} \sum^n_{j=i} s[i-1]^2 - 2\sum^n_{i=1} \sum^n_{j=i}s[j]*s[i-1] \\ &=n \sum ^n_{i=1}s[i]^2 -(\sum^n_{i=1}s[i])^2+\sum^n_{i=1}s[i]^2\\ &=(n+1) \sum ^n_{i=1}s[i]^2 -(\sum^n_{i=1}s[i])^2 \end {aligned} ans=i=1nj=ins[j]2+i=1nj=ins[i1]22i=1nj=ins[j]s[i1]=ni=1ns[i]2(i=1ns[i])2+i=1ns[i]2=(n+1)i=1ns[i]2(i=1ns[i])2
这样就转换成 O ( 26 n ) O(26n) O(26n)

于是 … … 我写了一篇交到 h d u hdu hdu … 它 T T T 了 我他喵的 O ( 26 n ) O(26n) O(26n) ,你跟我说 T T T 了 … 世界的险恶啊

T T T 了的代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;

const int N=1e5+5, mo=998244353;
char s[N];
int dp[33][N];
int cont(int k, int n)
{
    int a=0, b=0;
    for(int i=1;i<=n;i++)
    {
        a = (a+dp[k][i]*dp[k][i]%mo)%mo;
        b = (b+dp[k][i])%mo;
    }
    int res = (a*(n+1)-b*b%mo+mo)%mo;
    return res;
}

signed main()
{
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int t;
    cin>>t;
    while(t--)
    {
        cin>>s+1;
        int n=strlen(s+1);
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<26;j++)
            {
                dp[j][i] = dp[j][i-1];
            } 
            dp[s[i]-'a'][i]++;
        }
        int ans=0;
        for(int i=0;i<26;i++) ans = (ans+cont(i, n))%mo;
        cout<<ans<<endl;
    }

    return 0;
}

再考虑还能如何优化???

O ( 26 n ) O(26n) O(26n) 的算法是够快了,但是还不是最快的,还有 O ( n ) O(n) O(n) 的,考虑优化常数,每个字母都要遍历一遍会有很多的相同计算,就是说只有当前要考虑的字母出现的下标会对贡献产生影响

来看样例: a b a b a ababa ababa

考虑a,那么,

s [ 1 ] = 1 s[1]=1 s[1]=1 , s [ 2 ] = 1 s[2]=1 s[2]=1 , s [ 3 ] = 2 s[3]=2 s[3]=2 , s [ 4 ] = 2 s[4]=2 s[4]=2 , s [ 5 ] = 3 s[5]=3 s[5]=3

发现 1 1 1 2 2 2 ,都在重复计算,而且出现次数 = = = 两次 a a a 出现的下标的差值

所以对于每个字母只需要记录其在字符串中的每一个下标 g [ + + t o t ] = i g[++tot] = i g[++tot]=i

再遍历 t o t tot tot 即可,总的时间复杂度为 O ( n ) O(n) O(n) ,详见代码

#include<bits/stdc++.h>
#define int long long
#define V vector <int>
using namespace std;
typedef long long ll;

const int N=1e5+5, mo=998244353;
char s[N];
V g[33];

int calc(V a, int len)
{
    a.push_back(len+1); // 这步别忘了,算最后一次贡献要用
    int sum=0, cum=0;
    for(int i=1;i<a.size()-1;i++)
    {
        sum = (sum+i*i%mo*(a[i+1]-a[i]))%mo;
        cum = (cum+i*(a[i+1]-a[i]))%mo;
    }
    int res = ((len+1)*sum%mo-cum*cum%mo+mo)%mo;
    return res;
}

signed main()
{
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int t;
    cin>>t;
    while(t--)
    {
        cin>>s+1;
        int n=strlen(s+1);
        for(int i=0;i<26;i++) g[i].clear(), g[i].push_back(0);
        // 从第0位开始太绕了,先把0位占掉
        for(int i=1;i<=n;i++)
        {
            g[s[i]-'a'].push_back(i);
        }
        int ans=0;
        for(int i=0;i<26;i++) ans = (ans+calc(g[i], n))%mo;
        cout<<ans<<endl;
    }

    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yezzz.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值