Codeforces Round #780 (Div. 3)
导语
菜菜菜菜
涉及的知识点
树状数组,思维
链接:Codeforces Round #780 (Div. 3)
题目
A Vasya and Coins
题目大意:有 a a a个1和 b b b个2,找出一个最小的数 s s s,使得这 a a a个1与 b b b个2任意的和不能表达出来 s s s
思路:特判一下有没有1,如果没有1那就是所有之和+1,因为在一定范围内所有数字都可以用二进制表示,而1其实是20
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int t,n,a[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
int a,b;
cin >>a>>b;
if(a==0) {
cout <<1<<endl;
continue;
}
cout <<2*b+a+1<<endl;
}
return 0;
}
B Vlad and Candies
题目大意: n n n种糖果,每种有数量,每天吃一个数量最多的,询问是否能够与前一天吃的种类不同
思路:直接判断数量最多的是不是比次多的至少多2即可
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int t,n,a[maxn];
char s[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
int n;
cin >>n;
for(int i=1; i<=n; i++)cin >>a[i];
sort(a+1,a+1+n);
if(a[n]>a[n-1]+1)cout <<"NO\n";
else cout <<"YES\n";
}
return 0;
}
C Get an Even String
题目大意:定义一个字符串为偶满足下列两个条件:
- 字符串长度为 n n n
- 对于一个奇数 i ( 1 ≤ i ≤ n − 1 ) , a i = a i + 1 i(1\le i\le n-1),a_i=a_{i+1} i(1≤i≤n−1),ai=ai+1
现在给出一个由小写字母组合的字符串,移除一些字符能够使得字符串为偶,求出移除字符的最小个数
思路:一开始总想着删除哪些元素来获得,这样去考虑就会变得复杂,因为删除了一个元素之后会改变序列下标的奇偶性,每次删除一个就考虑一次的话难以实现并且时间开销很大
正难则反,既然考虑删除不行,那就考虑配对,如果将所有元素两两配对,删除其余不配对的元素,那么元素间的奇偶关系自然就满足了
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
int t;
bool vis[30];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
string s;
cin >>s;
int len=s.length(),ans=0;
memset(vis,0,sizeof(vis));
for(int i=0; i<len; i++) {
int pos=s[i]-'a';
if(vis[pos]) {
vis[pos]=0;
ans+=2;//记录配对数
memset(vis,0,sizeof(vis));//更新标记数组
} else
vis[pos]=1;
}
cout <<len-ans<<endl;//需要删除的数量
}
return 0;
}
D Maximum Product Strikes Back
题目大意:给出一个包含 n n n个整数的序列 a a a,对于每一个下标 i ( 1 ≤ i ≤ n ) i(1\le i\le n) i(1≤i≤n)保证 − 2 ≤ a i ≤ 2 -2\le a_i\le 2 −2≤ai≤2,可以从数组的开头和结尾移除任意数量的元素(可以是0,也可以删除整个数组),判断从数组开头和结尾各移除多少个元素,使得剩余元素的积最大,输出开头移除和结尾移除的个数,默认空数组的积为1
思路:根据题设条件,所获得的答案中必不能包括0,那么可以以每个0位分界线,判断所获得的区间的最值是多少,由于区间长度很长,有效的数字其实只有2,所以可以直接统计区间内-2,2的数量即可,对于一个区间来说,如果负数个数为偶,那么答案就是区间内2,-2的数量,如果负数的个数为奇,就需要考虑左删除还是右删除了,贪心的想,只删除损失最小的一边即可,不需要删除两边,为了使损失最小,只删到最左/右负数即可
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int t,n,a[maxn],cnt;
struct node {
int l,r,posr,posl,ans;
//区间左右端点,最右负数下标,最左负数下标,可用2数量
bool def;//标记奇数数量是否为偶
bool operator<(const node&a) {//根据区间内的可用2的数量排序
return ans>a.ans;
}
} e[maxn<<1];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>n;
cnt=0;
int l=0,r=0,posr=0,posl=0,ans=0;
bool def=0;//对应每个区间的临时变量
for(int i=1; i<=n; i++)
cin >>a[i];
for(int i=1; i<=n; i++) {
if(a[i]!=0) {//如果不为0,不用分割
if(l==0)l=i;//第一次赋值
if(a[i]<0) {
def^=1;
if(a[i]==-2)ans++;//可用2
if(posl==0)//最左负数
posl=i;
posr=i;//最右负数
}
if(a[i]==2)ans++;//可用2
r++;//更新右端点
if(i!=n)continue;//防止没有0的情况,把最后一个区间录入
}
e[++cnt].l=l;
e[cnt].r=r;
e[cnt].ans=ans;
e[cnt].posl=posl;
e[cnt].posr=posr;
e[cnt].def=def;
//录入区间相关信息
def=0;
l=i+1,r=i;//r=i保证每次拓宽时为右端点
posl=0,posr=0;
ans=0;
//初始化
}
for(int i=1; i<=cnt; i++)
if(e[i].def) {//如果奇数个负数
int d1=0,d2=0;
for(int j=e[i].r; j>=e[i].posr; j--)
if(abs(a[j])==2)
d1++;//损失2数量
for(int j=e[i].l; j<=e[i].posl; j++)
if(abs(a[j])==2)
d2++;
if(d1>d2) {//判断是去掉左边还是右边
e[i].l=e[i].posl+1;
e[i].ans-=d2;//不要写反了
} else {
e[i].r=e[i].posr-1;
e[i].ans-=d1;
}
}
sort(e+1,e+1+cnt);
if(e[1].ans==0)//特判无2的情况
cout <<n<<" "<<0<<endl;
else
cout <<e[1].l-1<<" "<<n-e[1].r<<endl;
}
return 0;
}
E Matrix and Shifts
题目大意:给出一个 n × n n×n n×n的01矩阵 A A A,给出四种移动操作
- 循环上移
- 循环下移
- 循环左移
- 循环右移
执行若干次移动操作后,可以若干次挑选矩阵中一个元素将其异或1,每次执行这个异或操作花费为1,但是移动操作无花费,询问使得矩阵为主对角矩阵的最小花费
思路:无论是向哪边移动,格子元素间的相对位置是不会变的,为了使得花费最小,那么最好一开始就能使得主对角线上全部为1,如图,这三个元素能在移动中组成主对角线,那么找到所有能够组成主对角线的“链”,判断1数量最多的那条链1的个数是多少,剩余的1直接变0,不够的0直接变1,统计花费即可
代码
#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int maxn=2121;
int t,n,cnt;
bool maze[maxn][maxn];
bool vis[maxn][maxn];
int main() {
// ios::sync_with_stdio(0);
// cin.tie(0);
cin >>t;
while(t--) {
cin >>n;
int zero=0,one=0,mx=0;
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++) {
int x;
scanf("%1d",&x);//只扫1位
maze[i][j]=x;
x?one++:zero++;//记录1个数
vis[i][j]=0;//输入的时候顺带清空
}
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++) {
if(vis[i][j])continue;
vis[i][j]=1;
int tmp=0;
for(int k=0; k<n; k++) {
int x=i+k>n?i+k-n:i+k,y=j+k>n?j+k-n:j+k;
//这里不能取模,取模就会出现0
vis[x][y]=1;//标记已经访问过了
if(maze[x][y])tmp++;//记录可用1的个数
}
mx=max(mx,tmp);
}
cout <<one+n-2*mx<<endl;
}
return 0;
}
F1 Promising String(easy version)
题目大意:定义一个字符串为平衡当前仅当字符串内的加减号个数,定义一个字符串为可变的当前且仅当如果执行若干次替换操作后,字符串可以变成平衡(替换操作:将两个相邻的减号替换成一个加号),现在给出一个字符串 s s s,判断 s s s有多少个子串是可变的, 1 ≤ s t r l e n ( s ) ≤ 3000 1\le strlen(s)\le3000 1≤strlen(s)≤3000
思路:可以找到这样一个规律,对于一个区间,如果-号比+至少多3两个,那么一定存在两个相邻-,如果进行一次替换,+和-的差值就增加了3,那么,对于每个区间,判断是否-比+正好多3的倍数个(0也算),如果多3的倍数,一定能通过替换使得区间内±互等
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=3e3+5;
int n,pre[maxn],t;
char s[maxn];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>n;
cin >>s+1;
int len=strlen(s+1),ans=0;
for(int i=1; i<=len; i++)
if(s[i]=='+')pre[i]=pre[i-1]-1;//遇到+,-1
else pre[i]=pre[i-1]+1;
for(int i=1; i<=len-1; i++)
for(int j=i+1; j<=len; j++)
if((pre[j]-pre[i-1])%3==0&&pre[j]>=pre[i-1])//-比+多并且正好是3的倍数
ans++;
cout <<ans<<endl;
}
return 0;
}
F2 Promising String(hard version)
题目大意:略,和F1一样,只是数据范围扩大
思路:规律同F1一样,但是数据范围变大,就不能暴力获得区间,提取F1的条件,对于一对下标 i , j i,j i,j,如果对应位置前缀和后者大于前者,并且差值为3的倍数,那么这个区间就可行,等价于在模3情况下,i,j对应前缀和相等,那么可以使用树状数组统计前缀和,对于一个位置i,前面是否存在已经出现的同余项前缀和,如果存在,代表可以凑出一个合法区间,由于树状数组下标不能为0,所以需要将坐标整体进行偏移,具体见代码
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e6+10;
int n,sum[maxn],t;
int tree[maxn][3];
char s[maxn];
void update(int x,int c) {
for(int i=x; i<=maxn; i+=i&(-i))
tree[i][c]++;
}
int query(int x,int c) {
int res=0;
for(int i=x; i>=1; i-=i&(-i))
res+=tree[i][c];
return res;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >>t;
while(t--) {
cin >>n;
cin >>s+1;
int ans=0,mn=0;
sum[0]=0;
for(int i=1; i<=n; i++) {
if(s[i]=='+')sum[i]=sum[i-1]-1;//遇到+,-1
else sum[i]=sum[i-1]+1;
mn=min(mn,sum[i]);//获得最小值,便于偏移
}
for(int i=0; i<=n-mn+10; i++)//清空
tree[i][0]=tree[i][1]=tree[i][2]=0;
for(int i=0; i<=n; i++)sum[i]-=mn-1;
for(int i=0; i<=n; i++) {
int c=sum[i]%3;//找同余项
ans+=query(sum[i],c);//统计在其前面出现的同余项的个数
update(sum[i],c);//更新
}
cout <<ans<<endl;
}
return 0;
}