Codeforces Round #814 (Div. 2)A~D2

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;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值