【杂题合集】震惊!这种生活习惯可能致癌!99%的人都有......

杂题合集

基本上都是不太难的思维题,难的我也写不出来。
标题党去死。

CF1714E Add Modulo 10

题意概述:

给出一个序列,可以给其中任意一个数加上当前它的个位数,问进行若干次操作后这个序列里的数能否全部相等。

解析:

思维题,不算难。

观察个位数,找规律:

  • 个位数是 \(0\):只能是 \(0\)
  • 个位数是 \(1\)\(1 → 2\)
  • 个位数是 \(2\)\(2 → 4 → 8 → 6 → 2\)
  • 个位数是 \(3\)\(3 → 6 → 2\)
  • 个位数是 \(4\)\(4 → 8 → 6 → 2\)
  • 个位数是 \(5\)\(5 → 0\)
  • 个位数是 \(6\)\(6 → 2\)
  • 个位数是 \(7\)\(7 → 4 → 8 → 6 →2\)
  • 个位数是 \(8\)\(8 → 6 → 2\)
  • 个位数是 \(9\)\(9 → 8 → 6 →2\)

可见,除个位数为 \(0\)\(5\) 外,所有的数都可将个位数变为 \(2\)

对于个位数是 \(5\) 的数,就给它加上 \(5\),此时只有其他数都与它相等,序列才合法。

对于其他数,都把它变成以 \(2\) 为个位数的数。注意到个位数从 \(2\) 再变到 \(2\),相当于加了 \(20\)
将变化后的数列sort一遍,判断相邻的两数之差是不是 \(20\) 的倍数就行了。

Code

点击查看代码
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAXN = 2e5 + 10;
int t, n;
int num[MAXN];

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

bool Check(){
    sort(num + 1, num + 1 + n);
    if(num[1] % 10 == 0){
        if(num[n] == num[1]) return true;
        else return false;
    }

    for(register int i = 2; i <= n; i++){
        int cut = num[i] - num[i - 1];
        if(cut % 20) return false;
    }

    return true;
}

int main(){
    t = read();
    while(t--){
        n = read();
        for(register int i = 1; i <= n; i++){
            num[i] = read();

            if(num[i] % 10 != 2){
                switch(num[i] % 10){
                    case 1 : num[i] += 1; break;
                    case 3 : num[i] += 9; break;
                    case 4 : num[i] += 18; break;
                    case 5 : num[i] += 5; break;
                    case 6 : num[i] += 6; break;
                    case 7 : num[i] += 25; break;
                    case 8 : num[i] += 14; break;
                    case 9 : num[i] += 23; break;
                }
            }
        } 

        if(Check()) puts("Yes");
        else puts("No");
    }

    return 0;
}

CF1711B Party

题目概述:

给出一个 \(n\) 个点,\(m\) 条边的无向图,问删去多少的点使得删去的点权最小,且留下的点组成的图中有偶数条边。输出删去的点的点权和。

解析:

如果 \(m\) 为偶数,不用删点。

如果 \(m\) 为奇数,删点的同时删掉的边要为奇数条,大力分类讨论:

  1. 删一个点:显然要选择一个度数为奇数的点。
  2. 删两个点:再度大力分类讨论:
    1. 删去两个相连的,且度数都为偶数的点。
    2. 删去两个相连的,且度数都为奇数的点。
    3. 删去不相连的,度数一奇一偶的点:
      但是我们可以只删去那个度数为奇数的点,这样做会更优,所以这种情况一定不会有最优解。
  3. 删三个点:先考虑删去一个度数为奇,两个度数为偶且两两不相连的点。但这样完全可以只删去那个度数为奇的点,所以删去三个点一定不会有最优解。

所以,最优解只有可能在三种情况中出现,即删掉一个度数为奇的点、删掉有相连的两个度数都为奇的点、删掉相连的两个度数都为偶数的点。

因为 \(n,m ≤ 10 ^ {5}\),删去一个点的直接枚举点,删去两个点的直接枚举边即可。

Code

点击查看代码
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int MAXN = 1e5 + 10, MAXM = 1e5 + 10;
int t, n, m, ans;
int unhap[MAXN];
int from[MAXM], to[MAXM], deg[MAXN];

inline void Clear(){
    ans = 2147483647;
    memset(deg, 0, sizeof(deg));
}

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

