NZOJ 模拟赛7

T1 字符串

小X十分热爱学习。有一天,他刚学完“漂亮的k字符串”的概念:给定长度为n的字符串和整数k,k能整除n,如果该字符串满足以下两个条件:

  1. s是一个回文串,即对于任意1≤i≤n,Si=Sn+1-i(其中Si表示字符串中第i个字母)
  1. s以k为周期,即对于任意1≤i≤n-k, Si=Sk+i(其中Si表示字符串中第i个字母)

就称该字符串为“漂亮的k字符串“。

比如说“abaaba“就是一个漂亮的3字符串,而”abccba“则不是。

小Y是小X 的同学,她对“漂亮的K字符串”也很感兴趣,她有一些字符串,每个字符串都是由小写字母构成,并且对于每个字符串她都会给出一个k,想让小X帮自己把该字符串修改成“漂亮的k字符串”。小Y可以选择任意位置的字母并将他们改成任意其他小写字母。由于小Y的字符串又长又多,小X又想在小Y前表现自己,在1s内把每个字符串修改为“漂亮的k字符串”,于是就请你来帮忙求出每个字符串最少需要修改多少字符才能成为“漂亮的k字符串”。

分析该字符串的性质,我们可以发现要满足上述性质的字符串必须满足两点:

  • 对于任意 \([n(k - 1) + 1, ~ nk + 1]\) 的子串 \(S\),必须满足 \(S_{i} = S_{k - i + 1}\)

  • 对于所有的串 \(S_i(1 \le i \le \frac{n}{k})\),第 \(t\) 个字符满足 \(S_{i,t} = S_{j,t}(i \ne j)\)

综上,我们可以得出上述的字符可以划分为若干个不相交的集合,对于每个集合中的元素,我们考虑求出所有字符变为同一个字符需要的最少次数,累加即可。

参考代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, k, sum;
int cnt[26];
char s[N];

void solve()
{
    cin >> n >> k;
    cin >> s + 1;
    int ans = 0;
    for (int i = 1; i <= (k + 1) >> 1; i ++ )
    {
        int sum = 0, res = 1e9;
        for (int j = 0; j < 26; j ++ ) cnt[j] = 0;
        for (int j = i; j <= n; j += k) cnt[s[j] - 'a'] ++ , sum ++ ;
        if (i != k / 2 + 1) for (int j = k - i + 1; j <= n; j += k) cnt[s[j] - 'a'] ++ , sum ++ ;
        for (int j = 0; j < 26; j ++ ) res = min(sum - cnt[j], res);
        ans += res;
    }
    cout << ans << endl;
}

int main()
{
    int T;
    cin >> T;
    while (T -- ) solve();
    return 0;
}

T2 抓

小X这天兴致勃勃的要跑去小Y家玩。在他向小Y家行走了t秒后,小Y这时得知小X要来他家玩,认为上周小X弄丢自己作业后没有道歉是非常不对的,于是怒火中烧,要抓住小X让他给自己道歉。小X也突然意识到上周自己刚把小Y的作业弄丢,于是放弃去往小Y家,开始逃跑。

小X和小Y居住的城市有n个小区,这些小区由n-1条双向道路连接起来,每两个小区之间都可以通过这n-1条道路中的某几条到达,并且每条道路的长度只有1m。小X住在1号小区,小Y住在n号小区。小X的跑步速度为1m/s(在小Y开始抓小X时,小X既可以选择跑向和当前小区有道路的小区,也可以选择原地不动),而小Y的跑步速度为2m/s。并且小Y对整个城市和小X都十分了解,每时每刻都在以最短的路径跑向小X。小X想要让小Y尽可能晚的抓到自己,因为时间越长小Y越不愤怒,这样他就能获得小Y的原谅了。你能帮小X计算出从小Y出发抓小X最长经过几秒后抓住小X吗?

第一行包括两个整数n(3≤n≤105),t(1≤t≤n-1),分别表示小区个数和最开始经过了t秒后小Y出发抓小X。
接下来n-1行每行包含两个整数x(1≤x≤n),y(1≤y≤n)表示第x和第y个小区之间有一条长度为1m的双向道路。

对于100%的数据, 3≤n≤10^5, 1≤t≤n-1且t小于1号小区到n号小区的距离。

