2021牛客寒假算法基础集训营3【解题报告】

A 模数的世界

题目描述

牛牛非常喜欢模数的世界。有一天他想到了一个问题:如果已知 , 那么 最大是多少呢? 此时的 又可以怎么取值呢?
稍加思索后,牛牛找到了自己的答案,可惜这个地方太小,写不下。
聪明的你们可以写一个程序,帮帮牛牛算出这个问题的答案吗?
注意你取的 必须在 范围以内。 的最大公约数。

输入描述:

第一行为一个整数 ,代表测试组数
接下来 行,每行都是以空格分隔的三个整数 , 其中。

题目保证 是素数

输出描述:

输出 行,每行以空格分隔的三个非负整数 ,分别表示 的最大值 ,,解不唯一,任何满足题意的解都被认为是正确的答案。

分析:

打表可以发现
同时为 ,则最大值为 0,此时 可以直接取 .
否则,最大值为 .

因此下面只需要讨论当 不同时为 时, 该如何取值.

.

因此 .

因此只需要找到一组 满足 即可.

由于笔者太菜了,这里采用了暴力枚举的方法,嘤嘤嘤

代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;

const int M = (int)1e5;
const int N = (int)5e2;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;

int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

void work()
{
    int a, b, p; scanf("%d %d %d", &a, &b, &p);
    if(a == 0 && b == 0) {printf("0 0 0\n"); return;}
    for(int k1 = 1; k1 <= 10; ++k1)
    {
        for(int k2 = 1; k2 <= 10; ++k2)
        {
            int x = (k1 * p - a) * (p - 1), y = (k2 * p - b) * (p - 1);
            if(gcd(x, y) == p - 1)
            {
                printf("%d %d %d\n", p - 1, x, y);
                return;
            }
        }
    }
    assert(0);
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
    int T; scanf("%d", &T);
    while(T--) work();
//    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}

B 内卷

题目描述

在一个班级里有 个同学,在这个即将进行数据结构期末考试的关头,为了减少内卷,他们决定聚在一起商量对策。
每个人在考试后会得到一个 的等级和一个分数,学校要求得到等级 的人不超过 个。
已知第 个同学在得到五个等级时预期的分数分别为
请问在最理想的情况且不违反上述条件的情况下,他们的预期分数最大值和最小值之差最小为多少。

输入描述:

第一行以空格分隔的两个正整数
接下来 行每行五个以空格分隔的正整数


输出描述:

输出一行一个正整数代表答案。

分析:

赛中模拟退火到上火...

个三元组 按照 从小到大排序,之后尺取即可(确实很妙).

详见代码.

代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;

const int M = (int)1e5;
const int N = (int)5e2;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;

int n, k, m;

struct node
{
    int val, isA, id;
    node(int _val = 0, int _isA = 0, int _id = 0): val(_val), isA(_isA), id(_id){}
    bool operator<(const node& b)const
    {
        return val < b.val;
    }
}s[M * 5 + 5];

int idCnt, aCnt;
int idVis[M + 5], aVis[M + 5];

inline bool check()
{
    return idCnt == n && aCnt <= k;
}

void add(int p)
{
    if(s[p].isA)
    {
        if(!idVis[s[p].id]) ++aCnt;
        ++aVis[s[p].id];
    }
    if(++idVis[s[p].id] == 1) ++idCnt;
}

void del(int p)
{
    if(--idVis[s[p].id] == 0) --idCnt;
    if(s[p].isA)                                  --aVis[s[p].id], --aCnt;
    else if(aVis[s[p].id] && idVis[s[p].id] == 1) ++aCnt;
}

void work()
{
    scanf("%d %d", &n, &k);
    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1, a; j <= 5; ++j)
        {
            scanf("%d", &a);
            s[++m] = node(a, j == 1, i);
        }
    }
    sort(s + 1, s + m + 1);
    int mi = inf;
    for(int l = 1, r = 0; l <= m; ++l)
    {
        while(r < m && !check()) add(++r);
        if(check()) mi = min(mi, s[r].val - s[l].val);
        del(l);
    }
    printf("%d\n", mi);
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
//    int T; scanf("%d", &T);
//    while(T--) work();
    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}

C 重力坠击

题目描述

