Codeforces Round 964 (Div. 4) (A~G1)题解

题目传送门

A. A+B Again?

题意:给一个数,要求求出它的各个位的数字之和

思路:用字符串表示这个数,然后直接遍历就可以相加了:

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>

using namespace std;

int ans;

void solve() {
	string a;
	cin>>a;
	ans=0;
	for(int i=0;i<a.size();i++){
		ans+=(a[i]-'0');
	}
	cout<<ans<<'\n';
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

B. Card Game

题意:给四个数,前两个数是Suneet 的,后两个是Slavic的,必须要一张牌严格大于另一张牌才有胜负,如果相等就是平局,总共有两个回合(因为每个人有两张牌),文Suneet胜利的情况有多少种。WA了好几次,中途看到通过率只有20%,我还以为题目出问题了,没想到是我出问题了。

思路:先考虑一共有多少种情况,有:a1与b1 + a2与b2 a1与b2 + a2与b1a2与b1 + a1与b2a2与b2 + a1与b1 四种情况,实际上就是两种情况,因为后两者只是前面的换个位置就行,所以只需要判断前两种情况就可以了,判断的时候先判断是不是相同的,如果是相同的话,下一张牌就决定胜负了,否则需要结合上一张牌来看,直接看代码会比较容易理解:

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>

using namespace std;

int ans;


void solve() {
	int a1,a2,b1,b2;
	cin>>a1>>a2>>b1>>b2;
	ans=0;
	//if(a1>a2)swap(a1,a2);
	//if(b1>b2)swap(b1,b2);
	
	if(a1==b1){
		if(a2>b2){
			ans++;
		}
	}else{
		if(a1>b1){
			if(a2>=b2){
				ans++;
			}
		}
	}
	
	if(a1==b2){
		if(a2>b1){
			ans++;
		}
	}else{
		if(a1>b2){
			if(a2>=b1){
				ans++;
			}
		}
	}
	ans*=2;
	cout<<ans<<'\n';
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

C. Showering

题意:

给出一天已经占了的时间段,问是否可以有空闲时间来洗澡而不影响其他工作时间?

思路:看似很复杂,实际上只需要在输入开始时间和结束时间的时候判断上一个工作结束的时间到这一个工作开始的时间间隔是不是够洗澡,当然一开始的是一个工作结束时间就是 0,如果够洗澡,标记一下flag,后续直接输出YES就行,需要注意的是,在说明完工作间隔后,并不一定能将整天占满,比如工作12点结束,他还有半天时间来洗澡这种情况,所以输入结束后还需判断一下一天的剩下时间够不够洗澡:

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>

using namespace std;

int ans;

void solve() {
	int n,s,m;
	cin>>n>>s>>m;
	int lastl=0,lasrr=0;
	int nl,nr;
	bool flag=false;
	for(int i=1;i<=n;i++){
		cin>>nl>>nr;
		if(nl-lasrr>=s){
			flag=true;
		}
		lastl=nl;
		lasrr=nr;
	}
	if(m-lasrr>=s)flag=true;
	cout<<(flag?"YES\n":"NO\n");
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

D. Slavic's Exam

题意:给两个字符串,问第一个字符串中如果可以把'?'改为任何一个字母,能不能得到一个子序列是第二个字符串?如果可以,就输出YES并且附带上第一个字符串没有'?'的样子(就是把?全部改了),否则输出No。

思路:直接遍历字符串a(第一个字符串),给一个指针指向b(第二个字符串)的开头,利用贪心的思想,如果第一个字符串当前字符与第二个字符串的相等,则两个指针都后移,当出现'?'时,我们需要注意,如果现不“使用”这个问号,后面这个问好将不会有任何作用,不能使得能够构成第二个字符串的重要因素,所以遇到'?'直接将其改为第二个字符串的当前指针所指字符,然后两个指针都后移。问题来了,如果第二个字符串已经遍历丸辣,该怎么办,这个时候我们就特判一下,如果第二个指针大于了第二个字符串长度,我们可以直接continue掉,如果出现'?',我们只需要将它改为任意合理字符就行了:

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>

using namespace std;

int ans;

void solve() {
	string a, b;
	cin >> a >> b;
	int cot = 0;
	for (int i = 0; i < a.size(); i++) {
		if (cot < b.size()) {
			if (a[i] == '?') {
				a[i] = b[cot];
				cot++;
				continue;
			}
			if (a[i] == b[cot]) {
				cot++;
				continue;
			}
		}else{
			if(a[i]=='?'){
				a[i]=a[i-1];
			}
		}
	}
	if(cot==b.size()){
		cout<<"YES\n";
		cout<<a<<'\n';
	}else{
		cout<<"NO\n";
	}
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

E. Triple Operations

题意:给定一个区间[l,r],求出将这个区间的数都变成0的次数最少是多少,每次可以选择区间内的两个数(修改后可能不属于该区间,但是还是认为是这个区间的)让以告诉乘3,以告诉除3并且向下取整。

思路:因为0乘任何数为0,所以这样就不必要再有额外的操作,只需要先将其中一个数变为0,剩下的数就可以一直除3向下取整,直到为0,那么将谁变为0好呢?根据手操可以得到,将一个数变成1/3倍的时候,不得不将另一个数变为原来的3倍,之后消到该数的时候无疑会增加与变为0的数相同操作的次数。比如说1 2 3 4,将1除以3得到0,无论将剩下的任何一个数乘以3,到更新到该数的时候操作都会多1次,若是6 7 8 9,将6变为0需要除以两次3,7,8,9无论选谁,到时候都会无法避免这个问题。那么我们需要变为0的数就必然是最小的数,因为最小的数变为0要的操作数最少,例如将9变为0需要3次,但将6变为0只需要两次,这样后续将其他数变为0也会相应的减少次数了。接下来只需要讨论每次将区间的数变为0的操作数就行了,感觉这个具有一种规律,我不由得想到打表操作,将其次数打出来。那么该如何打表,手动推演一下:

    dp[0]=0;
    dp[1]=1;
    dp[2]=1;
    dp[3]=2;    
    dp[4]=2;    
    dp[5]=2;    
    dp[6]=2;    
    dp[7]=2;

可以看到,dp[i]=dp[i/3]+1;那么打表就打出来了,数据比较多,就算是打表也无法在有限的时间里完成这个题,因为数据大小在1e4*2e5也就是2e9的数据,单个枚举是不行的,由于我们需要的是dp[l]+dp[l+1]+dp[l+2]+......+dp[r],这个很眼熟,就算前缀和了,我们利用前缀和优化一下时间就行,这样可以在一次询问的时间里面得到答案。接下来直接看代码,记住答案需要多加一份dp[l],原因前面也说过了:

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>

using namespace std;
using ll=long long;

const ll N=2e5+5;
ll dp[N],sum[N];

void solve() {
	int l,r;
	cin>>l>>r;
	ll ans=dp[l];
	ans+=sum[r]-sum[l-1];
	cout<<ans<<'\n';
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int t;
	cin >> t;
	dp[0]=0;
	dp[1]=1;
	dp[2]=1;
	dp[3]=2;
	sum[0]=0;
	sum[1]=1;
	sum[2]=2;
	sum[3]=4;
	for(int i=4;i<=200001;i++){
		dp[i]=dp[i/3]+1;
		sum[i]=sum[i-1]+dp[i];
	}

	while (t--) {
		solve();
	}

	return 0;
}

F. Expected Median

题意:

也就是说:给出长度为 n的 01 序列,求其所有长度为 k 的子序列(可以不连续)的中位数之和。

不难发现,只有中位数为 1 才能对答案有用,即子序列中 1 的个数至少为(k+1)/2个,我们可以枚举子序列中 1的个数 i,从(k+1)/2开始枚举,剩下的数都是0,这就算从1中选取i个1,再选k-i个0的方案数,即是\sum_{(k+1)/2}^{min(cont1,k)}C_{_{cont1}}^{^{^{i}}}*C_{cont0}^{k-i}.这里需要使用到阶乘的乘除运算,我们可以先打表预处理一下阶乘的值,然后再继续算。因为b的-1次方是1/b,但是可能会变成分数,在取模没法取,所以直接乘一个更大的数,最后取模。在模 p 意义下,一个数除以 a 的值等于其乘以 a 的逆元的值。在模 p 意义下,当我们说一个数 b 除以 a 的值等于其乘以 a 的逆元的值时,我们实际上是在讨论模运算中的除法逆运算。但需要注意的是,在模运算中,并不是所有的数都有逆元,特别是当 a 和 p 不互质(即 gcd(a,p)!=1)时,a 在模 p 下没有逆元。

现在,假设我们有一个数 b,并且我们想知道 b 除以 a 在模 p 下的结果。在普通的算术中,这可以表示为 a/b​,但在模运算中,我们没有直接的除法操作。然而,如果 a 在模 p 下有逆元 a−1,那么我们可以将除法转化为乘法:

b÷a≡b⋅a−1(modp)

这里,b⋅a−1 就是 b 除以 a 在模 p 下的结果。简单来说,一个数除以 a 的值等于其乘以 a 的逆元的值,

在模 1e9+7(即 109+7=1000000007)下,寻找一个数 a 的逆元,我们首先需要确认 a 和 1000000007 是互质的,即它们的最大公约数(GCD)为1。如果 a 是 1000000007 的倍数,那么 a 没有逆元。

对于大多数非零的 a(即 a!=0 且 a!≡0(mod1000000007)),我们可以使用扩展欧几里得算法(Extended Euclidean Algorithm)来找到 a 的逆元。但是,由于 1000000007 是一个质数,且 1000000007−1=1000000006 是偶数,我们可以利用费马小定理(Fermat's Little Theorem)的一个推论来更高效地计算逆元。

费马小定理的推论是:如果 p 是一个质数,且 a 不是 p 的倍数,那么 ap−1≡1(modp)。由此,我们可以得出 a⋅a^(p−2)≡1(modp),即 a^(p−2) 是 a 在模 p 下的逆元。说了这么多,接下来直接看代码:

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>

using namespace std;
using ll=long long;

const ll mod=1e9+7,N=2e5+5;

ll pre[N];

ll pqmi(ll a,ll b){//快速幂
	ll res=1;
	while(b){
		if(b&1){
			res*=a;
			res%=mod;
		}
		a*=a;
		a%=mod;
		b>>=1;
	}
	return res;
}

ll C(ll x,ll y){//计算组合大小
	return pre[x]*pqmi(pre[y]*pre[x-y]%mod,mod-2)%mod;
}

void solve() {
	ll n,k,cnt0=0,cnt1=0,ans=0;
	
	cin>>n>>k;
	
	for(int i=1;i<=n;i++){
		int x;
		cin>>x;
		if(x){
			cnt1++;
		}else{
			cnt0++;
		}
	}
	for(int i=(k+1)/2;i<=(cnt1,k);i++){
		if(k-i<=cnt0){
			ans = (ans + C(cnt1, i) * C(cnt0, k - i) % mod) % mod;
		}
	}
	cout<<ans<<'\n';
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int t;
	cin >> t;
	pre[0]=1;
	for(int i=1;i<N;i++){//预处理i的阶乘
		pre[i]=(pre[i-1]*i)%mod;
	}
	
	while (t--) {
		solve();
	}

	return 0;
}

G1. Ruler (easy version)

题意:给了一个烂尺子,里面有一个数据没了,但是后面接上了,比如说123567,虽然只有6cm,但是实际测量是7cm,数据约定答案在[2,999],里面,每次输入

? a b ,系统会给出测量后的a*b作为输入,输入给评测,最多可以询问10次。

思路:

因为尺子的1刻度是完好的,考虑形式为 ?  1  y 的查询。自 ans> 1起,矩形的高度总是正确测量的。也就是说

  • 如果是 y<ans,则评测输入为 y 。
  • 如果是 y≥ans,则评测输入为 y+1 。

这是一个单调函数,这意味着我们可以使用二分搜索找出响应为 y+1的最小 y 。查询次数为 ⌈log2⁡(1000)⌉=10 。

接下来直接看代码:

#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>

using namespace std;

void solve() {
	int l=1,r=1000;
	while(l+1!=r){
		int mid=l+r>>1;
		cout<<"? 1 "<<mid<<endl;
		int ci;
		cin>>ci;
		if(ci==mid){
			l=mid;
		}else{
			r=mid;
		}
	}
	cout<<"! "<<r<<endl;
}

int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int t;
	cin >> t;
	while (t--) {
		solve();
	}

	return 0;
}

G2. Ruler (hard version)

没看,已经不想看了。

END QWQ.

好了,这就是本次的题解,如果有疑问可以在评论区讨论或者私信我,今天的学习到此结束了,下次见,拜拜吖!

  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值