Codeforces Round #721 (Div.2)部分题解
前段时间要准备考试,就没有刷codeforces;今天刚考完就来了(但是为什么是520!),不过状态不好+不想认真打就拿了小号打一下,代码质量也很差(甚至连样例都懒得测根据WA来改代码),大家随便看看思路就好。
A. And Then There Were K
题意:给定 n ,找到最大的 k 使得 n\&(n-1)\&\cdots\&k=0 .
题解:这题十分适合拿来做面试题,显然只要找到 n 二进制下的最高位(假设为 t ),然后取 k=2^t-1 即可,这是因为 k>2^t-1 时,最高位始终为1,没法取与成0.
代码:
#include<cstdio>
using namespace std;
int t,n;
int main()
{
scanf("%d",&t);
while (t --)
{
scanf("%d",&n);
int k = 0;
for (int i = 31;i >= 0;i --)
if (n & (1 << i))
{
k = i;
break;
}
printf("%d\n",(1 << k) - 1);
}
return 0;
}
B1. Palindrome Game (easy version)
题意:对一个01回文串,每次有两个操作(1)将一个0变为1,消耗1美元;(2)将字符串翻转,不消耗金钱,但只有在非回文情况下才可做该操作,且上家做完该操作后下家不可做同样的跟进。当串全为1时游戏结束,花钱少的人胜,问给定串后先后手是否存在必胜策略。
题解:先手是很绝望的,因为他初始时不能翻转,但只要他改变了一个数,就破坏了回文串的属性,使得后手可以翻转;因此显然大多数情况下后手能让先手多花两美元,从而后手胜。
但也有一些特殊情况,比如一开始串就全为1,那么直接平局;
假如回文串是奇数长度且最中间为0,那么先手还可以操作一下:如果此时只有这中间的一个0,那自然还是输;但假如不是,那么先手把中间的0转换了就先后手易位,两 极 反 转,此时虽然先手多花了1美元,但根据上面的结论,局势转换后后手方可以让先手多花两美元,因此可必胜。
代码:
#include<cstdio>
using namespace std;
int t;
int n;
char a[1001];
int main()
{
scanf("%d",&t);
while (t --)
{
int sum = 0;
scanf("%d",&n);
for (int i = 1;i <= n;i ++)
{
char c = getchar();
while (c != '0' && c != '1')
c = getchar();
a[i] = c;
if (c == '0')
++sum;
}
if (sum % 2)
{
if (sum == 1)
printf("BOB\n");
else printf("ALICE\n");
continue;
}
if (sum == 0)
printf("DRAW\n");
else printf("BOB\n");
}
return 0;
}
B2. Palindrome Game (hard version)
题意:在B1的基础上,初始串不一定为回文串;
题解:不为回文串的情况下先手主动权就很多,后手必须尽快使得串变为回文以抢夺翻转权;那么有多少个不对称的数就是一个关键,我们记为 num ;然后,对称的0的数目,我们记为 sum 。
绝大多数情况下先手必胜,当 n 为偶数或 n 为奇数但最中间的数为1,如果 num=1 ,假如 sum>0 ,先手直接取掉num,转换成B1题目,从而必胜,假如sum=0那么就翻转,后手取完num游戏结束,仍为先手胜利;如果 num\geq 2 ,此时若sum=0则先手维持翻转即可,若sum>0那么先手先翻转若干次,直到后手取剩下一个num的时候把它取走,又转化为B1题目,而且差距扩大到了 num-1+2 美元。
特殊情况是n为偶数且正中间的数为0,此时有且仅有一种和的可能:num=1且sum=1,也就是除了正中间的0以外没有对称的0,此时先后手必须各取一个0。
代码:
#include<cstdio>
using namespace std;
int t;
int n;
char a[1001];
int main()
{
scanf("%d",&t);
while (t --)
{
int sum = 0;
scanf("%d",&n);
for (int i = 1;i <= n;i ++)
{
char c = getchar();
while (c != '0' && c != '1')
c = getchar();
a[i] = c;
}
int num = 0;
bool isPalindrome = true;
for (int i = 1;i <= n / 2;i ++)
{
if (a[i] != a[n - i + 1])
{
isPalindrome = false;
++num;
}
if (a[i] == '0' && a[i] == a[n - i + 1])
sum += 2;
}
if (n % 2 && a[n / 2 + 1] == '0')
++sum;
if (isPalindrome)
{
if (sum % 2)
{
if (sum == 1)
printf("BOB\n");
else printf("ALICE\n");
continue;
}
if (sum == 0)
printf("DRAW\n");
else printf("BOB\n");
}
else
{
if (n % 2 && a[n / 2 + 1] == '0')
{
if (num == 1 && sum == 1)
printf("DRAW\n");
else printf("ALICE\n");
}
else
{
printf("ALICE\n");
}
}
}
return 0;
}
C. Sequence Pair Weight
题意:求:
\sum_{1\leq i< j\leq n}\sum_{i\leq l<r\leq j}[a_l==a_r]
题解:这一看就是个组合计数,而且需要一点点递推的思想,还是很巧妙的。首先每对相同的数对答案的贡献为 l(n-r+1) ,因为包含他们的子区间,从左可以延伸 l 个数,从右可以延伸 n-r+1 个数。那么我们先将数据离散化,然后维护每一个数值 l 的和,每次扫到这个数值时,它的贡献就为这个和与 n-i+1 的乘积(他的现实含义是以该数为较大index的pair的贡献),然后再把和加上 i ,时间复杂度为 O(n\log n+n)=O(n\log n) ,主要包含离散化的代价.
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
struct newdata
{
int v,label;
};
int t;
int n;
int a[100001];
int num[100001];
int last[100001];
int head[100001];
long long length[100001];
newdata b[100001];
bool cmp(newdata i,newdata j)
{
return i.v < j.v;
}
int main()
{
scanf("%d",&t);
while (t --)
{
scanf("%d",&n);
for (int i = 1;i <= n;i ++)
{
scanf("%d",&a[i]);
b[i].v = a[i];
b[i].label = i;
}
sort(b + 1,b + n + 1,cmp);
a[b[1].label] = 1;
for (int i = 2;i <= n;i ++)
if (b[i].v == b[i - 1].v)
a[b[i].label] = a[b[i - 1].label];
else a[b[i].label] = a[b[i - 1].label] + 1;
for (int i = 1;i <= n;i ++)
{
num[i] = 0;
head[i] = 0;
length[i] = 0;
}
for (int i = 1;i <= n;i ++)
{
last[i] = head[a[i]];
head[a[i]] = i;
}
long long ans = 0;
for (int i = 1;i <= n;i ++)
{
if (last[i])
{
ans += (long long)(n - i + 1) * length[a[i]];
}
length[a[i]] += i;
}
printf("%lld\n",ans);
}
return 0;
}