Codeforces Round #690 (Div. 3) A~F题解

原文链接

打着打着,学校停电了,gank了一波,应该是能写的更快的。

A 题目链接

题意

给你一个数组,按顺序输出最左、最右、次左、次右、次次左、次次右…

解题思路

水题,定义一左一右的两个指针去模拟就好了。

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<iostream>
#include<algorithm>
//#include<chrono>
#include<vector>
#include<list>
#include<queue>
#include<string>
#include<set>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
#define LL long long
#define li i<<1
#define ri i<<1|1
using namespace std;
inline LL read()
{
    char c=getchar();LL x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int maxn=305;
LL t,n;
LL a[maxn],l,r;
int main()
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);
	t=read();
	while(t--){
		n=read();
		for(int i=1;i<=n;++i)
			a[i]=read();
		l=1,r=n;
		for(int i=1;i<=n;++i){
			if(i&1)
				printf("%lld ",a[l++]);
			else
				printf("%lld ",a[r--]);
		}
		printf("\n");
	}
	return 0;
}

B 题目链接

题意

给你一个数字串,你可以删除连续的一段,也可以不删除,问能否构成‘2020’。

解题思路

水题。最后要构成‘2020’,就要删除len-4长度的连续的串,枚举删除的位置,去判断剩余的4个数字是否能构成’2020’即可。

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<iostream>
#include<algorithm>
//#include<chrono>
#include<vector>
#include<list>
#include<queue>
#include<string>
#include<set>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
#define LL long long
#define li i<<1
#define ri i<<1|1
using namespace std;
inline LL read()
{
    char c=getchar();LL x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int maxn=205;
char s[maxn];
LL t,n;
bool init(){
	LL len=strlen(s+1);
	if(len<4)
		return false;
	if(s[1]=='2'&&s[2]=='0'&&s[3]=='2'&&s[4]=='0')
		return true;
	if(s[1]=='2'&&s[2]=='0'&&s[3]=='2'&&s[len]=='0')
		return true;
	if(s[1]=='2'&&s[2]=='0'&&s[len-1]=='2'&&s[len]=='0')
		return true;
	if(s[1]=='2'&&s[len-2]=='0'&&s[len-1]=='2'&&s[len]=='0')
		return true;
	if(s[len-3]=='2'&&s[len-2]=='0'&&s[len-1]=='2'&&s[len]=='0')
		return true;
	return false;
}
int main()
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);
	t=read();
	while(t--){
		n=read();
		scanf("%s",s+1);
		if(init())
			printf("YES\n");
		else
			printf("NO\n");
	}
	return 0;
}

C 题目链接

题意

水题。给你一个正数x。找到一个最小的正整数,它的位数和等于x,并且所有数字都是不同的。

解题思路

很明显由1~9的数字最多构成45,要求最小,则可以将最大的数放在个位,次大的放在十位,以此类推。

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<iostream>
#include<algorithm>
//#include<chrono>
#include<vector>
#include<list>
#include<queue>
#include<string>
#include<set>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
#define LL long long
#define li i<<1
#define ri i<<1|1
using namespace std;
inline LL read()
{
    char c=getchar();LL x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
LL t,n;
LL get_ans(LL a){
	if(a>45)
		return -1;
	LL ans=0,cas=0;
	for(int i=9;i>=1;--i){
		if(a>=i){
			ans+=i*pow(10,cas++);
			a-=i;
		}
	}
	return ans;
}
int main()
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);
	t=read();
	while(t--){
		n=read();
		LL ans=get_ans(n);
		printf("%lld\n",ans);
	}
	return 0;
}

D 题目链接

题意

有一个数组,每次可以合并相邻的两个位置,求让数组内的所有的元素都是相等时,最小合并次数。

解题思路

要求所有元素都相等,sum为所有a[i]之和,则此时元素的值一定是sum的因子,然后去枚举因子p,将数组分为sum/p份,对于每一份来说,它们肯定是连续的一整段,去判断是否可以满足要求即可。因为最后是有sum/p份,所以合并次数为n-sum/p。

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<iostream>
#include<algorithm>
//#include<chrono>
#include<vector>
#include<list>
#include<queue>
#include<string>
#include<set>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
#define LL long long
#define li i<<1
#define ri i<<1|1
using namespace std;
inline LL read()
{
    char c=getchar();LL x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int maxn=3005;
LL t,n,a[maxn],sum;
bool init(LL p){
	LL q=0;
	for(int i=1;i<=n;++i){
		q+=a[i];
		if(q>p)
			return false;
		if(q==p)
			q=0;
	}
	return true;
}
int main()
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);
	t=read();
	while(t--){
		n=read();
		for(int i=1;i<=n;++i)
			a[i]=read();
		sum=0;
		for(int i=1;i<=n;++i)
			sum+=a[i];
		for(int p=1;p<=sum;++p)
			if(sum%p==0&&init(p)){
				printf("%lld\n",n-sum/p);
				break;
			}
	}
	return 0;
}

