Codeforces Round #717 (Div. 2)

链接

https://codeforces.com/contest/1516

A题

挺水的~就不说了。。

Code

#include <iostream>

using namespace std;
const int N = 1e2+10;
int a[N];
int main()
{
	int t;
	cin >> t;
	while(t--){
		int n,k;
		cin >> n >> k;
		for(int i=1;i<=n;i++) cin >> a[i];
		for(int i=1;i<n&&k;++i){
			while(a[i]&&k){
				--a[i];
				++a[n];
				--k;
			}
		}
		for(int i=1;i<=n;i++){
			cout << a[i] << ' ';
		}
		cout << '\n';
	}
	return 0;
}

B题

题意

在这里插入图片描述
给定一个序列,每次可以从中选相邻的两个数;

使得这两个数合并成这两个数的异或结果;

问最后这个序列能否合并成至少两个相等的数;

思路

在这里插入图片描述
枚举两个区间一个for,枚举三个区间两个for就够啦;

所以 O(n^2)就可以跑完了;

Code

#include <iostream>
#include <cstring>
using namespace std;
const int N = 2e3+10;
int s[N];
int main()
{
    int t;
    cin >> t;
    while(t--){
        memset(s,0,sizeof(s));
        int n;
        cin >> n;
        int tmp;
        for(int i=1;i<=n;i++){
            cin >> tmp;
            s[i]=s[i-1]^tmp;
        }
        bool flag = false;
        for(int i=1;i<n-1;i++){
            for(int j=i+1;j<n;j++){
                //[1,i] [i+1,j] [j+1,n]
                if(s[i]==(s[j]^s[i])&&s[i]==(s[n]^s[j])){
                    //ok
                    flag = true;
                    break;
                }
            }
        }
        //s[n]==0是两段的情况
        //flag是三段的情况
        //有一种即可
        if(flag||s[n]==0){
            cout << "YES\n";
        }else{
            cout << "NO\n";
        }
    }
    return 0;
}

C题

题意

给一个序列,想让这个序列变"好";

"好"的序列指的是,不存在两个子序列的和相等;

如果这个序列不好,

那么问我们最少删除多少个元素能使得这个序列变"好",并且输出被删除元素的下标;

思路

第一部分

首先,如果这个序列的总和是奇数;

那么我们无法对奇数分成两半;

这就是个好的序列,不需要我们去删除元素;

如果总和是偶数,我们采用0-1背包的思想;

定义f(i,j) 表示前i个数,总和为j时,能否达到;

如果f(n,sum/2)是不可以达到的,

那么既然不存在一部分数能够拼出sum/2;

这样的序列也是"好"的

第二部分

否则的话,我们看看怎么移除元素;

如果我们这个序列中存在某个奇数;

让我们的总和减去这个奇数,就使得总和变成奇数,那么序列就变"好"了;

如果我们的序列中不存在任何奇数;

我们这样想:

总和为偶数的序列中存在一个奇数(下标记为idx),我们将这个序列乘2;

得到的新序列中,总和还是偶数,每个元素也都是偶数了;

但如果我们删除新序列下标对应idx的这个元素,得到的新总和肯定也是不能再分的了;

也就是:相对论,只是我们放缩了原数组而已;

那么我们现在反过来,不断的给每个元素除以2,让它变成奇数;

我们只需要找到除以2次数最少的那个偶数,把它干掉即可。

至于为什么要干掉最小的:

因为他最小,如果要干掉别的数;大家统一除以二的过程,这个最小的数岂不是要小于1,变成分数了?

Code

#include <iostream>
#include <cstring>
using namespace std;
const int N = 2e5+10;
bool f[N];
int a[N];
int main()
{
    int sum = 0;
    int n;
    cin >> n;
    for(int i=1;i<=n;++i){
        cin >> a[i];
        sum+=a[i];
    }
    f[0]=1;
    for(int i=1;i<=n;++i){
        for(int j=sum;j>=a[i];--j){
            f[j]=f[j-a[i]]||f[j];
        }
    }
    //如果f(sum/2) 是不可达到的
    if(f[sum/2]==0||sum&1){
        cout << "0\n";
    }else{
        int idx = 1;
        int mn_count = 0x3f3f3f3f;
        for(int i=1;i<=n;++i){
            int c = 0;
            while(a[i]%2==0){
                a[i]/=2;
                c++;
            }
            if(mn_count>c){
                mn_count=c;
                idx = i;
            }
        }
        cout << 1 << '\n' << idx << '\n';
    }
    return 0;
}

补充

看到一个dalao的写法----------

在这里插入图片描述
不用背包来判断是否可达,直接位运算,帅的一0.0

D

尺取法 + 倍增 + 分解质因数

#include <iostream>

using namespace std;

const int N = 1e5+10;

int a[N];
int cnt[N];
int f[N][25];//f(i,j)表示从点i跳2^(j-1)次所能到达的地方+1,+1这一步刚好到达新区间
//log2(1e5) 差不多16~17

void del(int x){
    for(int i=2;i*i<=x;++i){
        while(x%i==0){
            x/=i;
            --cnt[i];
        }
    }
    if(x>1){
        --cnt[x];
    }
}

int main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int n,q;
    cin >> n >> q;
    for(int i=1;i<=n;++i){
        cin >> a[i];
    }
    //尺取法
    //先往右拓展 再从左紧缩
    int l=1,r=1;
    while(r<=n){
        int x = a[r];
        for(int i=2;i*i<=n;++i){
            if(x%i==0){
                //不断删除 直到干净为止
                while(cnt[i]){
                    //需要左缩了
                    f[l][1] = r;
                    //,删掉左边第一个数的全部因子
                    del(a[l]);
                    ++l;
                }
                while(x%i==0){
                    x/=i;
                    ++cnt[i];
                }
            }
        }
        if(x>1){
            while(cnt[x]){
                f[l][1] = r;
                //,删掉左边第一个数的全部因子
                del(a[l]);
                ++l;
            }
            ++cnt[x];
        }
        ++r;
    }
    //到这里 r == n+1
    //最后一个区间的跨一步直接到n+1
    for(int i=l;i<r;++i) f[i][1]=n+1;
    //越界的部分全部给n+1
    for(int i=1;i<=20;++i) f[n+1][i] = n+1;
    //倍增
    for(int i=n;i>=1;--i){
        for(int j=2;j<=20;++j){
            f[i][j] = f[min(n+1,f[i][j-1])][j-1];
        }
    }
    while(q--){
        int L,R,ans=0;
        cin >> L >> R;
        for(int i=20;i>=1;--i){
            //不会达到R 因为f(i,j)表示跳过去后再+1
            if(f[L][i]<=R){
                ans += (1<<(i-1));//跳一次切一刀
                L = f[L][i];
            }
        }
        cout << ans + 1 << '\n';//切2刀则有三段,3刀则有四段;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值