寒假训练赛第二场 -- 思维题

A - 01 Game

题意:

Alice 和 Bob 在玩游戏。
初始有一个仅由01构成的字符串。Alice 和 Bob 轮流进行游戏,Alice 先行。轮到某个人的时候,他需要从原串中找到并删除两个相邻且不同的字符(01或10),无法操作者输。
两人都用最优的策略进行,你需要确定谁能够赢得游戏。

思路:

如果字符串是010101…或者是101010…这种格式,则求出01或10的对数就能求出获胜方;
那如果字符串不是上面这样的格式呢,其实同理我们也是记录01或者10对数,因为如果1和0都存在,必然会至少1个10是挨着的,删除了过后,又会挨在一起。

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 200;
char s[MAXN];
void solve() {
    scanf("%s", s);
    int len = strlen(s);
    int cnt1 = 0, cnt0 = 0;
    for (int i = 0; i < len; ++i) {
        if (s[i] == '0') {
            cnt0++;
        } else {
            cnt1++;
        }
    }
    int t = min(cnt0, cnt1);
    if (t & 1) {
        puts("DA");
    } else {
        puts("NET");
    }
}
int main() {
    int t;
    scanf("%d", &t);
    for (int i = 0; i < t; ++i) {
        solve();
    }
    return 0;
}

B - String Task

题意

将一个字符串中的元音字母删除(Y也是哦),同时在每个辅音字母前面加入’.'以及变成小写。

思路

直接模拟即可。

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 200;
char s[MAXN];
set<char> st;
void init() {
    st.insert('A'); st.insert('a');
    st.insert('O'); st.insert('o');
    st.insert('Y'); st.insert('y');
    st.insert('E'); st.insert('e');
    st.insert('U'); st.insert('u');
    st.insert('I'); st.insert('i');
}
void solve() {
    scanf("%s", s);
    int len = strlen(s);
    init();
    string ans = "";
    for (int i = 0; i < len; ++i) {
        if (st.find(s[i]) == st.end()) {
            ans += '.';
            ans += s[i] < 97 ? s[i] + 32: s[i];
        }
    }
    cout << ans;
}
int main() {
    int t = 1;
    //scanf("%d", &t);
    for (int i = 0; i < t; ++i) {
        solve();
    }
    return 0;
}

C - Sereja and Suffixes

题意

nuoyanli有一个数组 a,包含了 n 个整数 a1, a2, …, an。 他吃饱了撑着想研究一下这个数组。 于是他拿出一张纸,写下了 m 个整数 l1, l2, …, lm (1 ≤ li ≤ n)。他想知道,在数组 a 中,对于每个指定的位置li , 有多少个不同的数字处于位置li, li + 1, …, n。(即从第 li 个元素开始到结尾,有多少个不同的元素)?
nuoyanli写出了必要的数组元素,但是数组太大了,时间紧迫,请最最最可爱的yy帮帮他,为每个 li找到所描述问题的答案。

思路

后缀数组+标记法
从后往前遍历,如果没有出现过的数字,就等于后一个位置一共有多少个不同的数+1,如果出现过,则就等于后一个位置一共有多少个不同的数。

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e5 + 5;
int a[MAXN], sum[MAXN], vis[MAXN];
void solve() {
    int n, m;
    scanf("%d%d", &n, &m);
    memset(vis, 0, sizeof(vis));
    memset(sum, 0, sizeof(sum));
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
    }
    for (int i = n; i >= 1; --i) {
        if (vis[a[i]]) {
            sum[i] = sum[i + 1];
        } else {
            sum[i] = sum[i + 1] + 1;
            vis[a[i]] = 1;
        }
    }
    while(m--) {
        int x;
        scanf("%d", &x);
        printf("%d\n", sum[x]);
    }
}
int main() {
    solve();
    return 0;
}

D - XXXXX

题意

埃哈布(Ehab)喜欢数字理论,但出于某种原因,他讨厌数字x。 给定一个数组a,找到其最长子数组的长度,以使其元素之和不能被xx整除,或者确定该子数组不存在.

思路

