Codeforces Round 926 (Div. 2)(A,B,C,D,E,F)

这场还是很有含金量的,B题开始就有难度了,B是个推结论的题,C要推结论然后递推,D题是有点难的树上DP(主要是状态转移方程不好写),E题是个二进制预处理然后状压DP,F题是个数论(把树映射成中序遍历dfs序,然后跑隔板法),很推荐打一打这场。

比赛链接


A. Sasha and the Beautiful Array

题意:

Sasha决定送给女友一个数组 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,,an 。他发现女友会评估数组的美丽值: 对所有整数 i i i 2 2 2 n n n ( a i − a i − 1 ) (a_i - a_{i - 1}) (aiai1) 的和。

请帮助Sasha,告诉他,如果他能以任何方式重新排列数组 a a a 中的元素,他能得到的最大数组美丽值是多少。

思路:

其实跟差分数组的思想差不多,推一下式子就能发现,这个美丽值其实就是 a n − a 1 a_n - a_1 ana1,所以把最大数放在位置 n n n 上,最小数放在位置 1 1 1 上。最大值和最小值的差值就是答案。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int inf=2e9;int T,n;int main(){cin>>T;while(T--){cin>>n;int minn=inf,maxx=-inf;for(int i=1,t;i<=n;i++){cin>>t;minn=min(minn,t);maxx=max(maxx,t);}cout<<maxx-minn<<endl;}return 0;
} 

B. Sasha and the Drawing

题意:

自从在幼儿园时,Sasha就喜欢上了一个女孩。因此,他想给她画一幅画,吸引她的注意。

作为一幅画,他决定画一个大小为 n × n n \times n n×n 的正方形网格,在网格中给一些单元格涂上颜色。但是给单元格上色很困难,所以他想给尽可能少的单元格上色。但同时,他又希望至少 k k k 个对角线上至少有一个着色的单元格。请注意,大小为 n × n n \times n n×n 的正方形网格共有 4 n − 2 4n - 2 4n2 条对角线。

帮助小萨沙让女孩爱上他,并告诉他需要涂色的最少格子数。

思路:

这个题主要是得找张纸划拉划拉,推结论。

我们假设有个 3 ∗ 3 3*3 33 的矩形,然后把格子缩成点,先看从左上到右下的所有对角线,如图。
在这里插入图片描述

在这里插入图片描述

再顺时针旋转45度:
在这里插入图片描述

我们想要尽可能少的涂色,那就要求每次涂色占到尽可能多的没有涂色的对角线。因为一个格子最多就在两条对角线上(一条从左上到右下的,一条从右上到左下的)。所以在上图中,就是选多个点使得每个点尽量占到一条竖直的线和水平的线(水平的没画)。

从最左边的 7 7 7 号点所在竖线开始看,显然可以占到竖直的和水平的线各一个,再看 4 , 8 4,8 4,8 所在竖线,它们和 2 , 6 2,6 2,6 点所在的横线会产生冲突,不过不打紧,如果我们选 4 4 4 号点,那么另一条选择 6 6 6 号点,如果我们选 8 8 8 号点,那么另一条选择 2 2 2 号点,就行了。类似的,对中间所有的对称的线都可以这样操作。

但是到了最后一条竖线,也就是 3 3 3 号点所在竖线的时候,它无论怎么选都只能产生一次贡献。同理横线,如果在 1 , 5 , 9 1,5,9 1,5,9 这条竖线的选取中,选取了 5 5 5 ,那么 9 9 9 所在横线就没人选了,同理选取了 9 9 9 ,那么 5 5 5 所在横线就没人选了,假设矩形边长为 n n n,那么一个方向的对角线条数有 2 ∗ n − 1 2*n-1 2n1 条,选取了 2 ∗ n − 1 − 1 2*n-1-1 2n11 个点,每个点占两个对角线,这时就拿到了 2 ∗ ( 2 ∗ n − 1 − 1 ) = 4 ∗ n − 4 2*(2*n-1-1)=4*n-4 2(2n11)=4n4 条对角线,还差最后两条没有选,差的就是这两条,这两条要选点的话,选一个点只能产生一次贡献。

所以分类讨论,如果 k ≤ 4 ∗ n − 4 k\le 4*n-4 k4n4 ,答案就是 ⌈ k 2 ⌉ \left\lceil \dfrac k 2 \right\rceil 2k,如果 4 ∗ n − 4 < k ≤ 4 ∗ n − 2 4*n-4\lt k\le 4*n-2 4n4<k4n2,答案就是 2 ∗ n − 2 + ( k − 4 ∗ n + 4 ) 2*n-2+(k-4*n+4) 2n2+(k4n+4)

