小Z 系列 解题报告
在你谷刷题时偶然发现有这么一个系列,大概\(15\)道题目左右。
而且蒟蒻发现,这个系列的出题人基本上全是LittleZ
大佬OrzOrz。
于是心血来潮,想把这个系列全部写完,然后便有了本文。
\(P.s.:\)按蒟蒻自己的做题顺序排列,不一定代表难易。
\(1.\)小Z的矩阵
因为题目中给出的特征函数\(G(A)\)是对\(2\)取模后的结果,所以很容易就可以发现除对角线以外所有元素对答案的贡献均为\(0\),所以第一步单累计对角线贡献即可。
然后我们发现每翻转一行一列对答案无影响,即刚好翻转了所求的和\((\)在\(mod\ 2\)的条件下\()\),所以每次翻转直接取异或即可。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
int n, q, g, x;
int main() {
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &x);
if (i == j) g = (g + x) % 2;
}
}
for (int i = 1; i <= q; i++) {
scanf("%d", &x);
if (x == 3) printf("%d", g);
else {
scanf("%d", &x);
g ^= 1;
}
}
return 0;
}
\(2.\)小Z的k紧凑数
最近刚学数位\(DP\)就看到了这道题\(……\)而且简直和windy数一模一样
具体处理方法和\(windy\)数基本一样,不了解数位\(DP\)的可以去看蒟蒻整理的学习笔记,内含\(windy\)数的详细题解。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long
#define abs(a) ((a) < 0 ? -(a) : (a))
int f[25][15], num[25], a, b, opt;
void init() {
for (int i = 0; i <= 9; i++) f[1][i] = 1;
for (int i = 2; i <= 20; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = 0; k <= 9; k++)
if (abs(j - k) <= opt) f[i][j] += f[i - 1][k];
}
}
}
int calc(int x) {
int len = 0, ans = 0;
memset(num, 0, sizeof(num));
while (x) {
num[++len] = x % 10;
x /= 10;
}
for (int i = 1; i <= len - 1; i++) {
for (int j = 1; j <= 9; j++)
ans += f[i][j];
}
for (int i = len; i >= 1; i--) {
for (int j = 0; j < num[i]; j++) {
if (i == len && !j) continue;
if (abs(num[i + 1] - j) <= opt || i == len) ans += f[i][j];
}
if (abs(num[i + 1] - num[i]) > opt && i != len) break;
}
return ans;
}
signed main() {
scanf("%lld%lld%lld", &a, &b, &opt);
init();
printf("%lld\n", calc(b + 1) - calc(a));
return 0;
}
\(3.\)小Z的车厢
根据题意,我们只需要统计一下同一时刻在列车上最多有多少人即可,然后若人数\(mod\ 36\)为零,直接输出人数\(/36\),否则再\(+1\)。
但是我们要注意,这题的轨道是环形的,所以就会出现这种情况:
路线应是:
然后我们考虑一下,这种情况应该怎么处理。
仔细想一下我们发现:这种情况似乎等价于在\(4\)号下车,再在\(1\)号上车,所以我们每次遇到\(x>y\)的情况时,直接在一号点多累加一次即可。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
#define max(a, b) ((a) > (b) ? (a) : (b))
const int maxn = 1e6 + 6;
int on[maxn], off[maxn], n, m, x, y, z;
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d%d%d", &x, &y, &z);
on[x] += z;
off[y] += z;
if (x > y)
on[1] += z;
}
int ans = 0, sum = 0;
for (int i = 1; i <= n; i++) {
sum += on[i];
sum -= off[i];
ans = max(ans, sum);
}
if (ans % 36 == 0)
std::cout << ans / 36 << '\n';
else std::cout << ans / 36 + 1 << '\n';
return 0;
}
\(4.\)小Z的情书
比较好的一道模拟题目\(……\)
由样例我们可以看出来上面的那张透明纸片是顺时针旋转的,然后把每次翻转后点写下来会发现它是有规律的:
所以我们每次将当前可看到的累加到答案中,然后按规律将坐标进行变换,进行三次就好惹。
突然想到,这个规律貌似和数学上笛卡尔坐标系中的点顺时针翻转\(90\)度的规律类似\(……\)
code:
#include <iostream>
#include <cstdio>
#include <cstring>
using std::string;
int n;
string ans;
char s[1007][1007], ch;
bool p[1007][1007], q[1007][1007];
void reverse() {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
q[i][j] = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (p[i][j] == 1) q[j][n - i + 1] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
p[i][j] = q[i][j];
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
std::cin >> ch;
if (ch == 'O') p[i][j] = 1;
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
std::cin >> s[i][j];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (p[i][j]) ans += s[i][j];
reverse();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (p[i][j]) ans += s[i][j];
reverse();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (p[i][j]) ans += s[i][j];
reverse();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (p[i][j]) ans += s[i][j];
std::cout << ans << '\n';
return 0;
}
\(5.\)小Z的笔记
设计状态:令\(f[i]\)表示前\(i\)个位置变为合法状态最少要删的个数。
朴素的\(DP\)转移方程:
表示当\(i\)和\(j\)可以相邻时,将\(i\sim j\)的所有字母全部删除。
然后很显然这个转移是\(O(n^2)\)的,所以我们考虑如何优化。
我们先将有关\(i\)的部分全部提出来,方程变为:
同时,我们发现只有\(26\)个字母,所以我们对每个字母维护一个数组\(g[i]\),表示:
虽然有两层循环,但第二层只有\(26\),因此总时间复杂度为\(O(26n)\)。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <algorithm>
using std::map;
using std::string;
const int maxn = 1e5 + 5;
bool vis[55][55];
int f[maxn], n, m, g[maxn];
string x;
char s[100005];
int main() {
scanf("%d", &n);
scanf("%s", s + 1);
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
std::cin >> x;
vis[x[0] - 'a'][x[1] - 'a'] = vis[x[1] - 'a'][x[0] - 'a'] = true;
}
memset(f, 0x3f, sizeof(f));
f[1] = 0;
g[s[1] - 'a'] = 0 - 1;
for (int i = 2; i <= n; i++) {
for (int j = 0; j < 26; j++) {
if (vis[s[i] - 'a'][j]) continue;
f[i] = std::min(f[i], g[j] + i - 1);
}
g[s[i] - 'a'] = std::min(g[s[i] - 'a'], f[i] - i);
}
printf("%d\n", f[n]);
return 0;
}
//f[i] = min{f[j] - j} + i - 1;
\(6.\)小Z的栈函数
一道\(……\)恶心的模拟工业题\(……\)
没什么思路可说,按照题目所说的模拟即可,可以选择\(STL\)的\(stack\),也可以选择像蒟蒻一样手写一个栈。
但是\(……\)一定要注意细节!!!一定要注意细节!!!一定要注意细节!!!
简单列举下需要判断的:
-
栈内元素个数不够这次操作所需
-
中途出现绝对值大于\(1e9\)的数
-
除或模时要判断除数是否为\(0\)
然后基本上就没什么了,但它很恶心\(……\)
没有封装又丑陋无比的代码……
code:
#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long
#define abs(a) ((a) < 0 ? -(a) : (a))
using std::string;
string opt;
int st[100050], top, n, m, fr;
struct Node {
string x;
int num;
}a[2005];
signed main() {
while (1) {
++n;
std::cin >> a[n].x;
if (a[n].x == "END") break;
if (a[n].x == "NUM") scanf("%d", &a[n].num);
}
scanf("%d", &m);
bool flag = false;
for (int j = 1; j <= m; j++) {
top = 0; flag = false;
scanf("%d", &fr);
if (fr > 1000000000) {puts("ERROR"); continue;}
st[++top] = fr;
for (int i = 1; i <= n; i++) {
if (a[i].x == "NUM") {
if (a[i].num > 1000000000) {flag = true; break;}
st[++top] = a[i].num;
}
else if (a[i].x == "POP") {
if (top == 0) {flag = true; break;}
else top--;
}
else if (a[i].x == "INV")
if (top == 0) {flag = true; break;}
else {int temp = st[top--]; st[++top] = -temp;}
else if (a[i].x == "DUP")
if (top == 0) {flag = true; break;}
else {int temp = st[top]; st[++top] = temp;}
else if (a[i].x == "SWP")
if (top < 2) {flag = true; break;}
else std::swap(st[top], st[top - 1]);
else if (a[i].x == "ADD")
if (top < 2) {flag = true; break;}
else {
int temp1 = st[top--], temp2 = st[top--];
if (abs(temp1 + temp2) > 1000000000) {flag = true; break;}
st[++top] = temp1 + temp2;
}
else if (a[i].x == "SUB")
if (top < 2) {flag = true; break;}
else {
int temp1 = st[top--], temp2 = st[top--];
if (abs(temp2 - temp1) > 1000000000) {flag = true; break;}
st[++top] = temp2 - temp1;
}
else if (a[i].x == "MUL")
if (top < 2) {flag = true; break;}
else {
int temp1 = st[top--], temp2 = st[top--];
if (abs(temp1 * temp2) > 1000000000) {flag = true; break;}
st[++top] = temp1 * temp2;
}
else if (a[i].x == "DIV")
if (top < 2) {flag = true; break;}
else {
int temp1 = st[top--], temp2 = st[top--];
if (temp1 == 0) {flag = true; break;}
if (abs(temp2 / temp1) > 1000000000) {flag = true; break;}
st[++top] = temp2 / temp1;
}
else if (a[i].x == "MOD")
if (top < 2) {flag = true; break;}
else {
int temp1 = st[top--], temp2 = st[top--];
if (temp1 == 0) {flag = true; break;}
if (abs(temp2 % temp1) > 1000000000) {flag = true; break;}
st[++top] = temp2 % temp1;
}
}
if (flag || top != 1) puts("ERROR");
else std::cout << st[top] << '\n';
}
return 0;
}
\(7.\)小Z的AK计划
算法分析标签\(:\)二叉堆优先队列
这道题本蒟蒻的第一想法是贪心,以一个点坐标与题目数量的乘积为关键字,但这个想法是很容易被\(hack\)的,而且这也没有办法考虑走回去的情况。
那么来找一下本题的突破口,题面中有这样一句话\(:\)当然,也可以过机房而不入
,那么顺着这句话,我们考虑,能否一路走下去,不考虑回头,只考虑进不进入某一机房。
那么我们用一个大根堆优先队列来记录进入过哪些机房,每进入一个新机房,我们统计至今为止所有到过机房所耗的总时间,然后加上当前点坐标,若\(>m\)不断减去队首同时机房数量\(--\),当这一操作进行完后更新答案即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define int long long
#define max(a, b) ((a) > (b) ? (a) : (b))
std::priority_queue<int>q;
const int maxn = 1e5 + 5;
int n, m, tot, sum, ans;
struct Node {
int x, t;
bool operator < (const Node &rhs) const {
return x < rhs.x;
}
}a[maxn];
template<class T>
inline T read(T &x) {
x = 0; int w = 1, ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - 48; ch = getchar();}
return x *= w;
}
signed main() {
read(n), read(m);
for (int i = 1; i <= n; i++)
read(a[i].x), read(a[i].t);
std::sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++) {
if (a[i].x > m) break;
q.push(a[i].t);
++tot;
sum += a[i].t;
while (!q.empty() && sum + a[i].x > m) {
--tot;
sum -= q.top();
q.pop();
}
ans = max(ans, tot);
}
printf("%lld\n", ans);
return 0;
}