Educational Codeforces Round 76 题解
-
A. Two Rival Students #include<bits/stdc++.h> 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<bits/stdc++.h> 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<bits/stdc++.h> 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 1−n排好队接受因胸的挑战,每个怪物有一个 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) (k−1)个,当前战斗的是第 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<bits/stdc++.h> 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] [n−R+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,v−1]的值就要减一,如果从 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+1nj∈a)+query_min(i,n)其中 i i i是当前枚举的前缀长度,注意这里是查询 [ i , n ] [i,n] [i,n]的最小值而不是 [ 0 , n ] [0,n] [0,n]的最小值,赛场上就是因为这个小细节被搞自闭了。
-
代码
#include<bits/stdc++.h> 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,230−1]的数,让你找一个数 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<vector<int>,int> map<vector<int>,int>,也可以双哈希,也可以 t r i e trie trie树
-
代码
#include<bits/stdc++.h> 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"); }