A. Divide and Conquer
题目链接:Problem - A - Codeforces
样例输入:
4
4
1 1 1 1
2
7 4
3
1 2 4
1
15
样例输出:
0
2
1
4
题意:一个数组是好的当且仅当所有的元素和是一个偶数,现在给我们一个初始数组,我们可以对这个数组进行操作,每次操作可以选择一个数并将其缩小为原来的一半,注意是下取整,现在问我们至少需要操作多少次才能把给定数组变为好的。
分析:容易发现,一个数组的元素和要么是奇数要么是偶数,如果一开始是偶数的话那么我们呢就没有必要进行操作,否则我们必须要改变一个数的奇偶性才能把数组变为好的,因为任意两个数之间是不会相互影响的,所以我们不需要同时对两个数进行操作,我们只需要遍历一遍所有的数,记录每一个数改变奇偶性所需要的最少操作次数,那么答案就是所有最小操作次数中的最小值。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=53;
int a[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int mi=0x3f3f3f3f;
int ans=0;
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
ans+=a[i];
int cnt=1;
while((a[i]&1)==((a[i]/2)&1))
cnt++,a[i]/=2;
mi=min(mi,cnt);
}
if(ans&1) printf("%d\n",mi);
else puts("0");
}
return 0;
}
B. Make Array Good
题目链接:Problem - B - Codeforces
样例输入:
4
4
2 3 5 5
2
4 8
5
3 4 343 5 6
3
31 5 17
样例输出:
4
1 2
1 1
2 2
3 0
0
5
1 3
1 4
2 1
5 4
3 7
3
1 29
2 5
3 3
题意:一个数组是好的当且仅当这个数组中的任意两个数a和b都存在整除关系,即a|b或者b|a成立,现在我们需要对数组进行操作,每次操作可以把一个数a加上x,x是一个不大于a的非负整数,现在让我们进行不超过n次的操作使得数组变为好的,输出一种可行方案。
分析:我们可以考虑把所有数都变为2的幂次,这样的话一定有任意两个数之间存在整除关系,而且我们可以发现,对于任意一个数a,都会有一个x是2的幂次且满足a<=x<=2*a,那么我们只需要让a加上x-a即可将a变为大于等于a的第一个2的幂次,这样的操作次数一定是n,满足题意。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+10;
int a[N],x[N];
vector<int>p;
int main()
{
int T;
cin>>T;
long long t=1;
while(t<=2e9) p.push_back(t),t<<=1;
while(T--)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
x[i]=*lower_bound(p.begin(),p.end(),a[i])-a[i];
}
printf("%d\n",n);
for(int i=1;i<=n;i++)
printf("%d %d\n",i,x[i]);
}
return 0;
}
C. Binary Strings are Fun
题目链接:Problem - C - Codeforces
样例输入:
6
1
1
1
0
2
11
3
010
9
101101111
37
1011011111011010000011011111111011111
样例输出:
1
1
3
3
21
365
题意:一个奇数长度的01串b是好串需要满足以下条件:
对于任意一个奇数下标idx:
如果b[idx]=1,那么前idx个位置中1的个数要大于0的个数
如果b[idx]=0,那么前idx个位置中0的个数要大于1的个数。
对于一个长度为k的01串a,扩展后的01串长度就为2*k-1,假设扩展后的01串为c,那么就满足a[i]=c[2*i-1]
现在给定一个长度为n的01串a,求a的扩展串中是好串的数量,对998244353取模。
分析:设f[i]表示前i位扩展后占多数的情况数,g[i]表示前i位扩展后只多一个的方案数
那么我们现在来分析第i位的更新:
初始条件肯定有f[1]=g[1]=1
如果第i位和第i-1位是相同的,那么扩展后我们多出来需要自己填写的那一位可以填1也可以填0,这个时候就有f[i]=f[i]*2,要想保证前i位扩展后占多数的那一位比占少数的那一位只多1,那么需要在前i-1位扩展后占多数的那一位比占少数的那一位只多1的基础上在新扩展的那一位上填上与这一位相反的数字即可,即g[i]=g[i-1].
如果第i位和第i-1位是相反的,那么扩展后我们多出来需要自己填写的那一位一定要填与第i位相同的数字,而且要保证前i-1位扩展的扩展后占多数的那一位比占少数的那一位只多1,这样的话才能够实现前i位扩展后第i位上的数字出现次数多,而且只会多1,所以有f[i]=g[i],而且g[i]=g[i-1].
通过这样我们就能递推出最终的结果了。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e5+10,mod=998244353;
int f[N];//f[i]表示前i位扩展后占多数的情况数
int g[N];//g[i]表示前i位扩展后只多一个的方案数
char s[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
scanf("%d%s",&n,s+1);
f[1]=g[1]=1;
int ans=1;
for(int i=2;i<=n;i++)
{
if(s[i]!=s[i-1]) g[i]=g[i-1],f[i]=g[i];
else f[i]=f[i-1]*2%mod,g[i]=g[i-1];
ans=(ans+f[i])%mod;
}
printf("%d\n",ans);
}
return 0;
}
但是我们发现无论相邻两位是否相同都有g[i]=g[i-1],又因为一开始g[1]=1,那么可以知道所有的g数组都是1,所以我们可以省掉g数组了。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e5+10,mod=998244353;
int f[N];//f[i]表示前i位扩展后占多数的情况数
char s[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
scanf("%d%s",&n,s+1);
f[1]=1;
int ans=1;
for(int i=2;i<=n;i++)
{
if(s[i]!=s[i-1]) f[i]=1;
else f[i]=f[i-1]*2%mod;
ans=(ans+f[i])%mod;
}
printf("%d\n",ans);
}
return 0;
}
D. GCD Queries
题目链接:Problem - D - Codeforces
样例输入:
2
2
1
1
5
2
4
1
样例输出:
? 1 2
! 1 2
? 1 2
? 2 3
! 3 3
题意:给定一个长度为n的排列0~n-1,现在让我们找出来0的下标,只需要找出来两个数i,j,只要满足其中一个数是0的下标即可。每次我们可以给出两个不同的下标i和j进行询问,电脑会反馈pi和pj的最大公约数,其中定义0与a的最大公约数就是a。让我们在询问次数不超过2*n的情况下找到满足题目条件的i和j。
分析:我们首先固定一个数,让这个数与其余的n-1个数进行询问,这样我们就可以唯一的确定第一个数的值。
比如说第一次我们固定的数是2,那么我们用2与其余所有数进行询问,那么询问的答案最大就是2,因为任何一个数与2的最大公约数不可能比2还大,所以第一个数就是2,同理可以得到其他数也是一样的。这样我们通过一次操作就可以得到第一个固定的数是2.我们已经知道了其余所有数与2的最大公约数是几了,对于最大公约数不是2的数我们就可以直接排除了,因为0与2的最大公约数是2,所以通过这一次操作我们就将n个数变为了n/2个数,花费了n次(注意这里确切是n-1次操作,不过为了方便书写方便就写n了,由于n>n-1,所以如果n可行那么n-1更可行)操作。下一次我们就至少可以将n/2个数变为n/4个数,花费的操作次数就是n/2,依次类推就有n+n/2+n/4+……<2*n的,所以这个方法是可行的,最后只剩下两个数时直接退出即可。
如果我们第一个数是k,那么n次操作就可以将n个数变为n/k个数,那么这样最后花费的次数会更少,所以我们不必担心次数的问题。
但是我们忽略了一个问题,就是当第一个数是1的时候,我们花费了n次操作,结果全部是1,那么我们这n次操作就相当于只排除了第一个数。所以我们需要额外注意一下这种情况,我们让第一个数与第二个数进行一次询问,再让第一个数与第三个数进行一次询问,如果两次答案是一样的话那么说明第一个数不是0,我们直接把第一个数清空即可,如果两次答案不一样虽然不能说明第一个数一定是0,但是可以说明第一个数一定不是1,因为1与任何数询问得到的结果都是相同的,那么根据上面的分析我们可以发现询问次数一定是够用的。我们通过两次操作排除了一个数,就相当于原来问题是在2*n次询问中从n个数中找,现在变为了在(2*n-2)次询问中从n-1个数中找,这是等价的,只是问题规模变小了而已。
代码中是从最后一个数开始往前询问的。
因为每次询问完都会保留第一个数,所以每询问完一轮我都会对所有的数进行一次反转,这样上次固定的数一定会在这次被删除。
注意这个细节。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<bits/stdc++.h>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
scanf("%d",&n);
vector<int>p;
for(int i=1;i<=n;i++) p.push_back(i);
int x;
while(p.size()>2)
{
reverse(p.begin(),p.end());
vector<int> t;
t.push_back(p[p.size()-1]);
int mx=0;
for(int i=0;i<=1;i++)
{
printf("? %d %d\n",p[p.size()-1],p[i]);
fflush(stdout);
scanf("%d",&x);
if(x>mx)
{
mx=x;
t.clear();
t.push_back(p[p.size()-1]);
t.push_back(p[i]);
}
else if(x==mx)
t.push_back(p[i]);
}
if(t.size()==3)//说明最后一个数肯定不是0,主要是用于排除最后一个数是1的情况
{
p.pop_back();
continue;
}
for(int i=2;i<p.size()-1;i++)
{
printf("? %d %d\n",p[p.size()-1],p[i]);
fflush(stdout);
scanf("%d",&x);
if(x>mx)
{
mx=x;
t.clear();
t.push_back(p[p.size()-1]);
t.push_back(p[i]);
}
else if(x==mx)
t.push_back(p[i]);
}
p=t;
}
printf("! %d %d\n",p[0],p[1]);
fflush(stdout);
scanf("%d",&x);
}
return 0;
}