链接
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;
}