小Z 系列 解题报告

Posted hydrogen-helium

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小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
", calc(b + 1) - calc(a));
    return 0;
}

(3.)小Z的车厢

根据题意,我们只需要统计一下同一时刻在列车上最多有多少人即可,然后若人数(mod 36)为零,直接输出人数(/36),否则再(+1)

但是我们要注意,这题的轨道是环形的,所以就会出现这种情况:

[4->3]

路线应是:

[4->1->2->3]

然后我们考虑一下,这种情况应该怎么处理。

仔细想一下我们发现:这种情况似乎等价于在(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 << '
';
    else std::cout << ans / 36 + 1 << '
';
    return 0;
}

(4.)小Z的情书

比较好的一道模拟题目(……)

由样例我们可以看出来上面的那张透明纸片是顺时针旋转的,然后把每次翻转后点写下来会发现它是有规律的:

[(i,j)Rightarrow (j,n-i+1)]

所以我们每次将当前可看到的累加到答案中,然后按规律将坐标进行变换,进行三次就好惹。

突然想到,这个规律貌似和数学上笛卡尔坐标系中的点顺时针翻转(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 << '
';
    return 0;
}

(5.)小Z的笔记

设计状态:令(f[i])表示前(i)个位置变为合法状态最少要删的个数。

朴素的(DP)转移方程:

[f[i]=min{f[j]-j+i-1},jepsilon[a,z]]

表示当(i)(j)可以相邻时,将(isim j)的所有字母全部删除。

然后很显然这个转移是(O(n^2))的,所以我们考虑如何优化。

我们先将有关(i)的部分全部提出来,方程变为:

[f[i]=min{f[j]-j}+i-1]

同时,我们发现只有(26)个字母,所以我们对每个字母维护一个数组(g[i]),表示:

[g[j] = min{f[j]-j}]

虽然有两层循环,但第二层只有(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
", 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] << '
';
    }
    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
", ans);
    return 0;
}

技术图片

以上是关于小Z 系列 解题报告的主要内容,如果未能解决你的问题,请参考以下文章

SPOJ QTREE 系列解题报告

解题报告(十八)Codeforces - 数学题目泛做(难度:2000 ~ 3000 + )

[解题报告]迷宫 状压+最短路 SPFA

解题报告多项式求值与插值(拉格朗日插值)(ACM / OI)

SPOJ GSS系列解题报告

解题报告(十七)概率与期望(概率论)(ACM / OI)