菜鸟补题
题意:给定a个1和b个2,询问不能通过1和2组成的数的最小值;
如1 1则输出4,最大只能组成3;4 0则输出5,最大只能组成4.
思路:对于没有1的情况,我们发现永远无法组成1,即输出1.
对于有1的情况,我们发现可以组成最大可以组成a+2*b,输出a+2*b+1.
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
int t;
cin>>t;
while(t--){
ll a,b,ans;
cin>>a>>b;
if(a==0)ans=1;
else ans=a+b*2+1;
cout<<ans<<endl;
}
return 0;
}
B. Vlad and Candies
题意:有n种数,对于每种数输出他的个数,每次选择其中数量最大的一个数减掉,不能连续选两次同个数,即不能减掉该数后,该数仍旧是最大的数。
这道题一开始直接看提示和样例,吸取教训了,没看到要选数量最大的数,于是卡了很久。
思路:每次选最大的数,且不能连续选他两次,我们发现,连续两次这个数都是唯一的最大数量,那么肯定无法满足条件输出NO,即当最大值和第二大值差距大于2时不满足。当最大值第二大值差距小于等于1时,你总是能实现每次有至少两个最大值相同,于是可以循环把这些数消掉。于是只需判断最大数和第二大数。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
cin>>t;
while(t--){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
int ma1=a[n];
int ma2=a[n-1];
int flag=1;
if(ma1-ma2>=2)flag=0;
if(flag)cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
题意:给定一个字符串,问最少删除多少个字符,使得长度为偶数且每个奇数位有ai=ai+1;
如aacbc答案为1,答案字符串为aacc.
总结:小白推不出什么结论菜哭,打完看题解是贪心,或者dp,问了人有人给出线段覆盖的解法(但是我不太理解就不打这一个解法了)
思路:原字符串一定时被划分成若干段,每段都只保留两个相同的,于是贪心解决,从前往后枚举字符,若新的字符在集合中存在,则这一段取两个该字符,其他的丢掉,知乎的证明为:这样子把更多的机会留给了后面。如序列时abacc,扫描到第三个位置时集合中有a和b,发现a存在,则这一段答案为aa,丢掉b.
刚刚又想了想发现原来很简单,确实线段覆盖的贪心算法,将更多的潜力留给后面进来的元素,于是代码就很好写了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
int cnt[26];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
cin>>t;
while(t--){
memset(cnt,0,sizeof cnt);
int ans=0;
string s;
cin>>s;
for(int i=0;i<s.size();i++){
int c=s[i]-'a';
cnt[c]++;
if(cnt[c]==2){
memset(cnt,0,sizeof cnt);
ans+=2;
}
}
cout<<s.size()-ans<<endl;
}
}
D-Maximum Product Strikes Back
题意:给定一个只有-2 -1 0 1 2 的序列,找出这一段中相乘最大的一段,输出删除两端的个数;
如-1 2 2 2则输出1 0 代表删除左端1个,删除右端0个。
思路:用前缀和维护每一段的负数的个数和绝对值为2的个数,通过o(1)查询,同时对将0作为分段的标志,还要注意当这一段负数个数为奇数时要删掉左端或者右端第一个出现负数的一段再更新答案。
具体思路代码注释有给出,对我这种小白来说这种题刚好是不太难又不太简单的题,希望以后能多突破。纪念一下第一次对d题有想写的冲动。
#include<bits/stdc++.h>
using namespace std;
//预处理前缀和以便O(1)查询区间负数个数和区间2,-2的个数。
int sumfu[200010],sumtwo[200010],a[200010];
int main(){
int t;
cin>>t;
while(t--){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
sumfu[i]=sumfu[i-1];
sumtwo[i]=sumtwo[i-1];
if(a[i]<0)sumfu[i]++;
if(abs(a[i])==2)sumtwo[i]++;
}
//分块解决问题,将问题规模减小;
//用0将每个段分开得到子区间
a[0]=0;a[n+1]=0;
//用数组存每个区间端点下标;
vector<int> v;
for(int i=0;i<=n+1;i++){
if(a[i]==0)v.push_back(i);
}
int num=v.size();
int x,y,ma=-1e9;
for(int i=1;i<num;i++){
//l,r代表区间左右端点
int l=v[i-1]+1,r=v[i]-1;
int sfu=sumfu[r]-sumfu[l-1];
int stwo=sumtwo[r]-sumtwo[l-1];
//如果区间负数的个数为偶数,则区间的值为2的区间2的个数次方
if(sfu%2==0){
if(stwo>ma){
ma=stwo;
x=l-1;
y=n-r;
}
}
//如果区间负数个数为奇数,则区间的最值为前端减掉第一个负数,或后端减掉第一个负数
//于是直接枚举得到两者的最大值。
else {
int j=l;
while(a[j]>0)j++;
int sum1=sumtwo[r]-sumtwo[j];
if(sum1>ma){
ma=sum1;
x=j;
y=n-r;
}
j=r;
while(a[j]>0)j--;
int sum2=sumtwo[j-1]-sumtwo[l-1];
if(sum2>ma){
ma=sum2;
x=l-1;
y=n-j+1;
}
}
}
cout<<x<<" "<<y<<endl;
}
}
E. Matrix and Shifts
题意:题面说的比较冗长,简单来说就是给定一个n*n的01矩阵,能进行四种操作将整个矩阵上下左右移动,即当i!=n时移动在i+1,i=n时移动到1,进行完这些操作后可以进行将0变成1,1变成0的操作,每次操作消耗为1,问最终将矩阵变成主对角线都是1,其余位置都是0最小的消耗。
思路:我们可以发现对于任意两个对角位置的数字,无论进行怎么移动,他们之间的相对位置总是在一条对角线上的(越界的话就跳到第一行或者第一列)。于是可以从从1-n向右下遍历得到该对角线上1出现次数的最大值cnt,ans=n-cnt,其余位置若为1则ans++;
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
string a[2010];
int main(){
int t;
cin>>t;
while(t--){
getchar();
int n,sum=0;
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
int ma=0;
for(int i=0;i<n;i++){
int cnt=0,heng=i,shu=0,cnt1=0;
while(cnt!=n){
cnt1+=a[heng][shu]==1+'0';
if(a[heng][shu]==1+'0')sum++;
heng=(heng+1)%n;
shu=(shu+1)%n;
cnt++;
}
if(cnt1>ma){
ma=cnt1;
}
}
int ans=n-ma+sum-ma;
cout<<ans<<endl;
}
}
F1. Promising String (easy version)
easy version 是真的简单我滴妈呀,泪目div3原来是题目最容易的(不是最好上分的不是)感觉题目后面几道比跟前面的难度也差不多啊,还是太菜了,吸取教训,争取下次写div3争取拿4道吧,感觉前面A-F1最难的应该是D吧;
好了题意:easy version给定一个长度为n的字符串,输出他有多少个有希望的子字符串(子字符串是连续的一段区间),定义成功的字符串为该字符串的’+‘和’-‘数量相同,有希望的字符串则是该字符串的中每相邻的两个‘-’可以变成一个‘+’,进行有限个操作后可以变成成功的字符串。
思路:对于这道题,我们知道两个‘-’可以变成一个‘+’,于是我们知道要使该字符串有希望的条件为‘-’的个数-‘+’的个数是三的倍数,且'-'个数大于等于‘+’。对于需要相邻两个-才能变成一个+的条件,我们发现无论只要该字符串满足上述条件,当没有相邻‘-’时,则一定满足‘+’的个数一定等于‘-’的个数,不需要进行操作,而当有相邻‘-’时,就可以进行操作将相邻‘-’变成正。于是只需要满足上面的条件即可。
注:easy version 中n<=3000,于是先预处理出前缀和,暴力枚举每个区间O(1)查询区间中‘-’和‘+’的个数即可。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
int sf[3010],sz[3010];
int main(){
int t;
cin>>t;
while(t--){
int n,ans=0;
cin>>n;
string s;
cin>>s;
sf[0]=s[0]=='-';
sz[0]=s[0]=='+';
for(int i=1;i<s.size();i++){
sf[i]=sf[i-1]+(s[i]=='-');
sz[i]=sz[i-1]+(s[i]=='+');
}
for(int i=0;i<s.size();i++){
for(int j=i+1;j<s.size();j++){
int sumf=sf[j]-sf[i-1];
int sumz=sz[j]-sz[i-1];
if(sumf-sumz>=0&&(sumf-sumz)%3==0)ans++;
}
}
cout<<ans<<endl;
}
}
F2.Promising String (easy version)
n的数据范围为2e5,肯定不能暴力,看了严格鸽的题解才学到了树状数组还能这样用(学到了学到了)用’-‘代表加一,用’+‘代表减一,上面已经说明只要区间内-的个数减+的个数%3等于0则该区间满足条件,于是我们将其转化为数字就能得到一个有用的性质,设区间内-的个数为n,+的个数为m,则区间的总和为n-m,也就是-的个数减去+的个数,于是我们只需要满足该区间和sum>=0并且sum%3==0即可,我们又可以发现%3的情况只有0,1,2,于是当我们新加入一个字符时更新前缀,然后再找他前面有多少个和他一样mod3值相同的个数,如当新加入一个字符,前缀mod3等于1,我们就只需要找他前面有多少个mod3等于1的,于是可以维护三个树状数组。还要注意一个问题,c++取余是去不掉负号的,但是在求前缀的过程中又会出现负数,所有我们可以预处理一下,遍历所有前缀和找到该区间中的最小值,初始化min为0,当min<0时我们将整个前缀数组加上min即可,类似于离散化的操作。(学到了芜湖)
注意:树状数组mod3维护的是当前小于pri[i]且和pri[i]%3的值一样的个数
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
struct BIT
{
const static int maxnum=5e5+10;
int tree[maxnum];
int lowbit(int x){
return (x)&(-x);
}
void add(int idx,int x){
for(int pos=idx;pos<maxnum;pos+=lowbit(pos)){
tree[pos]+=x;
}
}
int que(int n){
int ans=0;
for(int pos=n;pos;pos-=lowbit(pos)){
ans+=tree[pos];
}
return ans;
}
int que(int a,int b){
return que(b)-que(a-1);
}
void init(int n){
for(int i=0;i<=n+2;i++)tree[i]=0;
}
} tree[3];
int pre[N];
char s[N];
signed main(){
int t;
cin>>t;
while(t--){
int n,ans=0,mi=0;
cin>>n;
cin>>(s+1);
pre[0]=0;
for(int i=1;i<=n;i++){
if(s[i]=='-')pre[i]=pre[i-1]+1;
else pre[i]=pre[i-1]-1;
mi=min(pre[i],mi);
}
mi=-mi;
for(int i=0;i<=n;i++){
pre[i]+=(mi+1);
}
for(int i=0;i<3;i++){
tree[i].init(n+(mi+1));
}
for(int i=0;i<=n;i++){
int now=pre[i]%3;
ans+=tree[now].que(pre[i]);
tree[now].add(pre[i],1);
}
cout<<ans<<endl;
}
}