Codeforces Round #814 (Div. 2)A~D2
感觉最难的是B,笑死。
Problem - A - Codeforces
问题解析
题意是说有一个n*m的棋盘,一开始棋子在左下角,两个人轮流移动它,每次只能向上或向右移动奇数个格子,谁做不成操作谁就输了。
既然要没有操作,那就是把它移动到右上角了,因为此时既不能向左也不能向上。
因为每次每个人只能移动奇数个格子,所以易知Burenka操作后,当前两人走过的总距离一定是奇数;Tonya操作后一定是偶数。那么谁是最后一个移动棋子的就是谁赢,所以我们看有多少格子可以让我们走就行:
- (n+m-1)%2==0,Burenka赢;
- (n+m-1)%2==1,Tonya赢。
AC代码
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include <random>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<fstream>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#include<bitset>
//#pragma GCC optimize(3)
#define endl '\n'
#define int ll
#define PI acos(-1)
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll>PII;
const int N = 1e5 + 50, MOD = 1e11 + 3;
void solve()
{
int n, m;
cin >> n >> m;
if ((n + m - 1) % 2 == 1)cout << "Tonya" << endl;
else cout << "Burenka" << endl;
}
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
Problem - B - Codeforces
问题解析
题意是说,给你两个数n,k;问你能不能用1~n所有的数组成n/2个数对{a,b},满足(a+k)*b能被4整除,如果可以就输出全部的结果,不能就输出no。
我们直接枚举{1,2}、{3,4}…{x,y}…{n-1,n}。看是(x+k)*y能被4整除,还是(y+k) *x能被4整除,那个满足条件就是那种。
如果能满足全部n/2个数对,我们就输出yes并把数对全部输出。反之输出no。
AC代码
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include <random>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<fstream>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#include<bitset>
//#pragma GCC optimize(3)
#define endl '\n'
#define int ll
#define PI acos(-1)
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll>PII;
const int N = 1e5 + 50, MOD = 1e11 + 3;
void solve()
{
int n, k;
cin >> n >> k;
vector<PII>v;
for (int i = 1; i <= n; i+=2)
{
if ((i + k) * (i + 1) % 4 == 0)v.push_back({ i,i + 1 });
else if ((i + k + 1) * i % 4 == 0)v.push_back({i + 1, i});
}
if (v.size() == n / 2)
{
cout << "YES" << endl;
for (auto i : v)cout << i.first << " " << i.second << endl;
}
else cout << "NO" << endl;
}
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
Problem - C - Codeforces
问题解析
题意说有n个运动员,对应的能量是a[i],能量高的可以打赢低的(能量没有重复),每次比赛只有最前面的两个运动员在比,赢得留在前面,输的放到最后面,现在q次询问,问你前k场比赛,第i个运动员一共能赢几场。
首先我们知道,赢得留在前面,那么留在前面的只能是能量大的。而当最大的能量留在前面后,其他人都赢不了了。
那么我们可以先找到能量最大的那个选手,把它记作king,只要是出现在他之后的选手,胜场数永远是0.
那么就只有king前面的选手有可能赢几场,而且他们赢得场数是有限的,我们可以先从前往后遍历一下,记录下每个选手第一次获胜的场次和最后一次获胜的场次(初始化都为0),遍历过程中变量cnt表示当前是第几场,pos表示当前选手的对手是谁(也就是前面能量最大的那个人),如果当前a[i]>a[pos],那么当前场次就是这个选手赢得第一场,当前场次-1就是他的对手赢的最后一场。以此类推。
那么对于q次询问,我们分类讨论:
- 如果当前选手i在king后面,赢得场次为0;
- 如果k小于这个选手第一次赢的场次,赢得场次为0;
- 如果这个选手的第一次赢得场次为0,说明他一直没赢过,赢得场次为0;
- 如果这个k大于这个选手的最后一次赢得场次,那他赢得场次为:最后一次赢得场次 - 第一次赢得场次 + 1
- 如果这个k小于这个选手的最后一次赢的场次,那他赢得场次为:k - 第一次赢得场次 + 1
- 如果这个选手是king,他赢得的场次为:k - 第一次赢的场次 + 1
AC代码
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include <random>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<fstream>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#include<bitset>
//#pragma GCC optimize(3)
#define endl '\n'
#define int ll
#define PI acos(-1)
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll>PII;
const int N = 1e5 + 50, MOD = 1e11 + 3;
void solve()
{
int n, q, k, king = -1;
cin >> n >> q;
vector<int>a(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
if (a[i] == n)king = i;
}
int mx = a[1], pos = 1, cnt = 1;
//b数组记录{第一次获胜场数,最后一次获胜场数}
vector<PII>b(n + 1);
for (int i = 2; i <= n; i++)
{
if (a[i] < mx)
{
if (b[pos].second == 0)b[pos].first = cnt;
b[pos].second = cnt;
}
else
{
mx = a[i];
pos = i;
b[pos].first = cnt;
b[pos].second = cnt;
}
cnt++;
}
while (q--)
{
cin >> pos >> k;
if (pos > king || pos > k + 1 || b[pos].first > k || b[pos].first == 0)
{
cout << 0 << endl;
}
else if (pos == king)
{
cout << k - b[king].first + 1 << endl;
}
else
{
if (k >= b[pos].second)cout << b[pos].second - b[pos].first + 1 << endl;
else cout << k - b[pos].first + 1 << endl;
}
}
}
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
D1 - Burenka and Traditions (easy version)和D2 - Burenka and Traditions (hard version)
问题解析
题面是说,给你一个长度为n的数组,每次选一个区间l~r,把他们都异或上同一个数x,时间代价为(r-l+1)/2,问把数组全变成0的最低花费时间是多少。
其实很容易能看出,我们一个个数的异或过去,每次所用时间是1,所以最大的花费时间就是n。
而因为时间代价为(r-l+1)/2,所以异或长度1和2的区间用时是一样的。那么很容易就能想出最优的操作就是只异或长度1和2的区间。(假如选择一个长度为3的区间,这个区间都只能异或同一个数,而分成长度1和2的话用时一样,而且可以异或两种数,显然后者更优)
一个优化答案的结论就是:如果一个区间的区间异或和为0,那么把这个区间变为0的所用时间为:区间长度-1。
如果一个长度为len的区间的区间异或和为0,那我们就可以用len-2次操作把区间的前len-2个数都变成0,且最后两个相邻的数相等,这样只要再用一次操作就可以把整个区间都变成0。例如样例中的:
5
1822 1799 57 23 55
我们每次把当前数异或上上一位数(a[i]=a[i]^a[i-1],实际就是对一个长度为2的区间都异或上a[i-1]),这样只要三次操作后,数组就会变成:0 0 0 55 55,此时我们只要再一次操作就能全部变成0,所用时间为4.
我们用数组cnt记录答案,cnt[i]表示把前i个数全变成0所需要的最低时间。
每次把a[i]异或上a[i-1],cnt[i]=cnt[i-1]+1,并用map记录下最近一个a[i]的位置,如果之前就有位置pos的值a[pos]==a[i],说明pos到当前位置整个区间的区间异或和为0,cnt[i]=min(cnt[i],pos-i+1-1);
我们只用遍历一次数组,每次用map记录下a[i]的位置,时间复杂度为:nlogn。所以D1和D2可以一起过。
AC代码
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include <random>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<fstream>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>
#include<bitset>
//#pragma GCC optimize(3)
#define endl '\n'
#define int ll
#define PI acos(-1)
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll>PII;
const int N = 1e5 + 50, MOD = 1e11 + 3;
int a[N], cnt[N];
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)cin >> a[i];
map<int, int>mymap;
//要初始化a[i]=0的位置为0
mymap[0] = 0;
cnt[0] = 0;
for (int i = 1; i <= n; i++)
{
//异或上一位数
a[i] ^= a[i - 1];
cnt[i] = cnt[i - 1] + 1;
//如果之前有和a[i]一样的数,说明这一段区间异或和为0
if (mymap.count(a[i]))
{
cnt[i] = min(cnt[i], cnt[mymap[a[i]]] + i - mymap[a[i]] - 1);
}
//更新最近一个a[i]所在位置
mymap[a[i]] = i;
}
cout << cnt[n] << endl;
}
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t = 1;
cin >> t;
while (t--)
{
solve();
}
return 0;
}