CodeTON Round 8 (Div. 1 + Div. 2, Rated, Prizes!)(A,B,C1,C2,D,E)

比赛链接

DE借鉴的这篇题解。A是guess结论,B是递推,考察了mex的性质,C题是不太好想的贪心,D题是个不太好写的dp,E是博弈加组合数学。


A. Farmer John’s Challenge

题意:

当数组 a a a 满足 a 1 ≤ a 2 ≤ … ≤ a n − 1 ≤ a n a_1 \leq a_2 \leq \ldots \leq a_{n - 1} \leq a_{n} a1a2an1an 时,称之为有序。

给你两个农夫约翰最喜欢的整数: n n n k k k 。他向你提出挑战,要求你找出满足以下要求的数组 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_{n} a1,a2,,an

  • 对每个 1 ≤ i ≤ n 1 \leq i \leq n 1in 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1ai109
  • a a a n n n 个循环移位中,正好有 k k k 个被排序。

如果没有这样的数组 a a a ,则输出 − 1 -1 1

† ^\dagger 数组 a a a 的第 x x x 1 ≤ x ≤ n 1 \leq x \leq n 1xn循环移位 a x , a x + 1 … a n , a 1 , a 2 … a x − 1 a_x, a_{x+1} \ldots a_n, a_1, a_2 \ldots a_{x - 1} ax,ax+1an,a1,a2ax1 。如果 c x , i c_{x, i} cx,i 表示 a a a 的第 x x x 循环移位的第 i i i 元素,那么恰好 k k k 这样的 x x x 应该满足 c x , 1 ≤ c x , 2 ≤ … ≤ c x , n − 1 ≤ c x , n c_{x,1} \leq c_{x,2} \leq \ldots \leq c_{x, n - 1} \leq c_{x, n} cx,1cx,2cx,n1cx,n

例如, a = [ 1 , 2 , 3 , 3 ] a = [1, 2, 3, 3] a=[1,2,3,3] 的循环移动如下:

  • x = 1 x = 1 x=1 : [ 1 , 2 , 3 , 3 ] [1, 2, 3, 3] [1,2,3,3] (排序);
  • x = 2 x = 2 x=2 : [ 2 , 3 , 3 , 1 ] [2, 3, 3, 1] [2,3,3,1] (未排序);
  • x = 3 x = 3 x=3 : [ 3 , 3 , 1 , 2 ] [3, 3, 1, 2] [3,3,1,2] (未排序);
  • x = 4 x = 4 x=4 : [ 3 , 1 , 2 , 3 ] [3, 1, 2, 3] [3,1,2,3] (未排序)。

思路:

一开始是有序的,但是我们把最前面那个最小的数丢到后面去后,按常理来想应该不管怎样都会变得无序,因为前半段是升序的,而后半段是降序的。只有当最前面的最小值和后面的最大值相等时,才会仍然有序,这时整个数组所有元素是相同的,所有循环移位都是有序的。

因此猜想循环移位只有可能 k = 1 k=1 k=1 或者 k = n k=n k=n 的时候有解。

code:

#include <cstdio>
#include <iostream>
using namespace std;
 
int n,k,T;
 
int main(){
	cin>>T;
	while(T--){
		cin>>n>>k;
		if(k==1){
			for(int i=1;i<=n;i++)
				cout<<i<<" \n"[i==n];
		}
		else if(n==k){
			for(int i=1;i<=n;i++)
				cout<<1<<" \n"[i==n];
		}
		else cout<<-1<<endl;
	}
	return 0;
}

B. Bessie and MEX

题意:

农夫约翰有一个排列组合 p 1 , p 2 , … , p n p_1, p_2, \ldots, p_n p1,p2,,pn ,其中从 0 0 0 n − 1 n-1 n1 的每个整数都正好出现一次。他给了贝西一个长度为 n n n 的数组 a a a ,让她根据 a a a 构造 p p p

数组 a a a 的构造使得 a i a_i ai = MEX ( p 1 , p 2 , … , p i ) − p i \texttt{MEX}(p_1, p_2, \ldots, p_i) - p_i MEX(p1,p2,,pi)pi ,其中数组的 MEX \texttt{MEX} MEX 是该数组中没有出现的最小非负整数。例如, MEX ( 1 , 2 , 3 ) = 0 \texttt{MEX}(1, 2, 3) = 0 MEX(1,2,3)=0 MEX ( 3 , 1 , 0 ) = 2 \texttt{MEX}(3, 1, 0) = 2 MEX(3,1,0)=2

