Educational Codeforces Round 76 (Rated for Div. 2) 题解

Educational Codeforces Round 76 题解

  • 比赛链接

  • A. Two Rival Students

    #include
    using namespace std;
     
    int main() {
        int t;
        scanf("%d",&t);
        while(t--) {
        	int n,x,a,b;
        	scanf("%d %d %d %d",&n,&x,&a,&b);
        	printf("%d\n",min(n-1,abs(a-b)+x));
        }
    }
    
  • B. Magic Stick

    #include
    using namespace std;
    bool ok(int x,int y) {
    	if(x==1) return y<=1;
    	else if(x<=3) return y<=3;
    	else return 1; 
    }
    int main() {
    	int t,x,y;scanf("%d",&t);
    	while(t--) {
    		scanf("%d %d",&x,&y);
    		printf(ok(x,y)?"YES\n":"NO\n");
    	}
    }
    
  • C. Dominated Subarray

  • 题意

    就是给你一个数组,让你找出一个最短的子段,是的子段内元素出现的最多次数对应的元素唯一,比如 [ 1 , 1 , 2 , 2 , 2 ] [1,1,2,2,2] [1,1,2,2,2]就可以,而 [ 1 , 1 , 2 , 2 ] [1,1,2,2] [1,1,2,2]就不可以

  • 题解

    贪心,计算每两个相同元素的距离对 a n s ans ans m i n min min

  • 代码

    #include
    using namespace std;
    const int maxn=2e5+10;
    int n,a[maxn],pre[maxn];
    int main() {
        int t;
        scanf("%d",&t);
        while(t--) {
        	scanf("%d",&n);
        	int ans=0x3f3f3f3f;
        	for(int i=1;i<=n;i++) pre[i]=-1;
        	for(int i=1;i<=n;i++) {
        		scanf("%d",&a[i]);
        		if(pre[a[i]]!=-1) ans=min(ans,i-pre[a[i]]+1);
        		pre[a[i]]=i;
        	}
        	printf("%d\n",ans==0x3f3f3f3f?-1:ans);
        }
    }
    
  • D. Yet Another Monster Killing Problem

  • 题意

    就是说有有 n n n个怪物, m m m个英雄,怪物排好队从 1 − n 1-n 1n排好队接受因胸的挑战,每个怪物有一个 p o w e r power power a i a_i ai,每个英雄有两个属性值: p o w e r power power p i p_i pi和忍耐值 s i s_i si。每个回合你需要派一个英雄取战斗。设这 n n n个怪物已经被消灭了 ( k − 1 ) (k-1) (k1)个,当前战斗的是第 k k k个。如果英雄的 p o w e r power power值严格小于第 k k k个怪物的power值 或者本次回合英雄打败的怪物数量等于它的忍耐值 s i s_i si 或者 所有怪物都已经被消灭,他会结束这一回合;否则继续与第 k + 1 k+1 k+1个怪物战斗。问最少的回合消灭所有怪物,无解输出 − 1 -1 1

  • 题解

    贪心,对于每一个忍耐值,当然选择对应的最大 p o w e r power power值的英雄战斗,令 b [ i ] b[i] b[i]表示忍耐值为 i i i的所有英雄中最大的 p o w e r power power值,然后维护 b b b数组的后缀最大值 s u f [ i ] suf[i] suf[i],然后指针从左往右扫 a a a数组,如果能选择当前怪物就选,判断方法就是大于当前区间长度的忍耐值的最大 p o w e r power power s u f [ l e n ] suf[len] suf[len]如果大于区间最大 p o w e r power power值,则可以选

  • 代码

    #include
    using namespace std;
    const int maxn=2e5+10;
    
    int n,m,a[maxn],suf[maxn],b[maxn],p[maxn],s[maxn];
    int main() {
       int t;scanf("%d",&t);
       while(t--) {
       	scanf("%d",&n);
       	for(int i=1;i<=n;i++) b[i]=-1;
       	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
       	scanf("%d",&m);
       	for(int i=1;i<=m;i++) scanf("%d %d",&p[i],&s[i]),b[s[i]]=max(b[s[i]],p[i]);
       	suf[n+1]=-1;
       	for(int i=n;i>=1;i--) suf[i]=max(suf[i+1],b[i]);
       	int pos=1,ans=0;
       	while(pos<=n) {
       		int cur=pos;
       		int maxx=a[pos];
       		while(cur<=n && suf[cur-pos+1]>=maxx) {
       			cur++;
       			maxx=max(maxx,a[cur]);
       		}
       		if(cur==pos) {ans=-1;break;}
       		ans++;
       		pos=cur;
    
       	}
       	printf("%d\n",ans);
       }
    }
    
  • E. The Contest

  • 题意

    就是说有 n n n个数 ( 1 , 2 , 3... n ) (1,2,3...n) (1,2,3...n)分给了 a , b , c a,b,c a,b,c三个同学,其中 a a a k 1 k_1 k1个, b b b k 2 k_2 k2个, c c c k 3 k_3 k3个,保证这三个集合的并集是一个长度为 n n n的全排列,现在需要最少的操作使得 a a a手中的所有数是 1 , 2 , 3... n 1,2,3...n 1,2,3...n的一个前缀, c c c手中的所有数是 1 , 2 , 3... n 1,2,3...n 1,2,3...n的一个后缀,其余的在 b b b手中,每次操作可以让一个人给一个自己手中的数给另外一个人

  • 题解

    考虑枚举 a a a最后手中的前缀的长度,然后怎样让操作最少使得 c c c成为一个后缀呢?首先声明两个数组的含义:

    • p r e [ i ] pre[i] pre[i]:当前 c c c手中小于等于 i i i的数的个数
    • s u f [ i ] suf[i] suf[i]:当前 b b b手中大于等于 i i i的数的个数

    那么显然最小操作数就等于 m i n ( p e w [ i ] + s u f [ i + 1 ] ) i ∈ [ 0 , n ] min(pew[i]+suf[i+1]) i\in [0,n] min(pew[i]+suf[i+1])i[0,n],然后有个问题就是对于你枚举的 a a a的前缀长度 L L L [ L + 1 , n ] [L+1,n] [L+1,n]中的每一个数不一定都在 b b b或者 c c c手中,而且 [ 1 , L ] [1,L] [1,L]中的每一个数也不一定都在 a a a手中,如果不在 a a a手中,那么一定需要把 b b b c c c手中的所有在区间 [ 1 , L ] [1,L] [1,L]内的数都换到 a a a手中,同样 a a a手中的在区间 [ L + 1 , n ] [L+1,n] [L+1,n]的数也都要换到 b b b或者 c c c手中,这里具体换到 b b b还是 c c c取决于 c c c对应的最优后缀长度 R R R,如果 a a a中的某个数在区间 [ n − R + 1 , n ] [n-R+1,n] [nR+1,n]中,则给 c c c,否则给 b b b,花费都是 1 1 1,所以我们不用关心他会给谁,总花费一定是 a a a手中的在区间 [ L + 1 , n ] [L+1,n] [L+1,n]中的个数,那么这样看来当你从 b b b或者 c c c中取走一个数时, p r e [ i ] pre[i] pre[i] s u f [ i ] suf[i] suf[i]是在变化的,考虑直接用线段树维护 p r e [ i ] + s u f [ i + 1 ] pre[i]+suf[i+1] pre[i]+suf[i+1]的最小值,那么当从 b b b中取走一个数 v v v a a a时,区间 [ 0 , v − 1 ] [0,v-1] [0,v1]的值就要减一,如果从 c c c中取走一个数 v v v a a a,那么区间 [ v , n ] [v,n] [v,n]的值减一,并且维护当前取到 a a a中的总个数 t o t tot tot,然后当前答案就是 t o t + ( ∑ j = i + 1 n j ∈ a ) + q u e r y _ m i n ( i , n ) tot+(\sum_{j=i+1}^{n}{j\in a})+query\_min(i,n) tot+(j=i+1nja)+query_min(i,n)其中 i i i是当前枚举的前缀长度,注意这里是查询 [ i , n ] [i,n] [i,n]的最小值而不是 [ 0 , n ] [0,n] [0,n]的最小值,赛场上就是因为这个小细节被搞自闭了。

  • 代码

    #include
    using namespace std;
    const int maxn=2e5+10;
    int n,k1,k2,k3,a[maxn],b[maxn],c[maxn],pre[maxn],suf[maxn],ea[maxn],eb[maxn],ec[maxn],cnt[maxn],sufa[maxn];
     
    namespace segment_tree{
    	int mark[maxn<<2],minn[maxn<<2],maxx[maxn];
    	void pushup(int id) {
    		minn[id]=min(minn[id<<1],minn[id<<1|1]);
    	}
    	void down(int id) {
    		mark[id<<1]+=mark[id];mark[id<<1|1]+=mark[id];
    		minn[id<<1]+=mark[id];minn[id<<1|1]+=mark[id];
    		mark[id]=0;
    	}
    	void build(int id,int l,int r) {
    		mark[id]=minn[id]=0;
    		if(l==r) {minn[id]=cnt[l];return;}
    		int mid=(l+r)>>1;
    		build(id<<1,l,mid);build(id<<1|1,mid+1,r);
    		pushup(id);
    	}
    	void update(int id,int L,int R,int l,int r,int add) {
    		if(l<=L&&R<=r) {
    			mark[id]+=add;minn[id]+=add;
    			return;
    		}
    		if(mark[id]!=0) down(id);int mid=(L+R)>>1;
    		if(l<=mid) update(id<<1,L,mid,l,r,add);
    		if(r>mid) update(id<<1|1,mid+1,R,l,r,add);
    		pushup(id);
    	}
    	int query_max(int id,int L,int R,int l,int r) {
    		if(l<=L&&R<=r) return maxx[id];int res=-0x3f3f3f3f;
    		if(mark[id]!=0) down(id);int mid=(L+R)>>1;
    		if(l<=mid) res=max(res,query_max(id<<1,L,mid,l,r));
    		if(r>mid) res=max(res,query_max(id<<1|1,mid+1,R,l,r));
    		return res;
    	}
    	int query_min(int id,int L,int R,int l,int r) {
    		if(l<=L&&R<=r) return minn[id];int res=0x3f3f3f3f;
    		if(mark[id]!=0) down(id);int mid=(L+R)>>1;
    		if(l<=mid) res=min(res,query_min(id<<1,L,mid,l,r));
    		if(r>mid) res=min(res,query_min(id<<1|1,mid+1,R,l,r));
    		return res;
    	}
    }
    using namespace segment_tree;
    int main() {
    	scanf("%d %d %d",&k1,&k2,&k3);
    	n=k1+k2+k3;
    	for(int i=1;i<=k1;i++) scanf("%d",&a[i]),ea[a[i]]=1;
    	for(int i=1;i<=k2;i++) scanf("%d",&b[i]),eb[b[i]]=1;
    	for(int i=1;i<=k3;i++) scanf("%d",&c[i]),ec[c[i]]=1;
    	for(int i=1;i<=n;i++) pre[i]=pre[i-1]+ec[i];
    	for(int i=n;i>=1;i--) suf[i]=suf[i+1]+eb[i],sufa[i]=sufa[i+1]+ea[i];
    	for(int i=0;i<=n;i++) cnt[i]=pre[i]+suf[i+1];
    	build(1,0,n);
    	int ans=0x3f3f3f3f;
    	for(int i=0,tot=0;i<=n;i++) { //tot表示转入多少到第一个人
    		ans=min(ans,tot+sufa[i+1]+query_min(1,0,n,i,n));
    		if(eb[i+1]) tot++,update(1,0,n,0,max(i-1,0),-1);
    		else if(ec[i+1]) tot++,update(1,0,n,i,n,-1);
    	}
    	printf("%d\n",ans);
    }
    
  • F. Make Them Similar

  • 题意

    就是给你 100 100 100个在区间 [ 0 , 2 30 − 1 ] [0,2^{30}-1] [0,2301]的数,让你找一个数 x x x,将所有数都异或上 x x x后,所有数比特位为 1 1 1的数量都相同

  • 题解

    感觉还是很容易想到答案的。只是因为太菜 E E E题被搞没时间看这个题了。考虑 m e e t   i n   t h e   m i d d l e meet\ in\ the\ middle meet in the middle即将 30 30 30 b i t bit bit为一分为二,然后两边暴力枚举 x x x的所有状态然后异或上这 100 100 100个数,把异或后的状态存下来,然后考虑两边是否存在一种组合情况使得加起来都相等,对于怎么找出一组合法的解这里有一个技巧就是,把所有数减去数组内最小值,另外一半先用 15 15 15减去所有值,然后再都减去最小值,如果这两个数组相同,则可以。对于怎么判断数组相同,可以直接 m a p < v e c t o r < i n t > , i n t > map,int> map<vector<int>,int>,也可以双哈希,也可以 t r i e trie trie

  • 代码

    #include
    using namespace std;
    const int maxn=105;
    map<vector<int>,int>mp;
    int n,a[maxn];
    int main() {
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	for(int i=0;i<(1<<15);i++) {
    		vector<int> vec;
    		for(int j=1;j<=n;j++) vec.push_back(__builtin_popcount((a[j]>>15) ^ i ));
    		int minn=*min_element(vec.begin(),vec.end());
    		for(int j=0;j<vec.size();j++) vec[j]-=minn;
    		mp[vec]=i;	
    	}
    	int all=(1<<15)-1;
    	for(int i=0;i<(1<<15);i++) {
    		vector<int>vec;
    		for(int j=1;j<=n;j++) vec.push_back(15-__builtin_popcount((a[j] & all) ^ i));
    		int minn=*min_element(vec.begin(),vec.end());
    		for(int j=0;j<vec.size();j++) vec[j]-=minn;
    		if(mp.count(vec)) {
    			int left=mp[vec];
    			return printf("%d\n",left<<15|i),0;
    		}
    	}
    	printf("-1\n");
    }
    

你可能感兴趣的:(线段树,哈希,meet,in,the,middle)