我们需要先求出 \(t\) 秒后小X的位置,才能够进行下一步判断,考虑从 \(1\)\(n\) 分别跑一次最短路,记小Y为起点的最短路为 \(dist1\),小X为起点的最短路为 \(dist2\),那么经过 \(t\) 秒后的节点 \(u\) 一定满足 \(dist1[i] + t = dist1[1]\)\(dist2[i] - t = 0\),因为 \(u\)\(1 \to n\) 的路径上,所以两者需要都满足才可以。

我们求出 \(u\) 后,考虑重新求 \(u\) 为起点的最短路,替换原来的 \(dist2\),考虑小X能达到的地方必定满足 \(dist1[i] \ge dist2[i] \times 2\),否则小Y可以比小X先到达目的地,小X在途中就会被抓,对于合法的目的地,求出小Y到目的地的时间的最大值即可。

参考代码

#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int n, t, ans;
int h[N], e[N << 1], ne[N << 1], idx;
int st[N], dist1[N], dist2[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dijkstra(int S, int dist[])
{
    memset(st, 0, sizeof st);
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, S}), dist[S] = 0;
    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();
        int u = t.second, distance = t.first;
        if (st[u]) continue;
        st[u] = true;
        for (int i = h[u]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + 1)
            {
                dist[j] = distance + 1;
                heap.push({dist[j], j});
            }
        }
    }
}

int main()
{
    cin >> n >> t;
    memset(h, -1, sizeof h);
    memset(dist1, 0x3f, sizeof dist1);
    memset(dist2, 0x3f, sizeof dist2);
    for (int i = 1; i < n; i ++ )
    {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);
    }
    dijkstra(n, dist1), dijkstra(1, dist2);
    int now = 0;
    for (int i = 1; i <= n; i ++ )
        if (dist1[i] + t == dist1[1] && dist2[i] - t == 0)
            now = i;
    memset(dist2, 0x3f, sizeof dist2);
    dijkstra(now, dist2);
    for (int i = 1; i <= n; i ++ )
        if (dist1[i] >> 1 >= dist2[i])
            ans = max(ans, (dist1[i] + 1) >> 1);
    cout << ans;
    return 0;
}

T3 彩虹

多年以后,小X和小Y结婚了,他们搬到了一个新的城市。该城市有r条南北方向的街道,也有r条东西方向的街道。每一条南北街道中的都与每一条东西街道交叉;第x条南北街道和第y条东西街道之间的十字路口用(x,y)表示。为了从十字路口(x,y)移动到十字路口(x’,y’),小需要|x - x‘|+|y - y’|分钟。

小X知道这座城市有n条美丽的彩虹会在十字路口出现。他想带领小Y尽可能多地观看这些彩虹。对于每一个i=1,…,n,小X知道第i条彩虹将第ti分钟开始时在十字路口(x,y)出现并在很短的时间内消失。小X和小Y必须在第ti分钟开始时就出现(x,y)处才会看到彩虹,并且小X和小Y对于每条彩虹只观看一瞬间即可。

目前小X和小Y在第0分钟开始时在他们的新家,位于十字路口(1,1)。作为小X的好兄弟,小X希望你能帮他求出他最多可以带小Y参观多少条彩虹?

在任意时刻最多只有一条彩虹。

第一行两个整数r(1≤r≤500),和n(1≤n≤100000),表示南北街道和东西街道的条数、彩虹出现的个数。

接下来n行数据,每行包括三个整数ti(1≤ti≤1000000),xi(1≤xi≤r),yi(1≤yi≤r),表示第ti分钟有一条彩虹出现在十字路口(xi,yi)处。

考虑先对 \(t_i\) 排序,设 \(dp[i]\) 表示到 \(t_i\) 时间节点时最多能看见多少彩虹。

\(t_0 = 0\),对于任何可达的 \(i\),如果 \(j ~ (0 \le j < i)\) 可以转移到 \(i\),必有 \(dp[i] = \max\limits_{j = 0}^{i - 1} dp[j] + 1\)

不妨将 \(dp\) 初始化为 \(-1\),对每个 \(j\) 判断是否可达,转移的必要条件是 \(\mid x_i - x_j \mid + \mid y_i - y_j \mid \le t_i - t_j\),如果可达且可以转移则 \(dp[i] = \max(dp[i], dp[j] + 1)\)

但是这样是 \(O(n^2)\) 的,我们需要进一步优化,对于状态 \(i\),我们事实上只用考虑最近的 \(2r\) 个状态,因为 \(2r\) 个状态内必然可以跑到所有地点,因此再往前找状态是不优的,因此时间复杂度 \(O(rn)\)

