Cf #830 Div.2
A. Bestie
-
题意
- 给定一个长度为n的数组a,可进行操作:对于任意位置i,a[i]=gcd( a[i], i ),代价为n-i+1
- 问最少需要花费多少能使得数组所有元素gcd=1
-
题解
- 贪心+思维+数论,首先如果数组本来所有的元素已经gcd=1则花费为0;如果需要进行操作,那么我们肯定从尾部开始操作会使得答案最小,下述具体做法。
- 假设数组的公共gcd(最大公因数)为g,显然gcd( n, n-1 )=1恒成立,而根据操作可知,每次选位置i进行gcd,相当于在原数组基础上又gcd一个i,所以最多只需在原数组的基础上再选连续的两个位置去gcd即可,贪心看,肯定选择最后两个数。
- 如果gcd( g ,n )=1那么花费1,如果gcd( g, n-1 )=1那么花费2,否则两个位置都选花费3
-
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N=25;
int n,a[N];
int solve() {
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
int g=a[0];
for(int i=1;i<n;i++) g=__gcd(g,a[i]);
if(g==1) return 0;
if(__gcd(g,n)==1) return 1;
else if(__gcd(g,n-1)==1) return 2;
else return 3;
}
int main() {
int t; cin>>t;
while(t--) cout<<solve()<<'\n';
return 0;
}
B. Ugu
-
题意
- 给定长度为n的01串
- 每次可以选择位置i,使得s[i+1,n]的字符01互换
- 问最少需要几次可以把01串s变成单调不递减的串
-
题解
- 贪心+思维,首先每次选一个位置都会使得后面段发生改变,如果是连续的一段相同字符,贪心来看,不可能从相同段中间去选位置,所以该相同段的变化完全一致,那么相同段可以直接看成一个字符来代表即可
- 接着分析如何维持单调性。假设相同段的总段数为cnt,分类讨论,如果是以0段开头的,那么后面的都需要变成1,例子分析可得答案为cnt-2(减去已经有序的0段和1段);如果是以1段开头的,那么答案为cnt-1(减去已经有序的1段)
-
代码
#include <iostream>
#include <cstring>
using namespace std;
int n;
void solve() {
cin>>n;
string s; cin>>s;
int cnt=0;
for(int i=0;i<n;i++) {//双指针找段数
int j=i; bool f=0;
while(j<n && s[j]==s[i]) j++,f=1;
cnt+=f;
if(f) j--;
i=j;
}
if(s[0]=='0') cnt=max(0,cnt-2);//分类讨论,注意非负数
else cnt=max(0,cnt-1);
cout<<cnt<<'\n';
}
int main() {
int t; cin>>t;
while(t--) solve();
return 0;
}
C1. Sheikh (Easy version)
-
题意
- 给定一个长度为n的数组a,有计算f(l,r)=sum(a[l]~a[r]) - xor(a[l]~a[r])
- 问区间[1,n]中使得f最大的长度最小区间是什么
-
题解
- 贪心+双指针,xor是不进位的加法,所以对于非负数a[i],一定是区间长度越长f越大。即f[1,n]一定是所求的最大f,只需要从两端向里面缩缩到最小的长度还能保证区间f=f[1,n],那么答案就是该最小区间。注意缩完后要检验一遍。因为两端缩的不一定最优
- 贪心思路不变,同时可以二分长度找到答案,check函数即为找到的区间f是否等于f[1,n]
-
代码
双指针
#include <iostream>
using namespace std;
const int N=1e5+10;
typedef long long LL;
int n,q,a[N];
void solve() {
cin>>n>>q;
LL sum=0,xo=0;
for(int i=1;i<=n;i++) {
cin>>a[i];
sum+=a[i];
xo^=a[i];
}
//往两端缩
int l,r; cin>>l>>r;
while(l<r && (sum-a[l]-(xo^a[l]))==(sum-xo)) {//注意运算的优先级
sum-=a[l]; xo^=a[l];
l++;
}
while(l<r && (sum-a[r]-(xo^a[r]))==(sum-xo)) {
sum-=a[r]; xo^=a[r];
r--;
}
//检验,以缩完的长度为基准,再向前移动一下,观察是否能再缩短
int ansl=l,ansr=r;
while(l>1) {
l--; xo^=a[l];
while(l<r && (sum-a[r]-(xo^a[r]))==(sum-xo)) {
sum-=a[r]; xo^=a[r];
r--;
}
if(r-l < ansr-ansl) ansl=l,ansr=r;
}
cout<<ansl<<' '<<ansr<<'\n';
}
int main() {
int t; cin>>t;
while(t--) solve();
return 0;
}
二分
#include<cstdio>
#define ll long long
using namespace std;
ll a[100001],s[100001],r[100001];
int main(){
int t;
scanf("%d",&t);
while(t--){
ll n,q;
scanf("%lld%lld",&n,&q);
for(ll i=0;i<n;++i){
scanf("%lld",a+i);
s[i+1]=s[i]+a[i];
r[i+1]=r[i]^a[i];
}
scanf("%*d%*d");
ll x=1,y=n;
ll f=(s[n]-s[0])-(r[n]^r[0]);
while(1){
ll z=(x+y)/2;
ll l=-1;
for(ll i=0;i+z<=n;++i){
if((s[i+z]-s[i])-(r[i+z]^r[i])==f)l=i;
}
if(x==y){
printf("%lld %lld\n",l+1,l+z);
break;
}
if(l==-1)x=z+1;
else y=z;
}
}
}
D1. Balance (Easy version)
-
题意
- +k给集合中加入元素k,?k询问能整除k且没有在集合中的最小数
-
题解
- 单调指针,用set维护集合,用map维护集合中元素x,第一次没有出现的倍数p,即p*x不在集合中,且p最小。
- 由于只添加元素,所以对于同一个数k的询问,其p值只增不减,即单调递增,所以以后询问元素k时,直接从上一次寻找的位置向后寻找即可
- 时间复杂度为O(nlogn)
-
代码
#include <iostream>
#include <set>
#include <map>
using namespace std;
#define int long long
int n;
map<int,int> h;
set<int> a;
void solve() {
char op; cin>>op;
int x; cin>>x;
if(op=='+') a.insert(x),h[x]=1;//加元素,指针初始化为1
else {
int p=(a.count(x) ? h[x]:1);//从上一次询问的位置p开始
bool f=0;
while(h[p*x]) p++,f=1;//找后面的元素
if(f) h[x]=p;//赋值新的指针位置
cout<<p*x<<'\n';
}
}
signed main() {
int t; cin>>t;
while(t--) solve();
return 0;
}