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

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 Σn5e6,Σm5e6

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

  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 = ( 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 &lt; B A &lt; B A<B ,当且仅当存在 1 ≤ p ≤ m 1 \leq p \leq m 1pm 使得 a p &lt; b p a_p &lt; b_p ap<bp 且对于所有的 1 ≤ i &lt; p 1 \leq i &lt; p 1i<p 都有 a i = b i a_i = b_i ai=bi. 进一步地,定义 A ≤ B A \leq B AB 当且仅当 A &lt; B A &lt; 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 S1S2Sn,其中 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 1n,m2000
1 ≤ C i , j ≤ 1 0 9 1 \leq C_{i, j} \leq 10^9 1Ci,j109
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 ] &lt; = C [ i + 1 ] [ j ] C[i][j] &lt;= 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 ] &gt; C [ i + 1 ] [ j ] ) (C[i][j] &gt; C[i+1][j]) C[i][j]>C[i+1][j]。如果为0,则表示这一列可以加入。这样把检查优化到了O(1),但是每一次加入一列之后要去更新其他没加入的列的 c n t cnt cnt
什么情况下一列的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 ( 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 1un),那么在 ( 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 &gt; n u &gt; 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+m500)
多组输入,至多 100 100 100 组数据,除了 1 1 1 组外都满足 n + m ≤ 50 n + m \leq 50 n+m50.

解题思路:(来自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=Pi1X
我们要求的值为 P n = P 0 ∗ X n ( n → ∞ ) P_n=P_0*X^n(n\to\infty) Pn=P0Xn(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 &amp; B \\ 0 &amp; 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 &amp; (B+AB..+A^{n-1}B)\\ 0 &amp; E \\ \end{matrix}\Bigg] Xn=[An0(B+AB..+An1B)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..+An1B)
很容易想到等比数列求和公式,得到:
( 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..+An1B)=EAEAnB
因为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 EAEAnB=EAB=(EA)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);
    }
}

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值