Codeforces Round#631(Div.2) ABCD(E题待补)

题目入口戳我

A. Dreamoon and Ranking Collection

题意:给一个数组ai给你,你从中数组中选一些数字凑成1~p的所有数字,允许有x个数没有的,意思就是如果x = 2,你可以拿1,3,5,但是可以当做你已经凑出了1 ~ 5的所有数字了,因为空出来的数只有2和4,只有两个数小于等于x,问p最大是多少?

这里因为数据范围太小了,最大才100,就直接写了个暴力

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <set>
using namespace std;
int n,x;
int ans = 0;
const int N= 105;
int a[N];
set<int> myset;
int main(){
	int t;scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&x);
		int ans = x;
		int mx = 0;
		myset.clear();
		for(int i = 1;i <= n;i++){
			scanf("%d",a+i);
			myset.insert(a[i]);
			mx = max(mx,a[i]);	
		}
		for(int i = 1;i <= mx+x;i++){
			int cnt = 0;
			for(int j = 1;j <= i;j++){
				if(!myset.count(j)) cnt++;
			}
			if(cnt <= x) ans = max(ans,i);
		}
		printf("%d\n",ans);
	}
	
	return 0;
}

B. Dreamoon Likes Permutations

题意:给你一个n个元素的数组,你中间划一刀分开两半,要求一半是1~x的排列,另一半是1 ~ y的排列,问一共有几种切法,输出几种和各自的x和y

怎么判断一段是不是1 ~ x的排列,首先这里每个数字都只能出现一次,其次这段的和要是以1为首项,公差为1,项数位这段的长度的等差数列之和,所以先求个前缀和,其次记录一下出现了两次的数字的情况有几次,注意如果这个数组有一个数字出现了三次,是没有答案的;

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <set>
using namespace std;
typedef long long ll;
vector<int> ans;
int n;
const int N = 2e5+10;
int cnt,a[N],num[N],num2[N];
ll sum[N]; 
inline int read(){
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
   return s*w;
}
void init(){
	for(int i = 1;i <= n;i++){
		num[i] = num2[i] = 0;
	}
	cnt = 0;
	ans.clear();
}
int main(){
	int t;scanf("%d",&t);
	while(t--){
		bool f = 0;
		n = read();
		init();
		for(int i = 1;i <= n;i++){
			a[i] = read();
			sum[i] = (sum[i-1]+1ll*a[i]);
			num[a[i]]++;
			if (num[a[i]] == 2) cnt++;
			else if(num[a[i]] > 2) f = 1;
		}		
		if(f){puts("0");continue;}
		for(int i = 1;i <= n;i++){
			num2[a[i]]++;
			if(num2[a[i]] > 1) break;
			if(num[a[i]] == 2) cnt--;
			if(cnt == 0){
				ll sum1 = (1ll*(1+i)*i)>>1;
				ll sum2 = (1ll*(n-i)*(1+n-i))>>1;
				if(sum[i] == sum1&&sum[n]-sum[i] == sum2) ans.push_back(i);
			}	 
		}
		printf("%d\n",ans.size());
		for(int i = 0;i < ans.size();i++){
			printf("%d %d\n",ans[i],n-ans[i]);
		}
	}
	return 0;
}

C. Dreamoon Likes Coloring

首先,我觉得C题比D题要难想多了
题意: 有n个格子,然后你去涂m种颜色,每种颜色涂一次,第i次涂第i种颜色,且你涂第i次颜色时会涂掉连续的一段长为Li的格子,后面涂的颜色会覆盖掉前面涂的颜色,让你构造出你每次涂颜色的起点使得每个格子都有颜色,且每种颜色都至少出现在一个格子上,如果构造不出来输出-1;

首先一个比较好想出来的特判是,如果Li全部加起来都小于n,显然是不能让每个格子都有颜色的,输出-1;
因为后面涂的颜色会覆盖掉前面的,且要把所有格子涂满,那么先从最后一种颜色开始想,从后往前涂,对于第i种颜色它的最大起点是n-Li+1,最小的起点是i(因为至少要留下i-1个格子给前面涂,否则接下来就会把它全覆盖掉了),若最大起点和最小起点也不符合,那么也是-1;
然后剩下的是可行的,就要构造,额,实在没想到怎么构造~~~~
是看了官方题解:

首先如果可行,有多种构造方法;
我们只取其中一种,就让Pm = n - Lm+1,然后从前往后涂色,Pi+1必须要小于或等于Pi+Li,才能保证不会在中间漏格子没涂色,即有Pi >= Pi+1 - Li
然后继续缩,即有 P1 >= P2 - L2 >= P3 - L1 - L2…>= Pm - L1+L2+Lm-1 = n - L1-L2-…-Lm+1,可以推出来Pi >= n - sum[i] +1 (sum[i]为后缀和,表示Li+Li+1+…+Lm)然后还有前面的一个条件Pi >= i 才能保证第 i 种颜色不被后面的颜色覆盖,在两个约束条件中取个交集;

#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int n,m;
ll l[N],sum;
int main(){
	scanf("%d%d",&n,&m);
	bool f = 1;
	for(int i = 1;i <= m;i++){
		scanf("%lld",l+i);
		sum+=l[i];
		if(n-l[i]+1 < i) f = 0;
	}
	if(sum < n) f = 0;
	if(!f) { puts("-1");return 0;}
	for(ll i = 1;i <= m;i++){
		cout<<max(i,n-sum+1)<<" ";
		sum-=l[i];
	} 
	return 0;
}

D. Dreamoon Likes Sequences

题意:让你构造一个数列,这个数列至少要有一个元素,它需要满足它是严格递增的,且它的异或前缀和也是严格递增的,可用元素范围为1 ~ d,问你能构造出多少个这样的数列;

首先考虑异或前缀和递增这一点,要做到这个即第一个选了x,那么要接y,那么y至少要比x二进制高一位,为什么呢,因为因为要严格递增所以y至少要大于x,然后如果x和y二进制高位相同,异或起来最高位就是0了,然后无论低位异或结果是什么都不能让异或前缀和递增了,所以至少要二进制高一位,即如果x选了5,那么y至少要用8以上;类似分组背包【1】一组,【2,3】一组,【4,5,6,7】一组,【8,9,10,11,12,13,14,15】一组,同组元素最多选一个,至少选一个元素,用计数DP来做,dp[i]表示以第i组结尾的数列有几个,显然dp[1] = 1,然后以第i组结尾,倒数第二个可以什么都不放,就是cnt[i],cnt[i]就是这组有几个元素,也可以倒数第二个放小于i的组的元素就是dp[j]*cnt[i](j < i), 具体请看代码,很难描述清楚;

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
ll d,m;
const int N = 40;
int idx;
ll dp[N];//以第i组为结尾的数列的个数
ll cnt[N];//每一组的数量 
void init(){
	ll l = 1,r;
	idx = 0;
	while(l <= d){
		r = min(d,(l<<(1ll))-1);
		cnt[++idx] = r-l+1;
		l = r+1;
	} 
} 
inline ll read(){
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
   return s*w;
}
int main(){
	int t;scanf("%d",&t);
	while(t--){
		d = read();m = read();
		init();
		memset(dp,0,sizeof(dp));
		dp[1] = 1;
		for(int i = 2;i <= idx;i++){
			dp[i] = cnt[i];
			for(int j = 1;j < i;j++){
				dp[i]+=(dp[j]*cnt[i]);
				dp[i]%=m;
			}
		}
		ll res = 0;
		for(int i = 1;i <= idx;i++){
			res+=dp[i];
			res%=m;
		}
		printf("%lld\n",res);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值