文章目录

  • T1:复读数组
    • 题目
    • 题解
    • 代码实现
  • T2:路径计数机
    • 题目
    • 题解
    • 代码实现
  • T3:排列计数机
    • 题目
    • 题解
    • CODE

T1:复读数组

题目

有一个长为n×k的数组,它是由长为n的数组A1,A2,…,An重复k次得到的。
定义这个数组的一个区间的权值为它里面不同的数的个数,
现在,你需要求出对于这个数组的每个非空区间的权值之和。
答案对10^9+7取模
点击下载大样例
输入描述:
第一行两个整数n和k。
接下来一行n个整数,第i个整数为Ai
输出描述:
输出一个整数,表示答案。

示例1
输入
2 2
1 2
输出
16
说明
数组为1, 2, 1, 2
对于长为1的区间,共4个,权值为1
对于长度>1的区间,可以发现权值均为2,共6个
那么权值和为1×4+2×6=16

备注:
对于前10%的数据n≤5
对于前20%的数据n≤100
对于前40%的数据n≤1000
对于另外10%的数据n≤100,k=1
对于另外10%的数据n≤1000,k=1
对于另外10%的数据n≤105,k=1
对于所有数据,1≤n≤105,1≤k≤109,1≤Ai≤109

题解

我们来考虑某一个a[x]如果出现在一个区间[l,r][l,r][l,r],那么就会对这个区间贡献1
所以我们可以去找有多少个区间包含a[x],这些区间的贡献就会+1

但是马上我们就意识到一个区间可能多个a[x],则这个区间a[x]的贡献就多算了
于是我们调转思路,去找多少个区间不包含a[x]


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

观察这幅图,假设a[x]在区间出现的位置分别在a,b,c
那么不包含a[x]的区间会在哪些地方取呢??易得如下图:

红色区域就是不包含a[x]的所有可能区间,
接着我们来计算着一个区间中有多少种[l,r][l,r][l,r]
举栗说明:
假设a[x]出现在4,12,那么[5,10][5,10][5,10]就是可选的区间,对于不同的l,对应的r个数也不一样

l r
5 5,6,7,8,9,10
6 6,7,8,9,10
7 7,8,9,10
8 8,9,10
9 9,10
10 10

有木有发现规律,这其实就是一个等差数列,令T=b-a-1(真正能选区间的端点个数)
则答案个数就是T∗(T+1)/2T*(T+1)/2T∗(T+1)/2


接下来我们把k的限制考虑进去,看图↓

如果[1,n][1,n][1,n]的黄色区间就对应[n+1,2n][n+1,2n][n+1,2n],[2n+1,3n][2n+1,3n][2n+1,3n]中的黄色区间
是不是他们是完全相等的,[1,n][1,n][1,n]中黄色区间的答案个数就是后面对应区间的个数
那么一个有k个这样的区间,所以…是不是乘个k

怎么算呢?肯定是上面说的等差数列,那么这个区间端点个数T又有多少个呢?(r−l−1)(r-l-1)(r−l−1),
举个栗子:a[x]出现在4,12,则可选端点就是[5,11][5,11][5,11],可取的左右端点就是l+1l+1l+1和r−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)


如果是交叉了两个块怎么办呢??如图↓

与上面情况一样处理,但是我们发现这个时候只会有k-1个,所以…懂了吧!

怎么算呢?照样是等差数列,但是端点个数T发生了改变
推理一波:能跨块的区间是不是a[x]在[1,n][1,n][1,n]区间最后一次出现的位置到a[x]在[1,n][1,n][1,n]第一次出现的位置
只不过此时a[x]相对应在了第二个区间块

考虑在第一个区间块的可取端点为[l+1,n][l+1,n][l+1,n],个数就是n−(l+1)+1=n−ln-(l+1)+1=n-ln−(l+1)+1=n−l
考虑跨越在了第二个区间块的可取端点为[1,r−1][1,r-1][1,r−1],个数就是r−1−1+1=r−1r-1-1+1=r-1r−1−1+1=r−1
把二者加在一起即是所有可取的端点个数了


代码实现

#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:路径计数机

题目

有一棵n个点的树和两个整数p, q,求满足以下条件的四元组(a, b, c, d)的个数:

  1. 1⩽a,b,c,d⩽n。
  2. 点a到点b的经过的边数为p。
  3. 点c到点d的经过的边数为q。
  4. 不存在一个点,它既在点a到点b的路径上,又在点c到点d的路径上。
    点击下载大样例

输入描述:
第一行三个整数n,p,q。
接下来n - 1行,每行两个整数u, v,表示树上存在一个连接点u和点v的边。
输出描述:
输出一个整数,表示答案。
示例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%的数据,n,p,q≤50。
对于前40%的数据,n,p,q≤200。
对于另外10%的数据,p = 2, q = 2。
对于另外10%的数据,树是一条链。
对于另外10%的数据,树随机生成。
对于所有数据1≤n,p,q≤3000,1≤u,v≤n,保证给出的是一棵合法的树。

题解

