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

比赛链接

这场看着比较吓人,C是数学题,D沾点数学,是个三分(通过一些优化可以不写三分),E题还沾点数学,然后是带打印路径的完全背包DP,写起来还是比较劝退的。因为数学推理占模,所以有很多解法就很多了,我的思路可能写起来会比较困难。可以参考一下别的大佬的解法。


A. Rectangle Cutting

题意:

鲍勃有一个大小为 a × b a \times b a×b 的矩形。他尝试将这个矩形切成两个边长为整数的矩形,切口平行于原矩形的一条边。然后,鲍勃试图用这两个矩形拼成另一个矩形,他可以随意旋转和移动这两个矩形。

请注意,如果两个矩形仅有 9 0 ∘ 90^{\circ} 90 次旋转的区别,那么它们就被视为**个相同的矩形。例如,矩形 6 × 4 6 \times 4 6×4 4 × 6 4 \times 6 4×6 被认为是相同的。

因此,从 2 × 6 2 \times 6 2×6 矩形可以形成另一个矩形,因为它可以切割成两个 2 × 3 2 \times 3 2×3 矩形,然后用这两个矩形形成 4 × 3 4 \times 3 4×3 矩形,它与 2 × 6 2 \times 6 2×6 矩形不同。

但是,从 2 × 1 2 \times 1 2×1 矩形中却不能形成另一个矩形,因为它只能被切割成两个 1 × 1 1 \times 1 1×1 矩形,而从这两个矩形中只能形成 1 × 2 1 \times 2 1×2 2 × 1 2 \times 1 2×1 矩形,这两个矩形被认为是相同的。

帮助鲍勃确定他是否能得到其他矩形,或者他是否只是在浪费时间。

思路:

首先可以比较容易推出 只有可能从一条边的中点处剪开 的结论的,这样的话只有两种裁剪拼接方式,那就是从长边中点剪开,然后拼到短边上。或者从短边中点剪开,然后拼到长边上(前提是中点得是个整数)。

可以用set将原来的矩形和两个情况得到的新矩形放入set里,之后看set大小就知道有没有拼出新矩形了。

code:

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;

int main(){
	int T;
	cin>>T;
	while(T--){
		int a,b;
		cin>>a>>b;
		if(b>a)swap(a,b);
		set<pair<int,int> > S;
		S.insert(make_pair(a,b));
		if(~a&1)S.insert(make_pair(max(a/2,b*2),min(a/2,b*2)));
		if(~b&1)S.insert(make_pair(max(a*2,b/2),min(a*2,b/2)));
		if(S.size()==1)puts("No");
		else puts("Yes");
	}
	return 0;
}

B. Equalize

题意:

瓦夏有两个爱好——给数组添加排列组合 † ^{\dagger} 和找出出现频率最高的元素。最近,他发现了一个数组 a a a ,于是决定找出在数组 a a a 中添加一些排列组合后,数组 a a a 中等于相同数字的元素的最大数目。

更具体地说,瓦夏可以选择一些长度为 n n n 的排列 p 1 , p 2 , p 3 , … , p n p_1, p_2, p_3, \ldots, p_n p1,p2,p3,,pn ,然后根据规则 a i : = a i + p i a_i := a_i + p_i ai:=ai+pi 改变数组 a a a 中的元素。之后,瓦夏会计算每个数字在数组 a a a 中出现的次数,并取其中的最大值。你需要确定他能得到的最大值。

† ^{\dagger} 长度为 n n n 的排列是由 n n n 个不同的整数组成的数组,这些整数从 1 1 1 n n n 按任意顺序排列。例如, [ 2 , 3 , 1 , 5 , 4 ] [2,3,1,5,4] [2,3,1,5,4] 是一个排列,但 [ 1 , 2 , 2 ] [1,2,2] [1,2,2] 不是一个排列( 2 2 2 在数组中出现了两次), [ 1 , 3 , 4 ] [1,3,4] [1,3,4] 也不是一个排列( n = 3 n=3 n=3 ,但数组中有 4 4 4 )。

思路:

