A. Bestie

样例输入:
9
1
1
1
2
2
2 4
3
3 6 9
4
5 10 15 20
5
120 60 80 40 80
6
150 90 180 120 60 30
6
2 4 6 9 12 18
6
30 60 90 120 125 125
样例输出:
0
1
2
2
1
3
3
0
1
题意:初始给定一个长度为n的数组a,你可以花费(n-i+1)的代价把第i个数a[i]变为gcd(i,a[i]),问将原始数组操作成满足gcd(a[1],a[2],……,a[n])=1的最小代价。
分析:首先我们先对最优代价求一个上限,假如我们对后两个数分别进行一次操作,那么我们的总代价就是1+2=3,这个时候我们可以保证对于操作后的a[n]=gcd(a[n],n)和a[n-1]=gcd(a[n-1],n-1)一定有gcd(a[n],a[n-1])互质,因为这个结果等价于原始的a[n]和a[n-1]和n和n-1这四个数取一个最大公约数,由于n和n-1是两个相邻的数,那么他们一定互质,所以这四个数的最大公约数一定是1.那么也就是说我们达到目的所需要花费的最优代价一定不会超过3.这个时候我们就要对不同的输入去判断有没有更优的解,要想代价小于3,那么只能对最后两个数中的一个进行操作,或者初始序列就满足gcd(a[1],a[2],……,a[n])=1,这个时候代价就是0,我们只需要对这三种情况取一个最小值即可。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
int a[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int gcd=a[1];
for(int i=2;i<=n;i++)
gcd=__gcd(gcd,a[i]);
if(gcd==1) puts("0");
else if(__gcd(gcd,__gcd(n,a[n]))==1) puts("1");
else if(__gcd(gcd,__gcd(n-1,a[n-1]))==1) puts("2");
else puts("3");
}
return 0;
}
B. Ugu

样例输入:
8
1
1
2
10
3
101
4
1100
5
11001
6
100010
10
0000110000
7
0101010
样例输出:
0
1
2
1
2
3
1
5
题意:给定一个长度为n的01串,我们每次操作可以选定一个位置,操作后的结果就是将该位置及之后的所有字符都反转,现在问至少需要操作多少次能够使得字符串变成一个非递减的字符串。
分析:题意中所给的操作是限定了操作区间的右边界的,而我们希望进行的操作是对某一块区间进行反转操作,那么我们就能够发现一个规律:对区间[l,r]进行一次反转就相当于先对第l个位置进行一次操作,然后再对第r+1个位置进行一次操作,通过两次操作我们就可以完成一个区间的反转。当然我们还能发现一个问题就是如果要是r等于n,那么我们直接对第i个位置进行一次操作即可。由于操作后的字符串一定是一个非递减字符串,那么也就是前半段为0,后半段为1,所以我们只要使得后面的字符0全部反转为字符1即可,所以直接按照这个方法暴力判断一下即可。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
char s[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
scanf("%d",&n);
scanf("%s",s+1);
int l=1,r=n,ans=0;
while(r>=1&&s[r]=='1') r--;
while(l<=n&&s[l]=='0') l++;
if(r==n&&l<=r) ans=-1;
while(l<=r)
{
while(l<=r&&s[r]=='0') r--;
ans+=2;
while(l<=r&&s[r]=='1') r--;
}
printf("%d\n",ans);
}
return 0;
}
C1. Sheikh (Easy version)
题目链接:Problem - C1 - Codeforces

样例输入:
6
1 1
0
1 1
2 1
5 10
1 2
3 1
0 2 4
1 3
4 1
0 12 8 3
1 4
5 1
21 32 32 32 10
1 5
7 1
0 1 0 1 0 1 0
1 7
样例输出:
1 1
1 1
1 1
2 3
2 3
2 4
题意:给定一个长度为n的数组,有q组询问,每次询问给定一个l和r,问区间[l,r]内的一个区间[ll,rr]满足sum(a[ll~rr])-xor(a[ll~rr])取得最大值,而且要使得区间长度尽可能小。
注:在easy virson版本中,q=1
分析:能够发现对于所有的区间均满足区间和是大于区间异或和的,而且如果在当前区间边界的基础上进行扩展区间,那么扩展后两者的差值一定不会小于扩展前两者的差值,也就是说对于任意的值a,b均有 b - a <= ( ( b + c ) - a ^ c ),这个值具有单调性,于是我们就可以利用这个性质进行二分,首先我们知道最大差值一定在区间[l,r]可以取到,然后我们就可以利用这个区间最大差值来进行二分判断当前区间是否满足题意,暴力枚举左区间边界,二分枚举右区间边界。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
long long s[N],x[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n,q;
scanf("%d%d",&n,&q);
s[0]=x[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&s[i]);
x[i]=x[i-1]^s[i];
s[i]+=s[i-1];
}
int l,r;
while(q--)
{
scanf("%d%d",&l,&r);
long long ans=(s[r]-s[l-1])-(x[r]^x[l-1]);
int ansl=l,ansr=r;
for(int i=l;i<=r;i++)
{
int ll=i,rr=r;
while(ll<rr)
{
int mid=ll+rr>>1;
if((s[mid]-s[i-1])-(x[mid]^x[i-1])==ans) rr=mid;
else ll=mid+1;
}
if(((s[ll]-s[i-1])-(x[ll]^x[i-1])==ans)&&(ll-i<ansr-ansl)) ansl=i,ansr=ll;
}
printf("%d %d\n",ansl,ansr);
}
}
return 0;
}
C2. Sheikh (Hard Version)
题目链接:Problem - C2 - Codeforces