在一个二维平面上有 个敌人,第 个敌人可以描述为一个以 为圆心,为半径的圆。
你每次可以对一个半径为 的圆范围内进行攻击(圆心自选,但圆心的横纵坐标必须为整数),对于与你攻击范围有交点的敌人都会被消灭。
你总共可以发动 次攻击,问最多能消灭多少敌人。

输入描述:

第一行以空格分隔的三个整数
接下来 行每行以空格分隔的三个整数





敌人的位置可能会有重叠。

输出描述:

输出一行一个正整数代表答案。

分析:

显然,圆心选在 一定是最优的.
因此只需要暴力枚举圆心所在位置,更新最大值即可.

代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;

const int M = (int)1e5;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;

int n, k, R;
int x[20], y[20], r[20];
int X[20], Y[20];
bool vis[20];
int ans = 0;

bool xiangjiao(int i, int j)
{
    int dx = X[i] - x[j], dy = Y[i] - y[j];
    int dis2 = dx * dx + dy * dy;
    return dis2 <= (R + r[j]) * (R + r[j]);
}

void cal()
{
    memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= k; ++i)
    {
//        printf("X[%d] = %d Y[%d] = %d\n", i, X[i], i, Y[i]);
        for(int j = 1; j <= n; ++j)
        {
            if(vis[j]) continue;
            if(xiangjiao(i, j)) vis[j] = 1;
        }
    }
//    printf("------- end -------\n");
    int c = 0;
    for(int i = 1; i <= n; ++i) if(vis[i]) ++c;
    ans = max(ans, c);
}

void dfs(int u, int ck)
{
    if(k - ck > 225 - u) return;
    if(u == 225)
    {
        cal();
        return;
    }
    int x = u % 15 - 7, y = u / 15 - 7;
    dfs(u + 1, ck);
    if(ck < k)
    {
        X[ck + 1] = x, Y[ck + 1] = y;
        dfs(u + 1, ck + 1);
    }
}

void work()
{
    scanf("%d %d %d", &n, &k, &R);
    for(int i = 1; i <= n; ++i)
    {
        scanf("%d %d %d", &x[i], &y[i], &r[i]);
    }
    dfs(0, 0);
    printf("%d\n", ans);
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
//    int T; scanf("%d", &T);
//    while(T--) work();
    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}

D Happy New Year!

题目描述

还有一个周就要过年啦!这一天牛牛盯着新的台历出神,他突然想知道对于第 年来说,大于 且与 的数位和相同的最小年份是多少。
一个数字的数位和等于他各数位上的数字之和,例如的数位和等于

输入描述:

一个正整数

输出描述:

输出一个正整数代表答案。

分析:

暴力.

代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;

const int M = (int)1e5;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;

int cal(int n)
{
    int s = 0;
    while(n)
    {
        s += n % 10;
        n /= 10;
    }
    return s;
}

void work()
{
    int n; scanf("%d", &n);
    for(int i = n + 1; ; ++i)
    {
        if(cal(i) == cal(n))
        {
            printf("%d\n", i);
            return;
        }
    }
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
//    int T; scanf("%d", &T);
//    while(T--) work();
    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}

E 买礼物

题目描述

在卖礼物的超市中有 个柜子,每个柜子里都摆放了一个礼物,每个礼物有自己的一个编号,第 个柜子里的礼物编号为
茶山牛想给牛牛和牛妹买相同编号的礼物,但礼物有可能在某个时刻被其他人买走,而且柜子数量太多,因此茶山牛在某个时刻只想知道某一个柜子区间是否能买到两件相同编号的礼物。
具体来说,有 次操作,格式如下:

,第个柜子里的礼物被买走,保证此时这个柜子里的礼物还在。

,茶山牛询问第 到第 个柜子未被买走的礼物中是否有两个礼物编号相同。

输入描述:

第一行以空格分隔的两个正整数
接下来一行以空格分隔的 个正整数
接下来 行每行一个操作。



输出描述:

对每次茶山牛的询问输出一行一个整数,如果在指定的区间内有两个礼物编号相同则输出 ,否则输出

分析:

对于操作2, 中存在相同的数就等价于 .

对于操作1,用链表维护即可.

最后套上个线段树维护 的最大值.

代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;

const int M = (int)1e6;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;

int n, q, a[M + 5];
int pre[M + 5];
int suf[M + 5];
int last[M + 5];
int nx[M + 5];
int tree[M * 4 + 5];

inline int lc(int k) {return k<<1;}
inline int rc(int k) {return k<<1|1;}