帮助贝西构造满足 a a a 的任何有效排列 p p p 。输入的内容至少要有一个有效的 p p p 。如果存在多个可能的 p p p ,只需打印其中一个即可。

思路:

题解

code:

code在上面题解里给了


C1. Bessie’s Birthday Cake (Easy Version)

题意:

这是问题的简单版本。两个版本的唯一区别在于对 y y y 的限制。在此版本中为 y = 0 y = 0 y=0 。只有在两个版本都解出的情况下,你才能进行破解。

贝西从她最好的朋友埃尔西那里收到了一个生日蛋糕,它是一个边长为 n n n 的正多边形。蛋糕的顶点顺时针从 1 1 1 排列到 n n n 。你和贝西将选择其中的一些顶点在蛋糕上切出不相交的对角线。换句话说,对角线的端点必须是所选顶点的一部分。

为了保持一致性,贝西只想分发三角形的蛋糕。蛋糕块的大小并不重要,整个蛋糕也不一定要分成三角形(蛋糕中可以有其他形状,但不计算在内)。

贝西已经选择了 x x x 个顶点,可以用来组成对角线。她希望你选择个不超过 y y y 的其他顶点,使她能分出的三角形蛋糕的数量达到最大。

贝西最多能分出多少块三角形蛋糕?

思路:

注意 y = 0 y=0 y=0,因此这个题没有自选的点。

对已经有的点,我们把外围的边都画出来,它不会和内部的对角线相交,也能分隔开点内部和外部的部分。这时我们就得到了内部的一个多边形,这个多边形我们可以随便切,而外部我们一刀都不能切,不过外部剩下的部分有可能一开始就剩的三角形,如下图:

在这里插入图片描述
而对内部的凸多边形,发现不管怎么切,最后得到的最多的三角形个数是固定的,三角形个数=点的个数-2。

所以这个题就解决了,我们去统计一下外部剩下的部分有几个是三角形,再加上 选出的点的个数-2,就是总的三角形个数了。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=2e5+5;
 
int T,n,x,y;
int ans=0,a[maxn];
 
int main(){
	cin>>T;
	while(T--){
		cin>>n>>x>>y;
		ans=max(0,x-2);
		for(int i=1;i<=x;i++)cin>>a[i];
		sort(a+1,a+x+1);
		for(int i=2;i<=x;i++)
			ans+=(a[i]-a[i-1])==2;
		ans+=(a[1]+n-a[x])==2;
		cout<<ans<<endl;
	}
	return 0;
}

C2. Bessie’s Birthday Cake (Hard Version)

题意:

这是问题的困难版本。两个版本的唯一区别在于 y y y 的约束条件。在此版本中为 0 ≤ y ≤ n − x 0 \leq y \leq n - x 0ynx 。只有两个版本都解决了,你才能进行破解。

贝西从她最好的朋友埃尔西那里收到了一个生日蛋糕,它是一个边长为 n n n 的正多边形。蛋糕的顶点按顺时针方向从 1 1 1 编号到 n n n 。你和贝西将选择其中一些顶点在蛋糕上切出**不相交的对角线。换句话说,对角线的端点必须是所选顶点的一部分。

为了保持一致性,贝西只想分发三角形的蛋糕。蛋糕块的大小并不重要,整个蛋糕也不一定要分成三角形(蛋糕中可以有其他形状,但不计算在内)。

贝西已经选择了 x x x 个顶点,可以用来组成对角线。她希望你选择个不超过 y y y 的其他顶点,使她能分出的三角形蛋糕的数量达到最大。

贝西最多能分出多少块三角形蛋糕?

思路:

题解

code:

上面的链接里给了。


D. Learning to Paint

题意:

艾尔西正在学习如何画画。她的画布上有 n n n 个单元格,编号从 1 1 1 n n n ,她可以画任意(可能是空)单元格子集。

艾尔西有一个二维数组 a a a ,她将用这个数组来评价绘画作品。假设一幅画中绘画单元格的最大连续间隔为 [ l 1 , r 1 ] , [ l 2 , r 2 ] , … , [ l x , r x ] [l_1,r_1],[l_2,r_2],\ldots,[l_x,r_x] [l1,r1],[l2,r2],,[lx,rx] 。这幅画的美丽程度是所有 1 ≤ i ≤ x 1 \le i \le x 1ix a l i , r i a_{l_i,r_i} ali,ri 的总和。在上图中,绘画单元的最大连续间隔为 [ 2 , 4 ] , [ 6 , 6 ] , [ 8 , 9 ] [2,4],[6,6],[8,9] [2,4],[6,6],[8,9] ,画作的美感为 a 2 , 4 + a 6 , 6 + a 8 , 9 a_{2,4}+a_{6,6}+a_{8,9} a2,4+a6,6+a8,9