说白了就是构造一个排列数组,然后给的a数组和构造的p数组各位相加得到的所有数中,问每种数字的个数的最大值最大是什么。

这样的话其实顺序是没有什么意义的,可以对原来的数组进行排序,容易想到原数组中重复的数是不会重复产生贡献的,因为我们构造的是排列,每种数只有一个,因此还可以对这个数组去重,考虑如何构造排列数组。

如果一段区间内的数相差不是很多的话,那么我们给小的数加上一个大数,给大数加上一个小数,从而使得它们的和相等,而小数最多加n,大数最小加1,因此小数和大数最多相差n-1。反之,如果有x个数互不相等,而且它们的最大值和最小值相差小于等于n-1,那么我们就有办法使用 1 ∼ n 1\sim n 1n 每种只用一个把它们凑成一个相同的数,这样答案就是x了。

因为已经去过重了,考虑怎么找到这样的最大值和最小值相差小于等于n-1的一段区间。因为有序,所以可以二分。这里也可以使用双指针:右指针 r r r 向右移一位,看一下左指针 l l l 的数是否满足条件,不满足就向右移动,这样可以保证 l ∼ r l\sim r lr 区间是满足条件的最大的区间,因为 l r lr lr都只向右移动,最多移动 n n n 次,因此双指针的时间复杂度就是 O ( n ) O(n) O(n) 了。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=2e5+5;

int T,n,cnt,a[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++)
			cin>>a[i];
		sort(a+1,a+n+1);
		cnt=unique(a+1,a+n+1)-a-1;
		
		int ans=1;
		for(int l=1,r=1;r<=cnt;r++){
			while(a[r]-a[l]+1>n)l++;
			ans=max(ans,r-l+1);
		}
		cout<<ans<<endl;
	}
}

C. Physical Education Lesson

题意:

在一所著名的学校里,有一节体育课。像往常一样,每个人都排成一排,并被要求站在 "第 k k k 个 "的位置上。

众所周知,在 “第一个第 k k k 个” 位置落座的情况如下:第一个 k k k 人的号码是 1 , 2 , 3 , … , k 1, 2, 3, \ldots, k 1,2,3,,k ,接下来的 k − 2 k - 2 k2 人的号码是 k − 1 , k − 2 , … , 2 k - 1, k - 2, \ldots, 2 k1,k2,,2 ,接下来的 k k k 人的号码是 1 , 2 , 3 , … , k 1, 2, 3, \ldots, k 1,2,3,,k ,以此类推。这样,每隔 2 k − 2 2k - 2 2k2 个位置就重复一次结算。结算示例见 "注释 "部分。

男孩瓦夏经常忘记所有事情。例如,他忘记了上面描述的数字 k k k 。但是他记得他在队伍中的位置,以及他在结算时得到的数字。请帮助瓦夏理解在给定的限制条件下,有多少个自然数 k k k 符合要求。

请注意,当且仅当 k > 1 k > 1 k>1 存在时,结算才存在。特别是,这意味着 k = 1 k = 1 k=1 不存在结算。

思路:

题目看的云里雾里,其实玩一下样例就比较清晰了。说白了就是按顺序报数 1 , 2 , 3 , … , k − 1 , k , k − 1 , k − 2 , … , 2 , 1 , 2 , 3 , … , k − 1 , k , k − 1 , … 1,2,3,\dots,k-1,k,k-1,k-2,\dots,2,1,2,3,\dots,k-1,k,k-1,\dots 1,2,3,,k1,k,k1,k2,,2,1,2,3,,k1,k,k1, 这样报数, 1 , 2 , 3 , … , k − 1 , k , k − 1 , k − 2 , … , 2 1,2,3,\dots,k-1,k,k-1,k-2,\dots,2 1,2,3,,k1,k,k1,k2,,2 2 k − 2 2 k-2 2k2 个数就是一个循环。现在知道男孩在第 n n n 个位置上,报数为 x x x。问有几个满足条件的 k k k