void push_up(int k)
{
    tree[k] = max(tree[lc(k)], tree[rc(k)]);
}

void build(int k, int l, int r)
{
    if(l == r)
    {
        tree[k] = pre[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(lc(k), l, mid);
    build(rc(k), mid + 1, r);
    push_up(k);
}

void update(int k, int l, int r, int a, int b)
{
    if(l == r)
    {
        tree[k] = b;
        return;
    }
    int mid = (l + r) >> 1;
    if(a <= mid) update(lc(k), l, mid, a, b);
    else         update(rc(k), mid + 1, r, a, b);
    push_up(k);
}

int query(int k, int l, int r, int a, int b)
{
    if(l >= a && r <= b) return tree[k];
    int mx = 0, mid = (l + r) >> 1;
    if(a <= mid) mx = max(mx, query(lc(k), l, mid, a, b));
    if(mid < b)  mx = max(mx, query(rc(k), mid + 1, r, a, b));
    return mx;
}

void work()
{
    scanf("%d %d", &n, &q);
    for(int i = 1; i <= n; ++i)
    {
        scanf("%d", &a[i]);
        pre[i] = last[a[i]];
        last[a[i]] = i;
    }
    for(int i = n; i >= 1; --i)
    {
        if(nx[a[i]]) suf[i] = nx[a[i]];
        else         suf[i] = n + 1;
        nx[a[i]] = i;
    }
    build(1, 1, n);
    int op, x, l, r;
    for(int i = 1; i <= q; ++i)
    {
        scanf("%d", &op);
        if(op == 1)
        {
            scanf("%d", &x);
            update(1, 1, n, x, 0);
            if(suf[x] == n + 1) continue;
            suf[pre[x]] = suf[x];
            pre[suf[x]] = pre[x];
            update(1, 1, n, suf[x], pre[x]);
        }
        else if(op == 2)
        {
            scanf("%d %d", &l, &r);
            printf("%d\n", query(1, 1, n, l, r) >= l);
        }
    }
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
//    int T; scanf("%d", &T);
//    while(T--) work();
    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}

F 匹配串

题目描述

一个模式串指仅包含小写英文字母和至少一个'#'的字符串,其中'#'可以匹配一段任意长度的任意小写字母字符串。
一个匹配串指待匹配的只包含小写字母的字符串。
一个模式串和一个匹配串相匹配当且仅当把模式串里面的'#'全部分别替换成空或一段小写字母字符串后,两个串完全相同。
现在给出 个模式串,问有多少不同的匹配串与这些模式串全部相匹配。
如果答案有无穷多个,输出

输入描述:

第一行一个正整数
接下来 行每行一个只包含小写字母和'#'的模式串。
保证输入模式串的长度总和不超过 且每个模式串至少包含一个'#'

输出描述:

一行一个整数代表答案。
如果答案有无穷多个,输出

分析:

CodeJam 原题???
把最短的含有 # 的前缀求出来,把最短的含有 # 的后缀求出来.
然后比较 个前缀能否相容, 个后缀能否相容.
最后答案一定是非 的.

代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;

const int M = (int)1e6;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;

int n;
string s[M + 5];
string pre[M + 5];
string suf[M + 5];

void getPre()
{
    for(int i = 1; i <= n; ++i)
    {
        int p = 0;
        while(s[i][p] != '#') ++p;
        pre[i] = s[i].substr(0, p);
    }
}

void getSuf()
{
    for(int i = 1; i <= n; ++i)
    {
        int p = s[i].size() - 1;
        while(s[i][p] != '#') --p;
        suf[i] = s[i].substr(p + 1);
    }
}

bool cmp(const string& a, const string& b)
{
    return a.size() < b.size();
}

bool comPre(const string& a, const string& b)
{
    if(a.empty() || b.empty()) return 1;
    int n = a.size(), m = b.size();
    for(int i = 0; i < n; ++i)
    {
        if(a[i] != b[i]) return 0;
    }
    return 1;
}

bool comSuf(const string& a, const string& b)
{
    if(a.empty() || b.empty()) return 1;
    int n = a.size(), m = b.size();
    for(int i = n - 1; i >= 0; --i)
    {
        if(a[i] != b[m - n + i]) return 0;
    }
    return 1;
}

/**
2
a#efb
ac#defb
**/

bool check()
{
    for(int i = 1; i < n; ++i)
    {
        if(!comPre(pre[i], pre[i + 1])
           || !comSuf(suf[i], suf[i + 1])) return 0;
    }
    return 1;
}

void work()
{
    cin >> n;
    for(int i = 1; i <= n; ++i) cin >> s[i];
    getPre(); getSuf();
    sort(pre + 1, pre + n + 1, cmp);
    sort(suf + 1, suf + n + 1, cmp);

//    for(int i = 1; i <= n; ++i)
//    {
//        cout << "pre = " << pre[i] << " suf = " << suf[i] << "\n";
//    }

    cout << (check() ? -1 : 0) << "\n";
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
//    int T; scanf("%d", &T);
//    while(T--) work();
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}

G 糖果

题目描述

在一个幼儿园里面有 个小朋友,分别编号 。在这些小朋友中有一些小朋友互为朋友关系,总共有 对朋友。
作为幼儿园老师,你想买一些糖果分给小朋友,你知道第 个小朋友想要至少 个糖果,否则他就会不开心。
同时,如果一个小朋友得到的糖果数小于他某个朋友得到的糖果数,他也会不开心。
请问你最少买多少糖果才能保证每个小朋友都不会不开心呢?

输入描述:

第一行以空格分隔的两个整数
第二行以空格分隔的 个正整数
接下来 行每行以空格分隔的两个正整数 ,代表 的朋友, 的朋友。




输出描述:

购买的最少糖果数以保证每个小朋友都不会不开心。

分析:

并查集维护组内大小和最内最大值.

代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;

const int M = (int)1e6;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;

int sz[M + 5];
int fa[M + 5];

int tofind(int x)
{
    if(x == fa[x]) return x;
    return fa[x] = tofind(fa[x]);
}

ll a[M + 5];
ll mx[M + 5];

void work()
{
    int n, m; scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]), sz[i] = 1, fa[i] = i;
    for(int i = 1, a, b; i <= m; ++i)
    {
        scanf("%d %d", &a, &b);
        a = tofind(a), b = tofind(b);
        if(a != b)
        {
            fa[a] = b;
            sz[b] += sz[a];
        }
    }
    for(int i = 1, u; i <= n; ++i)
    {
        u = tofind(i);
        mx[u] = max(mx[u], a[i]);
    }
    ll ans = 0;
    for(int i = 1, u; i <= n; ++i)
    {
        u = tofind(i);
        if(u == i) ans += mx[u] * sz[u];
    }
    printf("%lld\n", ans);
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
//    int T; scanf("%d", &T);
//    while(T--) work();
    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}