2 n 2^n 2n 种方法可以绘制这条带子。请帮助艾尔西找出在所有这些方法中,她可以得到的 k k k 最大的绘画美感值。请注意,这些 k k k 值并不一定要截然不同。可以肯定的是,至少有 k k k 种不同的方法可以在画布上作画。

思路:

还是比较容易往 d p dp dp 上想的,毕竟真想不到还能怎么做了。

d p [ i ] dp[i] dp[i] 表示前 i i i 个位置能得到的前 k k k 大的美丽值,这里 d p dp dp 值是个容器,存储前 k k k 大的美丽值。转移方程也比较好想,是: d p [ i − 1 ] dp[i-1] dp[i1]以及 d p [ j − 2 ] + a j , i ( 1 ≤ j ≤ i ) dp[j-2]+a_{j,i}\quad(1\le j\le i) dp[j2]+aj,i(1ji)所有取值的前 k k k 个,上面 d p dp dp 值有可能访问到下标 0 0 0 − 1 -1 1,根据递推式实际意义其实就是前面什么都不选,直接看作是 0 0 0 即可。因为 d p dp dp 值最多有 k k k 个取值,所以上面的式子最多有约 k ∗ i k*i ki 个取值,因此每次递推都要花 k ∗ n ∗ l o g k*n*log knlog 级别的时间,总的时间复杂度为 O ( k ∗ n 2 ∗ l o g ) O(k*n^2*log) O(kn2log),我们无法接受。

考虑到 d p dp dp 值存储的值是有序的(递减),如果 d p [ j − 2 ] + a j , i dp[j-2]+a_{j,i} dp[j2]+aj,i 中较大的 d p dp dp 的值不是前 k k k 大的话,后面的数也一定不是前 k k k 大的,我们就不需要向后考虑了。所以我们用堆来动态维护最大的值,这样第 i i i 次取出来的就是第 i i i 大的值,对每一项 d p [ j − 2 ] + a j , i dp[j-2]+a_{j,i} dp[j2]+aj,i 先放入最大的 d p dp dp 值,如果它被取出来了,我们才再把接下来的值放进去,这样最多放入 i + k i+k i+k 个值。每次递推就优化为了 n ∗ l o g n*log nlog 级别的时间。取出k个值后,再加上 d p [ i − 1 ] dp[i-1] dp[i1] k k k 个取值,排个序,并取出前 k k k 个值即可,就得到了 d p [ i ] dp[i] dp[i]。总的时间复杂度就为 O ( n 2 ∗ l o g ) O(n^2*log) O(n2log)

code:

#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e3+5;
 
int T,n,k;
int a[maxn][maxn];
 
struct Node{
	int val,j,idx;
	Node(int val,int j,int idx):val(val),j(j),idx(idx){};
	bool operator<(const Node x)const{return val<x.val;}
};
 
int main(){
	cin.tie(0)->sync_with_stdio(false);
	
	cin>>T;
	while(T--){
		cin>>n>>k;
		for(int i=1;i<=n;i++)
			for(int j=i;j<=n;j++)
				cin>>a[i][j];
		vector<vector<int> > f(n+1);
		f[0].push_back(0);
		for(int i=1;i<=n;i++){
			priority_queue<Node> h;
			f[i]=f[i-1];
			for(int j=1;j<=i;j++){
				h.push(Node(a[j][i]+((j>=2)?f[j-2][0]:0),j-2,0));
			}
			for(int cnt=1;cnt<=k && !h.empty();cnt++){
				auto [v,j,idx]=h.top();
				h.pop();
				f[i].push_back(v);
				if(j>=0 && idx+1<f[j].size()){
					v=v-f[j][idx]+f[j][idx+1];
					h.push(Node(v,j,idx+1));
				}
			}
			sort(f[i].begin(),f[i].end(),greater<int>());
			if(f[i].size()>k)f[i].erase(f[i].begin()+k,f[i].end());
		}
		for(auto x:f[n])cout<<x<<" ";
		cout<<endl;
	}
	return 0;
}

E. Farm Game

