C:Distinct Substrings(扩展KMP)
题目描述:
For a string
s
1
,
s
2
,
…
,
s
n
s_1, s_2, \dots, s_n
s1,s2,…,sn, Bobo denotes the number of its distinct substrings as
f
(
s
1
,
s
2
,
…
,
s
n
)
f(s_1, s_2, \dots, s_n)
f(s1,s2,…,sn). He also defines defines
h
(
c
)
=
f
(
s
1
,
s
2
,
…
,
s
n
,
c
)
−
f
(
s
1
,
s
2
,
…
,
s
n
)
h(c) = f(s_1, s_2, \dots, s_n, c) - f(s_1, s_2, \dots, s_n)
h(c)=f(s1,s2,…,sn,c)−f(s1,s2,…,sn) for character
c
c
c.
Given a string s 1 , s 2 , … , s n s_1, s_2, \dots, s_n s1,s2,…,sn and m m m, find the value of ⨁ c = 1 m ( h ( c ) ⋅ 3 c   m o d   ( 1 0 9 + 7 ) ) \bigoplus_{c = 1}^{m}\left(h(c) \cdot 3^c \bmod (10^9+7)\right) ⨁c=1m(h(c)⋅3cmod(109+7)).
Note that
⊕
\oplus
⊕ denotes the bitwise exclusive-or (XOR).
Σ
n
≤
5
e
6
,
Σ
m
≤
5
e
6
\Sigma n\le5e6,\Sigma m\le5e6
Σn≤5e6,Σm≤5e6
解题思路:By_DCN
很容易想到用后缀数组能直接求到一个字符串有多少个不同字串:
n
∗
(
n
+
1
)
/
2
−
∑
h
e
i
g
h
t
n*(n+1)/2-∑height
n∗(n+1)/2−∑height,其中
n
∗
(
n
+
1
)
/
2
n*(n+1)/2
n∗(n+1)/2是所有子串个数,
∑
h
e
i
g
h
t
∑height
∑height就是重复子串个数
令
h
e
i
g
h
t
1
height1
height1表示加了c之后的串的height数组,
h
e
i
g
h
t
0
height0
height0表示加c之前的height数组。
则
h
(
c
)
=
(
n
+
1
)
∗
(
n
+
2
)
/
2
−
∑
h
e
i
g
h
t
1
−
(
n
∗
(
n
+
1
)
/
2
−
∑
h
e
i
g
h
t
0
)
h(c)=(n+1)*(n+2)/2-∑height1-(n*(n+1)/2-∑height0)
h(c)=(n+1)∗(n+2)/2−∑height1−(n∗(n+1)/2−∑height0),化简得到
n
+
1
+
∑
h
e
i
g
h
t
0
(
y
)
−
∑
h
e
i
g
h
t
1
(
x
)
n+1+∑height0(y)-∑height1(x)
n+1+∑height0(y)−∑height1(x)。 因为
f
(
s
1
,
s
2
,
,
,
,
,
s
n
,
c
)
f(s_1, s_2, , , , , s_n, c)
f(s1,s2,,,,,sn,c)只是在原来字符串的基础上加上一个c,所以很容易察觉出x是在y的基础上加上一个数字t,接下来就是计算这个t(也就是height数组的增量)。
比如
s
[
]
=
(
1
,
2
,
1
,
3
,
1
,
2
,
1
)
s[]=(1, 2, 1, 3, 1, 2, 1)
s[]=(1,2,1,3,1,2,1)如果添加一个3的话
∑Height会增加4 ,因为(3和3 1 2 1 3) (1 2 1 3和1 2 1 3 1 2 1 3) (2 1 3和2 1 3 1 2 1 3) (1 3和1 3 1 2 1 3)每一组都添加了1。
所以现在需要求的就是这个原字符串以0为起点以i为结尾的子串和原字符串有多少公共后缀;倒过来就可以用扩展kmp来求公共前缀z[i](z[i]表示倒过来的字符串中以i为起点的后缀子串与倒过来的字符串的最长公共前缀)
求的z数组之后就可以计算best数组(best[i]表示在原字符串中添加i之后会得到比原字符串新加的重复子串个数(也就是t))
S
[
]
=
(
1
,
2
,
1
,
3
,
1
,
2
,
1
)
S[]=(1 ,2 ,1, 3, 1, 2, 1)
S[]=(1,2,1,3,1,2,1)
倒过来 1 2 1 3 1 2 1
z
[
]
=
(
0
,
0
,
1
,
0
,
3
,
0
,
1
)
z[] =( 0 ,0 ,1 ,0 ,3, 0 ,1)
z[]=(0,0,1,0,3,0,1)
B
e
s
t
[
s
[
i
]
]
=
m
a
x
(
b
e
s
t
[
s
[
i
]
]
,
1
+
z
[
i
+
1
]
)
Best[s[i]]=max(best[s[i]],1+z[i+1])
Best[s[i]]=max(best[s[i]],1+z[i+1]):因为在第i+1个数字有z[i+1]个公共前缀,所以添加i的话,就会多出z[i+1]+1个重复的子串。
标程:
#include <bits/stdc++.h>
static const int MOD = 1e9 + 7;
int main() {
int n, m;
while (scanf("%d%d", &n, &m) == 2) {
std::vector<int> s(n);
for (int i = 0; i < n; ++i) {
scanf("%d", &s[i]);
s[i]--;
}
std::reverse(s.begin(), s.end());
std::vector<int> z(n + 1);
for (int i = 1, j = 0; i < n; ++i) {
if (i < j + z[j]) {
z[i] = std::min(j + z[j] - i, z[i - j]);
}
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
z[i]++;
}
}
std::vector<int> best(m);
for (int i = 0; i < n; ++i) {
best[s[i]] = std::max(best[s[i]], 1 + z[i + 1]);
}
int three = 1;
int result = 0;
for (int i = 0; i < m; ++i) {
three = 3LL * three % MOD;
result ^= 1LL * three * (n + 1 - best[i]) % MOD;
assert(best[i] <= n + 1);
}
printf("%d\n", result);
}
}
D:Modulo Nine(DP)
题目描述:
Bobo has a decimal integer
a
1
a
2
…
a
n
‾
\overline{a_1 a_2 \dots a_n}
a1a2…an, possibly with leading zeros. He knows that for
m
m
m ranges
[
l
1
,
r
1
]
,
[
l
2
,
r
2
]
,
…
,
[
l
m
,
r
m
]
[l_1, r_1], [l_2, r_2], \dots, [l_m, r_m]
[l1,r1],[l2,r2],…,[lm,rm], it holds that
a
l
i
×
a
l
i
+
1
×
⋯
×
a
r
i
 