我们可以利用双指针往中间扫的思想来做,目的一直在维护一个不被x整除的子数组。当满足数组和为下的倍数时收缩,有以下四种情况
1.如果最左边的元素不是x的倍数,最右边的元素是x的倍数,则收左边。
2.如果最右边的元素不是x的倍数,最左边的元素是x的倍数,则收右边。
3.如果两边都是x的倍数,没办法都要往中间靠。
4.如果两边都不是x的倍数,随便收缩一边即可。
当然这个思路会有一个弊端,可能把连续0删除了,例如
n = 5,x = 2.
0 1 1 1 1 0
这样我们最终得到的是111,但是还有最优的是0111.
所以最后还需要往两边扫连续0的情况。

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e5 + 5;
int a[MAXN];
void solve() {
    int n, x, sum = 0;
    scanf("%d%d", &n, &x);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        sum += a[i];
    }
    int ans = n, l = 1, r = n;
    while(l <= r) {
        if (sum % x == 0) {
            if (a[r] % x == 0 && a[l] % x) {
                sum -= a[l];
                l++;
                ans--;
            } else if (a[l] % x == 0 && a[r] % x){
                sum -= a[r];
                r--;
                ans--;
            } else if (a[l] % x ==0 && a[r] % x == 0){
                sum -= a[l] + a[r];
                l++;
                r--;
                ans -= 2;
            } else {
                sum -= a[l];
                l++;
                ans--;
            }
        } else {
            break;
        }
    }
    // 处理有0的情况。
    if (sum % x != 0) {
        while(a[l - 1] % x == 0 && l > 1) {
            l--;
            ans++;
        }
        while(a[r + 1] % x == 0 && r < n) {
            r++;
            ans++;
        }
    }
    printf("%d\n", ans == 0 ? -1 : ans);
}
int main() {
    int t;
    scanf("%d", &t);
    for (int i = 0; i < t; ++i) {
        solve();
    }
    return 0;
}

E - Swap Adjacent Elements

题意

您有一个由n个整数组成的数组。 从1到n的每个整数在此数组中仅出现一次。
对于某些索引 i(1≤i≤n-1),可以用第i + 1交换第i个元素,而对于其他索引,则不可能。 您可以按任何顺序执行任意数量的交换操作。 对于将第i个元素与第(i + 1)个交换的次数没有限制(如果位置不被禁止)。
您可以执行一些交换操作序列来使此数组升序排序吗?

思路

如果利用冒泡排序思想标记哪些位置可以交换,最后判断交换(n + 1)*n/2次过后是否升序,那么这个题就T了,冒泡不行,我们就可以使用sort对连续的1进行一个排序,这样会大大降低复杂度,算法复杂度为O(nlogn)。

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 200000 + 20;
int a[MAXN], vis[MAXN];
void solve() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
    }
    getchar();
    char c;
    for (int i = 1; i <= n; ++i) {
        scanf("%c", &c);
        c == '1' ? vis[i] = 1 : vis[i] = 0;
    }
    for (int i = 1; i <= n; ++i) {
        if (vis[i] == 1) {
            int j = i;
            for ( ;vis[j] && j <= n; ++j) {

            }
            sort(a + i, a + j + 1);
            i = j;
        }
    }
    for (int i = 2; i <= n; ++i) {
        if (a[i] < a[i - 1]) {
            puts("NO");
            return;
        }
    }
    puts("YES");
}
int main() {
    solve();
    return 0;
}

F - Nice Matrix

题意

做出如下定义,
一个n*m的矩阵,如果它的所有行以及所有列都是回文串,则称这个矩阵为nice
每次操作:你可以对矩阵中的一个元素进行+1或者-1
请回答使得该矩阵变为nice矩阵所需要的最少操作数

思路

设当前元素为a[i][j], 由于题目说形成所有行和列都是回文,则会联系到a[n - i + 1][j], a[i][m - j + 1]以及a[n - i + 1][m - j + 1],这四个位置上的数必然要相等的,不妨设四个位置上的数为a,b,c,d;将问题的子问题转化为,设一个x,abcd与x距离和最小。
令y = |x - a| + |x - b| + |x - c| + |x - d|, a < b < c < d, 求y在哪些范围内取得最小值?
使用分类讨论法:
当x < a时,y = a + b + c + d - 4x;当x = a时最小,y = b + c + d - 3a;
当a <= x < b时, y = -a + b + c + d - 2x; 当 x = b时最小, y = c + d - a - b;
当b <= x < c时,y = -a - b + c + d; y = c + d - a - b;
当c <= x < d时,y = - a - b - c + d + 2x; 当 x = c时最小, y = c + d - a - b;
当x >= d时, y = - a - b - c - d + 4x;当x = d时最小,y = 3d - a - b - c;
综上可得,当 b <= x <= c 时y取得最小值。
即我们将四个相连位置上的数变成第二小或者第三小的数即可。

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 200;
ll a[MAXN][MAXN], b[MAXN][MAXN];
ll ans;
void solve() {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            scanf("%lld", &a[i][j]);
            b[i][j] = a[i][j];
        }
    }
    ans = 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            ll c[4];
            c[0] = a[i][j];
            c[1] = a[n - i + 1][j];
            c[2] = a[i][m - j + 1];
            c[3] = a[n - i + 1][m - j + 1];
            sort(c, c + 4);
            a[i][j] = a[n - i + 1][j] = a[i][m - j + 1] = a[n - i + 1][m - j + 1] = c[1];// c[1] <= x <= c[2]都可
        }
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            ans += abs(a[i][j] - b[i][j]);
        }
    }
    printf("%lld\n", ans);
}
int main() {
    int t;
    scanf("%d", &t);
    for (int i = 0; i < t; ++i) {
        solve();
    }
    return 0;
}

