日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[2019 牛客CSP-S提高组赛前集训营4题解] 复读数组(数论)+ 路径计数机(数上DP)+ 排列计数机(线段树+二项式定理)

發(fā)布時間:2023/12/3 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [2019 牛客CSP-S提高组赛前集训营4题解] 复读数组(数论)+ 路径计数机(数上DP)+ 排列计数机(线段树+二项式定理) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • T1:復(fù)讀數(shù)組
    • 題目
    • 題解
    • 代碼實現(xiàn)
  • T2:路徑計數(shù)機(jī)
    • 題目
    • 題解
    • 代碼實現(xiàn)
  • T3:排列計數(shù)機(jī)
    • 題目
    • 題解
    • CODE

T1:復(fù)讀數(shù)組

題目

有一個長為n×k的數(shù)組,它是由長為n的數(shù)組A1,A2,…,An重復(fù)k次得到的。
定義這個數(shù)組的一個區(qū)間的權(quán)值為它里面不同的數(shù)的個數(shù),
現(xiàn)在,你需要求出對于這個數(shù)組的每個非空區(qū)間的權(quán)值之和。
答案對10^9+7取模
點擊下載大樣例
輸入描述:
第一行兩個整數(shù)n和k。
接下來一行n個整數(shù),第i個整數(shù)為Ai
輸出描述:
輸出一個整數(shù),表示答案。

示例1
輸入
2 2
1 2
輸出
16
說明
數(shù)組為1, 2, 1, 2
對于長為1的區(qū)間,共4個,權(quán)值為1
對于長度>1的區(qū)間,可以發(fā)現(xiàn)權(quán)值均為2,共6個
那么權(quán)值和為1×4+2×6=16

備注:
對于前10%的數(shù)據(jù)n≤5
對于前20%的數(shù)據(jù)n≤100
對于前40%的數(shù)據(jù)n≤1000
對于另外10%的數(shù)據(jù)n≤100,k=1
對于另外10%的數(shù)據(jù)n≤1000,k=1
對于另外10%的數(shù)據(jù)n≤105,k=1
對于所有數(shù)據(jù),1≤n≤105,1≤k≤109,1≤Ai≤109

題解

我們來考慮某一個a[x]如果出現(xiàn)在一個區(qū)間[l,r][l,r][l,r],那么就會對這個區(qū)間貢獻(xiàn)1
所以我們可以去找有多少個區(qū)間包含a[x],這些區(qū)間的貢獻(xiàn)就會+1

但是馬上我們就意識到一個區(qū)間可能多個a[x],則這個區(qū)間a[x]的貢獻(xiàn)就多算了
于是我們調(diào)轉(zhuǎn)思路,去找多少個區(qū)間不包含a[x]


先不考慮k的限制,就看[l,r][l,r][l,r]

觀察這幅圖,假設(shè)a[x]在區(qū)間出現(xiàn)的位置分別在a,b,c
那么不包含a[x]的區(qū)間會在哪些地方取呢??易得如下圖:

紅色區(qū)域就是不包含a[x]的所有可能區(qū)間,
接著我們來計算著一個區(qū)間中有多少種[l,r][l,r][l,r]
舉栗說明:
假設(shè)a[x]出現(xiàn)在4,12,那么[5,10][5,10][5,10]就是可選的區(qū)間,對于不同的l,對應(yīng)的r個數(shù)也不一樣

lr
55,6,7,8,9,10
66,7,8,9,10
77,8,9,10
88,9,10
99,10
1010

有木有發(fā)現(xiàn)規(guī)律,這其實就是一個等差數(shù)列,令T=b-a-1(真正能選區(qū)間的端點個數(shù))
則答案個數(shù)就是T?(T+1)/2T*(T+1)/2T?(T+1)/2


接下來我們把k的限制考慮進(jìn)去,看圖↓

如果[1,n][1,n][1,n]的黃色區(qū)間就對應(yīng)[n+1,2n][n+1,2n][n+1,2n][2n+1,3n][2n+1,3n][2n+1,3n]中的黃色區(qū)間
是不是他們是完全相等的,[1,n][1,n][1,n]中黃色區(qū)間的答案個數(shù)就是后面對應(yīng)區(qū)間的個數(shù)
那么一個有k個這樣的區(qū)間,所以…是不是乘個k

