Codeforces Round #742 (Div. 2)

MEXor Mixup

m e x mex mex意思就是不在集合里面最小的正整数.所以给出一个 m e x mex mex那么他前面的所有的东西都是要选上的,然后前面的这些所有的异或之和(假设为 x x x)就可以预处理然后用 O 1 O1 O1的计算出来,因为给出的必须异或和为 y y y,所以我们还差一个 z = x   x o r   y z=x\ xor \ y z=x xor y,也就是 z   x o r   x = y z \ xor \ x=y z xor x=y,然后就是分情况了:

  • z = m e x z=mex z=mex就需要额外的两个数才行,因为不能取 m e x mex mex这个值嘛,不然 m e x mex mex就会变
  • z > m e x z>mex z>mex或者 0 < z < m e x 0<z<mex 0<z<mex,注意元素是可以重复的,所以就再要一个 z z z就行
  • z = 0 z=0 z=0,就不需要额外的了
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 400000
using namespace std;
int T,a[maxn],x,y;
signed main(){
	scanf("%d",&T);
	for(int i=1;i<=300000;i++) a[i]=a[i-1]^i;
	while(T--){
		scanf("%d %d",&x,&y);
		if(a[x-1]==y) {printf("%d\n",x);continue;}
		int temp=a[x-1]^y;
		if(temp>x) printf("%d\n",x+1);
		else if(temp==x) printf("%d\n",x+2);
		else printf("%d\n",x+1);
	}
	return 0;
}

Carrying Conundrum

这个题目的进位方式有一点奇怪,他是进到下下位去的.

  • 一开始的想法是:用 d p dp dp来转移,然后 d p [ i ] [ 1 / 0 ] dp[i][1/0] dp[i][1/0]表示第 i i i位是否进位得到的答案,然后转移就是很麻烦,和这一位,下下位都有关系,唯独下一位和这个没什么关系,所以就感觉像是有什么该转移的没有转移.
  • 于是我们就发现把单双数位单独的列出来就行了,这样子看的话就是正常的进位,也就是说我们如果要让和等于 15005 15005 15005的话,就拆成 105 105 105 50 50 50就好了,然后答案就是 106 ∗ 51 − 2 106*51-2 106512.
  • 因为这种拆法使得进位变成正常的十进制,所以有:

106 = 0 + 106        = 1 + 105 . . . . . . . . . . . . .        = 106 + 0 106=0+106\\ \ \ \ \ \ \ =1+105\\.............\\ \ \ \ \ \ \ =106+0 106=0+106      =1+105.............      =106+0

  • 所以一共有 107 107 107种,同理 51 51 51就一共有 52 52 52种,所以乘法原理就是 106 ∗ 51 106*51 10651,只不过还要减去 2 2 2,就是因为不能 0 + 15005 = 15005 0+15005=15005 0+15005=15005这种情况.
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 50
#define int long long
using namespace std;
int T,n,dp[maxn],a[maxn];
signed main(){
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&n);
		int k=0; memset(dp,0,sizeof dp);
		while(n){
			a[++k]=n%10;
			n/=10;
		}
		int temp1=0,temp2=0;
		for(int i=k;i;i--){
			if(i%2) temp1=temp1*10+a[i];
			else temp2=temp2*10+a[i];
		}
		if(temp1==0 || temp2==0) printf("%lld\n",temp1+temp2-1);
		else{
			printf("%lld\n",(temp2+1)*(temp1+1)-2);
		}
	}
	return 0;
}

Expression Evaluation Error
  • 因为是改了之后是十一进制,所以高位尽量保持不变,用低位来保持加和的一样.
  • 然后 5000 5000 5000分成个 5 5 5个的话,虽然要让高位保持不变,但是 500 0 11 = 100 0 11 ∗ 5 5000_{11}=1000_{11}*5 500011=1000115,这样来看的话因为要拆成很多个,所以在保持最大的情况下每一个拆得越小越好,才让后面有东西可以拆,像上面那样拆就很好,虽然 500 0 11 = 200 0 11 + 300 0 11 5000_{11}=2000_{11}+3000_{11} 500011=200011+300011也行,但是明显少了三个可以拆出来的.
  • 假设 5000 5000 5000分成 6 6 6个的话,先分成 1000 , 1000 , 1000 , 1000 1000,1000,1000,1000 1000,1000,1000,1000,现在剩下 1000 1000 1000,但是要分成两个,所以将 1000 1000 1000再先分成 100 100 100,最后只剩下一个了,那就是 5000 5000 5000减去所有之前的,也就是 900 900 900.至于为什么知道 1000 1000 1000分成两个时候是 100 100 100,这个就是和向下取整有点类似的操作,见代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int T,n,m;
signed main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d %d",&n,&m);
		for(int i=1;i<m;i++){
			double temp=pow(10,(int)log10(n-(m-i)));
			printf("%.0f ",temp); n-=temp;
		}
		printf("%d\n",n);
	}
	return 0;
}

Non-Decreasing Dilemma