其实这道题与集训营1的B题思路上很相似,下面就写得比较乱,如果想理解明白一点的,可以移步我之前的博客它会让你耳目一新,里面的讲解很清楚

话不多说,直接上思路,本蒟蒻还有很多题解没打
正难反易,考虑反求问题,不相交的路径数=所有路径数-相交路径数

思考哪些情况,存在一个点,它既在点a到点b的路径上,又在点c到点d的路径上
如果我们固定了a到b的路径,那么这个特殊的点会出现在哪里,肯定会经过lca(a,b)lca(a,b)lca(a,b)


情况1:c和d均在lca的子树内,两者相连必须经过lca

情况2:c和d通过lca相连

情况3:c和d在a到lca或者b到lca路径上相连

不难发现如果我们把a的父亲,lca的儿子假设成新的lca,上面的情况2和情况3都是一样的,而且c到d的绿色路径总是经过lca的
他可以是两条都在子树内的链或者是一条子树内的链和一条从子树内(可以不进)到子树外的链
这启示我们可以通过枚举lca来进行转移


设dp[u][j]dp[u][j]dp[u][j]表示:u的子树内,与u的距离为j的节点个数
那么转移就很简单,通过儿子v完成
dp[u][j]+=dp[v][j−1]dp[u][j]+=dp[v][j-1]dp[u][j]+=dp[v][j−1]
设fp[u]fp[u]fp[u]表示:情况1两条都在子树内的链的距离为p的方案数量
那么每个点都会与u的其它子树链上点构成一种方案,所以我们可以规定顺序,x与之前搜索到的点进行匹配,后面的点再与前面的点进行匹配,这样就不会多算
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]
这样保证了,一定会经过u成为lca的这个点
那么情况1中距离为q的方案数量与上面的转移则是一模一样,不再重复

接着设gp[v][j]gp[v][j]gp[v][j]表示:除开v的子树,整棵树与v的距离为j的节点个数,来处理情况2
我们用v的父亲u来更新v,v外面的点距离u的距离应该是j-1,那么会出现这种情况,与集训营1的一道题类似,在v的子树内的点距离v就不会是j-1,而应该是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便进行更新
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]
最后算出来的方案数要∗4*4∗4,因为点对顺序不一样算不同的方案,看样例就可知了

代码实现

#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:排列计数机

题目

定义一个长为k的序列A1,A2,…,AkA_1, A_2, \dots, A_kA1​,A2​,…,Ak​的权值为:对于所有1≤i≤k,max⁡(A1,A2,…,Ai)1 \le i \le k,\max(A_1, A_2, \dots, A_i)1≤i≤k,max(A1​,A2​,…,Ai​)有多少种不同的取值。
给出一个1到n的排列B1,B2,…,BnB_1, B_2, \dots, B_nB1​,B2​,…,Bn​,求B的所有非空子序列的权值的m次方之和。
答案对109+710^9 + 7109+7取模。

点击下载大样例
输入描述:
第一行两个整数n、m。
接下来一行n个整数,第i个整数为BiB_iBi​
输出描述:
输出一个整数,表示答案。

示例1
输入
3 2
1 3 2
输出
16
说明
在所有非空子序列中:
(1), (3), (2), (3, 2)权值为1,
(1, 3), (1, 2), (1, 3, 2)权值为2。
那么所有非空子序列权值的2次方和为4×12+3×22=164 \times 1^2 + 3 \times 2^2 = 164×12+3×22=16
备注:
对于前10%10\%10%的数据,n≤20n \le 20n≤20
对于前20%20\%20%的数据,n≤100n \le 100n≤100
对于前40%40\%40%的数据,n≤1000n \le 1000n≤1000
对于另外20%20\%20%的数据,m = 1。
对于所有数据,1≤n≤1051 \le n \le 10^51≤n≤105,1≤m≤201 \le m \le 201≤m≤20,保证B是1到n的排列。

题解

考虑从左到右一个一个加入数
当加入一个数的时候,只有最大值小于这个数的子序列,权值才会被更新(+1+1+1)
但我们不可能把每个子序列的权值都求出来后才乘方再加起来,三秒也会T得你怀疑人生

但我们根据二项式定理,可以发现对于任意一个数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
通过这个,启发我们可以维护每个子序列权值的m次方和,m-1次方和,m-2次方和…1次方和,0次方和,就可以通过上面的公式得到这些子序列权值集体加1后的乘方的和

由于每次插入值的时候更新只跟最大值有关,而且题目中保证了B是一个排列,因此分情况处理:
1.我们可以把最大值相同的子序列一起处理,维护它们的m次方和,m-1次方和,m-2次方和
加入一个新的数的时候,找到所有最大值比它小的子序列,将它们的m次方和,m-1次方和…加起来,再用二项式定理得到加1后的m次方和,得到一组新的子序列的信息

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

这个可以用线段树维护,线段树的每个位置维护相同最大值的子序列的一些信息

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]维护的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;
}