怎么算呢?肯定是上面說的等差數(shù)列,那么這個區(qū)間端點個數(shù)T又有多少個呢?(r?l?1)(r-l-1)(r?l?1)
舉個栗子:a[x]出現(xiàn)在4,12,則可選端點就是[5,11][5,11][5,11],可取的左右端點就是l+1l+1l+1r?1r-1r?1
即是(r?1?(l+1)+1)=(r?l?1)(r-1-(l+1)+1)=(r-l-1)(r?1?(l+1)+1)=(r?l?1)


如果是交叉了兩個塊怎么辦呢??如圖↓

與上面情況一樣處理,但是我們發(fā)現(xiàn)這個時候只會有k-1個,所以…懂了吧!

怎么算呢?照樣是等差數(shù)列,但是端點個數(shù)T發(fā)生了改變
推理一波:能跨塊的區(qū)間是不是a[x]在[1,n][1,n][1,n]區(qū)間最后一次出現(xiàn)的位置到a[x]在[1,n][1,n][1,n]第一次出現(xiàn)的位置
只不過此時a[x]相對應(yīng)在了第二個區(qū)間塊

考慮在第一個區(qū)間塊的可取端點為[l+1,n][l+1,n][l+1,n],個數(shù)就是n?(l+1)+1=n?ln-(l+1)+1=n-ln?(l+1)+1=n?l
考慮跨越在了第二個區(qū)間塊的可取端點為[1,r?1][1,r-1][1,r?1],個數(shù)就是r?1?1+1=r?1r-1-1+1=r-1r?1?1+1=r?1
把二者加在一起即是所有可取的端點個數(shù)了


代碼實現(xiàn)

#include <cstdio> #include <vector> #include <algorithm> using namespace std; #define mod 1000000007 #define LL long long #define MAXN 100005 struct node {int id, val; }b[MAXN]; vector < int > G[MAXN]; int n; int a[MAXN]; LL result, k, tot;bool cmp ( node x, node y ) {return x.val < y.val; }LL count ( LL x ) {return ( ( x * ( x + 1 ) ) >> 1 ) % mod; }int main() {scanf ( "%d %lld", &n, &k );for ( int i = 1;i <= n;i ++ ) {scanf ( "%d", &a[i] );b[i].val = a[i];b[i].id = i;}sort ( b + 1, b + n + 1, cmp );for ( int i = 1;i <= n;i ++ )if ( b[i].val != b[i - 1].val )a[b[i].id] = ++ tot;elsea[b[i].id] = tot;for ( int i = 1;i <= n;i ++ )G[a[i]].push_back( i );result = tot * count ( k * n % mod ) % mod;for ( int i = 1;i <= tot;i ++ ) {for ( int j = 1;j < G[i].size();j ++ )result = ( result - k * count ( G[i][j] - G[i][j - 1] - 1 ) % mod + mod ) % mod;result = ( result - ( k - 1 ) * count ( n - G[i][G[i].size() - 1] + G[i][0] - 1 ) % mod + mod ) % mod;result = ( result - count ( G[i][0] - 1 ) + mod ) % mod;result = ( result - count ( n - G[i][G[i].size() - 1] ) + mod ) % mod;}printf ( "%lld", result % mod );return 0; }

T2:路徑計數(shù)機(jī)

題目