code:

#include <iostream>
#include <cstdio>
using namespace std;int T,n,k;int main(){cin>>T;while(T--){cin>>n>>k;if(k<=4*n-4)cout<<(k+1)/2<<endl;else cout<<2*n-2+(k-4*n+4)<<endl;}return 0;
}

其实可以发现只有 k = 4 ∗ n − 2 k=4*n-2 k=4n2 时答案会比 ⌈ k 2 ⌉ \left\lceil \dfrac k 2 \right\rceil 2k 多1,其他时候是一样的,所以还可以写的简单些。

#include <iostream>
#include <cstdio>
using namespace std;int T,n,k;int main(){cin>>T;while(T--){cin>>n>>k;cout<<(k+1)/2+(k==4*n-2)<<endl;}return 0;
}

C. Sasha and the Casino

题意:

Sasha决定把最好的手提包送给他的女朋友,但不幸的是,这个手提包非常昂贵。因此,Sasha想赚取它。在网上查看了赚钱技巧后,他决定去赌场。

Sasha知道赌场的运作规则如下。如果萨沙下注 y y y 个硬币(其中 y y y 为正整数),那么如果赢了,他将获得 y ⋅ k y \cdot k yk 个硬币(即他的硬币数量将增加 y ⋅ ( k − 1 ) y \cdot (k - 1) y(k1) )。如果输了,他将输掉全部赌注( 即他的硬币数量将减少 y y y )。

请注意,投注金额必须始终是一个正( > 0 \gt 0 >0 )整数,并且不能超过Sasha当前的硬币数量。

Sasha还知道赌场有一个促销活动:他不能连续输超过 x x x 次。

最初,Sasha有 a a a 枚硬币。他想知道自己是否可以下注保证赢取任意数量的硬币。换句话说,对于任意整数 n n n ,Sasha是否可以通过合理下注,且不违背上述规则,在某个时刻他可以拥有至少 n n n 个硬币。

思路:

有点东西。手玩样例,比如第三个样例,下注策略是 1 2 4 a l l i n 1\ 2\ 4\ allin 1 2 4 allin

玩一会就应该发现端倪了。赢了翻 k k k 倍,输 x x x 次就保底,手里有 a a a 块钱。每次下注赢了之后奖池刷新。

因为我们要保证一定可以拥有任意多的钱(其实就是赚到钱),所以即使在最坏情况下我们也得赚到钱。比如第三个样例 k = 2 , x = 3 , a = 15 k=2,x=3,a=15 k=2,x=3,a=15,我们一开始下注 1 1 1 块钱,输了就失去一块钱,但是我们离保底近了一步,赢了就皆大欢喜,白赚一块,然后刷新保底重新下注。

我们如果上次输了,这次如果下注 1 1 1 块,赢了就只能拿到两块钱,并没有赚到钱,如果运气不好就会一直输一次赢一次,然后钱数永远不变。这就赚不到钱了。所以我们第二次下注两块,赢了就能赚到两块钱,输了就离保底更近一步。

如果第二次下注输了,同理,如果这次下注两块,我们三次总投入五块,赢了就只能得到四块,如果后面循环这种最坏情况,我们就会一直亏钱。如果这次下注三块,我们三次总投入六块,赢了就只能得到六块,如果后面循环这种最坏情况,我们就会一直赚不到钱。所以我们需要下注四块,这样投入七块,赢了虽然丢了保底但也能赚到,输了能能保底了。

如果第三次也输了,那就直接all in,反正一定赢,这时我们手上还剩 8 8 8 块,能得到 16 16 16 块,这样也赚到了。

手玩一下过程其实就很明了了,下注策略就是:我们要保证中间任何一次下注赢了都可以赚到钱,以及最后保底all in的时候能赚到钱就行了。这就需要你的初始钱包够硬气。

所以我们可以模拟这种下注策略,然后检查每个时刻时候钱包还够不够。以及最后all in的时候能不能赚到就行了。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=105;
typedef long long ll; ll T,k,x,a;//翻k倍 x次保底 初始 ll st[maxn],lose[maxn];int main(){cin>>T;while(T--){cin>>k>>x>>a;bool f=false;st[1]=1;lose[1]=1;for(int i=2;i<=x;i++){st[i]=(lose[i-1])/(k-1)+1;lose[i]=lose[i-1]+st[i];if(lose[i]>a){f=true;break;}}if(f || (a-lose[x])*k<=a)puts("NO");else puts("YES");}return 0;
}

D. Sasha and a Walk in the City

题意:

