NZOJ 模拟赛7
T1 字符串
小X十分热爱学习。有一天,他刚学完“漂亮的k字符串”的概念:给定长度为n的字符串和整数k,k能整除n,如果该字符串满足以下两个条件:
- s是一个回文串,即对于任意1≤i≤n,Si=Sn+1-i(其中Si表示字符串中第i个字母)
- 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;
}