码迷,mamicode.com
首页 > 其他好文 > 详细

小Z 系列 解题报告

时间:2019-10-25 14:55:24      阅读:72      评论:0      收藏:0      [点我收藏+]

标签:tac   矩阵   splay   函数   lld   维护   temp   选择   队列   

在你谷刷题时偶然发现有这么一个系列,大概\(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->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 << '\n';
    else std::cout << ans / 36 + 1 << '\n';
    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 << '\n';
    return 0;
}

\(5.\)小Z的笔记

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

朴素的\(DP\)转移方程:

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

表示当\(i\)\(j\)可以相邻时,将\(i\sim 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\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;
}

技术图片

小Z 系列 解题报告

标签:tac   矩阵   splay   函数   lld   维护   temp   选择   队列   

原文地址:https://www.cnblogs.com/Hydrogen-Helium/p/11737950.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!