样例输入:
6
1 1
0
1 1
2 2
5 10
1 2
2 2
3 3
0 2 4
1 3
1 2
2 3
4 4
0 12 8 3
1 4
1 3
2 4
2 3
5 5
21 32 32 32 10
1 5
1 4
1 3
2 5
3 5
7 7
0 1 0 1 0 1 0
1 7
3 6
2 5
1 4
4 7
2 6
2 7
样例输出:
1 1
1 1
2 2
1 1
1 1
2 2
2 3
2 3
2 3
2 3
2 3
2 3
2 3
2 3
3 4
2 4
4 6
2 4
2 4
4 6
2 4
2 4
分析:hard-version与easy-verson的区别就是hard-version每组测试点有多次询问。
但是这道题的思路和C1有着很大的不同,假如现在我们还是固定左边界,然后向左移动右边界,我们假设当前的和值和异或值分别是是s和xor,区间右边界对应的数是x,我们现在要判断去掉x会不会使得s-xor-==(s-x)-(xor^x),什么情况下会有这个等式成立呢?就是x的二进制表示中1的位置在xor中的对应位置都是1,这样xor^x在x的二进制表示中1的位置都为0,那么异或上x就相当于加上了x,这个时候差值就不变了由于xor是小于1e5的,二进制的位数也不会很大,保守估计不会超过32,那么每异或上一个这样的x至少需要将一位二进制从1修改为0这样才会使得贡献不变,那么这样的情况最多不会超过32次,所以我们直接暴力扩展即可。但是有一种情况需要注意:就是x等于0的情况,这个时候x的二进制表示中没有1,而且异或0和加0一定是等价的,所以这种情况可能会导致超时,所以我们可以预处理出来每一个位置后面的第一个非0数的位置以及每一个位置前面的第一个非0的位置,利用这个来代替传统意义上的+1和-1即可。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
long long s[N],x[N],a[N];
int l[N],r[N];
//l[i]记录第i个位置左边第一个不为0的数的位置
//r[i]记录第i个位置右边第一个不为0的数的位置
long long f(long long ll,long long rr)
{
return (s[rr]-s[ll-1])-(x[rr]^x[ll-1]);
}
int main()
{
int T;
cin>>T;
while(T--)
{
int n,q;
scanf("%d%d",&n,&q);
s[0]=x[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
x[i]=x[i-1]^a[i];
s[i]=a[i]+s[i-1];
if(a[i]!=0) l[i]=i;
else l[i]=l[i-1];
}
r[n+1]=n+1;
for(int i=n;i>=1;i--)
if(a[i]!=0) r[i]=i;
else r[i]=r[i+1];
while(q--)
{
long long L,R;
scanf("%lld%lld",&L,&R);
long long ans=f(L,R);
if(ans==0)
{
printf("%lld %lld\n",L,L);
continue;
}
long long ansl=L,ansr=R;
while(L<=R&&f(L,R)==ans)
{
long long j=R;
while(L<=j)
{
if(f(L,j)==ans)
{
if(j-L<ansr-ansl)
{
ansl=L;
ansr=j;
}
}
else break;
j=l[j-1];
}
L=r[L+1];
}
printf("%lld %lld\n",ansl,ansr);
}
}
return 0;
}
D1. Balance (Easy version)
题目链接:Problem - D1 - Codeforces

