题目链接:https://codeforces.com/contest/1291
A - Even But Not Even
题目大意:定义ebne类型,如果一个数本身不是2的倍数,且所有数字的和为2的倍数记为ebne。现在给你一个长度小于等于3000的数字,判断是否能通过删除操作将其变为ebne类型。t组数据,n,接下来长度为n的数字。
Example:
4
4
1227
1
0
6
177013
24
222373204424185217171912
1227
-1
17703
2237344218521717191
不用想得太复杂,题目没有要求剩下的数是什么,我们只要记录一下奇数的个数就好了,如果奇数的个数小于等于1,那么就无解。否则的话我们直接放两个奇数上去就完事了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=1e6+10; char s[3010]; int mk[3010]; int main(int argc, char const *argv[]) { int t,n; scanf ("%d",&t); while (t--){ scanf ("%d",&n); scanf("%s",s); for (int i=0; i<=n; i++) mk[i]=0; int cnt=0; for (int i=0; i<n; i++){ if ((s[i]-'0')%2) mk[++cnt]=s[i]-'0'; } if (cnt<=1) printf("-1 "); else printf("%d%d ",mk[1],mk[2]); } return 0; }
B - Array Sharpening
题目大意:给你一个非负数列a,问是否存在一个峰顶x,使得$a_{1}<a_{2}<...<a_{x}>a_{x+1}>a_{x+2}>...>a_{n}$。t组数据,n,n个数
Example:
10
1
248618
3
12 10 8
6
100 11 15 9 7 8
4
0 1 1 0
2
0 0
2
0 1
2
1 0
2
1 1
3
0 1 0
3
1 0 1
Yes
Yes
Yes
No
No
Yes
Yes
Yes
Yes
No
我们可以先做两个最小代价的上升序列即0到n-1和n-1到0。
我们假设峰顶为x,那么如果Yes的话在x之前一定成立$a_{i}>=i-1$,在x之后一定成立$a_{i}>=n-i$
我们从前往后找到最后一个$a_{i}>=i-1$的位置记为in_pos,从后往前找到最后一个$a_{i}>=n-i$的位置记为de_pos,那么如果in_pos>=de_pos那么也就是说他们可以成功衔接,即答案为Yes
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=3e5+10; int a[mac],b[mac],c[mac]; int main() { int t,n; scanf ("%d",&t); while (t--){ scanf ("%d",&n); for (int i=1; i<=n; i++){ scanf("%d",&a[i]); b[i]=i-1; } int cnt=0; for (int i=n; i>=1; i--) c[i]=cnt++; int end_inc=0,end_dec=0; for (int i=1; i<=n; i++){ if (a[i]>=b[i]) end_inc=i; else break; } for (int i=n; i>=1; i--){ if (a[i]>=c[i]) end_dec=i; else break; } if (end_inc>=end_dec) printf("Yes "); else printf("No "); } return 0; }
C - Mind Control
题目大意:t组数据,n,m,k(数列大小,你的位置,你能控制的人数)。每个人能够删除数列的头或尾,删除一次后出队,问轮到你删数的时候,你能够删除的最大的数至少是多少?其中你能够控制k个人按照你的方法来删头还是尾。
4
6 4 2
2 9 2 3 8 5
4 4 1
2 13 60 4
4 1 3
1 2 2 1
2 2 0
1 2
8
4
1
1
你能够控制x人,那么你不能控制的人为y,假设这y个人是被别人控制的,你要获得的是最大的值那么他一定会阻止你。但因为只能拿头和尾就好办了,我们假设在头部拿了0-x个(i),那么对手可以在头部拿0-y个(j),由于对手要阻止你,那么他一定会使你拿到最小的,所以我们枚举对手拿的数量,取答案最小的值就是当我们控制拿i个头时的解,然后我们取每个头的最大值就可以了。当那i,j个头的时候,尾巴会被拿x-i,y-j个,所以每次枚举的时候我们取的是$min(a[i+j],a[n-1-(x-i)-(y-j)])$
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=4e3+10; const int inf=1e9+10; int a[mac]; int main(int argc, char const *argv[]) { int t,n,pos,control; scanf ("%d",&t); while (t--){ scanf ("%d%d%d",&n,&pos,&control); int before=pos-1; int nbcontrol=min(before,control); for (int i=0; i<n; i++) scanf("%d",&a[i]); int ans=0; for (int i=0; i<=nbcontrol; i++){ int stp_ans=inf; for (int j=0; j<=before-nbcontrol; j++){ int ans1=max(a[i+j],a[n-1-(nbcontrol-i)-(before-nbcontrol-j)]); stp_ans=min(stp_ans,ans1); } ans=max(ans,stp_ans); } printf("%d ",ans); } return 0; }
当然$O(n^{2})$的复杂度还是不够好,实际上我们可以利用数据结构deque优化对手拿的情况从而将时间优化到$O(n)$
我们先预处理拿0-(m-1)个头的时候的解:
for (int i=0; i<m; i++) b[cnt++]=max(a[i],a[i+n-m]);
利用deque保存对手拿到的头的位置,那么一旦拿到某个位置的时候我们就不需要对对方枚举之前的了,那么时间就大大减少。
由于之前我们已经预处理出了拿到i时的解,所以我们枚举对手拿到的位置的时候我们只需要保留在对手扫过的一段中的最小值就好了:
deque<int> q; for(int i = 0, j = 0; i <= k; i++) {//可控的前k个 while(q.size() && q.front() < i)//已经在头拿了i个了,那么i之前的位置已经没用了 q.pop_front(); while(j < i + sz) {//对手拿到的头的位置+1 while(q.size() && b[q.back()] >= b[j])//如果新的位置的解更小,那么之前的位置出队 q.pop_back(); q.push_back(j++); } ans = max(ans, b[q.front()]); }
于是一个线性复杂度的解法就诞生了!
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=4e3+10; int a[mac],b[mac]; int main(int argc, char const *argv[]) { int t,n,pos,control; scanf ("%d",&t); while (t--){ scanf ("%d%d%d",&n,&pos,&control); int before=pos-1; int nbcontrol=min(before,control); int discontrol=before-nbcontrol; for (int i=0; i<n; i++) scanf ("%d",&a[i]); int cnt=0; for (int i=0; i<pos; i++) b[cnt++]=max(a[i],a[i+n-pos]); deque<int>q; int ans=0; for (int i=0,j=0; i<=nbcontrol; i++){ while (q.size() && q.front()<i) q.pop_front(); while (j<i+discontrol+1){ while (q.size() && b[q.back()]>=b[j]) q.pop_back(); q.push_back(j++); } ans=max(ans,b[q.front()]); } printf("%d ",ans); } return 0; }
D - Irreducible Anagrams
题目大意:。。。这个题目是真tm难读。给你一个字符串,每次取一个区间问你该区间组成的字符串是否存在至少一个不可约的重组字符串。如果字符串t是字符串s的可约重组字符串,那么一定存在一个k(k>=2)和2k个非空的字符串$s_{1},t_{1},s_{2},t_{2},...,s_{k},t_{k}$满足
1.$s_{1},s_{2},...,s_{k}$按照顺序可以组成s
2.$t_{1},t_{2},...,t_{k}$按照顺序可以组成t
3.$s_{i}$和$t_{i}$中所有的字母种类和数量一样(顺序可以不一样).
aaaaa
3
1 1
2 4
5 5
Yes
No
Yes
aabbbbbbc
6
1 2
2 4
2 2
1 9
5 7
3 5
No
Yes
Yes
Yes
No
No
总之要成为一个至少有一个不可约重组字符串的字符串只要满足3个条件就ok了:
1.字符串长度为1,
2.头尾字符不相等,
3.该字符串有至少3个不同的字符。
我们可以证明一下,条件1,这就没什么好说的了,因为k>=2,所以单个字符并不满足条件,
条件2,我们将尾巴放在头上,那么对于任意长度的$t_{1}$它绝对比$s_{1}$要多出一个尾字母,所以就存在一个不可约重组字符串,例如:gamegame,eegamgam
条件3,我们将所有不同的字符串整理在一起按任意顺序排列,那么由于头和尾是一样的,无论怎么排都会多出一个尾字母,例如:abccabba,ccbbbaaa
于是此题就结束了。。。。看起来似乎很简单。。。但要推出这三个条件不是那么容易的QAQ。前两个条件好搞,第三个也好搞,套个前缀和就行了总共26个字母,那么前缀和复杂度也就$O(26n)$
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=2e5+10; char s[mac]; int nb[mac][30]; int main() { int q; scanf ("%s",s+1); int len=strlen(s+1); for (int i=1; i<=len; i++){ for (int j=0; j<='z'-'a'; j++){ nb[i][j]=nb[i-1][j]+(s[i]=='a'+j); } } scanf("%d",&q); while (q--){ int l,r; scanf ("%d%d",&l,&r); l--; int sum=0; for (int i=0; i<='z'-'a'; i++) if (nb[r][i]-nb[l][i]) sum++; if (l+1==r) printf("Yes "); else if (s[l+1]!=s[r]) printf("Yes "); else { if (sum>=3) printf("Yes "); else printf("No "); } } return 0; }
E - Prefix Enlightenment
题目大意:你有n个灯,编号从1到n,每个灯的初始状态时关(0)或者开(1)。你有k个集合A1,...,Ak,其中任意三个集合的交集为空集,集合控制着一些灯的开关。每次操作你可以选择一个集合,然后改变这个集合控制的灯的状态(0变1,1变0)。询问你让前i个灯泡同时发亮需要最少多少次操作。输出前1到n个灯泡都亮的最少操作次数
7 3
0011100
3
1 4 6
3
3 4 7
2
2 3
1
2
3
3
3
3
3
由于任意三个集合的交集为空集,这就说明对于一个灯泡,他的开关最多只在两个集合当中。
也就是说:1.一个灯泡由两个开关集合控制。2.一个灯泡由一个开关集合控制。
那么对于每个开关集合,他有两个状态,一个是使用,一个是不使用。假设某个灯泡由集合$s_{1}$和$s_{2}$集合j控制,那么他会有两种状态:
1.当初始状态关闭:必须使用且仅使用其中一个集合。2.当初始状态打开:不使用或者全使用
我们把每个集合看做为一个节点,用染色法来表示是否选择(比如黑色表示选,白色表示不选).
设这个元素为x 包含x 的两个集合为$s_{1},s_{2}$如果第x位的灯泡为 打开,那么我要么不选,要么就把$s_{1},s_{2}$都选.即: $s_{1},s_{2}$为同一种颜色.如果第x位的灯泡为 关闭,那么我必须在$s_{1},s_{2}$中选一个.即: $s_{1},s_{2}$为不同的颜色.
我们用带权并查集来维护他们之间的关系.
边为0,表示为相同颜色,边为1,表示不同颜色.同时我们还要统计每个联通块中两种颜色的节点分别有多少个.每个联通块对答案的贡献为两种颜色中较小的数量.
对于只被一个集合包含的元素,我让一个特殊集合包含他(0集合). 因为特殊集合并不存在,因此对于0集合所在联通块,只能选0集合颜色之外的另一种颜色.
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=3e5+10; int n,k,lamp[mac][2]; int father[mac],num[mac][2],ans=0,col[mac]; char s[mac]; int find(int x) { if(x==father[x]) return x; int tmp=find(father[x]); col[x]^=col[father[x]]; return father[x]=tmp; } void solve(int id,int tp) { if(id==0) ans+=tp*num[0][1]; else ans+=tp*min(num[id][1],num[id][0]); } void merge(int a,int b,int tp) { int fa=find(a),fb=find(b); tp=tp^col[a]^col[b]; if(fa==fb) return ; if(fa>fb) swap(fa,fb); solve(fa,-1);solve(fb,-1); num[fa][0]+=num[fb][0^tp]; num[fa][1]+=num[fb][1^tp]; solve(fa,1); father[fb]=fa; col[fb]=tp; } int main() { scanf ("%d%d",&n,&k); scanf ("%s",s+1); for (int i=1; i<=k; i++) { int p; scanf ("%d",&p); while(p--) { int x; scanf ("%d",&x); if(!lamp[x][0]) lamp[x][0]=i; else lamp[x][1]=i; } } for (int i=0; i<=k; i++) { num[i][0]=1; father[i]=i; } for (int i=1; i<=n; i++) { if(s[i]=='1') merge(lamp[i][0],lamp[i][1],0); else merge(lamp[i][0],lamp[i][1],1); printf ("%d ",ans); } return 0; }
F - Coffee Varieties (easy version)
emmm,这是个交互题,题目贼难读。有一个咖啡序列,你不知道里面每个的种类,你想知道里面总共有多少种不同的种类。你有一个记忆大小为k的朋友,你请他品尝一些位置的咖啡,你朋友的记忆可以看做一个队列。当你请他品尝位置c的咖啡时你朋友会有的操作:
1.告诉你现在品尝的这种咖啡是否在他的记忆中。2.将这种咖啡加入队列。3.如果此时队列长度大于k,将弹出第一个元素。
你还可以使用R药剂将其记忆清空,那么也就是清空队列。输入n,k开始后互动(n,k为2的幂),你开始请朋友品尝(? c)表示询问位置c的咖啡是否在他记忆中,清空朋友的记忆(R),输出答案(! ans)
4 2
N
N
Y
N
N
N
N
? 1
? 2
? 3
? 4
R
? 4
? 1
? 2
! 3
8 8
N
N
N
N
Y
Y
? 2
? 6
? 4
? 5
? 2
? 5
! 6
样例1解释:咖啡序列为1,4,1,3总共有3种不同的咖啡。依次询问的种类是:1,4,1,3,3,1,4,不过3和3之间服用过遗忘药剂,所以3,1,4全部是N
我们可以先暴力一点儿,我们每次配对,1和其余的配一次,删去Y的,2和其余非1的配对,删去Y的。这样就ok了。
以下是暴力代码:(非AC)
#include <bits/stdc++.h> using namespace std; const int mac=2e3+10; int a[mac],vis[mac]; int main(int argc, char const *argv[]) { int n,m; scanf ("%d%d",&n,&m); for (int i=1; i<n; i++){ for (int j=i+1; j<=n; j++){ if (vis[j]) continue; char s[5]; printf("? %d ",i);fflush(stdout); scanf("%s",s); printf("? %d ",j);fflush(stdout); scanf("%s",s); if (s[0]=='Y') vis[j]=1; printf("R ");fflush(stdout); } } int ans=0; for (int i=1; i<=n; i++) if (!vis[i]) ans++; printf("! %d ",ans);fflush(stdout); return 0; }
但这样会耗费很多遗忘药剂,也会品尝很多次,这就会超出品尝次数限制
实际上我们每品尝一次就喝遗忘药剂这是很浪费空闲记忆的,因此,我们可以做个优化,我们充分利用记忆空间。
创建大小为k/2的块,(如果k=1,块大小为1)。对块内元素的相等性消除,接下来消除块与块之间的相等性。
为了消除块之间的相等性,比较所有无序对块(对于每对,重置内存,查看第一对中的所有元素,然后查看第二对中的所有元素,并为每个Yes答案在第二对中杀死元素)。
emmm,有些类似于数列分块的思想。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=2e3+10; int a[mac],vis[mac]; int ask(int pos) { printf("? %d ",pos+1);fflush(stdout); char s[5]; scanf("%s",s); if (s[0]=='Y') return 1; return 0; } int main(int argc, char const *argv[]) { int n,m; scanf ("%d%d",&n,&m); int block=m/2; if (!block) block=1; int nb_block=n/block; if (block!=1){ for (int i=0; i<nb_block; i++){//块内消除相等性 int st=i*block; for (int j=st; j<st+block; j++){ if (!vis[j]) if (ask(j)) vis[j]=1; } printf("R ");fflush(stdout); } } for (int i=0; i<nb_block; i++){//块块对消 for (int j=i+1; j<nb_block; j++){ int st=i*block; for (int k=st; k<st+block; k++) if (!vis[k]) ask(k); st=j*block; for (int k=st; k<st+block; k++) if (!vis[k]) if (ask(k)) vis[k]=1; printf("R ");fflush(stdout); } } int ans=0; for (int i=0; i<n; i++) if (!vis[i]) ans++; printf("! %d ",ans);fflush(stdout); return 0; }
然后发现这个询问次数稍微小于$frac{2n^{2}}{k}$,Hard模式就。。。。