Sasha想和他的女朋友在城市里散步。城市由 n n n 个路口组成,编号从 1 1 1 n n n 。其中一些路口由道路连接,从任何一个路口出发,都有一条简单的路径 † ^{\dagger} 通往其他路口。换句话说,十字路口和它们之间的道路组成了一棵树。

有些交叉路口被认为是危险的。由于在城市中独自行走很不安全,所以Sasha不想在行走过程中经过三个或三个以上的危险路口。

如果满足以下条件,Sasha就会将 一组 路口称为好路口:

  • 如果在城市中这组交叉路口是危险的,那么城市中的任何一条简单路径都包含不超过两个危险的交叉路口。

然而,Sasha并不知道哪些交叉路口是危险的,因此他感兴趣的是城市中不同的好交叉路口集的数量。由于这个数字可能非常大,因此输出它的模数 998 244 353 998\,244\,353 998244353

† ^{\dagger} 简单路径是指最多经过每个交叉路口一次的路径。

思路:

这题面绕的很,说白了就是:就是从树中选一些点。 把他们视为危险点。问有多少种 选点组合能保证 “树上不存在某个路径上有三个及以上危险点” 。

首先先想明白怎么样选点能保证 “树上不存在某个路径上有三个及以上危险点”。对一个树上路径,我们可以给它两边都一直延伸到某个叶子节点,如果这个长路径没有三个及以上危险点,那么这个路径就一定没有危险点,所以我们可以去保证任意两条根节点到叶子节点的链上没有三个及以上危险点(根节点为危险点的,两个链只算一次),这样就可以保证任意一条路径都满足要求了。

任取两条根到叶子节点的链需要经过两个儿子节点,换句话说,根到叶子节点的链其实就是儿子到叶子的链,最后再加上根。对这个儿子节点,我们肯定走这个儿子节点下包含最多危险节点的链,如果这个最危险的链都能满足条件存在,那么其他链也能满足条件。

d p [ u ] [ i ] dp[u][i] dp[u][i] 表示以 u u u 为根节点的子树包含最多危险点的链(假设就叫最危险链) 包含 i i i 个危险点的方法数。

v 1 , v 2 , … , v k v_1,v_2,\dots,v_k v1,v2,,vk 是节点 u u u 的儿子节点,考虑如何转移。

  1. d p [ u ] [ 0 ] dp[u][0] dp[u][0],肯定儿子节点的子树的所有点都不能包含危险点,这个点本身也不能是危险点,答案数就是 d p [ u ] [ 0 ] = 1 dp[u][0]=1 dp[u][0]=1
  2. d p [ u ] [ 1 ] dp[u][1] dp[u][1],两种情况:
    1. 这个点 u u u 不是危险点:至少一个子树的最危险链包含1个危险点,其他的子树的最危险链可以有0个或者1个危险点,每个子树的情况相加。枚举来算比较花时间,可以容斥定理来算:每个子树的最危险链可以有0个和1个危险点的情况的乘积减去所有子树的最危险链都为0个危险点的情况数,也就是 ∏ i = 1 k ( d p [ v i ] [ 0 ] + d p [ v i ] [ 1 ] ) − ∏ i = 1 k d p [ v i ] [ 0 ] = ∏ i = 1 k ( d p [ v i ] [ 0 ] + d p [ v i ] [ 1 ] ) − 1 \prod_{i=1}^k(dp[v_i][0]+dp[v_i][1])-\prod_{i=1}^kdp[v_i][0]=\prod_{i=1}^k(dp[v_i][0]+dp[v_i][1])-1 i=1k(dp[vi][0]+dp[vi][1])i=1kdp[vi][0]=i=1k(dp[vi][0]+dp[vi][1])1
    2. 这个点 u u u 是危险点:所有子树都不能有危险点,也就是子树最危险链包含0个危险点,这种情况只有 ∏ i = 1 k d p [ v i ] [ 0 ] = 1 \prod_{i=1}^kdp[v_i][0]=1 i=1kdp[vi][0]=1
  3. d p [ u ] [ 2 ] dp[u][2] dp[u][2],两种情况:
    1. 这个点 u u u 不是危险点:一个子树的最危险链包含两个危险点,其他子树保持0个危险点,情况数为 d p [ v 1 ] [ 2 ] + d p [ v 2 ] [ 2 ] + ⋯ + d p [ v k ] [ 2 ] = ∑ i = 1 k d p [ v i ] [ 2 ] dp[v_1][2]+dp[v_2][2]+\dots+dp[v_k][2]=\sum_{i=1}^kdp[v_i][2] dp[v1][2]+dp[v2][2]++dp[vk][2]=i=1kdp[vi][2]
    2. 这个点 u u u 是危险点:一个子树的最危险链包含一个危险点,其他子树保持0个危险点,情况数为 d p [ v 1 ] [ 1 ] + d p [ v 2 ] [ 1 ] + ⋯ + d p [ v k ] [ 1 ] = ∑ i = 1 k d p [ v i ] [ 1 ] dp[v_1][1]+dp[v_2][1]+\dots+dp[v_k][1]=\sum_{i=1}^kdp[v_i][1] dp[v1][1]+dp[v2][1]++dp[vk][1]=i=1kdp[vi][1]

