cf #830 Div.2(A-D1)

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;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值