G - Mortal Kombat Tower

题意

你和你的朋友去通过一个挑战塔,这个挑战塔有n层,每层有挑战等级(ai = 0表示容易,ai=1表示困难),你的朋友只能通过容易的层数,困难等级的只能跳过,而你都可以通过。你和你的朋友只能挑战1层或者两层,只能连续挑战不能跳越层数挑战,你和你的朋友来回调整。最开始的回合数是你朋友,挑战n层,问你的朋友跳过的次数。

思路

你朋友每次都是选择最优的,而你的选择也会影响到后面你朋友的选择,经典dp问题。
设dp[i][0]表示朋友回合时当前跳过数,dp[i][1]表示自己的回合朋友的跳过数。
转移方程为
dp[i][0] = min(dp[i - 1][1] + a[i], dp[i - 2][1] + a[i] + a[i - 1]);
dp[i][1] = min(dp[i - 1][0], dp[i - 2][0]);

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 2e5 + 5;
int a[MAXN], dp[MAXN][2];
void solve() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
    }
    dp[1][0] = a[1];
    dp[2][0] = a[1] + a[2];

    dp[1][1] = INF;//肯定不是我解决的。
    dp[2][1] = dp[1][0];
    for (int i = 3; i <= n; ++i) {
        dp[i][0] = min(dp[i - 1][1] + a[i], dp[i - 2][1] + a[i] + a[i - 1]);
        dp[i][1] = min(dp[i - 1][0], dp[i - 2][0]);
    }
    printf("%d\n", min(dp[n][1], dp[n][0]));
}
int main() {
    int t;
    scanf("%d", &t);
    for (int i = 0; i < t; ++i) {
        solve();
    }
    return 0;
}

H - Balanced Team

题意

下面给出一些元素,假定集合A为集合内元素差不超过5,输出这个集合可能的最大值。

思路

排序+二分:二分枚举a[i] + 5的位置再减去i的位置,即为集合元素。取最大即可。

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn = 2e5 + 5;
int a[maxn];
void solve() {
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
    }
    sort(a, a + n);
    int ans = 0;
    for (int i = 0; i < n; ++i) {
        ans = max(ans, upper_bound(a + i, a + n, a[i] + 5) - a - i);
    }
    printf("%d\n", ans);
}
int main() {
    solve();
    return 0;
}

I - Cutting

题意

给你一个正整数n.
以及一个整数序列a[1…n].
现在,你需要决定是否要在a[i]和ai+1切割一次,使得这个序列分成若干个序列。
且要求,全部切割完以后,每个序列中,奇数和偶数出现的次数都是一样的.
如{1,2,3,4}可以切割一次变成{1,2}{3,4}两个序列,且每个序列中奇数和偶数出现的次数都是一样的为1.
每切割一次的代价是|a[i]-a[i+1]|.
问你在不超过B代价的情况下,你最多能对这个序列切割几次?

思路

寻找哪些可以切割,可以切割的价值存进set里面(小->大),然后用B减去相应的代价,得到的最大切割次数即可。

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 200;
int a[MAXN], odd[MAXN], even[MAXN];
void solve() {
    int n, B;
    scanf("%d%d", &n, &B);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        if (a[i] & 1) {
            odd[i] = odd[i - 1] + 1;
            even[i] = even[i - 1];
        } else {
            odd[i] = odd[i - 1];
            even[i] = even[i - 1] + 1;
        }
    }
    if (odd[n] != even[n]) {
        puts("0");
    }
    multiset<int> st;
    for (int i = 2; i < n; i += 2) {
        if (odd[i] == even[i]) {
            st.insert(abs(a[i] - a[i + 1]));
        }
    }
    int ans = 0;
    for (set<int>::iterator it = st.begin(); it != st.end(); ++it) {
        if (B >= (*it)) {
            B -= *it;
            ans++;
        } else {
            break;
        }
    }
    printf("%d\n", ans);
}
int main() {
    solve();
    return 0;
}

J - Brutality

题意

有一个26个按键(每个写着一个字符的)的键盘 某个字母不能连续按超过k次,输出最多的按键次数

思路

将连续相同字母从大到小排序,取前最大的k个即可。

AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 2e5 + 5;
int a[MAXN];
char s[MAXN];
bool cmp(const int& x, const int& y) {
    return x > y;
}
void solve() {
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
    }
    scanf("%s", s);
    ll ans = 0;
    for (int i = 0; i < n;) {
        int j = i;
        for (;s[j] == s[i]; j++) {
        }
        //printf("j == %d\n", j);
        sort(a + i, a + j, cmp);
        for (int l = i; l < j; ++l) {
            if (l - i >= k) break;
            ans += a[l];
        }
        i = j;
    }
    printf("%lld\n", ans);
}
int main() {
    solve();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空皓月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值