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

文章讲述了在一场编程比赛中,选手们遇到的不同题目,包括寻找满足特定条件的字符串子序列问题、合理拆分问题以保持比赛平衡、验证字符串组合是否涵盖所有可能性以及涉及数论的友谊值计算。解题策略和代码实现都被详细描述。
摘要由CSDN通过智能技术生成

不愧是div2,很难,不过有状态,ABC直接速通了,然后D是个数论,赛时含恨TLE6,赛后立刻补掉了。E题看着像线段树。

比赛链接


A. We Got Everything Covered!

题意:

给你两个正整数 n n n k k k

您的任务是找出一个字符串 s s s ,使得所有用前 k k k 个小写英文字母构成长度为 n n n 的字符串都可以作为 s s s 的子序列出现。

如果有多个答案,则打印长度最小的答案。如果仍有多个答案,可以打印其中任意一个。

注: 如果从 b b b 中删除一些字符(可能为零)而不改变其余字符的顺序即可得到 a a a ,则字符串 a a a 称为另一个字符串 b b b 的子序列。

思路:

上来就是一道好题,考虑到长为 n n n 的字符串每一个位置都要能取到前 k k k 个字母,那么原字符串在分成 n n n 个大区的情况下,每个大区里还要有 k k k 个字母。相反,只要满足了这个条件,大区里面的字母是如何排列的也无所谓,它一定可以拿出所有用前 k k k 个小写英文字母构成的长度为 n n n 的字符串。

每个大区内的每种字母至少要有一个,那么就每种给一个,那么所有字符一定不会少于 n k nk nk,一旦少于 n k nk nk 就必然会有一种字母少于 n n n 个,肯定就凑不出来 n n n 个这种字母的子字符串。因此这种构造方法就是最少的了。

code:

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

int T,n,k;

int main(){
	cin>>T;
	while(T--){
		cin>>n>>k;
		for(int i=1;i<=n;i++)
			for(int j=0;j<k;j++)
				printf("%c",'a'+j);
		puts("");
	}
	return 0;
}

B. A Balanced Problemset?

题意:

杰伊成功地创造了一个难度为 x x x 的问题,并决定将其作为 Codeforces Round #921 的第二个问题。

但 Yash 担心这个问题会让比赛变得非常不平衡,协调员会拒绝接受它。因此,他决定把它分解成一个由 n n n 个子问题组成的问题集,使得所有子问题的难度都是一个正整数,并且它们的总和等于 x x x

协调人阿列克谢将问题集的平衡值定义为问题集 中所有子问题难度的 gcd(greatest commen divisor 最大公约数)。

求如果 Yash 最佳地选择子问题的难度,他能达到的最大平衡值。

思路:

假设答案就是 k k k,那么拆分出来的 n n n 个数一定都是 k k k 的倍数。反过来说,如果要想答案是 k k k,那么拆出来的数也都必须是 k k k 的倍数。这样的数最多可以拆出来 x / k x/k x/k 个,多余的加给最后一个数即可。也就是如果 n ≤ x / k n\le x/k nx/k,这个平衡值 k k k 就可以被凑出来。

所以想要最大的 k k k,我们只需要找到最小的 x / k x/k x/k,使得 n ≤ x / k n\le x/k nx/k 即可。可以 O ( x ) O(\sqrt x) O(x ) 暴力枚举因数,然后找其中满足条件最小的因数即可,答案就是 x x x 除以这个因数。

code:

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

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

int main(){
	cin>>T;
	while(T--){
		cin>>x>>n;
		cnt=0;
		for(int i=1;i*i<=x;i++){
			if(x%i==0){
				a[++cnt]=i;
				if(i*i!=x)a[++cnt]=x/i;
			}
		}
		sort(a+1,a+cnt+1);
		cout<<x/(*lower_bound(a+1,a+cnt+1,n))<<endl;
	}
	return 0;
}

C. Did We Get Everything Covered?

题意:

给你两个整数 n n n k k k 以及一个字符串 s s s

您的任务是检查是否所有长度为 n n n 的字符串都可以用前 k k k 种小写英文字母组成,并作为 s s s 的子序列出现。如果答案是否定的,那么您还需要打印一个长度为 n n n 的字符串,该字符串可以用前 k k k 种小写英文字母组成,但不会作为 s s s 的子序列出现。

如果有多个答案,您可以打印任意一个。

注: 如果从 b b b 中删除一些字符(可能为零)而不改变其余字符的顺序,就可以得到 a a a ,那么字符串 a a a 就被称为另一个字符串 b b b 的子序列。

思路:

好题啊,而且和A题是承接关系。