一个循环是 1 , 2 , 3 , … , k − 1 , k , k − 1 , k − 2 , … , 2 1,2,3,\dots,k-1,k,k-1,k-2,\dots,2 1,2,3,,k1,k,k1,k2,,2 形式的,如果 k k k 是确定的,报数为 x x x 的话,这个 x x x 可能出现在前 k k k 个数的上升段,也就是 1 , 2 , 3 , … , k − 1 , k 1,2,3,\dots,k-1,k 1,2,3,,k1,k,也有可能出现在后 k − 2 k-2 k2 个数的下降段,也就是 k − 1 , k − 2 , … , 2 k-1,k-2,\dots,2 k1,k2,,2

假设 x x x 是上升段上报出来的,那么显然 n − x n-x nx 去掉这多出来的 x x x 长度后,剩下的都是整的循环。所以找 n − x n-x nx 的约数,这个约数 k k k 只要满足 1 ≤ x ≤ k 1\le x\le k 1xk k k k为偶数 就是一个合法的 k k k 值。

假设 x x x 是下降段上报出来的,减是不好减的,因为要考虑 k k k 究竟是什么,那么不妨加,补齐一个整的循环,不难找到 n + x − 2 n+x-2 n+x2 就可以补齐一个整循环,得到的都是整的循环。所以找 n + x − 2 n+x-2 n+x2 的约数,这个约数 k k k 只要满足 2 ≤ x ≤ k − 1 2\le x\le k-1 2xk1 k k k为偶数 就是一个合法的 k k k 值。

为了防止重复找到一个 k k k 值,可以用 set 记录所有合法的 k k k 值,之后输出 set 的 size 即可

code:

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
typedef long long ll;

int T,n,x;

int main(){
	cin>>T;
	while(T--){
		cin>>n>>x;
		ll t=(n-x);
		set<ll> S;
		for(ll i=1,k;i*i<=t;i++){
			if(t%i==0){
				if(~i&1){
					k=i/2+1;
					if(k>=x && x>=1)S.insert(k);
				}
				if(~(t/i)&1){
					k=t/i/2+1;
					if(k>=x && x>=1)S.insert(k);
				}
				
			}
		}
		
		t=n+x-2;
		for(ll i=1,k;i*i<=t;i++){
			if(t%i==0){
				if(~i&1){
					k=i/2+1;
					if(k>x && x>=2)S.insert(k);
				}
				if(~(t/i)&1){
					k=t/i/2+1;
					if(k>x && x>=2)S.insert(k);
				}
			}
		}
		cout<<S.size()<<endl;
	}
}

D. Lonely Mountain Dungeons

题意:

曾经,中土世界的人们、精灵、矮人和其他居民聚集在一起,想要夺回被史矛革偷走的宝藏。为了这个伟大的目标,他们团结在强大的精灵提摩西周围,开始策划推翻孤山的统治者。

中土世界居民的军队将由几个小队组成。众所周知,每一对同种族的生物分属不同的小队,军队的总战斗力就会增加 b b b 个单位。但由于提摩西很难领导一支由大量小队组成的军队,因此由 k k k 个小队组成的军队的总战斗力会减少 ( k − 1 ) ⋅ x (k - 1) \cdot x (k1)x 个单位。注意,军队总是由个至少一个班组成。

众所周知,中土世界有 n n n 个种族,而 i i i 个种族的生物数量等于 c i c_i ci 。请帮助 "中土世界 "的居民确定他们所能组建的军队的最大战斗力。

思路:

这题是个三分。不过能否使用三分需要推导(或者凭经验直接日一发也不是不行)

首先看到数据范围是 2 ∗ 1 0 5 2*10^5 2105 的,所以应该是 O ( n l o g n ) O(nlog n) O(nlogn) 的做法,尝试如何计算战斗力。

不妨设现在有 t t t 个小队,那么最大战斗力的分配方案是什么?不难想到可以平均分配,为什么是平均分配最合适呢?