H 数字串

题目描述

牛牛发现了一种方法可以将只包含小写字母的字符串按照以下方式使其转换成一个数字串:
取其中的每个字母, 转换为 转换为 ......转换为,然后将这些数字拼接起来。
例如, 可以转换为
现在给出一个只包含小写字母的字符串 ,你需要找到一个只包含小写字母的字符串 ,使得两个串不相同但是能转换成相同的数字串。

输入描述:

一行一个长度不超过 的小写字母字符串

输出描述:

一行一个长度不超过 的小写字母字符串
如果无解,请输出
如果答案有解且你输出的字符串包含了除了小写字母以外的字符或长度超过了 ,那么你会得到“答案错误”的返回结果。
否则如果答案有解且你的答案与输入的字符串可以转换为一样的数字串,那么你的答案会被认为是正确的。

分析:

有一些小坑点的思维题,详见代码吧.

代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;

const int M = (int)1e6;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;

char s[M + 5];

void work()
{
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    bool f = 0;
    for(int i = 1; i <= n; ++i)
    {
        if(s[i] >= 'k' && s[i] != 't')
        {
            f = 1;
            break;
        }
    }
    if(f)
    {
        for(int i = 1; i <= n; ++i)
        {
            if(f && s[i] >= 'k' && s[i] != 't')
            {
                int num = s[i] - 'a' + 1;
                putchar(num / 10 + 'a' - 1);
                putchar(num % 10 + 'a' - 1);
                f = 0;
            }
            else    putchar(s[i]);
        }
        putchar('\n');
        return;
    }
    int p = -1;
    for(int i = 1; i < n; ++i)
    {
        if(s[i] == 't' || s[i] == 'j'
        || s[i + 1] == 't' || s[i + 1] == 'j') continue;
        int num = (s[i] - 'a' + 1) * 10 + (s[i + 1] - 'a' + 1);
        if(num <= 26) p = i;
    }
    if(~p)
    {
        for(int i = 1; i < p; ++i) putchar(s[i]);
        int num = (s[p] - 'a' + 1) * 10 + (s[p + 1] - 'a' + 1);
        putchar(num + 'a' - 1);
        for(int i = p + 2; i <= n; ++i) putchar(s[i]);
        putchar('\n');
        return;
    }
    printf("-1\n");
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
//    int T; scanf("%d", &T);
//    while(T--) work();
    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}