接着A题的思路,还是把一个字符串看成一个个大区,大区内包含所有的前 k k k 种字符串。找大区可以从前向后枚举每个位置,如果够 k k k 种字符,那么在这里分隔,前面就是一个大区,后面重新计数。如果总的大区个数不少于 n n n 个,那么显然是YES。

问题在于如何判断NO和找不存在的串。由于上面找大区的策略是贪心的,所以每个大区最后一个字符一定是从大区开始第一次出现,它在这个大区内是唯一的。那我们贪心地取这个位置最靠后的字母,这样如果大区个数少于 n n n 个,那么最后一个残缺的大区一定不包含至少一种字母,取它就可以构造出不存在的串了,答案显然就是NO了。

因此,我们先贪心地划分大区,然后判断大区个数是否不少于 n n n,如果少于,不存在的串可以每个大区取最后一个字母,最后一个残缺大区选择没有出现的字母,后面随便补即可得到。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
const int maxn=30;


int T,n,k,m;
string t;
set<char> s;

int itv[maxn],cnt;

int main(){
	cin>>T;
	while(T--){
		cin>>n>>k>>m>>t;
		s.clear();
		cnt=0;
		for(int i=0;i<t.length();i++){
			if(s.find(t[i])==s.end()){
				s.insert(t[i]);
				if(s.size()==k){
					itv[++cnt]=i;
					s.clear();
					if(cnt==n)break;
				}
			}
		}
		if(cnt==n)puts("YES");
		else {
			puts("NO");
			for(int i=1;i<=cnt;i++)
				printf("%c",t[itv[i]]);
			for(char tmp='a';tmp<='a'+k-1;tmp++)
				if(s.find(tmp)==s.end()){
					printf("%c",tmp);
					break;
				}
			for(int i=cnt+2;i<=n;i++)
				printf("a");
			puts("");
		}
	}
	return 0;
}

D. Good Trip

题意:

一个班有 n n n 个孩子,其中 m m m 对是朋友。第 i i i 对朋友的友谊值为 f i f_i fi

老师要去 k k k 次远足,每次远足她都会随机、等价、独立地选择一对孩子。如果选择了一对是朋友的孩子,他们的友谊值会永久增加 1 1 1 (教师可以多次选择一对孩子)。如果一对儿童不是朋友,那么他们的友谊值为 0 0 0 ,在以后的游览中不会改变。

求所有被选中参加远足的 k k k 对孩子的友谊值总和的期望值(在被选中时)。可以证明,这个答案总是可以用分数 p q \dfrac{p}{q} qp 表示,其中 p p p q q q 是共整数。计算 p ⋅ q − 1   m o d   ( 1 0 9 + 7 ) p\cdot q^{-1} \bmod (10^9+7) pq1mod(109+7)

思路:

稀罕,居然有数论。

先从现有条件入手,有 n n n 个人, m m m 个关系,每个关系有个友谊值 f i f_i fi。假如现在就只有一个关系,友谊值为 f i f_i fi

n n n 个人中随机选取两人的方法数有 t o t = C n 2 = n ( n − 1 ) 2 tot=C_n^2=\dfrac{n(n-1)}{2} tot=Cn2=2n(n1) 个,选到这对朋友的概率是 1 t o t \dfrac 1{tot} tot1,选不到的概率为 t o t − 1 t o t \dfrac {tot-1}{tot} tottot1

假如在 k k k 次远足中选中了 x x x 次这对朋友,那么总的可能性为: C k x ∗ ( 1 t o t ) x ∗ ( t o t − 1 t o t ) k − x = C k x ∗ ( t o t − 1 ) k − x t o t k C_k^x*\left(\dfrac 1{tot}\right)^x*\left(\dfrac {tot-1}{tot}\right)^{k-x}=C_k^x*\dfrac {(tot-1)^{k-x}}{tot^k} Ckx(tot1)x(tottot1)kx=Ckxtotk(tot1)kx

选中k次的贡献为: f i + ( f i + 1 ) + ( f i + 2 ) + . . . + ( f i + x − 1 ) f_i+(f_i+1)+(f_i+2)+...+(f_i+x-1) fi+(fi+1)+(fi+2)+...+(fi+x1) = ( f i + f i + x − 1 ) ∗ x 2 = ( 2 f i + x − 1 ) ∗ x 2 =\dfrac{(f_i+f_i+x-1)*x} 2=\dfrac{(2f_i+x-1)*x} 2 =2(fi+fi+x1)x=2(2fi+x1)x

那么期望为: C k x ∗ ( t o t − 1 ) k − x t o t k ∗ ( 2 f i + x − 1 ) ∗ x 2 C_k^x*\dfrac {(tot-1)^{k-x}}{tot^k}*\dfrac{(2f_i+x-1)*x} 2 Ckxtotk(tot1)kx2(2fi+x1)x

