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=1∑nj=i∑n(s[j]−s[i−1])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=1∑nj=i∑ns[j]2+i=1∑nj=i∑ns[i−1]2−2i=1∑nj=i∑ns[j]∗s[i−1]=ni=1∑ns[i]2−(i=1∑ns[i])2+i=1∑ns[i]2=(n+1)i=1∑ns[i]2−(i=1∑ns[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;
}