A
题意
给出一个数 n n n,两人每次可以选择一个数 a a a,使得 n = n − a n = n - a n=n−a。其中 a ≥ 1 a \geq 1 a≥1 且第一个人选的a应为偶数,第二个人选的应为奇数,谁不能选的时候为负。谁不能操作为负,问谁能赢。
思路
判断 n n n 的奇偶性即可,先手偶数不改变奇偶性,后手奇数改变奇偶性,当 n = = 0 n==0 n==0 时游戏结束,也就是说仅判断 n n n 的奇偶性即可。
代码
int main()
{
int n;
sd(n);
if(n&1) puts("Ehab");
else puts("Mahmoud");
return 0;
}
B
题意
给出 n n n 个字符串,以及他们的花费,然后将这些字符串分组,每个串可以用同组的字符串替换,然后给出 m m m 个目标串,问得到目标串的最小花费
思路
解决的核心在于如何记录每个字符串属于哪个组,以及该组的最小花费。一开始读错题了写了个并查集,但是这里也还能用。有更简单的维护办法
代码
map<string, int> mp;
int fa[maxn], cost[maxn];
void init(){
rep(i, 0, maxn){
fa[i] = i;
}
}
int found(int x){
return x==fa[x]?x:(fa[x] = found(fa[x]));
}
void merg(int x, int y){ // fa[y] = x;
int fax = found(x);
int fay = found(y);
if(fax == fay) return ;
else {
cost[fax] = min(cost[fax], cost[fay]);
fa[fay] = fax;
}
}
int main()
{
ios::sync_with_stdio(false);
int n, k, m;
init();
cin>>n>>k>>m;
rep(i, 1, n+1){
string str;
cin>>str;
// debug(str);
mp[str] = i;
}
rep(i, 1, n+1){
int x;
cin>>x;
cost[i] = x;
}
rep(i, 0, k){
int num, fi, tmp;
cin>>num>>fi;
rep(i, 1, num){
cin>>tmp;
merg(fi, tmp);
}
}
ll ans = 0;
rep(i, 0, m){
string str;
cin>>str;
// debug(str);
int fas = found(mp[str]);
// debug(fas);
ans += cost[fas];
}
cout<<ans<<endl;
return 0;
}
C
题意
在树上求一个点集,要求树上的任意一条边,都至少有一个端点,在这个集合内,求集合内元素的最小个数。现给出一种算法,以1号节点为根,按奇偶分层,答案为奇偶层节点分别求和的较小值。现需要构造出 n n n 个节点的两棵树,第一棵不满足算法,第二棵满足算法。
思路
满足条件的树好构造,只需要所有节点连向 1 1 1 号节点,那么 1 1 1 就是答案。不满足条件的树需要的一个条件是,答案应该同时小于奇数层结点数和偶数层节点数。经手推构造发现,当 n ≤ 5 n \leq 5 n≤5 时 ,不存在。当 n ≥ 6 n\geq6 n≥6 时,只需要 2    3    4 2\; 3 \; 4 234 号节点连向 1 1 1 其余节点连向 2 2 2 即可。
代码
int main()
{
int n;
sd(n);
if(n <= 5) puts("-1");
else {
rep(i, 2, n+1){
printf("%d %d\n", i<5?1:2, i);
}
}
rep(i, 2, n+1)
printf("1 %d\n", i);
return 0;
}
D
题意
给出一个序列 a a a,需要构造一个数列 b b b,使得 b b b 序列的字典序大于等于 a a a 序列,且 b b b 序列中任意两个元素两两互质,问满足条件的,字典序最小的 b b b 序列是多少。
思路
这道题很开心的一点是,又学会了一种预处理质因数分解的写法。第一种,可以对于每个质数,遍历
m
a
x
n
maxn
maxn 中它的倍数,加到vector中。第二种,只需要记录当前数的最小质因子,不断转移即可。
然后就贪心求每个数,判断当前数是否可用,以及加入当前数后,更新可用状态
代码
int fac[maxn], vis[maxn];
int a[maxn], b[maxn];
void init(){
rep(i, 2, maxn){
if(!fac[i]){
for(int j = i; j<maxn; j+=i) { // minn fac
if(!fac[j]) fac[j] = i;
}
}
}
}
bool judge(int x){
while(x > 1){
if(vis[fac[x]]) return false;
x = x / fac[x];
}
return true;
}
void push_up(int x){
while(x > 1){
vis[fac[x]] = 1;
x = x / fac[x];
}
}
int main()
{
init();
int n;
sd(n);
rep(i, 0, n) sd(a[i]);
rep(i, 0, n) {
if(judge(a[i])) {
b[i] = a[i];
push_up(a[i]);
}
else {
int now = a[i], nowp = i;;
while(!judge(now)) now++;
b[nowp++] = now;
push_up(now);
now = 2;
while(nowp < n){
while(!judge(now)) now++;
b[nowp++] = now;
push_up(now);
}
break;
}
}
rep(i, 0, n) {
printf("%d%c", b[i], i==n-1?'\n':' ');
}
return 0;
}
E
题意
给出 n n n 个点,编号为 [ 0 , n ) [0, n) [0,n) 任意两点之间的线段权值为两点的亦或值。
思路
每次贪心的把最小花费的边全部连上,即二进制只有一位不同的部分,相当于连续合并。
代码
int main()
{
ll n, sum = 0, bas = 1;
sld(n);
while(n > 1){
sum += (n>>1) * bas;
n = (n+1)>>1;
bas <<= 1;
}
pld(sum);
return 0;
}