假设第 i i i 个种族的数量为 c i c_i ci ,如果平均分配,那么会有两种人数的小队,两者差一人,假设小的那个小队的人数是 m = c i / t m=c_i/t m=ci/t(斜杠表示整数除法,直接舍弃余数),那么大的小队一共有 t 1 = c i − m ∗ t t1=c_i-m*t t1=cimt 个,小的小队一共有 t 2 = t − t 1 t2=t-t1 t2=tt1 个(下面我们直接默认小队有 m m m 人,大队有 m + 1 m+1 m+1 人)。这样第 i i i 个种族的总的贡献是 b ∗ ( C c i 2 − t 1 ∗ C m + 1 2 − t 2 ∗ C m 2 ) b*(C_{c_i}^2-t1*C_{m+1}^2-t2*C_{m}^2) b(Cci2t1Cm+12t2Cm2) (从 c i c_i ci 人里选取两人,不过这样会有两人在同一队里的不合法情况,如果两人在同一个大队里,选取方案就有 C m + 1 2 C_{m+1}^2 Cm+12 种, t 1 t1 t1 队就有 t 1 ∗ C m + 1 2 t1*C_{m+1}^2 t1Cm+12 种不合法情况,同理小队)。

证明为什么平均分配最优:其实通过上面的推导可以找到一个证明思路,总的选取方案是不变的,是 C c i 2 C_{c_i}^2 Cci2,我们只需要让总的不合法情况最少即可,平均分配的不合法情况有 t 1 ∗ C m + 1 2 + t 2 ∗ C m 2 ( t 1 + t 2 = t ) t1*C_{m+1}^2+t2*C_{m}^2 \quad (t1+t2=t) t1Cm+12+t2Cm2(t1+t2=t) 种。如果你要从一个队里取出一个人,把它放到另一个队里,有如下四种情况:

  1. 小队->小队,这样的话,失去 2 ∗ C m 2 2*C_m^2 2Cm2,得到 C m − 1 2 C_{m-1}^2 Cm12 C m + 1 2 C_{m+1}^2 Cm+12 C m − 1 2 + C m + 1 2 = m 2 − m + 2 = 2 ∗ C m 2 + 2 C_{m-1}^2+C_{m+1}^2=m^2-m+2=2*C_m^2+2 Cm12+Cm+12=m2m+2=2Cm2+2,显然不合法情况变多
  2. 小队->大队,失去 C m 2 C_m^2 Cm2 C m + 1 2 C_{m+1}^2 Cm+12,得到 C m − 1 2 C_{m-1}^2 Cm12 C m + 2 2 C_{m+2}^2 Cm+22 C m − 1 2 + C m + 2 2 = m 2 + 2 C_{m-1}^2+C_{m+2}^2=m^2+2 Cm12+Cm+22=m2+2,而 C m 2 + C m + 1 2 = m 2 C_m^2+C_{m+1}^2=m^2 Cm2+Cm+12=m2。显然不合法情况变多
  3. 大队->大队,失去 2 ∗ C m + 1 2 2*C_{m+1}^2 2Cm+12,得到 C m 2 C_{m}^2 Cm2 C m + 2 2 C_{m+2}^2 Cm+22。跟第一种情况是一样的,显然不合法情况变多
  4. 大队->小队,相当于没有变化

再从取出小队的人分给更大的队的不合法情况增加情况和上面是一致的,也就是会变大。综上,平均分配最优。

回到正题,那么总的n个种族的总贡献应该就是 b ∗ ( ∑ i = 1 n C c i 2 − t 1 ∗ C m + 1 2 − t 2 ∗ C m 2 ) − x ∗ ( t − 1 ) b*(\sum_{i=1}^{n}C_{c_i}^2-t1*C_{m+1}^2-t2*C_{m}^2)-x*(t-1) b(i=1nCci2t1Cm+12t2Cm2)x(t1)你会发现这个式子很难进行化简,而且需要 O ( n ) O(n) O(n) 来累加,因此只能用 l o g n log n logn 的次数内找到这个 t t t,而二分显然是错的,凭经验的话考虑三分。

那么为什么三分是对的?考虑从函数的斜率角度来证明。