样例输入:
15
+ 1
+ 2
? 1
+ 4
? 2
+ 6
? 3
+ 7
+ 8
? 1
? 2
+ 5
? 1
+ 1000000000000000000
? 1000000000000000000
样例输出:
3
6
3
3
10
3
2000000000000000000
题意:n次操作,每次操作可以选择以下两种中的一种:
1.+x 往集合中加入一个x
2.?k 询问集合中k的倍数中最少没有出现过的数
分析:我们直接用一个mp[k]标记一下上一次我们更新到的k的mex的位置,那么每一次我们都在上一次的基础上进行向后更新即可,也就是判断这些数是否出现在集合中出现过。对于+操作,我们直接把这个数放入集合中即可。
这样我们暴力寻找的次数就等价于,这个就是n*一个调和级数,就等价于nlogn,复杂度肯定是可以的。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
const int N=2e5+10;
signed main()
{
int n;
cin>>n;
map<long long,int>mp;
set<long long>lgc;
while(n--)
{
string s;
long long x;
cin>>s;
cin>>x;
if(s=="+")
lgc.insert(x);
else
{
long long now;
if(!mp.count(x))//第一次询问直接从1倍开始寻找
mp[x]=1;
now=mp[x];
while(lgc.count(now*x))
now++;
cout<<now*x<<endl;
mp[x]=now;
}
}
return 0;
}
D2. Balance (Hard version)
题目链接:Problem - D2 - Codeforces

样例1输入:
18
+ 1
+ 2
? 1
+ 4
? 2
+ 6
? 3
+ 7
+ 8
? 1
? 2
+ 5
? 1
+ 1000000000000000000
? 1000000000000000000
- 4
? 1
? 2
样例1输出:
3
6
3
3
10
3
2000000000000000000
3
4
样例2输入:
10
+ 100
? 100
+ 200
? 100
- 100
? 100
+ 50
? 50
- 50
? 50
样例2输出:
200
300
100
100
50
题意:n次操作,每次操作可以选择以下三种中的一种:
1.+x 往集合中加入一个x
2. -x 删除集合中的x
3.?k 询问集合中k的倍数中最少没有出现过的数
分析:这道题相对于简单版本是多了一个删除操作,但是明显发现我们完全按照上面那样的做法是不可行了,因为有可能一开始我们在找k-mex时已经超过了s(s为k的某个倍数),但是下一次询问之前已经把s给删除了,那么这个时候理论上我们的第一个没有出现过的k-mex应该是s,所以并不能直接套用上面的方法。所以我们现在来想一下能不能记录一下删除该数会对哪些数的k-mex造成影响,顺便再记录一下对某个数的k-mex造成影响的已经删除的数的集合,很容易发现这两个集合是对偶关系,我们在对k的mex的探寻中可以找到删除后影响k-mex的一些数,比如我们当前扩展到s,那么如果后来我们把s给删除,那么就会对k-mex造成影响,而且所有会对k-mex造成影响的数都会出现在对k进行扩展的过程中,所以我们在探寻过程中能够处理出来影响k-mex的集合,对于我们现在要删除一个数x,我们需要首先遍历一遍删除x会影响哪些数的k-mex,然后我们就把x存储于对应的集合中,而加入一个x,我们首先需要判断一下x是否之前已经被删除过一次,如果没有就直接加入即可,否则我们还需要消除x产生的影响,因为之前在删除x的时候我们对哪些可能会受到x影响的数都加入了一个x,现在我们需要把这些数里面的x都删除。对于一个询问x,我们就先判断集合里面现在是不是还存在影响x-mex的数没有添加,如果是的话直接从小到大排序输出第一个数即可(排序这个功能在set中自动实现),否则就按照我们D1的思路向后扩展即可,直至找到第一个没有出现过的k-mex。
细节见代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
const int N=2e5+10;
signed main()
{
int n;
cin>>n;
map<long long,int>mp;//mp[i]记录上次遍历到i-mex的位置
map<long long,int>lgc;//lgc[i]记录第i个数是否出现过
map<long long,set<long long> >mp1;//mp1[i]记录删除i可能会影响哪些数的k-mex
map<long long,set<long long> >mp2;//mp2[i]记录可能会影响i-mex的一些数
while(n--)
{
char s[2];
long long x;
scanf("%s%lld",&s,&x);
if(s[0]=='+')
{
lgc[x]++;
if(mp1[x].size())//代表x之前加入过集合
{
set<long long>::iterator iter=mp1[x].begin();
while(iter!=mp1[x].end())
{
mp2[*iter].erase(x);
iter++;
}
}
}
else if(s[0]=='-')
{
lgc[x]--;
set<long long>::iterator iter=mp1[x].begin();
while(iter!=mp1[x].end())
{
mp2[*iter].insert(x);
iter++;
}
}
else
{
long long now;
if(!mp[x])//第一次询问直接从1倍开始寻找
mp[x]=1;
now=mp[x];
while(lgc[now*x])
{
mp1[now*x].insert(x);
now++;
}
long long mi=now*x;//记录答案
if(mp2[x].size())
{
mi=min(mi,*mp2[x].begin());
}
printf("%lld\n",mi);
mp[x]=now;
}
}
return 0;
}
1459

被折叠的 条评论
为什么被折叠?