E1 题目链接

题意

有n个数字,让你求有多少个三元组满足max(a[i],a[j],a[k])-min(a[i],a[j],a[k])<=2 (i<j<k)。

解题思路

每次枚举a[i],得到a[i],a[i]+1,a[i]+2的个数,每次从这三类数里头选出三个数字,肯定满足要求,a[i]那组一定要选这样枚举下一个a[i]时就不会有重复的情况。方案数为:在a[i]中选择3个、在a[i]中选择2个且在a[i]+1中选择1个、在a[i]中选择2个且在a[i]+2中选择1个、在a[i]中选择1个且在a[i]+1中选择2个、在a[i]中选择1个且在a[i]+2中选择2个、在a[i]中选择1个且在a[i]+1中选择1个且在a[i]+2中选择1个。用排列组合去写。注意一点,由于T挺大的,直接memset会超时,没注意贡献一发罚时,先用map存一下用过哪些再去初始化。

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<iostream>
#include<algorithm>
//#include<chrono>
#include<vector>
#include<list>
#include<queue>
#include<string>
#include<set>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
#define LL long long
#define li i<<1
#define ri i<<1|1
using namespace std;
inline LL read()
{
    char c=getchar();LL x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int maxn=2e5+5;
LL t,n,m,k,conn[maxn+5],a;
LL C(LL n, LL r)     //返回C(n,r)
{
	if(n<r)
		return 0;
	LL sum = 1;
	for (LL i = 1; i <= r; i++)
		sum = sum*(n + 1 - i) / i;
	return sum;
}
map<int,int> use;
LL get_ans(LL pos){
	LL a=conn[pos],b=conn[pos+1],c=conn[pos+2];
	return C(a,3)+C(a,2)*C(b,1)+C(a,2)*C(c,1)+C(a,1)*C(b,2)+C(a,1)*C(c,2)+a*b*c;
}
int main()
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);
	t=read();
	while(t--){
		for(auto it=use.begin();it!=use.end();++it){
			int fi=it->first;
			conn[fi]=0;
		}
		use.clear();
//		memset(conn,0,sizeof conn);
		n=read();
		m=3;
		k=2;
		for(int i=1;i<=n;++i){
			a=read();
			++conn[a];
			use[a]=1;
		}			
		LL ans=0;
		for(auto it=use.begin();it!=use.end();++it){
			int fi=it->first;
			ans+=get_ans(fi);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

E2也是能写出来的,比赛时没想到,差一点,明天再来跟新。


跟新时间 2020-12-17 16:17
原本是打算昨天下午跟新的,室友找我打游戏就咕咕咕了
回到正题,比赛期间只看了下E2,没去看F题,其实F题也是很简单的,如果不停电应该能写出来。

E2 题目链接

题意

有n个数字,让你求有多少个m元组满足max(a[i_1],a[i_2],…a[i_m])-min(a[i_1],a[i_2],…a[i_m])<=k (i_1<i_2…<i_m)。

解题思路

E1的升级版。也是一样的做法,每次枚举a[i],得到a[i]的个数和a[i+1,i+k]个数的和sum。(当时写E1时死脑筋的把后面的情况全部枚举出来,其实没必要,只要在sum中选择就好了,不用再细分),每次只要再a[i]中选择p个,再从sum中选择m-p个(p>0),这样枚举下一个a[i]时就不会有重复的情况。之后就用组合数求就好了,由于要取模,所以先阶乘打表预处理一下。
由于T挺大的,直接memset会超时,先用map存一下用过哪些再去初始化。
 
还有就是求sum的过程,如果每次从for循环的去加[i+1,i+k]的和,O(n*k)会超时,考虑使用上次求出的sum递推去求这次的sum。
这里定义一下SUM(a[i,j])表示:a[i]的个数+a[i+1]的个数+…+a[j]的个数。
设fi为这次枚举的a[i]的值,last为上次枚举的fi,last_sum为上个状态的和,即last_sum=SUM(a[last+1,last+k])。以上三个值都是已知的,求此时的sum,即a[fi+1,fi+k]个数的和。如果fi>=last+k,则sum只能O(k)去计算,如果fi<last+k,则可以利用last_sum,先让sum=last_sum,再减去SUM(a[last+1,fi]),最后再加上SUM(a[last+k+1,fi+k]),即sum=last_sum-SUM(a[last+1,fi])+SUM(a[last+k+1,fi+k])。仔细思考一下,因为是map去枚举的a[i],所以last到fi之前是没有数的,所以SUM(last+1,fi-1)=0,所以最后sum=last_sum-a[i]的个数+SUM(a[last+k+1,fi+k])。时间复杂度为O(n)。
代码中并没有用到last_sum,这么说只是方便理解,最好再草稿纸上画画。注意下for循环的上界,这里就不说了,细节见代码吧。

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<iostream>
#include<algorithm>
//#include<chrono>
#include<vector>
#include<list>
#include<queue>
#include<string>
#include<set>
#include<map>
//#include<unordered_set>
#include<unordered_map>
#define LL long long
#define li i<<1
#define ri i<<1|1
using namespace std;
inline LL read()
{
    char c=getchar();LL x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int maxn=2e5+5;
const int mod=1e9+7;
LL t,n,m,k,a,conn[maxn],sum[maxn];
LL fact[maxn],infact[maxn];  // 阶乘  逆元 
LL inv[maxn];
void init(){  // 预处理阶乘与阶乘逆元 
	fact[0]=infact[0]=inv[1]=1;
	for(int i=2;i<maxn;++i)
		inv[i]=(LL)(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1;i<maxn;++i){
		fact[i]=fact[i-1]*i%mod;
		infact[i]=infact[i-1]*inv[i]%mod;  // 线筛求逆元 若mod不为质数 则用扩欧求逆元 
	}
}
LL C(LL n,LL r){
	if(n<r)
		return 0;
	return fact[n]%mod*infact[r]%mod*infact[n-r]%mod;
}
map<int,bool> use;
int main()
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);
	init();
	t=read();
	while(t--){
		n=read();
		m=read();
		k=read();
		for(auto it=use.begin();it!=use.end();++it)
			conn[it->first]=0;
		use.clear();
		//memset(conn,0,sizeof conn);
		for(int i=1;i<=n;++i){
			a=read();
			++conn[a];
			use[a]=true;
		}
		LL ans=0,last=0,sum=0,up;
		for(int i=last+1;i<=last+k;++i)
			sum+=conn[i];
		for(auto it=use.begin();it!=use.end();++it){
			LL fi=it->first;
			up=min(fi+k,n);
			if(fi>=last+k){
				sum=0;
				for(int i=fi+1;i<=up;++i)
					sum+=conn[i]; 
			}else{
				sum-=conn[fi];
				for(int i=last+k+1;i<=up;++i)
					sum+=conn[i];
			}
			last=fi;
			up=min(conn[fi],m);
			for(int i=1;i<=up;++i){
				ans+=C(conn[fi],i)*C(sum,m-i)%mod;
				ans%=mod;
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

F 题目链接

题意

有n个区间,问你最少删除多少个区间,使得存在一个区间与其它的区间都有交集。

解题思路

这题我觉得比E2简单,但是比赛时做对的人比E2少好多。
将区间的L和R分别拆开然后从小到大排序,得到排序后的L数组与R数组。
枚举每个区间,得到l与r,再R数组中二分查找第一个大于等于l的位置pos,pos-1就是在此区间左侧且不相交的区间个数。再L数组中二分查找第一个大于r的位置pos,n-pos+1就是在此区间右侧且不相交的区间个数。相加求个最小值即可。

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<iostream>
#include<algorithm>
//#include<chrono>
#include<vector>
#include<list>
#include<queue>
#include<string>
#include<set>
#include<map>
//#include<unordered_set>
//#include<unordered_map>
#define LL long long
#define li i<<1
#define ri i<<1|1
using namespace std;
inline LL read()
{
    char c=getchar();LL x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int maxn=2e5+5;
struct node{
	LL l,r;
};
node a[maxn]; 
LL t,n,a_l[maxn],a_r[maxn];
int main()
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);
	t=read();
	while(t--){
		n=read();
		for(int i=1;i<=n;++i){
			a[i].l=read();
			a[i].r=read();
			a_l[i]=a[i].l;
			a_r[i]=a[i].r;
		}
		sort(a_l+1,a_l+1+n);
		sort(a_r+1,a_r+1+n);
		LL ans=n;
		for(int i=1;i<=n;++i){
			LL l=a[i].l,r=a[i].r;
			LL pos_l=lower_bound(a_r+1,a_r+1+n,l)-a_r-1;
			LL pos_r=upper_bound(a_l+1,a_l+1+n,r)-a_l;
			ans=min(ans,pos_l+n-pos_r+1);
		}
		printf("%lld\n",ans);
	}
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值