有一棵n個點的樹和兩個整數(shù)p, q,求滿足以下條件的四元組(a, b, c, d)的個數(shù):

  • 1?a,b,c,d?n。
  • 點a到點b的經(jīng)過的邊數(shù)為p。
  • 點c到點d的經(jīng)過的邊數(shù)為q。
  • 不存在一個點,它既在點a到點b的路徑上,又在點c到點d的路徑上。
    點擊下載大樣例
  • 輸入描述:
    第一行三個整數(shù)n,p,q。
    接下來n - 1行,每行兩個整數(shù)u, v,表示樹上存在一個連接點u和點v的邊。
    輸出描述:
    輸出一個整數(shù),表示答案。
    示例1
    輸入
    5 2 1
    1 2
    2 3
    3 4
    2 5
    輸出
    4
    說明
    合法的四元組一共有:
    (1, 5, 3, 4),
    (1, 5, 4, 3),
    (5, 1, 3 ,4),
    (5, 1, 4, 3)。
    示例2
    輸入
    4 1 1
    1 2
    2 3
    3 4
    輸出
    8
    備注:
    對于前20%的數(shù)據(jù),n,p,q≤50。
    對于前40%的數(shù)據(jù),n,p,q≤200。
    對于另外10%的數(shù)據(jù),p = 2, q = 2。
    對于另外10%的數(shù)據(jù),樹是一條鏈。
    對于另外10%的數(shù)據(jù),樹隨機(jī)生成。
    對于所有數(shù)據(jù)1≤n,p,q≤3000,1≤u,v≤n,保證給出的是一棵合法的樹。

    題解

    其實這道題與集訓(xùn)營1的B題思路上很相似,下面就寫得比較亂,如果想理解明白一點的,可以移步我之前的博客它會讓你耳目一新,里面的講解很清楚

    話不多說,直接上思路,本蒟蒻還有很多題解沒打
    正難反易,考慮反求問題,不相交的路徑數(shù)=所有路徑數(shù)-相交路徑數(shù)

    思考哪些情況,存在一個點,它既在點a到點b的路徑上,又在點c到點d的路徑上
    如果我們固定了a到b的路徑,那么這個特殊的點會出現(xiàn)在哪里,肯定會經(jīng)過lca(a,b)lca(a,b)lca(a,b)


    情況1:c和d均在lca的子樹內(nèi),兩者相連必須經(jīng)過lca

    情況2:c和d通過lca相連

    情況3:c和d在a到lca或者b到lca路徑上相連

    不難發(fā)現(xiàn)如果我們把a(bǔ)的父親,lca的兒子假設(shè)成新的lca,上面的情況2和情況3都是一樣的,而且c到d的綠色路徑總是經(jīng)過lca的
    他可以是兩條都在子樹內(nèi)的鏈或者是一條子樹內(nèi)的鏈和一條從子樹內(nèi)(可以不進(jìn))到子樹外的鏈
    這啟示我們可以通過枚舉lca來進(jìn)行轉(zhuǎn)移


    設(shè)dp[u][j]dp[u][j]dp[u][j]表示:u的子樹內(nèi),與u的距離為j的節(jié)點個數(shù)
    那么轉(zhuǎn)移就很簡單,通過兒子v完成
    dp[u][j]+=dp[v][j?1]dp[u][j]+=dp[v][j-1]dp[u][j]+=dp[v][j?1]
    設(shè)fp[u]fp[u]fp[u]表示:情況1兩條都在子樹內(nèi)的鏈的距離為p的方案數(shù)量
    那么每個點都會與u的其它子樹鏈上點構(gòu)成一種方案,所以我們可以規(guī)定順序,x與之前搜索到的點進(jìn)行匹配,后面的點再與前面的點進(jìn)行匹配,這樣就不會多算
    fp[u]=dp[u][p?j]?dp[v][j?1]fp[u]=dp[u][p-j]*dp[v][j-1]fp[u]=dp[u][p?j]?dp[v][j?1]
    這樣保證了,一定會經(jīng)過u成為lca的這個點
    那么情況1中距離為q的方案數(shù)量與上面的轉(zhuǎn)移則是一模一樣,不再重復(fù)

    接著設(shè)gp[v][j]gp[v][j]gp[v][j]表示:除開v的子樹,整棵樹與v的距離為j的節(jié)點個數(shù),來處理情況2
    我們用v的父親u來更新v,v外面的點距離u的距離應(yīng)該是j-1,那么會出現(xiàn)這種情況,與集訓(xùn)營1的一道題類似,在v的子樹內(nèi)的點距離v就不會是j-1,而應(yīng)該是j-2,要減掉

    g[v][j]+=g[u][j?1]+dp[u][j?1]?dp[v][j?2]g[v][j] += g[u][j - 1] + dp[u][j - 1] - dp[v][j - 2]g[v][j]+=g[u][j?1]+dp[u][j?1]?dp[v][j?2]
    接著就是與上面一樣的思路,邊DP便進(jìn)行更新
    gp[u]+=dp[u][p?i]?g[u][i]gp[u] += dp[u][p - i] * g[u][i]gp[u]+=dp[u][p?i]?g[u][i]
    gq[u]+=dp[u][q?i]?g[u][i]gq[u] += dp[u][q - i] * g[u][i]gq[u]+=dp[u][q?i]?g[u][i]
    最后算出來的方案數(shù)要?4*4?4,因為點對順序不一樣算不同的方案,看樣例就可知了

    代碼實現(xiàn)

    #include <cstdio> #include <vector> #include <iostream> using namespace std; #define MAXN 3005 #define LL long long vector < int > G[MAXN]; int n, p, q; LL result; LL dp[MAXN][MAXN], fp[MAXN], fq[MAXN], g[MAXN][MAXN], gp[MAXN], gq[MAXN];void dfs1 ( int u, int fa ) {dp[u][0] = 1;for ( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if ( v == fa )continue;dfs1 ( v, u );for ( int j = 1;j <= p;j ++ )fp[u] += dp[u][p - j] * dp[v][j - 1];for ( int j = 1;j <= q;j ++ )fq[u] += dp[u][q - j] * dp[v][j - 1];for ( int j = 1;j <= p;j ++ )dp[u][j] += dp[v][j - 1];} }void dfs2 ( int u, int fa ) {for ( int i = 1;i <= p;i ++ )gp[u] += dp[u][p - i] * g[u][i];for ( int i = 1;i <= q;i ++ )gq[u] += dp[u][q - i] * g[u][i];for ( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if ( v == fa )continue;g[v][1] = 1;for ( int j = 2;j <= p;j ++ )g[v][j] += g[u][j - 1] + dp[u][j - 1] - dp[v][j - 2];dfs2 ( v, u );} }int main() {scanf ( "%d %d %d", &n, &p, &q );if ( p < q )swap ( p, q );for ( int i = 1;i < n;i ++ ) {int u, v;scanf ( "%d %d", &u, &v );G[u].push_back( v );G[v].push_back( u );}dfs1 ( 1, 0 );dfs2 ( 1, 0 );LL sum1 = 0, sum2 = 0;for ( int i = 1;i <= n;i ++ )sum1 += fp[i], sum2 += fq[i];result = sum1 * sum2;for ( int i = 1;i <= n;i ++ )result -= fp[i] * fq[i] + fp[i] * gq[i] + fq[i] * gp[i];printf ( "%lld\n", result * 4 );return 0; }

    T3:排列計數(shù)機(jī)

    題目

    定義一個長為k的序列A1,A2,…,AkA_1, A_2, \dots, A_kA1?,A2?,,Ak?的權(quán)值為:對于所有1≤i≤k,max?(A1,A2,…,Ai)1 \le i \le k,\max(A_1, A_2, \dots, A_i)1ikmax(A1?,A2?,,Ai?)有多少種不同的取值。
    給出一個1到n的排列B1,B2,…,BnB_1, B_2, \dots, B_nB1?,B2?,,Bn?,求B的所有非空子序列的權(quán)值的m次方之和。
    答案對109+710^9 + 7109+7取模。

    點擊下載大樣例
    輸入描述:
    第一行兩個整數(shù)n、m。
    接下來一行n個整數(shù),第i個整數(shù)為BiB_iBi?
    輸出描述:
    輸出一個整數(shù),表示答案。

    示例1
    輸入
    3 2
    1 3 2
    輸出
    16
    說明
    在所有非空子序列中:
    (1), (3), (2), (3, 2)權(quán)值為1,
    (1, 3), (1, 2), (1, 3, 2)權(quán)值為2。
    那么所有非空子序列權(quán)值的2次方和為4×12+3×22=164 \times 1^2 + 3 \times 2^2 = 164×12+3×22=16
    備注:
    對于前10%10\%10%的數(shù)據(jù),n≤20n \le 20n20
    對于前20%20\%20%的數(shù)據(jù),n≤100n \le 100n100
    對于前40%40\%40%的數(shù)據(jù),n≤1000n \le 1000n1000
    對于另外20%20\%20%的數(shù)據(jù),m = 1。
    對于所有數(shù)據(jù),1≤n≤1051 \le n \le 10^51n1051≤m≤201 \le m \le 201m20,保證B是1到n的排列。

    題解

    考慮從左到右一個一個加入數(shù)
    當(dāng)加入一個數(shù)的時候,只有最大值小于這個數(shù)的子序列,權(quán)值才會被更新(+1+1+1
    但我們不可能把每個子序列的權(quán)值都求出來后才乘方再加起來,三秒也會T得你懷疑人生

    但我們根據(jù)二項式定理,可以發(fā)現(xiàn)對于任意一個數(shù)x
    (x+1)m=Cm0xm+Cm1xm?1+Cm2xm?2+??+Cmm?2x2+Cmm?1x1+Cmmx0(x+1)^m=C^0_mx^m+C^1_mx^{m?1}+C^2_mx^{m?2}+??+C^{m?2}_mx^2+C^{m?1}_mx^1+C^m_mx^0(x+1)m=Cm0?xm+Cm1?xm?1+Cm2?xm?2+??+Cmm?2?x2+Cmm?1?x1+Cmm?x0
    通過這個,啟發(fā)我們可以維護(hù)每個子序列權(quán)值的m次方和,m-1次方和,m-2次方和…1次方和,0次方和,就可以通過上面的公式得到這些子序列權(quán)值集體加1后的乘方的和

    由于每次插入值的時候更新只跟最大值有關(guān),而且題目中保證了B是一個排列,因此分情況處理:
    1.我們可以把最大值相同的子序列一起處理,維護(hù)它們的m次方和,m-1次方和,m-2次方和
    加入一個新的數(shù)的時候,找到所有最大值比它小的子序列,將它們的m次方和,m-1次方和…加起來,再用二項式定理得到加1后的m次方和,得到一組新的子序列的信息

    2.對于那些最大值比它大的子序列,因為無法更新但可以形成新的子序列,使得答案為x的子序列多了整整一倍,所以直接將和?2*2?2就好了

    這個可以用線段樹維護(hù),線段樹的每個位置維護(hù)相同最大值的子序列的一些信息

    CODE

    #include <cstdio> #define mod 1000000007 #define MAXN 100005 int n, m; int a[MAXN], tmp[25], pre[25], sum[MAXN << 2][25], lazy[MAXN << 2][25]; //sum[i][k]維護(hù)的C(k,m)i^m和 int C[25][25];void pushdown ( int t, int l, int r, int k ) {lazy[t << 1][k] = 1ll * lazy[t << 1][k] * lazy[t][k] % mod;lazy[t << 1 | 1][k] = 1ll * lazy[t << 1 | 1][k] * lazy[t][k] % mod;sum[t << 1][k] = 1ll * sum[t << 1][k] * lazy[t][k] % mod;sum[t << 1 | 1][k] = 1ll * sum[t << 1 | 1][k] * lazy[t][k] % mod;lazy[t][k] = 1;return; }void add ( int t, int l, int r, int id, int v, int k ) {if ( l == r ) {sum[t][k] = v;return;}int mid = ( l + r ) >> 1;pushdown ( t, l, r, k );if ( id <= mid )add ( t << 1, l, mid, id, v, k );elseadd ( t << 1 | 1, mid + 1, r, id, v, k );sum[t][k] = ( sum[t << 1][k] + sum[t << 1 | 1][k] ) % mod; }int query ( int t, int l, int r, int id, int k ) {if ( r <= id )return sum[t][k];int mid = ( l + r ) >> 1;pushdown ( t, l, r, k );int sum1 = 0, sum2 = 0;sum1 = query ( t << 1, l, mid, id, k );if ( mid < id )sum2 = query ( t << 1 | 1, mid + 1, r, id, k );return ( sum1 + sum2 ) % mod; }void mul ( int t, int l, int r, int id, int k ) {if ( r < id )return;if ( id <= l ) {sum[t][k] = 2ll * sum[t][k] % mod;lazy[t][k] = lazy[t][k] * 2ll % mod;return;}int mid = ( l + r ) >> 1;pushdown ( t, l, r, k );if ( id <= mid )mul ( t << 1, l, mid, id, k );mul ( t << 1 | 1, mid + 1, r, id, k );sum[t][k] = ( 1ll * sum[t << 1][k] + sum[t << 1 | 1][k] ) % mod; }int main() {C[0][0] = 1;//先打出二項式定理C的表,仗著m小使勁搞for ( int i = 1;i <= 20;i ++ ) {C[i][0] = 1;for ( int j = 1;j <= i;j ++ )C[i][j] = C[i - 1][j - 1] + C[i - 1][j];}scanf ( "%d %d", &n, &m );for ( int i = 1;i <= ( n << 2 );i ++ )for ( int j = 0;j <= m;j ++ )lazy[i][j] = 1;//線段樹初始化,因為我們是乘法所以初始化為1 for ( int i = 1;i <= n;i ++ ) {scanf ( "%d", &a[i] );for ( int j = 0;j <= m;j ++)pre[j] = tmp[j] = 0;for ( int j = 0;j <= m;j ++ )pre[j] = query ( 1, 1, n, a[i], j );for ( int j = m;j >= 0;j -- )for ( int k = j;k >= 0;k -- )tmp[j] = ( tmp[j] + 1ll * pre[k] * C[j][k] % mod ) % mod;for ( int j = 0;j <= m;j ++ ) {add ( 1, 1, n, a[i], tmp[j] + 1, j );mul ( 1, 1, n, a[i] + 1, j );}}printf ( "%d", ( query ( 1, 1, n, n, m ) % mod + mod ) % mod );return 0; }

    終于補(bǔ)了這道題,這篇blog也算是有頭的了,就bb咯!

    總結(jié)

    以上是生活随笔為你收集整理的[2019 牛客CSP-S提高组赛前集训营4题解] 复读数组(数论)+ 路径计数机(数上DP)+ 排列计数机(线段树+二项式定理)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。