int main(){
    t = read();
    while(t--){
        Clear();
        n = read(), m = read();
        for(register int i = 1; i <= n; i++)
            unhap[i] = read();
        for(register int i = 1; i <= m; i++){
            int u, v;
            u = read(), v = read();
            deg[u]++, deg[v]++;
            from[i] = u, to[i] = v;
        }

        if(!(m & 1)){ 
            puts("0");
            continue;
        }

        for(register int i = 1; i <= n; i++)
            if(deg[i] & 1) ans = min(ans, unhap[i]);
        for(register int i = 1; i <= m; i++){
            int u = from[i], v = to[i];
            if(!((deg[u] + deg[v]) & 1))
                ans = min(ans, unhap[u] + unhap[v]);
        }

        printf("%d\n", ans);
    }

    return 0;
}

CF1605C Dominant Character

题目概述:

给定一个由 abc 组成的字符串,找出一个最短的子串,满足 a 的数量大于 b 的数量和 c 的数量。

解析:

大力讨论:

  1. 串长为 \(2\),两个 a 相邻。
  2. 串长为 \(3\),所以仅当两个 a 中间隔了一个 bc,即 abcaca
  3. 串长为 \(4\)a 的数量至少是 \(2\)a 不能相邻,只能是 abcaacba,不会有 \(3\)a的情况。
  4. 串长为 \(5\)a 的数量至少是 \(3\),则会有长为 \(3\) 的子串中有两个 a,不成立。
  5. 串长为 \(6\)a 的数量至少是 \(3\),会出现长为 \(3\) 的子串中有两个 a,不成立。
  6. 串长为 \(7\)a 的数量至少是 \(4\),且a 之间需要有三个不是 a 的字母隔开。

之后以此类推,发现答案只可能到 \(7\),再长的子串都能都被拆分成更短的满足要求的子串,之后枚举即可。

点击查看代码

Code

#include<cstdio>

using namespace std;

const int MAXN = 1e6 + 10;
int t, n;
char s[MAXN];

int main(){
    scanf("%d", &t);
    while(t--){
        bool flag = false;
        scanf("%d", &n);
        scanf("%s", s + 1);

        for(register int i = 2; i <= n && !flag; i++){
            if(s[i] == 'a' && s[i - 1] == 'a'){
                puts("2");
                flag = true;
            }
        }
        if(n >= 3)
            for(register int i = 2; i <= n - 1 && !flag; i++){
                if(s[i - 1] == 'a' && s[i + 1] == 'a'){
                    puts("3");
                    flag = true;
                }
            }
        if(n >= 4)
            for(register int i = 2; i <= n - 2 && !flag; i++){
                if(s[i - 1] == 'a' && s[i + 2] == 'a' && ((s[i] == 'b' && s[i + 1] == 'c') || (s[i] == 'c' && s[i + 1] == 'b'))){
                    puts("4");
                    flag = true;
                }
            }
        if(n >= 7)
            for(register int i = 4; i <= n - 3 && !flag; i++){
                if(s[i] == 'a' && s[i - 3] == 'a' && s[i + 3] == 'a'){
                    if(s[i - 1] == 'b' && s[i - 2] == 'b' && s[i + 1] == 'c' && s[i + 2] == 'c'){
                        puts("7");
                        flag = true;
                    }
                    else if(s[i - 1] == 'c' && s[i - 2] == 'c' && s[i + 1] == 'b' && s[i + 2] == 'b'){
                        puts("7");
                        flag = true;
                    }
                }
            }
        
        if(!flag) puts("-1");
    }

    return 0;
}

CF1714G Path Prefixe

题目概述:

给你一颗以一号节点为根的树,每个节点上有两个权值,分别为 \(a_{i}\)\(b_{i}\)。记 \(A_{i}\) 为从根到节点 \(i\)\(a_{i}\) 的前缀和,求最长的使得 \(b_{i}\) 的和小于等于 \(A_{i}\) 的前缀。

解析:

其实题目概述写啰嗦了,我的确概述不了这道题。读懂了题就很简单了。

看数据范围 \(n ≤ 2 \times 10 ^ {5}\),以及题目中的最长不大于,可以往二分上靠。

树论的话 dfs 是必不可少的,用 dfs 求出每个节点 \(a\)\(b\) 的前缀和,把当前节点的 \(b\) 的前缀和压进栈里,本层的 dfs 结束时再把它弹出栈,就可以保证栈里的元素全部是从根节点到当前节点路径上,每个点的 \(b\) 的前缀和,upper_bound 一下即可求出最长的前缀。