众所周知,三分要在答案函数是个抛物线形状的情况下跑才是正确的(也就是图像先增后减或者先减后增,当然只单调递增或者递减也是可以的。不能增减增或者变化更多次以上)。更确切的说,是函数斜率先正后负或者先负后正(一直正或负也是可以的,不过符号不能变化三次及以上)。

上面这个答案函数后半部分 − x ∗ ( t − 1 ) -x*(t-1) x(t1) 是一个一次函数,斜率是一个恒定负值。而前半去掉 b b b 的部分 ∑ i = 1 n C c i 2 − t 1 ∗ C m + 1 2 − t 2 ∗ C m 2 \sum_{i=1}^{n}C_{c_i}^2-t1*C_{m+1}^2-t2*C_{m}^2 i=1nCci2t1Cm+12t2Cm2 斜率是否稳定?第 i i i 个种族的贡献为 C c i 2 − t 1 ∗ C m + 1 2 − t 2 ∗ C m 2 C_{c_i}^2-t1*C_{m+1}^2-t2*C_{m}^2 Cci2t1Cm+12t2Cm2,前边 C c i 2 C_{c_i}^2 Cci2 是一个恒定值,后边 t 1 ∗ C m + 1 2 + t 2 ∗ C m 2 = t 1 ∗ C m + 1 2 + ( t − t 1 ) ∗ C m 2 = t ∗ C m 2 + m t 1 t1*C_{m+1}^2+t2*C_{m}^2=t1*C_{m+1}^2+(t-t1)*C_{m}^2=t*C_m^2+mt1 t1Cm+12+t2Cm2=t1Cm+12+(tt1)Cm2=tCm2+mt1 其实只和 t t t 有关。而感性的来想的话,队伍分的越多,每队的人就会越少,不合法情况就会越少,而不合法的情况减少的速度应该是越来越慢的。也就是 t 1 ∗ C m + 1 2 + t 2 ∗ C m 2 t1*C_{m+1}^2+t2*C_{m}^2 t1Cm+12+t2Cm2 这个式子的斜率是单调递减的,那么 C c i 2 − t 1 ∗ C m + 1 2 − t 2 ∗ C m 2 C_{c_i}^2-t1*C_{m+1}^2-t2*C_{m}^2 Cci2t1Cm+12t2Cm2 的斜率应该是单调递增的,总的答案函数的斜率就应该是单调的,这样增减性就不会出现反复变化。

非要证明的话(其实也不算严谨证明),就是不分大队小队,就假设一共 t t t 队,一队分 c i t \frac {c_i} t tci 个人,这样可以近似于原来的贡献,也就是 t 1 ∗ C m + 1 2 + t 2 ∗ C m 2 ≈ t ∗ c i t ∗ ( c i t − 1 ) 2 = c i ∗ ( c i − t ) 2 t1*C_{m+1}^2+t2*C_{m}^2\approx t*\frac {\frac {c_i} t*(\frac {c_i} t-1)} 2=\frac{c_i *(c_i-t)}2 t1Cm+12+t2Cm2t2tci(tci1)=2ci(cit)这就是个二次函数,它的斜率就是个一次函数,所以斜率是单调递增的,总的答案函数的斜率就应该是单调的,这样增减性就不会出现反复变化,因此可以三分。

还有一种算贡献的方式。假设第 i i i 个种族有 c i c_i ci 人,分成 t t t 队,得到每队 m = ⌊ c i t ⌋ m=\left\lfloor\frac {c_i} t\right\rfloor m=tci 人,还多出 d = c i % t d=c_i\% t d=ci%t 人,对第二队,它里面的每个人都可以和前 m m m 人配队,答案数就是 m ∗ m m*m mm,对第三队,它里面的每个人都可以和前 2 ∗ m 2*m 2m 人配队,答案数就是 2 m ∗ m 2m*m 2mm,同理第四队答案数就是 3 m ∗ m 3m*m 3mm,第 n n n 队答案数就是 ( t − 1 ) ∗ m 2 (t-1)*m^2 (t1)m2。这样不计算多出来的人的总的答案数就是 0 ∗ m 2 + 1 ∗ m 2 + 2 ∗ m 2 + ⋯ + ( t − 1 ) ∗ m 2 = t ( t − 1 ) 2 ∗ m 2 0*m^2+1*m^2+2*m^2+\dots+(t-1)*m^2=\frac {t(t-1)} 2*m^2 0m2+1m2+2m2++(t1)m2=2t(t1)m2

