Yiwen with Sqc
题意:
给一个字符串,对于每个小写字母
c
h
ch
ch ,问所有区间内
c
h
ch
ch 出现次数的平方和,其中
∣
s
∣
<
1
0
5
|s|<10^{5}
∣s∣<105
∑
c
=
97
122
∑
i
=
1
n
∑
j
=
i
n
s
q
c
(
s
,
l
,
r
,
c
h
)
2
\sum_{c=97}^{122}\sum_{i=1}^n\sum_{j=i}^nsqc(s,l,r,ch)^2
c=97∑122i=1∑nj=i∑nsqc(s,l,r,ch)2
s
q
c
(
s
,
l
,
r
,
c
h
)
sqc(s,l,r,ch)
sqc(s,l,r,ch) 表示的是
s
s
s 串
[
l
,
r
]
[l,r]
[l,r] 这段区间内
c
h
ch
ch 字符出现的次数
思路:
dp解法:
大佬的题解
另一个大佬的题解
公式显然表示的是对于每个字符,都求一遍所有子段的该字符的
s
q
c
sqc
sqc
可以考虑
d
p
dp
dp
d
p
[
i
]
dp[i]
dp[i] 表示所有以
i
i
i 结尾的所有字符串的答案总和
d
p
[
i
]
dp[i]
dp[i] 比
d
p
[
i
−
1
]
dp[i-1]
dp[i−1] 相对于每一个字符串都多了一个
s
[
i
]
s[i]
s[i] 字符,那么先考虑一个字符串多了一个字符的变化是什么,假设一个字符串本来有
c
n
t
cnt
cnt 个
s
[
i
]
s[i]
s[i] 字符,那么这个字符串的答案的变化为
(
c
n
t
+
1
)
2
(cnt+1)^2
(cnt+1)2
−
-
−
c
n
t
2
cnt^2
cnt2
=
=
=
2
c
n
t
+
1
2cnt+1
2cnt+1,即一个字符串多了
2
c
n
t
+
1
2cnt+1
2cnt+1,而对于前面
i
i
i 个字符串的答案,即变化了
∑
1
i
(
2
c
n
t
+
1
)
\sum_1^{i}(2cnt+1)
∑1i(2cnt+1),即为前面所有字符串的
s
[
i
]
s[i]
s[i] 字符的两倍
+
i
+i
+i 。
∑
1
i
(
2
c
n
t
+
1
)
\sum_1^i(2cnt+1)
∑1i(2cnt+1)
=
=
=
2
2
2
×
\times
×
s
u
m
[
x
]
sum[x]
sum[x]
+
+
+
i
i
i
(
x
=
s
[
i
]
−
′
a
′
+
1
)
(x=s[i]-'a'+1)
(x=s[i]−′a′+1)
对于一个字符串,维护
s
[
i
]
s[i]
s[i]在
i
i
i 之前出现的次数每次只需要
+
1
+1
+1,维护
i
i
i 个字符串每次加
i
i
i
code:
#include<bits/stdc++.h>
#define ll long long
#define _ 0
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 998244353;
ll n, m, k;
int dp[maxn], sum[30];
char s[maxn];
void work()
{
memset(sum, 0, sizeof(sum));
scanf("%s", s + 1);
n = strlen(s + 1);
ll ans = 0;
for (ll i = 1; i <= n; ++i)
{
int x = s[i] - 'a' + 1;
dp[i] = (dp[i-1] + 2ll * sum[x] + i) % mod;
sum[x] = (sum[x] + i) % mod;// 每个字符串+1,以i结尾的字符串有i个,所以+i
ans = (ans + dp[i]) % mod;
}
cout << ans << endl;
}
int main()
{
int TT;cin>>TT;while(TT--)
work();
return ~~(0^_^0);
}
C. Sequence Pair Weight
题意:
定义一个子段的权重为:子段内相同元素的数对个数
给定一个序列,求序列所有子段权重之和
思路:
核心思想计算贡献
考虑
f
[
i
]
f[i]
f[i] 表示以
i
i
i 结尾的所有子段的权重之和
首先以
i
i
i 结尾的子段所有的贡献,分为两部分
以
i
−
1
i-1
i−1 结尾的子段的贡献和加上
a
i
a_i
ai 后多出的贡献
a
i
a_i
ai 只会与前边与它相同的数匹配,假设
a
j
=
a
i
(
j
<
i
)
a_j=a_i(j<i)
aj=ai(j<i)
那么以
k
(
1
<
=
k
<
=
j
)
k(1<=k<=j)
k(1<=k<=j) 为左端点的子段,都会多出
1
1
1 的贡献,累计会多出
j
j
j 的贡献
i
i
i 之前所有与
a
i
a_i
ai 匹配的位置
k
k
k,都会产生
k
k
k 的贡献
m
a
p
map
map 维护即可
a
n
s
ans
ans 即为所有以
i
i
i 结尾的子段贡献之和
复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
code:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
ll f[maxn];
int a[maxn];
map <int, ll> ma;
void work()
{
cin >> n;
for(int i = 0; i <= n; ++i) f[i] = 0;
ma.clear();
for(int i = 1; i <= n; ++i) cin >> a[i];
ll ans = 0;
for(int i = 1; i <= n; ++i){
f[i] = f[i-1];
if(ma.count(a[i])){
f[i] += ma[a[i]];
}
ma[a[i]] += i;
ans += f[i];
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
int TT;cin>>TT;while(TT--)
work();
return 0;
}
还有一种思路:
枚举到
a
i
a_i
ai,假设
a
j
=
a
i
(
j
<
i
)
a_j=a_i(j<i)
aj=ai(j<i)
那么只考虑这个数对的贡献,找包含这个数对的子段个数,左端点可以选
[
1
,
j
]
[1,j]
[1,j],右端点可以选
[
i
,
n
]
[i,n]
[i,n]
那么就会贡献
j
∗
(
n
−
i
+
1
)
j * (n - i + 1)
j∗(n−i+1)
对于所有
a
j
=
a
i
a_j=a_i
aj=ai 的
j
j
j 都会产生贡献,因此累计
j
j
j 的下标和即可
扫一遍统计贡献即可