注意开 long longCF Div.3 光卡 long long

Code

点击查看代码
#include<cstdio>
#include<cstring>
#include<algorithm>

#define LL long long

using namespace std;

const int MAXN = 2e5 + 10;
int t, n, cnt, top;
int head[MAXN], ans[MAXN];
LL sum_a[MAXN], sum_b[MAXN]; 
LL stk[MAXN];

struct Edge{
    int to, next, dis_a, dis_b;
}e[MAXN << 1];

inline void Add(int u, int v, int a, int b){
    e[++cnt].to = v;
    e[cnt].dis_a = a;
    e[cnt].dis_b = b;
    e[cnt].next = head[u];
    head[u] = cnt;
}

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

void dfs(int rt, int fa){
    stk[++top] = sum_b[rt];

    for(register int i = head[rt]; i; i = e[i].next){
        int v = e[i].to;
        if(v == fa) continue;
        sum_a[v] = sum_a[rt] + e[i].dis_a;
        sum_b[v] = sum_b[rt] + e[i].dis_b;
        dfs(v, rt);
    }

    int pos = upper_bound(stk + 1, stk + 1 + top, sum_a[rt]) - stk;
    ans[rt] = pos - 2;
    top--;
}

void Clear(){
    cnt = 0;
    top = 0;
    memset(head, 0, sizeof(head));
}

int main(){
    t = read();
    while(t--){
        Clear();

        n = read();
        for(register int i = 2; i <= n; i++){
            int p, a, b;
            p = read(), a = read(), b = read();
            Add(p, i, a, b);
            Add(i, p, a, b);
        }

        dfs(1, 0);

        for(register int i = 2; i <= n; i++)
            printf("%d ", ans[i]);
        puts("");
    }

    return 0;
}

CF1714F Build a Tree and That Is It

题目概述:

概不出来了自己品吧。

树是一个没有环的无向连通图,注意,在本题中,我们讨论的是无根树

现有四个整数 \(n, d_{12}, d_{23}\)\(d_{31}\),构建一颗满足以下条件的树:

  • 包含从 \(1\)\(n\)\(n\) 个节点;
  • 从节点 \(1\) 到节点 \(2\) 的距离(最短路的长度)为 \(d_{12}\)
  • 从节点 \(2\) 到节点 \(3\) 的距离为 \(d_{23}\)
  • 从节点 \(3\) 到节点 \(1\) 的距离为 \(d_{31}\)

输出满足条件的任意一棵树;若不存在,输出 NO

解析:

每条边的边权为 \(1\)

题目说是无根树,可以先假设它有一个根节点,设 \(d_{1}\)\(d_{2}\)\(d_{3}\) 三条互不相交的分别连接根节点和 \(1\)\(2\)\(3\) 号节点的链的长度。

然后就可得到:

\[\left\{\begin{matrix} d_{12} = d_{1} + d_{2} \\ d_{23} = d_{2} + d_{3} \\ d_{31} = d_{1} + d_{3} \end{matrix}\right. \]

只后我们移项,消元:

\[\left\{\begin{matrix} d_{1} = \frac{d_{12} + d_{31} - d_{23}}{2} \\ d_{2} = \frac{d_{12} + d_{23} - d_{31}}{2} \\ d_{3} = \frac{d_{23} + d_{31} - d_{12}}{2} \end{matrix}\right. \]

其中 \(d_{1}\)\(d_{2}\)\(d_{3}\) 可能为 \(0\),代表该节点就是根节点。

至于无解的情况,显然,\(d_{1}\)\(d_{2}\)\(d_{3}\) 都要 \(≥0\),且\(d_{12} + d_{31} - d_{23}\)\(d_{12} + d_{23} - d_{31}\)\(d_{23} + d_{31} - d_{12}\) 都应为偶数。

构造完这 \(3\) 条链后的树即为满足 \(3\) 个点之间的约束条件的最小的树,判断总的节点数 \(tot = d_{1} + d{2} + d{3} + 1\) (从根节点到 \(1\)\(2\)\(3\) 号节点的链的长度就是链上的节点数,最后要加上根节点)与 \(n\) 的大小关系:

  • \(tot < n\),则剩余的节点在任意节点上都不会影响树的合法性。
  • \(tot = n\),构造结束。
  • \(tot > n\),不合法,构造失败。

之后我们需要找出根节点是谁,若 \(d_{1}\)\(d_{2}\)\(d_{3}\) 都不为 \(0\),我们可以让任意一个 \(>3\)\(≤n\) 的节点为根节点,这里选择了 \(4\) 号节点。

最后剩下的节点随便连谁都可以,这里选择接在 \(3\) 号节点。

Code

点击查看代码
#include<cstdio>

using namespace std;

int t, n, d12, d23, d31;
int root, tot, d[4];

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

void Build_Tree(int x){
    printf("%d ", root);
    for(register int i = 1; i < d[x]; i++){
        ++tot;
        printf("%d\n", tot);
        printf("%d ", tot);
    }
    printf("%d\n", x);
}

void Clear(){
    tot = 0;
    root = 0;
    d[1] = d[2] = d[3] = 0;
}

int main(){
    t = read();
    while(t--){
        Clear();

        n = read();
        d12 = read(), d23 = read(), d31 = read();

        d[1] = (d12 + d31 - d23) / 2;
        d[2] = (d12 + d23 - d31) / 2;
        d[3] = (d23 + d31 - d12) / 2;

        if(d[1] + d[2] + d[3] + 1 > n || d[1] < 0 || d[2] < 0 || d[3] < 0){
            puts("NO");
            continue;
        }
        if((d12 + d31 - d23) & 1 || (d12 + d23 - d31) & 1 || (d23 + d31 - d12) & 1){
            puts("NO");
            continue;
        }

        puts("YES");

        tot = 3;
        if(!d[1]) root = 1;
        if(!d[2]) root = 2;
        if(!d[3]) root = 3;
        if(!root){
            tot = 4;
            root = 4;
        }

        if(1 != root) Build_Tree(1);
        if(2 != root) Build_Tree(2);
        if(3 != root) Build_Tree(3);

        while(tot < n) printf("3 %d\n", ++tot);
    }

    return 0;
}

P7913 [CSP-S 2021] 廊桥分配

没考试,刷点历年原题。看着它是21年的T1,应该好欺负,然后想了一中午才想出来。

题目概述:

概NM,自己看。

解析:

廊桥先到先得,仅于飞机的起降顺序有关,加入第 \(i\) 条廊桥不会对前 \(i - 1\) 条廊桥的分配产生影响。我们设 \(f(x)\) 代表分配 \(x\) 条廊桥能停靠的国内航班数,\(g(x)\) 代表分配 \(x\) 条廊桥能停靠的国际航班数,可得 \(f(x) = f(x - 1) + 再加入一个廊桥产生的贡献\)\(g(x) = g(x - 1) + 再加入一个廊桥产生的贡献\)

考虑每加入一条廊桥会影响哪些飞机。用 set 来维护当前在远机位和未降落的飞机。每加入一条廊桥,当前 set 顶端的飞机 \(p\) 停靠到廊桥,之后,set 中在 \(p\) 起飞后第一个降落的飞机 \(p'\) 停靠到廊桥......以此类推。

直接在 set 中二分查找,记录次数,然后再把找到的飞机清出 set(停靠过一遍廊桥即起飞),最后加入这条廊桥的贡献就是找到的飞机的次数。

Code

点击查看代码
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAXN = 1e5 + 10;
int n, m1, m2, ans;
int sum1[MAXN], sum2[MAXN];

struct Plane{
    int st, ed;

    bool operator < (const Plane &a) const{
        return st < a.st;
    }
}p1[MAXN], p2[MAXN];

multiset<Plane> s1, s2;

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

int Get_Num(multiset<Plane> &s){
    int ans = 0;
    auto pos = s.begin();

    if(pos == s.end()) return 0;

    Plane p = *pos;
    p.st = p.ed;
    ++ans;
    s.erase(pos);

    while(!s.empty()){
        pos = s.lower_bound(p);
        if(pos == s.end()) break;
        ++ans;
        p = *pos;
        p.st = p.ed;
        s.erase(pos);
    }

    return ans;
}

int main(){
    n = read(), m1 = read(), m2 = read();
    for(register int i = 1; i <= m1; i++){
        p1[i].st = read(), p1[i].ed = read(); //国内航班
        s1.insert(p1[i]);
    }
    for(register int i = 1; i <= m2; i++){
        p2[i].st = read(), p2[i].ed = read(); //国外航班
        s2.insert(p2[i]);
    }

    sort(p1 + 1, p1 + 1 + m1);
    sort(p2 + 1, p2 + 1 + m2);

    for(register int i = 1; i <= n; i++)
        sum1[i] = sum1[i - 1] + Get_Num(s1);
    for(register int i = 1; i <= n; i++)
        sum2[i] = sum2[i - 1] + Get_Num(s2);

    for(register int i = 0; i <= n; i++)
        ans = max(ans, sum1[i] + sum2[n - i]);

    printf("%d", ans);

    return 0;
}

P2515 [HAOI2010]软件安装

边写边颓,结果一A了(坏了,我成wonder了)。

题目概述:

自己概去,语文功底不好,概不出来。

解析:

最开始当图论题打的,结果越打越不对,发现是个树上的背包问题。

首先,每个软件之间有依赖关系,即软件 \(i\) 只有在安装了软件 \(D_i\) 之后才能正常工作,考虑从 \(D_i\)\(i\) 连边,最后会得到一个类似树形的关系。然后就理所应当的想到拓扑排序或者树形DP了是吧。

问题在于这个关系并不能直接构成树,每个关系(每条边)都是单向的,所以可能出现游离在外的点或环,比如 \(D_1 = 2, D_2 = 3, D_3 = 1\),这时 \(1, 2, 3\) 就形成了一个独立的环。这个环要么都选,要么都不选,所以可以用tarjan缩点,把环缩成一个联通块。

缩完点之后仍然有独立的点,需要把它变成一个树状结构。考虑将所有入度为 \(0\) 的点和 \(0\) 连一条边,这样这张图就可以转化成一棵以 \(0\) 为根的树,然后就可以在树上跑01背包了。

设定状态 \(dp[i][j]\) 代表以 \(i\) 为根的子树在 \(j\) 的容量下能得到的最大的价值,在递归到当前节点的时候记得初始化一下DP数组就行了。为啥不讲转移?就是个普通的01背包,讲啥啊。

Code

点击查看代码
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int MAXN = 110, MAXM = 510;
int n, m, cnt, num, tot;
int head[MAXN], spa[MAXN], val[MAXN], from[MAXN], to[MAXN];
int dfn[MAXN], low[MAXN], belong[MAXN], room[MAXN], value[MAXN], in[MAXN];
int stk[MAXN], top;
int dp[MAXN][MAXM];
bool vis[MAXN];

struct Edge{
    int to, next;
}e[MAXN << 1];

inline void Add(int u, int v){
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

void Tarjan(int u){
    dfn[u] = low[u] = ++num;
    stk[++top] = u;
    vis[u] = true;

    for(register int i = head[u]; i; i = e[i].next){
        int v = e[i].to;
        if(!dfn[v]){
            Tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(vis[v]) low[u] = min(low[u], dfn[v]);
    }

    if(low[u] == dfn[u]){
        ++tot;
        int t;
        do{
            t = stk[top--];
            vis[t] = false;
            belong[t] = tot;
            room[tot] += spa[t];
            value[tot] += val[t];
        }while(t != u);
    }
}

void Rebuild(){
    cnt = num = 0;
    memset(head, 0, sizeof(head));

    for(register int i = 1; i <= n; i++){
        int u = from[i], v = to[i];
        int blu = belong[u], blv = belong[v];

        if(!u) continue;
        if(blu != blv){
            Add(blu, blv);
            ++in[blv];
        }
    }

    for(register int i = 1; i <= tot; i++)
        if(!in[i]) Add(0, i);
}

void dfs(int rt){
    for(register int i = room[rt]; i <= m; i++)
        dp[rt][i] = value[rt];

    for(register int i = head[rt]; i; i = e[i].next){
        int v = e[i].to;
        dfs(v);

        int k = m - room[rt];
        for(register int i = k; i >= 0; i--)
            for(register int j = 0; j <= i; j++)
                dp[rt][i + room[rt]] = max(dp[rt][i + room[rt]], dp[v][j] + dp[rt][i + room[rt] - j]);
    }
}

int main(){
    n = read(), m = read();
    for(register int i = 1; i <= n; i++)
        spa[i] = read();
    for(register int i = 1; i <= n; i++)
        val[i] = read();
    for(register int i = 1; i <= n; i++){
        int u;
        u = read();
        if(u) Add(u, i);
        from[i] = u, to[i] = i;
    }

    for(register int i = 1; i <= n; i++)
        if(!dfn[i]) Tarjan(i);
    
    Rebuild();

    dfs(0);

    printf("%d\n", dp[0][m]);

    return 0;
}
posted @ 2022-09-15 16:15  TSTYFST  阅读(48)  评论(0编辑  收藏  举报