然后dfs跑树上DP即可。假设起点是 1 1 1,答案就是 d p [ 1 ] [ 0 ] + d p [ 1 ] [ 1 ] + d p [ 1 ] [ 2 ] dp[1][0]+dp[1][1]+dp[1][2] dp[1][0]+dp[1][1]+dp[1][2]

code:

为了方便想,我在dfs时使用 n 0 , n 1 , n 2 n0,n1,n2 n0,n1,n2 分别表示不选取根节点 u u u 时,最危险链包含 0 , 1 , 2 0,1,2 0,1,2 个危险点的方法数, x 2 x2 x2 表示选取根节点 u u u 时,最危险链包含 2 2 2 个危险点的方法数。

实际上在累加或累乘时,这几个变量分别处理的是这几个式子:
n 0 = 1 n0=1 n0=1
n 1 = ∏ i = 1 k ( d p [ v i ] [ 0 ] + d p [ v i ] [ 1 ] ) n1=\prod_{i=1}^k(dp[v_i][0]+dp[v_i][1]) n1=i=1k(dp[vi][0]+dp[vi][1]) (计算结束后根据容斥定理减去 n 0 = 1 n0=1 n0=1
n 2 = ∑ i = 1 k d p [ v i ] [ 2 ] n2=\sum_{i=1}^kdp[v_i][2] n2=i=1kdp[vi][2]
x 2 = ∑ i = 1 k d p [ v i ] [ 1 ] x2=\sum_{i=1}^kdp[v_i][1] x2=i=1kdp[vi][1]

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=3e5+5;
typedef long long ll;
const ll mod=998244353;int T,n;ll dp[maxn][5];
//i点为根的子树所有到叶子的链上最多包含j个危险路口的方法数 
int head[maxn],cnt;
struct edge{int v,nxt;
}e[maxn<<1];
void add(int u,int v){e[++cnt].v=v;e[cnt].nxt=head[u];head[u]=cnt;
}
void init(){cnt=0;for(int i=1;i<=n;i++)head[i]=dp[i][0]=dp[i][1]=dp[i][2]=0;
}void dfs(int u,int rt){dp[u][0]=1;ll n0,n1,n2,x2;//儿子子树上最多 n0=n1=1;n2=x2=0;for(int i=head[u],v;i;i=e[i].nxt){v=e[i].v;if(v==rt)continue;dfs(v,u);n1=n1*(dp[v][1]+dp[v][0])%mod;n2=(n2+dp[v][2])%mod;x2=(x2+dp[v][1])%mod;}n1-=n0;dp[u][1]=(n1+n0)%mod;dp[u][2]=(n2+x2)%mod;
}int main(){cin>>T;while(T--){cin>>n;init();for(int i=1,u,v;i<n;i++){cin>>u>>v;add(u,v);add(v,u);}dfs(1,-1);cout<<(dp[1][0]+dp[1][1]+dp[1][2])%mod<<endl;}return 0;
}

E. Sasha and the Happy Tree Cutting

题意:

Sasha获得了一棵有 n n n 个顶点的树 † ^{\dagger} ,作为他又一次赢得比赛的奖品。然而,庆祝完胜利回家后,他发现树的某些部分不见了。Sasha记得他给这棵树的一些边涂了颜色。他可以肯定,在顶点 k k k 对顶点 ( a 1 , b 1 ) , … , ( a k , b k ) (a_1, b_1), \ldots, (a_k, b_k) (a1,b1),,(ak,bk) 中,他至少为顶点 a i a_i ai b i b_i bi 之间的简单路径 ‡ ^{\ddagger} 上的一条边涂了颜色。

Sasha不记得他到底给多少条边涂了颜色,因此他请你告诉他,为了满足上述条件,他至少可以给多少条边涂颜色。

† ^{\dagger} 一棵树是没有循环的不定向连接图。

‡ ^{\ddagger} 简单路径是指每个顶点最多经过一次的路径。

思路:

F感觉比E简单,而且过的人也比E多。E题感觉是个状压DP。

k很小,只有20,而且题目还专门说了是 2 k ≤ 2 20 2^k\le 2^{20} 2k220。估计是暗示咱们去状压这个东西。具体来说,对每个边,用一个二进制数的第 i i i 位记录一下它是不是第 i i i 个路径上含有的边。那么我们在涂这个边的时候,就知道我们涂好了哪几个路径。把每个边的这个二进制数当作一个物品,之后跑DP就行了。

具体做法首先是标准的链式前向星存边,然后找一个路径上的所有边并进行标记。这里因为 k k k 很小,直接dfs只有 20 n 20n 20n 次运算,直接暴力dfs找边即可,然后找到之后返回时进行标记,这里可以用链式前向星存边的特性:第 i i i 个遍存放在边的数组的第 2 ∗ i − 1 2*i-1 2i1 2 ∗ i 2*i 2i 个位置上。

用一个数组来存储每个边的路径信息。因为重复物品没有用,而且边最多可以有 1 0 5 − 1 10^5-1 1051,状态最多有 2 20 ≈ 1 0 6 2^{20}\approx10^6 220106 种,直接跑背包DP是 O ( 1 0 5 ∗ 1 0 6 ) O(10^5*10^6) O(105106) 的,时间会爆。所以对物品进行去重,之后对剩下的物品跑01背包就行了。就做完了。

另外说点:为什么去重之后时间复杂度就达标了?去重之后物品能剩下几个?我也感觉成谜,这里给出我的一点想法,如果有大佬会算欢迎指点。

假设树上已经有了 x x x 条路径,这里我们再向上添加一条路径,使得不同的边(不同指的是属于路径不同,就是上面说的那个二进制数不同)变化量最大。

首先如果要变化量最大,显然 x x x 条路径应该都经过一条边,然后我们新增的这个路径也经过这个边。在这个边的左边,新增的路径沿边向下延伸,因为在经过一个点之后,先前的 x x x 个路径一定会分出去1股或多股,要变化量最大,一次只分出去1股,这样左边最多会有 x x x 次就会把原来的路径分出去,只剩下新增路径,左边就有了 x x x 条不同的边。右边可以使用另一种分股方式,由于最后只剩下新增路径的和左边重复了,所以有 x − 1 x-1 x1 种不同的边,总的就是最多 2 ∗ x 2*x 2x 条不同的边。

因此上面 k = 20 k=20 k=20,去完重后最多剩下 40 40 40 个不同的物品,背包就是 O ( 40 ∗ 2 20 ≈ 4 ∗ 1 0 7 ) O(40*2^{20}\approx4*10^7) O(402204107) 的复杂度。代码跑了500ms,应该大体上是吻合的。

code:

#include <iostream>
#include <cstdio>
#include <stack>
#include <set>
using namespace std;
const int maxn=1e5+5;
const int inf=1e9;int T,n,k;int head[maxn],cnt;
struct edge{int v,nxt;
}e[maxn<<1];
void add(int u,int v){e[++cnt].v=v;e[cnt].nxt=head[u];head[u]=cnt;
}int a[maxn],dp[maxn<<4];//每条边的位掩码 到st状态时的最小边 bool dfs(int u,int lst,int ed,int x){if(u==ed)return true;for(int i=head[u],v;i;i=e[i].nxt){v=e[i].v;if(v==lst)continue;if(dfs(v,u,ed,x)){a[(i+1)>>1]|=x;return true;}}return false;
}
void pb(int x){stack<int> s;for(int i=1;i<=k;i++){s.push(x%2);x/=2;}while(!s.empty()){cout<<s.top();s.pop();}puts("");
}int main(){cin>>T;while(T--){cin>>n;cnt=0;for(int i=1;i<=n;i++)head[i]=a[i]=0;for(int i=1,u,v;i<n;i++){cin>>u>>v;add(u,v);add(v,u);}cin>>k;for(int i=1;i<=((1<<k)-1);i++)dp[i]=inf;for(int i=0,u,v;i<k;i++){cin>>u>>v;dfs(u,-1,v,(1<<i));}//		puts("");
//		for(int i=1;i<n;i++)
//			pb(a[i]);
//		puts("");//处理好了n-1个物品set<int> S;for(int i=1;i<n;i++)S.insert(a[i]);for(int j=0;j<=((1<<k)-1);j++){for(auto x:S)dp[j|x]=min(dp[j|x],dp[j]+1);}cout<<dp[(1<<k)-1]<<endl;}return 0;
} 

F. Sasha and the Wedding Binary Search Tree

题意:

Sasha克服了重重困难和艰辛,终于决定与女友结婚。为此,他需要送她一枚订婚戒指。然而,他的女朋友并不喜欢这种浪漫的举动,但她喜欢二叉搜索树 † ^{\dagger} 。于是,Sasha决定送给她这样一棵树。

在程序员婚礼网站上花了大量时间后,他找到了一棵完美的二叉搜索树,树根位于顶点 1 1 1 。在这棵树上,顶点 v v v 的值等于 v a l v val_v valv

但一段时间后,他忘记了一些顶点的值。为了记住找到的树,Sasha想知道,如果已知所有顶点的值都是 [ 1 , C ] [1, C] [1,C] 段中的整数,他可以在网站上找到多少棵二叉搜索树。因为这个数字可能非常大,所以输出它的模数 998 244 353 998\,244\,353 998244353

† ^{\dagger} 二叉搜索树是一棵有根的二叉树,其中任意顶点 x x x 都具有以下性质:顶点 x x x 左子树中所有顶点的值(如果存在)都小于或等于顶点 x x x 的值,顶点 x x x 右子树中所有顶点的值(如果存在)都大于或等于顶点 x x x 的值。

题意:

二叉搜索树(BST,Binary Search Tree)重要性质之一:二叉搜索树的中序遍历每个节点的值是有序的。

那么我们可以对这个二叉搜索树进行中序遍历,把它的DFS序记录为一个数组,这个数组的元素和二叉搜索树的节点就能一一对应起来了。这个数组就应该是有序的。我们现在的任务就变成了保持有序的前提下 对-1的位置进行填数,问有多少种可能。

对一段连续的 − 1 -1 1 区间,它左边的数组元素值就是区间填数的下界,它右边的数组元素值就是区间填数的上界,特别的,当左边没有元素了,也就是左边是 a 0 a_0 a0,下界是 1 1 1,右边没有元素了,也就是右边是 a n + 1 a_{n+1} an+1,下界是 C C C。现在假设这段连续的 − 1 -1 1 区间的左端点为 l l l,右端点为 r r r,那么就有 p = r − l + 1 p=r-l+1 p=rl+1 个位置,值域是 [ a [ l − 1 ] , a [ r + 1 ] ] [a[l-1],a[r+1]] [a[l1],a[r+1]],可以填 q = a [ r + 1 ] − a [ l − 1 ] + 1 q=a[r+1]-a[l-1]+1 q=a[r+1]a[l1]+1 种数。

现在可以把问题转化为:把 p p p 个位置分给 q q q 种数的方法数,分好之后我们就可以按顺序填入,这样就是一个可行解。位置名额互相之间是相同的,不同数之间是不同的,这是个同球不同盒可以空盒的球盒问题。使用隔板法计算,答案数就是 C p + q − 1 p = C p + q − 1 q − 1 C_{p+q-1}^{p}=C_{p+q-1}^{q-1} Cp+q1p=Cp+q1q1

这里的G题讲思路之前详细讲了 隔板法,这里不多赘述。

在书写的时候会发现一个问题,那就是 n ≤ 5 ∗ 1 0 5 , C ≤ 1 0 9 n\le 5*10^5,C\le 10^9 n5105,C109,这导致 p ≤ 5 ∗ 1 0 5 , q ≤ 1 0 9 p\le 5*10^5,q\le 10^9 p5105,q109,预处理阶乘然后使用组合数定义式来计算的话,空间和时间都不允许。这时候需要对式子进行一步转化: C p + q − 1 p = ( p + q − 1 ) ! p ! ∗ ( q − 1 ) ! = q ∗ ( q + 1 ) ∗ ⋯ ∗ ( p + q − 1 ) p ! C_{p+q-1}^{p}=\dfrac{(p+q-1)!}{p!*(q-1)!}=\dfrac{q*(q+1)*\dots*(p+q-1)}{p!} Cp+q1p=p!(q1)!(p+q1)!=p!q(q+1)(p+q1)
这里分母 p p p 的阶乘是 5 ∗ 1 0 5 5*10^5 5105 以内的,可以直接预处理。分子是 p p p 个式子相乘,可以在 5 ∗ 1 0 5 5*10^5 5105 次内计算得到。这样一次计算组合数最多就是 p = 5 ∗ 1 0 5 p=5*10^5 p=5105 量级的。而在一个 n n n 中, ∑ p ≤ n \sum p\le n pn ,而且 ∑ n ≤ 5 ∗ 1 0 5 \sum n\le 5*10^5 n5105 。所以组合数总的运算次数就不会超过 5 ∗ 1 0 5 5*10^5 5105 了。

这种 C n + m n C_{n+m}^n Cn+mn 中,n很大很大 的组合数计算的时候经常采用这种方式来计算。

code:

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=5e5+5;
//const int maxm=1e7+5;
const ll mod=998244353;int T,n,c;struct node{int lc,rc,val;
}nd[maxn];int a[maxn],counter;
void dfs(int u){//中序遍历 if(u==-1)return;dfs(nd[u].lc);a[++counter]=nd[u].val;dfs(nd[u].rc);
}ll fac[maxn];
ll qpow(ll a,ll b){ll ans=1,base=a;while(b){if(b&1){ans*=base;ans%=mod;}base*=base;base%=mod;b>>=1;}return ans;
}
ll inv(ll x){return qpow(x,mod-2);}
ll C(ll a,ll b){//C_a^b if(b>a || b<0)return 0;ll fz=1;for(int i=a-b+1;i<=a;i++)fz=fz*i%mod;return fz*inv(fac[b])%mod;
}int main(){fac[0]=fac[1]=1;for(int i=2;i<maxn;i++)fac[i]=fac[i-1]*i%mod;scanf("%d",&T);while(T--){scanf("%d%d",&n,&c);for(int i=1;i<=n;i++)scanf("%d%d%d",&nd[i].lc,&nd[i].rc,&nd[i].val);counter=0;dfs(1);ll ans=1;for(int i=1,p=0,l=1,r,q;i<=n;i++){if(a[i]==-1)p++;if(a[i]!=-1 || i==n){r=(a[i]!=-1)?a[i]:c;//值域[l,r] q=r-l+1;l=r;ans=ans*C(p+q-1,p)%mod;p=0;}}cout<<ans<<endl;;}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/685071.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

写一个程序,输入数量不确定的[0,9]范围内的整数,统计每一种数字出现的次数输入-1表示结束

#include <stdio.h> int main(void) {int x;int count[10];int i;for(i0;i<10;i){//初始化数组 count[i]0;}scanf("%d",&x);while(x!-1){if( x>0 && x<9){count[x];//数组参与运算 }scanf("%d",&x);}for(i0;i<10;i){pr…

车载诊断协议DoIP系列 —— 车辆以太网节点需求汇总

车载诊断协议DoIP系列 —— 车辆以太网节点需求汇总 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎么看自己。江湖一碗茶,…

安装 Windows Server 2003

1.镜像安装 镜像安装:Windows Server 2003 2.安装过程(直接以图的形式呈现) 按Enter(继续),继续后F8继续 直接Enter安装 下一步 秘钥:GM34K-RCRKY-CRY4R-TMCMW-DMDHM 等待安装成功即可

JavaWeb

一、技术栈 【1】 前端部分 HTML CSS JavaScript ES6 Nodejs npm vite vue3 router pinia axios element-plus … 【2】 后端部分 HTTP xml Tomcat Servlet Request Response Cookie Sesssion Filter Listener MySQL JDBC Druid Jackson lombok jwt … 二、JAVAWEB交互模…

【知识整理】产研中心岗位评定标准之大数据岗位

为贯彻执行集团数字化转型的需要,该知识库将公示集团组织内各产研团队不同角色成员的职务“职级”岗位的评定标准; 一、定级定档目的 通过对公司现有岗位及相应岗位员工的工作能力、工作水平进行客观公正评定,确定各岗位的等级及同等级岗位员工对应的档级,从而为员工以后的晋升…

三种输入输出函数

目录 printf函数 scanf函数 getchar函数 putchar函数 gets函数 puts函数 printf函数 当你需要将数据或文本输出到屏幕或其他输出设备时&#xff0c;C语言提供了一个非常有用的函数&#xff0c;即 printf() 函数。它是标准库中定义的函数&#xff0c;用于格式化输出。 pr…

React 的调度系统 Scheduler

原文地址1 原文地址2 其中startTime是任务开始的时间&#xff0c;默认是-1&#xff0c;任务开始时将任务开始时间赋值给了startTime&#xff0c; 这里意思是判断这个任务执行时间是否超过5ms(写死的)。若超过&#xff0c;则要交出。

Linux platform tree下的单总线驱动程序设计(DHT11)

目录 概述 1 认识DHT11 1.1 DHT11特性 1.2 DHT11数据格式 1.3 DHT11与MCU通信 1.4 DHT11信号解析 1.4.1 起始信号 1.4.2 解析信号0 1.4.3 解析信号1 2 驱动开发 2.1 硬件接口 2.2 更新设备树 2.2.1 添加驱动节点 2.2.2 编译.dts 2.2.3 更新板卡中的.dtb 2.3 驱…

【Git】上传本地文件到Git(以Windows环境为例)

Git 的下载参考&#xff1a;Git 安装及配置 一、Git 上传的整体流程 1、工作区 > 本地仓库 将本地文件上传到Git&#xff0c;需要先上传到本地仓库&#xff0c;然后再上传到远程仓库。要上传文件到本地仓库&#xff0c;不是直接拷贝进去的&#xff0c;而是需要通过命令一步…

【Linux网络编程六】服务器守护进程化Daemon

【Linux网络编程六】服务器守护进程化Daemon 一.背景知识&#xff1a;前台与后台二.相关操作三.Linux的进程间关系四.自成会话五.守护进程四步骤六.服务器守护进程化 一.背景知识&#xff1a;前台与后台 核心知识就是一个用户在启动Linux时&#xff0c;都会给一个session会话&a…

Java毕业设计-基于springboot的学院物资管理系统-第73期

获取源码资料&#xff0c;请移步从戎源码网&#xff1a;从戎源码网_专业的计算机毕业设计网站 项目介绍 基于springboot的学院物资管理系统&#xff1a;前端thymeleaf、jquery、layui&#xff0c;后端 maven、springmvc、spring、mybatis&#xff0c;有配套报告文档&#xff…

【动态规划】【记忆化搜索】【状态压缩】1681. 最小不兼容性

作者推荐 【数位dp】【动态规划】【状态压缩】【推荐】1012. 至少有 1 位重复的数字 本文涉及知识点 动态规划汇总 状态压缩 记忆化搜索 1681. 最小不兼容性 给你一个整数数组 nums​​​ 和一个整数 k 。你需要将这个数组划分到 k 个相同大小的子集中&#xff0c;使得同一…

指针的经典笔试题

经典的指针试题&#xff0c;让你彻底理解指针 前言 之前对于指针做了一个详解&#xff0c;现在来看一些关于指针的经典面试题。 再次说一下数组名 数组名通常表示的都是首元素的地址&#xff0c;但是有两个意外&#xff0c;1.sizeof&#xff08;数组名&#xff09;这里数组名…

如何在JavaScript中使用大于和小于运算符

在你的 JavaScript 程序中&#xff0c;你经常需要比较两个值&#xff0c;以确定一个是否大于另一个或小于另一个。这就是大于和小于运算符派上用场的地方。 在本文中&#xff0c;我们将通过代码示例更详细地介绍如何使用这些运算符。 &#xff08;本文内容参考&#xff1a;ja…

rabbitmq自用记录

参考博客RabbitMq安装与使用&#xff08;mac&#xff09;高效总结&#xff08;亲测&#xff09;_mac 安装rabbitmq 服务端口-CSDN博客 启动服务 这里提前把redis服务也启动了 这里看到前端更改数据,后端进行日志打印 登录后访问rabbitmq网址

java 线程安全介绍

所谓线程安全无非是要控制多个线程对某个资源的有序访问或修改。总结java的内存模型&#xff0c;要解决两个主要的问题&#xff1a;可见性和有序性。 那么&#xff0c;何谓可见性&#xff1f; 多个线程之间是不能互相传递数据通信的&#xff0c;它们之间的沟通只能通过共享变量…

MinIO 和 Apache Tika:文本提取模式

Tl;dr: 在这篇文章中&#xff0c;我们将使用 MinIO Bucket Notifications 和 Apache Tika 进行文档文本提取&#xff0c;这是大型语言模型训练和检索增强生成 LLM和RAG 等关键下游任务的核心。 前提 假设我想构建一个文本数据集&#xff0c;然后我可以用它来微调 LLM.为了做…

爬虫之牛刀小试(十):爬取某宝手机商品的销量,价格和店铺

首先淘宝需要登录&#xff0c;这一点如果用selenium如何解决&#xff0c;只能手动登录&#xff1f;如果不用selenium&#xff0c;用cookies登录也可。但是验证码又是一个问题&#xff0c;现在的验证码五花八门&#xff0c;难以处理。 我们回到正题&#xff0c;假设你已经登录上…

计算机设计大赛 深度学习YOLO图像视频足球和人体检测 - python opencv

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络4 Yolov5算法5 数据集6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习YOLO图像视频足球和人体检测 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非…

一览大模型长文本能力

前言 如今的大模型被应用在各个场景&#xff0c;其中有些场景则需要模型能够支持处理较长文本的能力(比如8k甚至更长)&#xff0c;其中已经有很多开源或者闭源模型具备该能力比如GPT4、Baichuan2-192K等等。 那关于LLM的长文本能力&#xff0c;目前业界通常都是怎么做的&…