m
o
d
 
9
=
0
a_{l_i} \times a_{l_i + 1} \times \dots \times a_{r_i} \bmod 9 = 0
ali×ali+1×⋯×arimod9=0. Find the number of valid integers
a
1
a
2
…
a
n
‾
\overline{a_1 a_2 \dots a_n}
a1a2…an, modulo
(
1
0
9
+
7
)
(10^9+7)
(109+7).
解题思路:
对于一个右端点r,有多个
[
l
i
,
r
]
[l_i,r]
[li,r]的时候,只要最小的那个区间满足,其他肯定都满足了。这样对于一个右端点的限制条件是否满足,取决于前面出现的最后两个3出现的位置(9和0算作两个3)
用
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]表示填了前i个数字,最后一个3出现在j,最后第二个3出现在k,且满足前面限制条件的方案数。
(i+1)这个位置填的数字有三种情况:
- 不是3的倍数
- 是3的倍数,但不是9的倍数(3,6)
- 是9的倍数(0,9)
注意可以转移到[i+1]的状态需要满足当前r[i]的限制,也就是l[i] <= k。即当前限制区间内至少要有两个3的倍数。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 1e9 + 7;
ll dp[55][55][55];
int l[55];
int n, m;
int main()
{
while(scanf("%d%d", &n, &m)!=EOF){
memset(l, 0, sizeof l);
memset(dp, 0, sizeof dp);
while(m--){
int a, b;
scanf("%d%d", &a, &b);
l[b] = max(l[b], a);
}
dp[0][0][0] = 1;
for(int i = 0; i < n; ++i){
for(int j = 0; j <= i; ++j){
for(int k = 0; k <= j; ++k){
if(l[i] > k) continue;
dp[i+1][j][k] = (dp[i+1][j][k] + 6*dp[i][j][k])%mod;//不是3的倍数
dp[i+1][i+1][j] = (dp[i+1][i+1][j] + 2*dp[i][j][k])%mod;//是3的倍数不是9的倍数
dp[i+1][i+1][i+1] = (dp[i+1][i+1][i+1] + 2*dp[i][j][k])%mod;//是9的倍数
}
}
}
ll ans = 0;
for(int i = 0; i <= n; ++i){
for(int j = 0; j <= i; ++j){
if(l[n] > j) continue;
ans = (ans + dp[n][i][j])%mod;
}
}
cout<<ans<<endl;
}
}
G:字典序(贪心)
题目描述:
对于序列
A
=
(
a
1
,
a
2
,
…
,
a
m
)
A = (a_1, a_2, \dots, a_m)
A=(a1,a2,…,am) 和
B
=
(
b
1
,
b
2
,
…
,
b
m
)
B = (b_1, b_2, \dots, b_m)
B=(b1,b2,…,bm),定义
A
A
A 的字典序比
B
B
B 小,记作
A
<
B
A < B
A<B ,当且仅当存在
1
≤
p
≤
m
1 \leq p \leq m
1≤p≤m 使得
a
p
<
b
p
a_p < b_p
ap<bp 且对于所有的
1
≤
i
<
p
1 \leq i < p
1≤i<p 都有
a
i
=
b
i
a_i = b_i
ai=bi. 进一步地,定义
A
≤
B
A \leq B
A≤B 当且仅当
A
<
B
A < B
A<B 或者
A
=
B
A = B
A=B.
Bobo 有一个 n n n 行 m m m 列的矩阵 C C C. 他想找字典序最小的 1 , 2 , … , m 1, 2, \dots, m 1,2,…,m 的排列 σ 1 , σ 2 , … , σ m \sigma_1, \sigma_2, \dots, \sigma_m σ1,σ2,…,σm, 使得 S 1 ≤ S 2 ≤ ⋯ ≤ S n S_1 \leq S_2 \leq \dots \leq S_n S1≤S2≤⋯≤Sn,其中 S i = ( C i , σ 1 , C i , σ 2 , … , C i , σ m ) S_i = (C_{i, \sigma_1}, C_{i, \sigma_2}, \dots, C_{i, \sigma_m}) Si=(Ci,σ1,Ci,σ2,…,Ci,σm).
数据范围:
1
≤
n
,
m
≤
2000
1 \leq n, m \leq 2000
1≤n,m≤2000
1
≤
C
i
,
j
≤
1
0
9
1 \leq C_{i, j} \leq 10^9
1≤Ci,j≤109
n
×
m
n \times m
n×m 的总和不超过
1
0
7
10^7
107
解题思路:
我们容易想到一个n^3的贪心:每次找到最小的可以放进来的第j列,同时记录当前连续的依然相等的行。第j列可以放进来的条件为:对于当前每一个依然相等的连续行
(
i
,
i
+
1
)
(i,i+1)
(i,i+1),有
C
[
i
]
[
j
]
<
=
C
[
i
+
1
]
[
j
]
C[i][j] <= C[i+1][j]
C[i][j]<=C[i+1][j]。
一共要找m次列,每次找列是枚举m个列,而检查列是否符合条件需要检查当前所有依然相等的连续行,最差情况下要检查n次。总的复杂度
O
(
m
2
n
)
O(m^2n)
O(m2n),肯定是不行的。考虑优化其中的一个步骤。
想到优化检查这一步,不是暴力检查,而是用
c
n
t
[
j
]
cnt[j]
cnt[j]记录第
j
j
j列有多少行满足
(
C
[
i
]
[
j
]
>
C
[
i
+
1
]
[
j
]
)
(C[i][j] > C[i+1][j])
(C[i][j]>C[i+1][j])。如果为0,则表示这一列可以加入。这样把检查优化到了O(1),但是每一次加入一列之后要去更新其他没加入的列的
c
n
t
cnt
cnt。
什么情况下一列的cnt会减少?也就是什么情况下对于还没放入的第j列,
(
C
[
i
]
[
j
]
>
C
[
i
+
1
]
[
j
]
)
(C[i][j] > C[i+1][j])
(C[i][j]>C[i+1][j])的影响被消除?
只要前面放入的列中,有一列k满足
(
C
[
i
]
[
k
]
<
C
[
i
+
1
]
[
k
]
)
(C[i][k]<C[i+1][k])
(C[i][k]<C[i+1][k]),那么
(
C
[
i
]
[
j
]
>
C
[
i
+
1
]
[
j
]
)
(C[i][j] > C[i+1][j])
(C[i][j]>C[i+1][j])对于第j列的影响就被消除了。
记录当前依然相等的连续行,加入新的一列j时,如果第j列可以把某连续行(i,i+1)分开(也就是C[i][j]<C[i+1][j]),那么就更新剩余列的cnt。因为只更新连续行,最多更新n次(一共就n行,每更新一次最少会少1个连续行),更新一次的复杂度是O(m)。
总的复杂度为
O
(
m
2
+
n
m
)
O(m^2+nm)
O(m2+nm)
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e3 + 50;
int n, m;
int c[maxn][maxn];
int cnt[maxn];// 有多少个不符合条件的
int dif[maxn];//dif[i]=1 表示第i行和i+1行已经不相等了
int vis[maxn];
void init(){
memset(cnt, 0, (m+1)<<2);
memset(vis, 0, (m+1)<<2);
memset(dif, 0, (n+1)<<2);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m ;++j){
scanf("%d", &c[i][j]);
if(i!=1){
cnt[j] += (c[i-1][j] > c[i][j]);
}
}
}
}
vector<int> ans;
void sol(){
ans.clear();
while(ans.size() < m){
int j;
for(j = 1; j <= m; ++j){
if(vis[j] || cnt[j] > 0) continue;
break;
}
if(j > m){//没找到
printf("-1\n"); return;
}
ans.push_back(j);
vis[j] = 1;
for(int i = 1; i < n; ++i){
if(dif[i]) continue;
if(c[i][j] >= c[i+1][j]) continue;
dif[i] = 1;
for(int k = 1; k <= m; ++k){
if(c[i][k] > c[i+1][k]) cnt[k]--;
}
}
}
for(int i = 0; i < ans.size(); ++i){
if(i > 0) printf(" ");
printf("%d", ans[i]);
}printf("\n");
}
int main()
{
while(scanf("%d%d", &n, &m)!=EOF){
init();sol();
}
}
H: 有向图 (推公式,线性代数)
题目描述:
Bobo 有一个
n
+
m
n + m
n+m 个节点的有向图,节点用
1
,
2
,
…
,
(
n
+
m
)
1, 2, \dots, (n + m)
1,2,…,(n+m) 编号。他还有一个
n
n
n 行
(
n
+
m
)
(n + m)
(n+m) 列的矩阵
P
P
P.
如果在
t
t
t 时刻他位于节点
u
u
u (
1
≤
u
≤
n
1 \leq u \leq n
1≤u≤n),那么在
(
t
+
1
)
(t + 1)
(t+1) 时刻他在节点
v
v
v 的概率是
P
u
,
v
/
10000
P_{u, v} / 10000
Pu,v/10000.
如果在
t
t
t 时刻他位于节点
u
u
u (
u
>
n
u > n
u>n),那么在
(
t
+
1
)
(t + 1)
(t+1) 时刻他在节点
u
u
u 的概率是
1
1
1.
0
0
0 时刻 Bobo 位于节点
1
1
1,求无穷久后,他位于节点
(
n
+
1
)
,
…
,
(
n
+
m
)
(n + 1), \dots, (n + m)
(n+1),…,(n+m) 的概率
p
1
,
p
2
,
…
,
p
m
p_1, p_2, \dots, p_m
p1,p2,…,pm。
(
n
+
m
≤
500
n+m\le500
n+m≤500)
多组输入,至多
100
100
100 组数据,除了
1
1
1 组外都满足
n
+
m
≤
50
n + m \leq 50
n+m≤50.
解题思路:(来自CZQ)
由题目可以得到一个(n+m) * (n+m)的概率转移矩阵。矩阵的前n行由输入给出,左下角的(m * n)部分全为0,右下角(m * m)部分为单位矩阵,设整个矩阵为
X
X
X.
用向量
P
i
=
(
p
1
,
p
2
,
…
,
p
n
+
m
)
P_i =(p_1,p_2,\dots,p_{n+m})
Pi=(p1,p2,…,pn+m)表示移动i次后在各个结点的概率,那么一开始
P
0
=
(
1
,
0
,
0
,
.
.
0
)
P_0=(1,0,0,..0)
P0=(1,0,0,..0)
有
P
i
=
P
i
−
1
∗
X
P_i=P_i-1*X
Pi=Pi−1∗X
我们要求的值为
P
n
=
P
0
∗
X
n
(
n
→
∞
)
P_n=P_0*X^n(n\to\infty)
Pn=P0∗Xn(n→∞)
我们把
X
X
X拆分成4个子矩阵:左上角(n * n)部分为A,右上角(n * m)部分为B,左下角(m * n)部分是零矩阵,右下角(m * m)部分为单位矩阵E。
X
=
[
A
B
0
E
]
X= \Bigg[ \begin{matrix} A & B \\ 0 & E \\ \end{matrix}\Bigg]
X=[A0BE]
算一下容易发现:
X
n
=
[
A
n
(
B
+
A
B
.
.
+
A
n
−
1
B
)
0
E
]
X^n= \Bigg[ \begin{matrix} A^n & (B+AB..+A^{n-1}B)\\ 0 & E \\ \end{matrix}\Bigg]
Xn=[An0(B+AB..+An−1B)E]
因为
A
A
A的行列式的值是小于1的,因此当n趋向无穷大的时候,
A
n
A^n
An为0矩阵。
我们需要求的部分只剩下:
(
B
+
A
B
.
.
+
A
n
−
1
B
)
(B+AB..+A^{n-1}B)
(B+AB..+An−1B)
很容易想到等比数列求和公式,得到:
(
B
+
A
B
.
.
+
A
n
−
1
B
)
=
E
−
A
n
E
−
A
∗
B
(B+AB..+A^{n-1}B)=\frac{E-A^n}{E-A}*B
(B+AB..+An−1B)=E−AE−An∗B
因为n趋向无穷,所以
E
−
A
n
E
−
A
∗
B
=
B
E
−
A
=
(
E
−
A
)
−
1
B
\frac{E-A^n}{E-A}*B=\frac{B}{E-A}=(E-A)^{-1}B
E−AE−An∗B=E−AB=(E−A)−1B
现在只需要求(E-A)的逆矩阵就可以了。利用高斯消元,O(n^3)复杂度得到该逆矩阵。
这样就得到了
X
n
X^n
Xn矩阵的值,带入计算就可以了。总的复杂度为
O
(
n
3
)
O(n^3)
O(n3)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 1e9 + 7;
ll inv(ll a){
if(a == 1) return 1;
else return (mod - mod/a)*inv(mod%a)%mod;
}
ll invw;
ll p[550][550];
void inv_mt(vector< vector<ll> > &a, int n){//矩阵求逆,求得的逆矩阵存在a[0~n-1][n~2n-1]
for(int i = 0; i < n; ++i){
int p = i;
while(p < n && a[p][i] == 0) p++;
if(p < n && a[p][i] != 0 && p!=i){//需要换行
for(int j = i; j < (n<<1); ++j) swap(a[i][j], a[p][j]);
}
assert(a[i][i] != 0);//检测换完是否不为0
ll t = inv(a[i][i]);
for(int j = i; j < n + n; ++j) a[i][j] = a[i][j]*t%mod;//整行乘a[i][i]的逆元,使a[i][i] = 1
for(int k = 0; k < n; ++k){//消掉其他行的第i列
if(k == i) continue;
t = a[k][i];
for(int j = 0; j < (n<<1); ++j) a[k][j] = (a[k][j] - t*a[i][j])%mod;
}
}
}
int n, m;
void init()
{
for(int i = 0; i < n; ++i){
for(int j = 0; j < n+m; ++j){
scanf("%lld", &p[i][j]);
p[i][j] = p[i][j] * invw % mod;
}
}
}
ll ans[550];
void sol()
{
vector< vector<ll> > a( n, vector<ll>(2*n) );
for(int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j){
a[i][j] = ((i==j)-p[i][j]+mod)%mod;
a[i][n+j] = 0;
}
a[i][n+i] = 1;
}
inv_mt(a, n);
for(int j = 0; j < m; ++j){
ans[j] = 0;
for(int i = 0; i < n; ++i){
ans[j] = (ans[j] + a[0][i+n]*p[i][n+j]%mod)%mod;
}
}
for(int i = 0; i < m; ++i){
if(i > 0) printf(" ");
printf("%lld", (ans[i]+mod)%mod);
}printf("\n");
}
int main()
{
// freopen("1.in", "r", stdin);
// freopen("1.txt","w", stdout);
invw = inv(10000);
while(~scanf("%d%d", &n, &m)){
init();sol();
}
}
因为没有OJ可以交题,思路也和标程不一样,这里自己生成数据对拍,对拍数据生成:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll a[550];
int main() {
int n = 200, m = 200;
freopen("1.in", "w", stdout);
cout<<n<<" "<<m<<endl;
srand(time(0));
for(int i = 0; i < n; ++i){
int res = 10000;
for(int j = 0; j < n+m-1; ++j){
int x = rand()%(10000/(n+m)) + 1;
while(res-x < n+m-j-1){
x = rand()%res + 1;
}
res -= x;
printf("%d ", x);
}printf("%d\n", res);
}
}