题意:

农夫 Nhoj 带着他的奶牛来到农夫 John 的农场玩游戏!农夫约翰的农场可以用一条在 0 0 0 l + 1 l + 1 l+1 点有墙的数线来模拟。农场里有 2 n 2n 2n 头奶牛,其中 n n n 头属于 FJ,另外 n n n 头属于 FN。他们把每头牛都放在一个不同的点上,FJ 的牛和 FN 的牛都不相邻。如果两头奶牛之间没有其他奶牛,它们就是相邻的。

形式上,如果 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,,an 表示 FJ 的奶牛的位置, b 1 , b 2 , … , b n b_1, b_2, \ldots, b_n b1,b2,,bn 表示 FN 的奶牛的位置,那么要么是 0 < a 1 < b 1 < a 2 < b 2 < … < a n < b n < l + 1 0 < a_1 < b_1 < a_2 < b_2 < \ldots < a_n < b_n < l + 1 0<a1<b1<a2<b2<<an<bn<l+1 ,要么是 0 < b 1 < a 1 < b 2 < a 2 < … < b n < a n < l + 1 0 < b_1 < a_1 < b_2 < a_2 < \ldots < b_n < a_n < l + 1 0<b1<a1<b2<a2<<bn<an<l+1

在一次移动中,农民选择一个数字 k k k ( 1 ≤ k ≤ n ) (1 \leq k \leq n) (1kn) 和一个方向(左或右)。然后,农夫选择 k k k 头奶牛,并将它们朝所选方向移动一个位置。农场主不能将自己的奶牛移动到墙上或其他农场主的奶牛上。如果一位农夫不能移动任何奶牛,那么他就输了。FJ 开始游戏,进行第一个回合。

给定 l l l n n n ,求如果两个农夫都以最佳方式下棋,农夫约翰可能获胜的棋局配置数。博弈可能无限期地进行下去,在这种情况下,没有农民获胜。如果有 i i i a i a_i ai b i b_i bi 不同,则该配置与另一配置不同。输出以 998   244   353 998\,244\,353 998244353 为模数的答案。

思路:

n n n 对奶牛的不好想,先想一对奶牛的。因为当左边的奶牛是后手,右边的奶牛是先手的情况我们直接翻转一下就转化成了左边的奶牛是先手,右边的奶牛是后手的情况。我们可以只讨论左边的奶牛是先手,右边的奶牛是后手的情况。

不难发现,当两个奶牛的距离为奇数的时候先手必败,否则先手必胜。证明:当两头奶牛距离为 1 1 1 的时候,这个时候先手只能向边界走,后手只要跟上去就行了,先手最后一定会被挤死在墙角。而当两头奶牛距离为更大的奇数时,先手可以向两边任意一个方向走,后手只要向先手奶牛的位置走,就可以保持两头奶牛的距离一定为奇数,无论怎么走,最后先手一定会被后手挤死在墙角。当两个奶牛的距离为偶数时,先手先随便移动一下,两头奶牛的距离就变成了奇数,这时就转化为了上面说的必败的局面,也就是说当两个奶牛的距离为偶数时先手必胜。

两对奶牛的时候也好想,左边那一对和右边那一对分开来看,因为胜负只和一对奶牛的距离有关,和墙的位置没有关系,所以右边的奶牛直接看作左边那对的墙,左边的奶牛看作右边那对的墙即可。对一对必胜的奶牛状态,我们可以不管他,如果对手动它了,我们再动,维持这一对必胜的局面。相反,如果某一对奶牛如果是必败的状态,那么我们也无法把它变成必胜的。

而一开始就能决定所有奶牛状态的先手一定是动所有必胜状态,必胜状态转为对方的必败状态,一开始的必败状态也留给对手。因为先手必须动至少一对奶牛,所以除非一开始所有的奶牛状态都是必败状态(也就是每对奶牛之间距离都相差为奇数),否则先手必胜。

我们直接去算必胜状态的个数会比较困难,但是总的状态个数和必败状态个数好算,我们可以用所有状态的个数减去必败状态的个数得到必胜状态的个数。

总的状态个数:

因为我们上面规定了先手奶牛靠左,后手靠右,因此奶牛的位置满足: 0 < a 1 < b 1 < a 2 < b 2 < … < a n < b n < l + 1 0 < a_1 < b_1 < a_2 < b_2 < \ldots < a_n < b_n < l + 1 0<a1<b1<a2<b2<<an<bn<l+1,先手奶牛靠右,后手靠左的情况和先手奶牛靠左,后手靠右的情况是一一对应的(翻转一下),因此总的状态个数就是先手奶牛靠左,后手靠右的个数乘以 2 2 2

