文章目录
原题链接:
A.Cirno’s Perfect Bitmasks Classroom
思路
对于x,如果x里面含有两个1,那么最小答案就是x的最低一位1所构成的数字。(最低一位and可以是非零,高位与另一个1异或,也是非零)。
如果要是x只有一个1,那么所产生的数字在这一位要有一个1,并且在最低的0位还要有一个1这样,才能保证最后的数字非零。
代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
int T;
cin >> T;
while(T--)
{
int ans;
int x;
cin >> x;
int low1;
int low0;
int cnt1;
low1 = low0 = -1;
cnt1 = 0;
bool fir1 = false;
bool fir0 = false;
for(int i = 0; i <= 31; i++)
{
int t = (x>>i) & 1;
if(!t && !fir0)
{
fir0 = true;
low0 = i;
}
if(t)
{
cnt1 ++;
if(!fir1)
{
low1 = i;
fir1 = true;
}
}
}
if(cnt1 > 1)
{
ans = (1<<low1);
}
else
{
ans = (1 << low1) + (1 << low0);
}
printf("%d\n", ans);
}
return 0;
}
B.Patchouli’s Magical Talisman
思路
使用的H数组,可以较快地求出最低的1是在哪一位。
H数组的大小稍微大一点没有问题。但是最后必须要mod37.可以处理的范围是0到(1<<35)%37.
在题目中,对于一个偶数,有两种操作:
- 把这个偶数除以2,但是可能会有偶数经过一次操作不能变成奇数,例如8
- 把偶数和奇数合在一起,这样就只需要1次就可以消灭一个偶数。
所以有贪心策略。
对于一个数列,如果存在奇数,那么最终所需要的操作次数就是所有偶数的个数之和(全部和奇数进行合并)
如果要是,没有偶数,那么就寻找把偶数变成奇数的最小操作次数,然后加上所有的偶数的个数。
代码
#include <bits/stdc++.h>
using namespace std;
int H[50];
void process()
{
for(int i = 0; i <= 35; i++)
{
H[((long long)1 << i)%37] = i;
}
}
int main()
{
process();
int T;
cin >> T;
while(T--)
{
int ans;
int num_even = 0;
int minn = INT_MAX;
int buf;
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &buf);
if(H[(buf & (-buf))%37] != 0)
{
num_even ++;
}
minn = min(minn, H[(buf & (-buf))%37]);
}
if(minn == 0)
ans = num_even;
else
ans = num_even-1+minn;
printf("%d\n", ans);
}
return 0;
}
C.Manipulating History
思路
一定要从题目的描述中跳出来,题目说的很复杂,但是要找一种更加合理的规律。
如果直接按照题目的进行,显然是不可行的。
所以把所有字母全部统计一遍,如果不是偶数,那么就是开始的哪一个字母
代码
#include <bits/stdc++.h>
using namespace std;
#define N 200010
char buf[N];
int cnt[300];
int main()
{
int T;
cin >> T;
while(T--)
{
char ans;
memset(cnt, 0, sizeof(cnt));
int n;
scanf("%d", &n);
for(int i = 1; i <= 2*n+1; i++)
{
scanf("%s", buf+1);
int len = strlen(buf+1);
for(int j = 1; j <= len; j++)
{
int p = buf[j];
cnt[p] = (cnt[p]+1) % 2;
}
}
for(int i = 'a'; i <= 'z'; i++)
{
if(cnt[i]&1 == 1)
{
ans = i;
break;
}
}
printf("%c\n", ans);
}
return 0;
}
D.The Enchanted Forest
思路
这一道题目中有两个贪心的维度
- 我要尽量把刚开始的蘑菇取的尽可能多。
这样的贪心策略就是不走回头路。 - 第二,我要把新产生的蘑菇尽可能地贪完。
由于蘑菇是在不断地生长,如果正过来考虑,会迷失方向,倒过来考虑,在给定时间内,长出的蘑菇是一个定值。而我每一个位置上的蘑菇与我最后到达这一点是在什么时候有关。
如果是最后一次到的那么这里剩下的就是1个蘑菇,如果是最后一次是倒数第二次到的,那么这里剩下的就是2个蘑菇。。。使用在给定时间内,长出的蘑菇总量减去最后剩下的蘑菇,就是我得到的到的生长出来的蘑菇。容易得到,最后的路径一定是单向的(如果时间可以走完全图,那么最后几次一定是从最左面到最右面。如果不能走完全图,一定是某个点一直往一个方向)
所以分为两种情况:
- 如果不能走完全图,那么就用尺取法,找到区间蘑菇最多的数目。然后加上第二种情况的最优值。
- 否则就所有蘑菇加上第二种的最优值。
代码
#include <bits/stdc++.h>
using namespace std;
#define N 200010
int s[N];
int main()
{
int T;
cin >> T;
while(T--)
{
long long ans = 0;
int n, k;
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++)
{
scanf("%d", s+i);
}
if(k >= n)
{
for(int i = 1; i <= n; i++)
{
ans += s[i];
}
ans += (long long)(k-1) * n;
ans -= (long long)n * (n-1) / 2;
}
else
{
long long tmp = 0;
int l = 1;
int r = k;
for(int i = 1; i <= r; i++) tmp += s[i];
ans = tmp;
while(r+1 <= n)
{
tmp-=s[l++];
tmp+=s[++r];
ans = max(tmp, ans);
}
ans += (long long)k*(k-1)/2;
}
printf("%lld\n", ans);
}
return 0;
}
E.Railway System
题目大意
有n个节点,m条边,你可以询问2m次,询问的m串中为1,代表包含这条边,如果为0,就代表不包含这条边。在这种情况下,会得到最大生成树的权值。
然后最后得出最小生成树的权值。
思路
这道题目需要知道最小生成树的克鲁斯卡尔算法。
对于最大生成树,可以进行类推:从把所有的边进行从大到小的排序,然后从最大的边开始,如果加上这一条边不会构成环,那么就选择这一条边。否则不选择这条边。
证明:按照树的定义:数的边数m是图的定点数n的n-1。
假设遇到一条边,满足与已经选择的边不构成回路,但是没有选择,那么在按照这种方法得到最大生成树之后,加上这条边,一定有回路。而这条边与比他大的边不会构成回路,那么只能是与比他小的边构成回路。这样的话,删除比他小的边,又称为了一颗树,但是权值比之前的大。
得证!
所以对于当前的最大的边,一定被选择在最大生成树里面。
如果把这条边删除,有两种情况
- 这条边是割边(删除之后图的连通分支会增加一)
那么删除之后,最大生成树的权值相比上一回,会减少这条边的权值。 - 如果删除之后还有回路,那么对于一种情况:使用回路来替代他,那么这种情况下,删除这条边的前后两次权值之差已经小于这条边的权值。如果还有更长的情况,那么删除前后权值的差值只会更小。
对于割边,最小生成树里面一定有它。而对于可以替代的边,最小生成树里面一定没有它。
对于下一次询问,删除这条边即可
- 如果不是割边,那么一定不选它,直接删除,减小求解的范围。
- 如果是割边,删除后也不会影响最大生成树的权值(删了可以缩小求解范围,使得下一条比他小的边一定在最大生成树里面)
对于2m次询问内容:
使用m次询问所有的边的长度
使用1次询问整个图的最大生成树
使用m-1次缩小范围。
最小的一条边一定在最小生成树里面。
代码
#include <bits/stdc++.h>
using namespace std;
priority_queue<pair<int, int> > pq;
int n, m;
int edge[600];
void ask(int k, bool tag)
{
printf("? ");
for(int i = 1; i <= k-1; i++)
{
if(tag) putchar('0');
else putchar('1');
}
if(tag) putchar('1');
else putchar('0');
for(int i = k+1; i <= m; i++)
{
if(tag) putchar('0');
else putchar('1');
}
putchar('\n');
fflush(stdout);
}
void que()
{
printf("? ");
for(int i = 1; i <= m; i++)
{
printf("%d", edge[i]);
}
putchar('\n');
fflush(stdout);
}
int main()
{
int ans = 0;
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++)
{
int buf = 0;
ask(i, true);
scanf("%d", &buf);
pq.push(make_pair(buf, i));
}
for(int i = 1; i <= m; i++) edge[i] = 1;
que();
int res;
int last;
scanf("%d", &res);
last = res;
for(int i = 1; i <= m-1; i++)
{
pair<int, int>t;
t = pq.top();
pq.pop();
edge[t.second] = 0;
que();
scanf("%d", &res);
if(last - res < t.first);//并不需要干什么事情
else
{
ans += t.first;
}
last = res;
}
ans += pq.top().first;
printf("! ");
printf("%d", ans);
fflush(stdout);
return 0;
}