2019湖南ACM省赛部分题解(C,D,G,H)

本文深入解析了算法竞赛中的经典题目,涵盖扩展KMP、DP、贪心、线性代数等多种算法策略,通过具体实例讲解解题思路与代码实现,是提升算法能力的宝贵资源。
部署运行你感兴趣的模型镜像
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Σn5e6,Σm5e6

解题思路:By_DCN
很容易想到用后缀数组能直接求到一个字符串有多少个不同字串:n∗(n+1)/2−∑heightn*(n+1)/2-∑heightn(n+1)/2height,其中n∗(n+1)/2n*(n+1)/2n(n+1)/2是所有子串个数,∑height∑heightheight就是重复子串个数
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)/2height1(n(n+1)/2height0),化简得到n+1+∑height0(y)−∑height1(x)n+1+∑height0(y)-∑height1(x)n+1+height0yheight1x。 因为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}a1a2an, 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&VeryThinSpace;mod&VeryThinSpace;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}a1a2an, 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)这个位置填的数字有三种情况:

  1. 不是3的倍数
  2. 是3的倍数,但不是9的倍数(3,6)
  3. 是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&lt;BA &lt; BA<B ,当且仅当存在 1≤p≤m1 \leq p \leq m1pm 使得 ap&lt;bpa_p &lt; b_pap<bp 且对于所有的 1≤i&lt;p1 \leq i &lt; p1i<p 都有 ai=bia_i = b_iai=bi. 进一步地,定义 A≤BA \leq BAB 当且仅当 A&lt;BA &lt; BA<B 或者 A=BA = BA=B.

Bobo 有一个 nnnmmm 列的矩阵 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_nS1S2Sn,其中 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 20001n,m2000
1≤Ci,j≤1091 \leq C_{i, j} \leq 10^91Ci,j109
n×mn \times mn×m 的总和不超过 10710^7107

解题思路:
我们容易想到一个n^3的贪心:每次找到最小的可以放进来的第j列,同时记录当前连续的依然相等的行。第j列可以放进来的条件为:对于当前每一个依然相等的连续行(i,i+1)(i,i+1)(i,i+1),有C[i][j]&lt;=C[i+1][j]C[i][j] &lt;= 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]&gt;C[i+1][j])(C[i][j] &gt; C[i+1][j])C[i][j]>C[i+1][j]。如果为0,则表示这一列可以加入。这样把检查优化到了O(1),但是每一次加入一列之后要去更新其他没加入的列的cntcntcnt
什么情况下一列的cnt会减少?也就是什么情况下对于还没放入的第j列,(C[i][j]&gt;C[i+1][j])(C[i][j] &gt; C[i+1][j])(C[i][j]>C[i+1][j])的影响被消除?
只要前面放入的列中,有一列k满足(C[i][k]&lt;C[i+1][k])(C[i][k]&lt;C[i+1][k])(C[i][k]<C[i+1][k]),那么(C[i][j]&gt;C[i+1][j])(C[i][j] &gt; 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 n1un),那么在 (t+1)(t + 1)(t+1) 时刻他在节点 vvv 的概率是 Pu,v/10000P_{u, v} / 10000Pu,v/10000.
如果在 ttt 时刻他位于节点 uuu (u&gt;nu &gt; 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+m500)
多组输入,至多 100100100 组数据,除了 111 组外都满足 n+m≤50n + m \leq 50n+m50.

解题思路:(来自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=Pi1X
我们要求的值为Pn=P0∗Xn(n→∞)P_n=P_0*X^n(n\to\infty)Pn=P0Xn(n)
我们把XXX拆分成4个子矩阵:左上角(n * n)部分为A,右上角(n * m)部分为B,左下角(m * n)部分是零矩阵,右下角(m * m)部分为单位矩阵E。
X=[AB0E]X= \Bigg[ \begin{matrix} A &amp; B \\ 0 &amp; E \\ \end{matrix}\Bigg] X=[A0BE]
算一下容易发现:
Xn=[An(B+AB..+An−1B)0E]X^n= \Bigg[ \begin{matrix} A^n &amp; (B+AB..+A^{n-1}B)\\ 0 &amp; E \\ \end{matrix}\Bigg] Xn=[An0(B+AB..+An1B)E]
因为AAA的行列式的值是小于1的,因此当n趋向无穷大的时候,AnA^nAn为0矩阵。
我们需要求的部分只剩下:(B+AB..+An−1B)(B+AB..+A^{n-1}B)(B+AB..+An1B)
很容易想到等比数列求和公式,得到:
(B+AB..+An−1B)=E−AnE−A∗B(B+AB..+A^{n-1}B)=\frac{E-A^n}{E-A}*B(B+AB..+An1B)=EAEAnB
因为n趋向无穷,所以
E−AnE−A∗B=BE−A=(E−A)−1B\frac{E-A^n}{E-A}*B=\frac{B}{E-A}=(E-A)^{-1}BEAEAnB=EAB=(EA)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);
    }
}

您可能感兴趣的与本文相关的镜像

ComfyUI

ComfyUI

AI应用
ComfyUI

ComfyUI是一款易于上手的工作流设计工具,具有以下特点:基于工作流节点设计,可视化工作流搭建,快速切换工作流,对显存占用小,速度快,支持多种插件,如ADetailer、Controlnet和AnimateDIFF等

评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值