参考代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int r, n, dp[N];
struct Node
{
    int t, x, y;
}q[N];

bool cmp(Node a, Node b)
{
    return a.t < b.t;
}

int calc(Node a, Node b)
{
    return abs(a.x - b.x) + abs(a.y - b.y);
}

int main()
{
    cin >> r >> n;
    for (int i = 1; i <= n; i ++ ) cin >> q[i].t >> q[i].x >> q[i].y;
    sort(q + 1, q + n + 1, cmp);
    memset(dp, -1, sizeof dp);
    q[0] = {0, 1, 1}, dp[0] = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = max(0, i - 2 * r); j < i; j ++ )
            if (calc(q[i], q[j]) <= q[i].t - q[j].t && dp[j] != -1)
                dp[i] = max(dp[i], dp[j] + 1);
    int ans = 0;
    for (int i = 1; i <= n; i ++ ) ans = max(ans, dp[i]);
    cout << ans;
    return 0;
}

T4 游戏

更多年之后,小X和小Y垂垂老矣,只能窝在家里玩游戏了。
有一个由非负整数组成的长度为n的数组a。小X和小Y轮流行动,每个人最初的得分都等于0。小X先行动。
让我们来描述一下游戏中的一个行动:
在一个行动中,玩家选择数组中的任何一个元素并将其从数组中移除,并使用玩家当前的分数对其进行异或。
即如果玩家当前的分数是x,而选择的元素是y,那么他的新分数将是x⊕y。这里⊕表示按位异或运算。
被移除的元素后在以后的行动中不能被选取。
当数组为空时游戏结束。
游戏结束时,赢家是得分最高的选手。如果两位选手得分相同,那就是平局。
从前面几道题我们就可以看出来,小X和小Y都是非常聪明的人,都会以最优的方式让自己获得最大的得分。请你来判定一下,谁会赢下比赛或者比赛平局。

第一行一个整数t(1≤t≤1000)表示数据的组数。
接下来有t组数据,每组数据的第一行包括一个整数n(1≤n≤100000),表示数组元素个数。第二行包含n个整数a1、a2……an,表示数组中的每个元素。且(0≤ai≤1000000000)

对于每组数据,输出一个字符串。如果小X赢下比赛,输出“WIN”(不含双引号)
如果小Y赢下比赛,输出“LOSE”(不含双引号),如果平局,输出“DRAW”。每组数据的输出独占一行。

首先考虑平局的情况,当且仅当所有数异或为 \(0\) 时才会平局,否则一定有胜负,所有的数都会被异或到两人手中,最后小X与小Y两人手中的数异或和为 \(0\) 就说明相等,不为零一定分胜负。

接下来考虑异或和的最高位,这个最高位的出现次数一定是奇数,如果这个位被人取走,那么他就是胜者。我们考虑什么情况下这个位会被人取走:

  • 总的数字个数为偶数时,先手优先取走这个 \(1\),无论后手取什么,先手必定可以对称的使这个 \(1\) 存在,先手必胜。

  • 总的数字个数为奇数时,考虑这个 \(1\) 的个数 \(cnt\),如果 \(cnt \equiv 1 \pmod 4\),那么先手取走这个 \(1\),然后后手取或不取先手只需要对称的做法即可,先手必胜。

  • 总的数字个数为奇数时,如果 \(cnt \equiv 3 \pmod 4\),无论先手取不取走这个 \(1\),后手对称的做同样操作,最后一个 \(1\) 一定会被先手取走,此时后手最高位为 \(1\),先手为 \(0\),因此后手必胜。

参考代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, a[N];

int lowbit(int x)
{
    return x & -x;
}

void solve()
{
    int sum = 0;
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]), sum ^= a[i];
    if (!sum) return puts("DRAW"), void();
    if (!(n & 1)) return puts("WIN"), void();
    while (sum > lowbit(sum)) sum -= lowbit(sum);
    int cnt = 0;
    for (int i = 1; i <= n; i ++ )
        if (sum & a[i]) cnt ++ ;
    if (cnt % 4 == 1) puts("WIN");
    else puts("LOSE");
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- ) solve();
    return 0;
}
posted @ 2024-10-13 17:06  YipChip  阅读(37)  评论(0编辑  收藏  举报