单点修改,区间查询,比较显然是要用数据结构的,这里要用到线段树合并,以前如果没有做过类似的题目的话是比较难想到的.

  • 先假设区间 [ a , b ] [a,b] [a,b] l l l,区间 [ b + 1 , c ] [b+1,c] [b+1,c] r r r,并假设现在已经处理好了 l , r l,r l,r这两个区间了,所以只用考虑如何合并这两个区间,更新到 [ a , c ] [a,c] [a,c],因为在叶子节点的时候,只有一个数的时候是已知答案的,然后慢慢向上更新,所以假设只考虑怎么合并更新
  • 设置几个变量: l m a x lmax lmax表示从左边第一个数字开始有几个数字是连续上升的, r m a x rmax rmax表示从右边开始最多有几个向左的是连续下降的,如 [ a , b ] = [ 1 , 2 , 3 , 5 , 7 , 1 , 5 , 2 , 3 , 4 ] [a,b]=[1,2,3,5,7,1,5,2,3,4] [a,b]=[1,2,3,5,7,1,5,2,3,4] l m a x = 5 , r m a x = 3 lmax=5,rmax=3 lmax=5,rmax=3. l v a l lval lval表示区间最左边那个数字是多少, r v a l rval rval表示最右边那个数是多少,还有一个 s u m sum sum也就是区间的所求答案是多少,还有 l e n len len,字面意思.
u.lval=l.lval; u.rval=r.rval;
u.lmax=l.lmax; u.rmax=r.rmax;
u.len=l.len+r.len; u.sum=l.sum+r.sum;
  • 上面这段代码我认为还是挺显然的,毕竟是合并嘛
  • 假设 l = [ 1 , 2 , 3 , 4 ] l=[1,2,3,4] l=[1,2,3,4],然后 r = [ 1 , 2 , 3 , 4 ] r=[1,2,3,4] r=[1,2,3,4]的话,就是上面那点点代码就行了,但是如果 r = [ 5 , 6 , 7 ] r=[5,6,7] r=[5,6,7]的话,就出现问题了,这两个合起来的 s u m sum sum不等于两个的直接相加,因为中间是可以连起来的比如可以有 [ 4 , 5 ] [4,5] [4,5],现在的 s u m sum sum还得再加上 l . r m a x ∗ r . l m a x l.rmax*r.lmax l.rmaxr.lmax,也就是乘法原理,左边的选一个,再在右边选一个,跨过来,这样的话答案就统计完全了.
  • 还有 l m a x lmax lmax r m a x rmax rmax也得更新,像上面的那个区间情况就是 l . l e n = l . m a x l.len=l.max l.len=l.max的情况,也就是整个区间都递增,所以 r r r的左边的数如果是递增的话, u u u l m a x lmax lmax就会边长,就是 l . l e n + r . l m a x l.len+r.lmax l.len+r.lmax,然后 r m a x rmax rmax同理

其他的就简单了,注意 q u e r y query query得用 n o d e node node形式,因为答案要用 m e r g e merge merge得到的答案,而不是 l . s u m + r . s u m l.sum+r.sum l.sum+r.sum.

#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 200010
#define int long long
using namespace std;
int n,q,a[maxn];
struct node{
	int sum,lmax,rmax,len,lval,rval;
}tree[maxn<<2];
void modify(node &x,int temp){
	x.len=x.sum=x.lmax=x.rmax=1;
	x.lval=x.rval=temp;
}
node merge(node l,node r){
	node u;
	u.lval=l.lval; u.rval=r.rval;
	u.lmax=l.lmax; u.rmax=r.rmax;
	u.len=l.len+r.len; u.sum=l.sum+r.sum;
	if(l.rval<=r.lval){
		if(l.lmax==l.len) u.lmax+=r.lmax;
		if(r.rmax==r.len) u.rmax+=l.rmax;
		u.sum+=l.rmax*r.lmax;
	}
	return u;
}
void build(int id,int l,int r){
	if(l==r) {modify(tree[id],a[l]); return ;}
	build(id*2,l,l+r>>1),build(id*2+1,(l+r>>1)+1,r);
	tree[id]=merge(tree[id*2],tree[id*2+1]);
}
void update(int id,int l,int r,int x,int val){
	if(l==r) {modify(tree[id],val); return ;}
	int mid=l+r>>1;
	if(mid>=x) update(id*2,l,mid,x,val);
	else update(id*2+1,mid+1,r,x,val);
	tree[id]=merge(tree[id*2],tree[id*2+1]);
}
node query(int id,int l,int r,int ll,int rr){
	if(l==ll && r==rr) return tree[id];
	int mid=l+r>>1;
	if(mid>=rr) return query(id*2,l,mid,ll,rr);
	else if(ll>mid) return query(id*2+1,mid+1,r,ll,rr);
	else return merge(query(id*2,l,mid,ll,mid),query(id*2+1,mid+1,r,mid+1,rr));
}
signed main(){
	scanf("%lld %lld",&n,&q);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	build(1,1,n);
	for(int i=1,opt,l,r;i<=q;i++){
		scanf("%lld %lld %lld",&opt,&l,&r);
		if(opt==1) update(1,1,n,l,r);
		if(opt==2) printf("%lld\n",query(1,1,n,l,r).sum);
	}
	return 0;
}

题解发的很晚,见谅,快开学了,更新的比较慢,但是我会努力的.

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

d3ac

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值