将多出来的 d d d 依次放入前 d d d 个小队,得到的贡献是:第一个人可以和所有不是本队的原来的 ( t − 1 ) ∗ m (t-1)*m (t1)m 人配队,以及前面放入的0个人配队,第一个人的贡献是 ( t − 1 ) ∗ m + 0 (t-1)*m+0 (t1)m+0。第二个人可以和所有不是本队的原来的 ( t − 1 ) ∗ m (t-1)*m (t1)m 人配队,以及前面放入的1个人配队,第二个人的贡献是 ( t − 1 ) ∗ m + 1 (t-1)*m+1 (t1)m+1,同理第三个人的贡献是 ( t − 1 ) ∗ m + 2 (t-1)*m+2 (t1)m+2,第 d d d个人的贡献是 ( t − 1 ) ∗ m + d − 1 (t-1)*m+d-1 (t1)m+d1。这 d d d 个人的贡献是 ( t − 1 ) ∗ m ∗ d + d ∗ ( d − 1 ) 2 (t-1)*m*d+\frac {d*(d-1)} 2 (t1)md+2d(d1)

综上,第 i i i 个种族的贡献是 b ∗ ( t ( t − 1 ) 2 ∗ m 2 + ( t − 1 ) ∗ m ∗ d + d ∗ ( d − 1 ) 2 ) b*(\frac {t(t-1)} 2*m^2+(t-1)*m*d+\frac {d*(d-1)} 2) b(2t(t1)m2+(t1)md+2d(d1)),把所有种族的贡献加起来再减去 x ∗ ( t − 1 ) x*(t-1) x(t1) 即可。

这个题的数据卡的很精准,虽然看起来很大,但是都不会超过longlong的范围。

code1:

三分:

整数三分的实现比较困难,这里有个小技巧,其实可以把范围缩小到一定范围后直接对小范围暴力枚举。

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2e5+5;
typedef long long ll;

ll T,n,b,x,c[maxn];
ll C[maxn];//C(2 x)

ll check(ll t){
	ll ans=0;
	for(ll i=1,t1,t2,m;i<=n;i++){
		m=c[i]/t;
		t1=c[i]-m*t;
		t2=t-t1;
		ans+=(C[c[i]]-t1*C[m+1]-t2*C[m]);
	}
	ans*=b;
	ans-=x*(t-1);
	return ans;
}

int main(){
	C[0]=C[1]=0;C[2]=1;C[3]=3;
	for(int i=4;i<maxn;i++)
		C[i]=C[i-1]*i/(i-2);
	
	cin>>T;
	while(T--){
		cin>>n>>b>>x;
		ll maxx=0;
		for(int i=1;i<=n;i++)
			cin>>c[i],maxx=max(maxx,c[i]);
		
		ll l=1,r=maxx,lm,rm;
		while(l+10<r){
			lm=(l*2+r)/3;
			rm=(l+r*2)/3;
			if(check(lm)<check(rm))l=lm;
			else r=rm;
		}
		
		ll ans=0;
		for(ll i=l;i<=r;i++)
			ans=max(ans,check(i));
		cout<<ans<<endl;
	}
	return 0;
}

看到一个很神的优化方式,可以把三分优化掉,因为数量相同的种族的贡献是一模一样的,那么我们可以把多个相同种族的用一个数组记录一下个数,然后累加贡献的时候直接乘上这个数量即可,就不用都算一遍了。