x可以从0到k枚举(不过x=0时式子一定等于0,所以可以从1开始枚举),所以这对朋友对答案期望的贡献为: ∑ x = 1 k C k x ∗ ( t o t − 1 ) k − x t o t k ∗ ( 2 f i + x − 1 ) ∗ x 2 \sum_{x=1}^{k} C_k^x*\dfrac {(tot-1)^{k-x}}{tot^k}*\dfrac{(2f_i+x-1)*x} 2 x=1kCkxtotk(tot1)kx2(2fi+x1)x = ∑ x = 1 k C k x ∗ ( t o t − 1 ) k − x t o t k ∗ ( x − 1 ) ∗ x 2 ‾ + ( ∑ x = 1 k C k x ∗ ( t o t − 1 ) k − x t o t k ∗ x ‾ ) ∗ f i =\underline{\sum_{x=1}^{k} C_k^x*\dfrac {(tot-1)^{k-x}}{tot^k}*\dfrac{(x-1)*x} 2}+\left(\underline{\sum_{x=1}^{k} C_k^x*\dfrac {(tot-1)^{k-x}}{tot^k}*x}\right)*f_i =x=1kCkxtotk(tot1)kx2(x1)x+ x=1kCkxtotk(tot1)kxx fi

发现划线部分可以预处理,之后对每对朋友关系按式子累加即可。

虽说式子推出来了,而且这个式子非常可做,不过如果计算方式太慢的还是会T的。预处理使用递推的方式来算,具体来说:

用变量 c c c 来递推 C k x C_k^x Ckx,用变量 f f f 来递推 ( t o t − 1 ) k − x t o t k \dfrac {(tot-1)^{k-x}}{tot^k} totk(tot1)kx,f1代表前一个画线式,f2代表后一个画线式。

只用一次循环就预处理完成, x x x 改为从 k k k 1 1 1 循环,这样 ( t o t − 1 ) (tot-1) (tot1) 这个式子的指数会从 0 0 0 k − 1 k-1 k1(不反向也可以,但是需要特判 k − x = 0 k-x=0 kx=0 t o t − 1 = 0 tot-1=0 tot1=0 时, f = 1 f=1 f=1),单独拿一个变量,每一趟乘 ( t o t − 1 ) (tot-1) (tot1) 即可。组合数从第 x x x 项到第 x − 1 x-1 x1 项递推如下式: C k x = k ∗ ( k − 1 ) ∗ ⋯ ∗ ( k − x + 1 ) x ∗ ( x − 1 ) ∗ ⋯ ∗ 1 C_k^x=\dfrac{k*(k-1)*\dots*(k-x+1)}{x*(x-1)*\dots*1} Ckx=x(x1)1k(k1)(kx+1) = k − x + 1 x ∗ k ∗ ( k − 1 ) ∗ ⋯ ∗ ( k − x + 2 ) ( x − 1 ) ∗ ⋯ ∗ 1 = k − x + 1 x ∗ C k x − 1 =\dfrac{k-x+1}{x}*\dfrac{k*(k-1)*\dots*(k-x+2)}{(x-1)*\dots*1}=\dfrac{k-x+1}{x}*C_k^{x-1} =xkx+1(x1)1k(k1)(kx+2)=xkx+1Ckx1
C k x − 1 = x k − x + 1 ∗ C k x C_k^{x-1}=\dfrac{x}{k-x+1}*C_k^x Ckx1=kx+1xCkx

f 1 f1 f1 f 2 f2 f2 就根据上面的式子每趟求和即可。

code:

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

int T,n,m,k;
ll tot;
//ll C[maxn],f[maxn];

ll qpow(ll a,ll b){
	ll base=a,ans=1;
	while(b){
		if(b&1){
			ans*=base;
			ans%=mod;
		}
		base*=base;
		base%=mod;
		b>>=1;
	}
	return ans;
}
inline ll inv(ll x){return qpow(x,mod-2);}


int main(){
	cin>>T;
	while(T--){
		scanf("%d%d%d",&n,&m,&k);
		tot=1ll*n*(n-1)/2%mod;
		
		ll c=1,f=inv(qpow(tot,k));
		ll ans=0,f1=0,f2=0;
		for(int x=k;x>=1;x--){
			f1=(f1+c*f%mod*(1ll*x*(x-1)/2%mod))%mod;
			f2=(f2+c*f%mod*x)%mod;
			c=c*x%mod*inv(k-x+1)%mod;
			f=f*(tot-1)%mod;
		}
		for(int i=1,a,b,fi;i<=m;i++){
			scanf("%d%d%d",&a,&b,&fi);
			ans=(ans+f1+f2*fi)%mod;
		}
		printf("%lld\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值