I 序列的美观度

题目描述

设一个长度为 的序列 的美观度等于有多少个整数 满足 ,其中 代表序列 的第 个元素。
给出一个长度为 的序列 ,问在他的所有子序列美观度最大是多少。
某个序列的子序列是从最初序列通过去除某些元素(也可以不去除,即序列本身也是子序列)但不破坏余下元素的相对位置(在前或在后)而形成的新序列。

输入描述:

第一行一个正整数
接下来一行 个以空格分隔的正整数


输出描述:

输出一个整数代表答案。

分析:

表示考虑了前 个数,且选择了第 个数的最大美观度.

.

答案即为 .

代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;

const int M = (int)1e6;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;

int n;
int a[M + 5];
int f[M + 5];
int pre[M + 5];

int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(!isdigit(ch))
    {
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch))
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

void work()
{
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    for(int i = 1; i <= n; ++i)
    {
        f[i] = f[i - 1] + (a[i - 1] == a[i]);
        if(pre[a[i]])
        {
            f[i] = max(f[i], f[pre[a[i]]] + 1);
        }
        pre[a[i]] = i;
//        printf("f[%d] = %d\n", i, f[i]);
    }
    int mx = 0;
    for(int i = 1; i <= n; ++i) mx = max(mx, f[i]);
    printf("%d\n", mx);
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
//    int T; scanf("%d", &T);
//    while(T--) work();
    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}

J 加法和乘法

题目描述

有一天牛牛和牛妹在做游戏,规则如下:
桌面上摆着 张纸牌,每张纸牌上写着一个正整数,由牛牛先手轮流执行以下操作:

如果桌面上只剩一张纸牌,游戏结束,这张纸牌上的数字如果是奇数则牛牛胜利,反之牛妹胜利。

当前行动玩家选择两张纸牌,设上面的数字分别为 ,接下来玩家从加法和乘法中选择一个并应用到这两个数字上,得到结果为 ,接下来将选择的两张纸牌丢弃,并拿一张新的纸牌放到桌面上,在上面写上
假设双方均以最优策略行动,最后谁会赢?

输入描述:

第一行一个正整数 ,代表开始的纸牌数。
第二行个空格分隔的正整数 代表开始纸牌上的数字。


输出描述:

如果牛牛能赢,输出 ,否则输出

分析:

每次操作共有 种,分别是:
奇 + 奇 = 偶 奇 = 奇
奇 + 偶 = 奇 偶 = 偶
偶 + 偶 = 偶 偶 = 偶

因为如果最后留下的数是奇数,牛牛获胜.
所以牛牛一定会优先选择产生奇数的操作,牛妹一定会优先选择产生偶数的操作.

之后模拟每一次操作就好啦,详见代码.

代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;

const int M = (int)1e6;
const int N = (int)1e3;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
const ll mod = (ll)1e9 + 7;

int n;
int a[M + 5];

int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(!isdigit(ch))
    {
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch))
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

void work()
{
    scanf("%d", &n);
    int odd = 0, even = 0;
    for(int i = 1, a; i <= n; ++i)
    {
        a = read();
        if(a & 1) ++odd;
        else      ++even;
    }
    for(int i = 1; i <= n - 1; ++i)
    {
        if(i & 1)
        {
            if(even) --even;
            else     --odd;
        }
        else
        {
            if(odd >= 2) odd -= 2, ++even;
            else if(odd) --odd;
            else if(even) --even;
        }
    }
    puts(odd ? "NiuNiu" : "NiuMei");
}

int main()
{
//    freopen("input.txt", "r", stdin);
//    freopen("output.txt", "w", stdout);
//    int T; scanf("%d", &T);
//    while(T--) work();
    work();
//    cerr << 1.0 * clock() / CLOCKS_PER_SEC << "\n";
    return 0;
}
全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务