通过这种优化方式,假设去重之后需要计算 n n e w ≤ n n_{new}\le n nnewn 个种族,最坏情况下就是去重后还是剩下 n n n 个种族,最坏情况下一次计算总贡献是 O ( n ) O(n) O(n) 的。那么最坏情况下要枚举几次队伍数量?队伍数量最少是1队,最多是最多的那个种族的人数,那么最坏情况下,前 n − 1 n-1 n1 个种族占用人数尽可能的少,剩下的种族占人数尽可能的多,假设是 x x x,也就是种族总人数是 1 + 2 + 3 + 4 + ⋯ + n − 1 + x = n ∗ ( n − 1 ) 2 ≤ 2 ∗ 1 0 5 − x 1+2+3+4+\dots+n-1+x=\frac {n*(n-1)}2\le 2*10^5-x 1+2+3+4++n1+x=2n(n1)2105x,所以这样优化一下的话, n n n 实际上是 2 ∗ 1 0 5 \sqrt {2*10^5} 2105 级别的。咱就算 x x x 很大,是 2 ∗ 1 0 5 2*10^5 2105 级别的,总的也不会超过 O ( 2 ∗ 1 0 5 ∗ 2 ∗ 1 0 5 ) O(2*10^5*\sqrt{2*10^5}) O(21052105 ),因此这样优化是正确的。

不过当然跑不过三分。

#include <iostream>
#include <cstdio>
#include <stack>
#include <algorithm>
using namespace std;
const int maxn=2e5+5;
typedef long long ll;

ll T,n,b,x,c[maxn],cnt[maxn];
ll C[maxn];//C(2 x)

ll check(ll t){
	ll ans=0;
	for(ll i=1,t1,t2,m;i<=n;i++){
		m=c[i]/t;
		t1=c[i]-m*t;
		t2=t-t1;
		ans+=cnt[c[i]]*(C[c[i]]-t1*C[m+1]-t2*C[m]);
	}
	ans*=b;
	ans-=x*(t-1);
	return ans;
}

int main(){
	C[0]=C[1]=0;C[2]=1;C[3]=3;
	for(int i=4;i<maxn;i++)
		C[i]=C[i-1]*i/(i-2);
	
	cin>>T;
	while(T--){
		cin>>n>>b>>x;
		for(int i=1;i<=n;i++)
			cin>>c[i],cnt[c[i]]++;
		sort(c+1,c+n+1);
		n=unique(c+1,c+n+1)-c-1;
		
		ll ans=0;
		for(ll i=1;i<=c[n];i++)
			ans=max(ans,check(i));
		cout<<ans<<endl;
		
		for(int i=1;i<=n;i++)
			cnt[c[i]]=0;
	}
	return 0;
}

E. Modular Sequence

题意:

给你两个整数 x x x y y y 。长度为 n n n 的序列 a a a 如果是 a 1 = x a_1=x a1=x ,且对于所有 1 < i ≤ n 1\lt i \le n 1<in a i a_{i} ai 值要么是 a i − 1 + y a_{i-1} + y ai1+y 要么是 a i − 1   m o d   y a_{i-1} \bmod y ai1mody ,则称为模数序列。这里的 x   m o d   y x \bmod y xmody 表示 x x x 除以 y y y 所得的余数。

判断是否存在长度为 n n n 的模数序列,其元素之和等于 S S S ,如果存在,请找出任何这样的序列。

思路:

其实除开第一段是以 x x x 为初项, y y y 为公差的等差数列,后面都是以 x % y x\%y x%y 为初项, y y y 为公差的等差数列。我们就是要构造一个长为 n n n 的几个等差数列的组合,使得它们的总和为 s s s

不妨把给每个元素都减去 x % y x\%y x%y ,这样它们都是 k ∗ y k*y ky 的形式了,再同时除以 y y y,这样就变成了以 x / y x/y x/y 为初项, 1 1 1 为公差的等差数列,后面都是以 0 0 0 为初项, 1 1 1 为公差的等差数列,我们需要让它们的和为 t = s − ( x % y ) ∗ n y ( t为整数且 0 ≤ t ≤ x / y ∗ n + n ∗ ( n − 1 ) 2 ) t=\dfrac{s-(x\%y)*n}y\quad(\text{t为整数且} 0\le t\le x/y*n+\dfrac {n*(n-1)}2) t=ys(x%y)n(t为整数且0tx/yn+2n(n1))