如果满足 0 < a 1 < b 1 < a 2 < b 2 < … < a n < b n < l + 1 0 < a_1 < b_1 < a_2 < b_2 < \ldots < a_n < b_n < l + 1 0<a1<b1<a2<b2<<an<bn<l+1,我们设前一项和后一项的距离为 d i d_i di,就有 d 1 + d 2 + ⋯ + d 2 n + d 2 n + 1 = l + 1 d_1+d_2+\dots+d_{2n}+d_{2n+1}=l+1 d1+d2++d2n+d2n+1=l+1。因为 d i > 0 d_i>0 di>0,所以这相当于算 l + 1 l+1 l+1 个相同的小球放到 2 n + 1 2n+1 2n+1 个不同的盒子里,不能空盒的方法数。先手奶牛靠左,后手靠右的个数为 C l 2 n C_{l}^{2n} Cl2n

必败状态的个数:

0 < a 1 < b 1 < a 2 < b 2 < … < a n < b n < l + 1 0 < a_1 < b_1 < a_2 < b_2 < \ldots < a_n < b_n < l + 1 0<a1<b1<a2<b2<<an<bn<l+1 的基础上,要求 a i , b i a_i,b_i ai,bi 的距离为奇数。和上面一样,先算先手奶牛靠左,后手靠右的个数,总的个数就是前者个数乘以 2 2 2

我们假设 a i , b i a_i,b_i ai,bi 之间的距离一开始都是 1 1 1,这样会先分走 n n n 单位的距离,然后我们再两个单位两个单位的距离往里塞。形象一点来说,我们把两个单位的距离看成是一个小球, a i , b i a_i,b_i ai,bi 之间的距离为一个盒子,其实还是把小球放到盒子里,不过这时可以空盒。假设我们给 a i , b i a_i,b_i ai,bi 之间分配了 i i i 单位的距离 ( i ≥ n 且 i = n + 2 ∗ k ( k ∈ N ) ) (i\ge n 且 i=n+2*k\quad(k\in N)) (ini=n+2k(kN)),这时就有 x = i − n 2 x=\frac{i-n}2 x=2in 个小球,那么这时分配小球的方法数就是 C n + x − 1 x C_{n+x-1}^{x} Cn+x1x,剩余 l + 1 − i l+1-i l+1i 个距离单位,以及 n + 1 n+1 n+1 d i d_i di(也就是 d 1 + d 3 + d 5 + ⋯ + d 2 n − 1 + d 2 n + 1 = l + 1 − i d_1+d_3+d_5+\dots+d_{2n-1}+d_{2n+1}=l+1-i d1+d3+d5++d2n1+d2n+1=l+1i),它们的方法数是 C l − i n C_{l-i}^{n} Clin。这种分配方式下总的方法数就是 C n + x − 1 x ∗ C l − i n C_{n+x-1}^{x}*C_{l-i}^{n} Cn+x1xClin,我们去枚举 i i i,把所有方法数算出来即可。

code:

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e6+5;
 
ll qpow(ll a,ll b){
	b%=mod-1;
	ll base=a%mod,ans=1;
	while(b){
		if(b&1)ans=(ans*base)%mod;
		base=(base*base)%mod;
		b>>=1;
	}
	return ans;
}
inline ll inv(ll x){return qpow(x,mod-2);}
 
ll fac[maxn+5],ifac[maxn+5];
 
inline ll C(ll x,ll y){//C_x^y
	if(y<0 || x<0 || y>x)return 0;
	return fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}
 
ll T,n,l;
 
int main(){
	fac[0]=ifac[0]=1;
	for(int i=1;i<=maxn;i++)fac[i]=fac[i-1]*i%mod;
	ifac[maxn]=inv(fac[maxn]);
	for(int i=maxn;i>=1;i--)ifac[i-1]=ifac[i]*i%mod;
	
	cin>>T;
	while(T--){
		cin>>l>>n;
		ll tot=C(l,2*n),t=0;
		for(ll i=n,x;i<=l-1;i+=2){
			x=(i-n)/2;
			t=(t+C(l-i,n)*C(n+x-1,x))%mod;
		}
		cout<<(2*(tot-t)%mod+mod)%mod<<endl;
	}
	return 0;
}
  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值