终于补了这道题,这篇blog也算是有头的了,就bb咯!

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

  1. 牛客网CSP-S提高组赛前集训营1题解(仓鼠的石子游戏 [博弈论] + 乃爱与城市的拥挤程度 [树上DP] + 小w的魔术扑克[dfs + 离线])

    文章目录 T1:仓鼠的石子游戏 题目 题解 代码实现 T2:乃爱与城市拥挤程度 题目 题解 代码实现 T3:小w的魔术扑克 题目 题解 代码实现 T1:仓鼠的石子游戏 题目 仓鼠和兔子被禁止玩电脑,无 ...

  2. 2019牛客多校训练营第一场 H题 HOR 题解

    题目描述: 输入描述: 输出描述: 示例1: 题解: 更多问题可关注牛客竞赛区,一个刷题.比赛.分享的社区. 传送门:https://ac.nowcoder.com/acm/contest/discu ...

  3. 2019牛客多校训练营第一场 E题 ABBA 题解

    问题描述: 输入描述: 输出描述: 示例1: 题解: 更多问题可关注牛客竞赛区,一个刷题.比赛.分享的社区. 传送门:https://ac.nowcoder.com/acm/contest/discu ...

  4. 牛客网 2018年全国多校算法寒假训练营练习比赛(第五场) H.Tree Recovery-完全版线段树(区间更新、区间求和)...

    H.Tree Recovery 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 131072K,其他语言262144K 64bit IO Format: %lld 链接:https:/ ...

  5. 2021牛客OI赛前集训营-提高组(第四场) T2空间跳跃

    2021牛客OI赛前集训营-提高组(第四场) 题目大意 给你三个整数 n , d , l n,d,l n,d,l, n n n为正整数.负整数或0, d , l d,l d,l为正整数,你现在有一个数 ...

  6. 2019牛客暑期多校训练营(第五场)C generator 2 (BSGS)

    2019牛客暑期多校训练营(第五场)C generator 2 思路 x0=x0x_0 = x_0x0​=x0​ x1=a∗x0∗bx_1 = a * x_0 * bx1​=a∗x0​∗b x2=a∗ ...

  7. 2019牛客多校第一场

    2019牛客多校第一场 题号 题目 知识点 A Monotonic Matrix B Symmetric Matrix C Fluorescent 2 D Two Graphs E Removal F ...

  8. 牛客题霸 [子数组最大乘积] C++题解/答案

    牛客题霸 [子数组最大乘积] C++题解/答案 题目描述 给定一个double类型的数组arr,其中的元素可正可负可0,返回子数组累乘的最大乘积. 题解: 同时记录最大值和最小值,为什么?因为给的元素 ...

  9. 牛客题霸 [子数组的最大累加和问题] C++题解/答案

    牛客题霸 [子数组的最大累加和问题] C++题解/答案 题目描述 给定一个数组arr,返回子数组的最大累加和 例如,arr = [1, -2, 3, 5, -2, 6, -1],所有子数组中,[3, ...

最新文章

  1. C#语言与面向对象技术(5)
  2. python内置函数用来返回数值型序列中所有元素之和_Python内置函数______用来返回数值型序列中所有元素之和...
  3. git 无法拉取项目,本地ping不通github的解决办法(详解)
  4. 根据日期时间和随机量生成唯一ID!!
  5. MyBatis 源码分析系列文章导读
  6. yolo-v2 v3实现笔记 mAP:mean average precision 平均精度均值
  7. VS中的C#项目怎样引入另一个项目
  8. [AHOI2014/JSOI2014]支线剧情
  9. Hibernate关联映射-数据对象三种关系
  10. 13.7.深入理解jstack日志
  11. AtCoder - arc120_c Swaps 2(思维+线段树+模拟)
  12. QT乱码总结9.编码测试和总结四
  13. python中创建集合的语句_Python 集合(set) 介绍
  14. DRDS SQL 审计与分析——全面洞察 SQL 之利器
  15. 【测试】接口测试介绍
  16. MySQL中的SQL Mode及其作用
  17. 实用hostname查看主机名的步骤
  18. 模板题——容斥原理、博弈论
  19. Java简单输出,输出Hello World,将代码上传到Github
  20. IOS版添加phonegap--美洽客服插件教程

热门文章

  1. 《SAS编程与数据挖掘商业案例》学习笔记之十九
  2. python中with as用法_python 中关于with...as的用法
  3. 弱引用什么时候被回收_Java基础 强引用、弱引用、软引用、虚引用
  4. micropython 网络驱动_network_网卡驱动
  5. php吞了throw错误,PHP 异常与错误处理
  6. python绘制蟒蛇绕圈圈_Python学习之循环--绕圈圈(蛇形盘)
  7. lol战绩查询接口_LOL:莫名其妙被封号?3把躺赢局被说是代打
  8. 如何在MATLAB中把变量填到Word中,matlab数据写入现有excel表格-如何将matlab中变量写入excel...
  9. 「offer来了」进程线程有啥关系?10个知识点带你巩固操作系统基础知识
  10. [Nginx]nginx 配置实例-动静分离