那么第一段以 x / y x/y x/y 为初项, 1 1 1 为公差的等差数列,后面都是以 0 0 0 为初项, 1 1 1 为公差的等差数列。怎么凑可以使得它们的总和等于 t t t,且长度小于等于 n n n(不够的部分直接补0)?考虑到等差数列的总和是 n 2 n^2 n2 级别的,所以等差数列的长度最长不会超过 n \sqrt n n 级别。第一段比较特殊可以直接暴力枚举,那么后面的以 0 0 0 为初项, 1 1 1 为公差的等差数列怎么办?

考虑到长度为 x x x 的等差数列的总和是 x ∗ ( x − 1 ) 2 \dfrac {x*(x-1)}2 2x(x1),把这样的一个等差数列看作一种物品的话,我们要凑总长度为 t t t,长度小于等于 n n n 的组合方式。其实就相当于完全背包问题了。等差数列长度 x x x 就是价值,总和 x ∗ ( x − 1 ) 2 \dfrac {x*(x-1)}2 2x(x1) 就是重量, t t t 就是总容量。设 d p [ i ] dp[i] dp[i] 为填满容量为 i i i 的背包时的最小价值,我们找到 d p [ t ] dp[t] dp[t] 的最小值,只要 d p [ t ] < = n dp[t]<=n dp[t]<=n 即可。

这里要记录路径,使用 l s t [ i ] lst[i] lst[i] 记录一下 d p [ i ] dp[i] dp[i] 是由哪一个状态推过来的。然后写一个函数回溯一下路径并打印即可。

d p dp dp 数组初始值就是第一段的等差数列,具体来说, d p [ x / y ] = 1 , d p [ x / y + 1 ] = 2 , d p [ x / y + 2 ] = 3 , … dp[x/y]=1,dp[x/y+1]=2,dp[x/y+2]=3,\dots dp[x/y]=1,dp[x/y+1]=2,dp[x/y+2]=3, 就可以了。

code:

#include <iostream>
#include <cstdio>
#include <stack>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
const int inf=1e9;

ll T,n,x,y,s;
int dp[maxn],lst[maxn];

void print(int t){
	stack<int> s;
	int cnt=0;
	for(int i=t,len;;){
//		cout<<i<<" "<<x<<endl;
		len=lst[i];
		if(~len){
			s.push(len);
		}
		else {
			for(int j=1,a=x;j<=dp[i];j++,a+=y){
				cout<<a<<" ";
				cnt++;
			}
			break;
		}
		i-=len*(len-1)/2;
	}
	while(!s.empty()){
		for(int i=1,a=x%y;i<=s.top();i++,a+=y){
			cout<<a<<" ";
			cnt++;
		}
		s.pop();
	}
	
	for(int i=cnt+1;i<=n;i++)
		cout<<x%y<<" ";
	
	cout<<endl;
}

int main(){
	cin>>T;
	while(T--){
		cin>>n>>x>>y>>s;
		ll t=s-(x%y)*n;
		if(t%y){
			puts("NO");
			continue;
		}
		t/=y;
		if(t<0 || t>x/y*n+n*(n-1)/2){
			puts("NO");
			continue;
		}
		
		for(int i=0;i<=t;i++)
			dp[i]=inf;
		for(int i=1,d=x/y,w=x/y;w<=t;i++){
			dp[w]=i;
			lst[w]=-1;
			d++;
			w+=d;
		}
		for(int i=2,d=1,w=1;w<=t;i++){//i区间长度,d这一项,w项之和 
			for(int j=0;j<=t-w;j++){
				if(dp[j]+i<dp[j+w]){
					dp[j+w]=dp[j]+i;
					lst[j+w]=i;
				}
			}
			d++;
			w+=d;
		}
		
		if(dp[t]!=inf && dp[t]<=n){
			puts("YES");
			print(t);
		}
		else puts("NO");
	}
	return 0;
} 
  • 20
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值