C:Distinct Substrings(扩展KMP)
题目描述:
For a string s1,s2,…,sns_1, s_2, \dots, s_ns1,s2,…,sn, Bobo denotes the number of its distinct substrings as f(s1,s2,…,sn)f(s_1, s_2, \dots, s_n)f(s1,s2,…,sn). He also defines defines h(c)=f(s1,s2,…,sn,c)−f(s1,s2,…,sn)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 ccc.
Given a string s1,s2,…,sns_1, s_2, \dots, s_ns1,s2,…,sn and mmm, find the value of ⨁c=1m(h(c)⋅3c mod (109+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≤5e6,Σm≤5e6\Sigma n\le5e6,\Sigma m\le5e6Σn≤5e6,Σm≤5e6
解题思路:By_DCN
很容易想到用后缀数组能直接求到一个字符串有多少个不同字串:n∗(n+1)/2−∑heightn*(n+1)/2-∑heightn∗(n+1)/2−∑height,其中n∗(n+1)/2n*(n+1)/2n∗(n+1)/2是所有子串个数,∑height∑height∑height就是重复子串个数
令height1height1height1表示加了c之后的串的height数组,height0height0height0表示加c之前的height数组。
则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)h(c)=(n+1)∗(n+2)/2−∑height1−(n∗(n+1)/2−∑height0),化简得到n+1+∑height0(y)−∑height1(x)n+1+∑height0(y)-∑height1(x)n+1+∑height0(y)−∑height1(x)。 因为f(s1,s2,,,,,sn,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)
Best[s[i]]=max(best[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 a1a2…an‾\overline{a_1 a_2 \dots a_n}a1a2…an, possibly with leading zeros. He knows that for mmm ranges [l1,r1],[l2,r2],…,[lm,rm][l_1, r_1], [l_2, r_2], \dots, [l_m, r_m][l1,r1],[l2,r2],…,[lm,rm], it holds that ali×ali+1×⋯×ari mod 9=0a_{l_i} \times a_{l_i + 1} \times \dots \times a_{r_i} \bmod 9 = 0ali×ali+1×⋯×arimod9=0. Find the number of valid integers a1a2…an‾\overline{a_1 a_2 \dots a_n}a1a2…an, modulo (109+7)(10^9+7)(109+7).
解题思路:
对于一个右端点r,有多个[li,r][l_i,r][li,r]的时候,只要最小的那个区间满足,其他肯定都满足了。这样对于一个右端点的限制条件是否满足,取决于前面出现的最后两个3出现的位置(9和0算作两个3)
用dp[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=(a1,a2,…,am)A = (a_1, a_2, \dots, a_m)A=(a1,a2,…,am) 和 B=(b1,b2,…,bm)B = (b_1, b_2, \dots, b_m)B=(b1,b2,…,bm),定义 AAA 的字典序比 BBB 小,记作 A<BA < BA<B ,当且仅当存在 1≤p≤m1 \leq p \leq m1≤p≤m 使得 ap<bpa_p < b_pap<bp 且对于所有的 1≤i<p1 \leq i < p1≤i<p 都有 ai=bia_i = b_iai=bi. 进一步地,定义 A≤BA \leq BA≤B 当且仅当 A<BA < BA<B 或者 A=BA = BA=B.
Bobo 有一个 nnn 行 mmm 列的矩阵 CCC. 他想找字典序最小的 1,2,…,m1, 2, \dots, m1,2,…,m 的排列 σ1,σ2,…,σm\sigma_1, \sigma_2, \dots, \sigma_mσ1,σ2,…,σm, 使得 S1≤S2≤⋯≤SnS_1 \leq S_2 \leq \dots \leq S_nS1≤S2≤⋯≤Sn,其中 Si=(Ci,σ1,Ci,σ2,…,Ci,σ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≤20001 \leq n, m \leq 20001≤n,m≤2000
1≤Ci,j≤1091 \leq C_{i, j} \leq 10^91≤Ci,j≤109
n×mn \times mn×m 的总和不超过 10710^7107
解题思路:
我们容易想到一个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(m2n)O(m^2n)O(m2n),肯定是不行的。考虑优化其中的一个步骤。
想到优化检查这一步,不是暴力检查,而是用cnt[j]cnt[j]cnt[j]记录第jjj列有多少行满足(C[i][j]>C[i+1][j])(C[i][j] > C[i+1][j])(C[i][j]>C[i+1][j])。如果为0,则表示这一列可以加入。这样把检查优化到了O(1),但是每一次加入一列之后要去更新其他没加入的列的cntcntcnt。
什么情况下一列的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(m2+nm)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+mn + mn+m 个节点的有向图,节点用 1,2,…,(n+m)1, 2, \dots, (n + m)1,2,…,(n+m) 编号。他还有一个 nnn 行 (n+m)(n + m)(n+m) 列的矩阵 PPP.
如果在 ttt 时刻他位于节点 uuu (1≤u≤n1 \leq u \leq n1≤u≤n),那么在 (t+1)(t + 1)(t+1) 时刻他在节点 vvv 的概率是 Pu,v/10000P_{u, v} / 10000Pu,v/10000.
如果在 ttt 时刻他位于节点 uuu (u>nu > nu>n),那么在 (t+1)(t + 1)(t+1) 时刻他在节点 uuu 的概率是 111.
000 时刻 Bobo 位于节点 111,求无穷久后,他位于节点 (n+1),…,(n+m)(n + 1), \dots, (n + m)(n+1),…,(n+m) 的概率 p1,p2,…,pmp_1, p_2, \dots, p_mp1,p2,…,pm。
(n+m≤500n+m\le500n+m≤500)
多组输入,至多 100100100 组数据,除了 111 组外都满足 n+m≤50n + m \leq 50n+m≤50.
解题思路:(来自CZQ)
由题目可以得到一个(n+m) * (n+m)的概率转移矩阵。矩阵的前n行由输入给出,左下角的(m * n)部分全为0,右下角(m * m)部分为单位矩阵,设整个矩阵为XXX.
用向量Pi=(p1,p2,…,pn+m)P_i =(p_1,p_2,\dots,p_{n+m})Pi=(p1,p2,…,pn+m)表示移动i次后在各个结点的概率,那么一开始 P0=(1,0,0,..0)P_0=(1,0,0,..0)P0=(1,0,0,..0)
有Pi=Pi−1∗XP_i=P_i-1*XPi=Pi−1∗X
我们要求的值为Pn=P0∗Xn(n→∞)P_n=P_0*X^n(n\to\infty)Pn=P0∗Xn(n→∞)
我们把XXX拆分成4个子矩阵:左上角(n * n)部分为A,右上角(n * m)部分为B,左下角(m * n)部分是零矩阵,右下角(m * m)部分为单位矩阵E。
X=[AB0E]X=
\Bigg[
\begin{matrix}
A & B \\
0 & E \\
\end{matrix}\Bigg]
X=[A0BE]
算一下容易发现:
Xn=[An(B+AB..+An−1B)0E]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]
因为AAA的行列式的值是小于1的,因此当n趋向无穷大的时候,AnA^nAn为0矩阵。
我们需要求的部分只剩下:(B+AB..+An−1B)(B+AB..+A^{n-1}B)(B+AB..+An−1B)
很容易想到等比数列求和公式,得到:
(B+AB..+An−1B)=E−AnE−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−AnE−A∗B=BE−A=(E−A)−1B\frac{E-A^n}{E-A}*B=\frac{B}{E-A}=(E-A)^{-1}BE−AE−An∗B=E−AB=(E−A)−1B
现在只需要求(E-A)的逆矩阵就可以了。利用高斯消元,O(n^3)复杂度得到该逆矩阵。
这样就得到了XnX^nXn矩阵的值,带入计算就可以了。总的复杂度为O(n3)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);
}
}
本文深入解析了算法竞赛中的经典题目,涵盖扩展KMP、DP、贪心、线性代数等多种算法策略,通过具体实例讲解解题思路与代码实现,是提升算法能力的宝贵资源。
827





