目录

Codeforces#811 Div3 题解

D. Color with Occurrences

You are given some text t and a set of n strings $s_1, s_2, \dots, s_n$ .

In one step, you can choose any occurrence of any string $s_i$ in the text $t$ and color the corresponding characters of the text in red. For example, if $t=\texttt{bababa}$ and $s_1=\texttt{ba}$ , $s_2=\texttt{aba}$ , you can get $t=\color{red}{\texttt{ba}}$ $\texttt{baba}$ , $t=\texttt{b}\color{red}{\texttt{aba}}$ $\texttt{ba}$ or $t=\texttt{bab}$ $\color{red}{\texttt{aba}}$ in one step.

You want to color all the letters of the text t in red. When you color a letter in red again, it stays red.

In the example above, three steps are enough:

  • Let’s color $t[2 \dots 4]=s_2=\texttt{aba}$ in red, we get $t=\texttt{b}\color{red}{\texttt{aba}}$ $\texttt{ba}$ ;
  • Let’s color $t[1 \dots 2]=s_1=\texttt{ba}$ in red, we get $t=\color{red}{\texttt{baba}}$ $\texttt{ba}$ ;
  • Let’s color $t[4 \dots 6]=s_2=\texttt{aba}$ in red, we get $t=\color{red}{\texttt{bababa}}$ . Each string $s_i$ can be applied any number of times (or not at all). Occurrences for coloring can intersect arbitrarily.

Determine the minimum number of steps needed to color all letters t in red and how to do it. If it is impossible to color all letters of the text t in red, output -1.

Input

The first line of the input contains an integer $q (1 \le q \le 100)$ —the number of test cases in the test.

The descriptions of the test cases follow.

The first line of each test case contains the text $t (1 \le |t| \le 100)$ , consisting only of lowercase Latin letters, where $|t|$ is the length of the text t.

The second line of each test case contains a single integer $n (1 \le n \le 10)$ — the number of strings in the set.

This is followed by n lines, each containing a string $s_i (1 \le |s_i| \le 10)$ consisting only of lowercase Latin letters, where $|s_i|$ — the length of string $s_i$ .

Output

For each test case, print the answer on a separate line.

If it is impossible to color all the letters of the text in red, print a single line containing the number -1.

Otherwise, on the first line, print the number m — the minimum number of steps it will take to turn all the letters t red.

Then in the next m lines print pairs of indices: $w_j$ and $p_j (1 \le j \le m)$ , which denote that the string with index $w_j$ was used as a substring to cover the occurrences starting in the text t from position $p_j$ . The pairs can be output in any order.

If there are several answers, output any of them.

解法一

题目核心其实是一个区间覆盖的问题,每个 $s_i$ 在 $t$ 中能匹配上的部分就是一个区间,题目求的就是用最少的区间去覆盖整个 $t$。所以我们按照贪心的思路,从$t_0$开始找到能overlap右边界的最大区间,然后更新右边界,最后覆盖完所有的区间,得到的就是最少的覆盖次数。在过程中记录下区间信息,输出步骤即可。

代码实现如下:

import java.util.*;
import java.io.*;

// AtCoder/AcWing 提交上面部分即可,CF需要将JavaMain移到上面然后提交
public class D {
    public static void main(String[] args) throws Exception {
        // 输入重定向,通过jvm参数判断环境
        if (args.length > 0 && "Resolmi_DEBUG".equals(args[0])) {
            System.setIn(new FileInputStream("./input.txt"));
        }
        new Main().main(args);
    }
}

class Main {

    public static void main(String[] args) throws Exception {
        PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int Q = read(br)[0];
        while (Q-- > 0) {
            String t = br.readLine();
            int n = read(br)[0];
            // match[i]: t第i个位置最远能匹配到哪里
            var match = new int[t.length()][2];

            String[] s = new String[n];
            for (int i = 0; i < n; i++) {
                s[i] = br.readLine();
                // 预处理每个s[i]在t中能匹配的位置
                for (int j = 0; j < t.length(); j++) {
                    var flag = true;
                    for (int k = 0; k < s[i].length(); k++) {
                        if (j + k >= t.length() || t.charAt(j+k) != s[i].charAt(k)) {
                            flag = false;
                            break;
                        }
                    }
                    if (!flag) continue;
                    if (match[j][1] <= s[i].length() + j) {
                        match[j] = new int[]{i, s[i].length() + j};
                    }
                }
            }
            // 下一次开始匹配的位置
            var last = 0;
            var flag = true;
            var cnt = 0;
            var lis = new ArrayList<String>();
            while (last < t.length()) {
                var temp = last;
                int w = 0, p = 0;
                for (int i = 0; i <= temp; i++) {
                    if (match[i][1] >= last) {
                        last = match[i][1];
                        w = match[i][0]+1;
                        p = i+1;
                    }
                }
                // 没有能cover last的区间
                if (last == temp) {
                    flag = false;
                    break;
                }
                cnt++;
                lis.add(w + " " + p);
            }
            if (flag) {
                out.println(cnt);
                lis.stream().forEach(out::println);
            } else {
                out.println(-1);
            }

        }
        out.flush();
    }


    public static int[] read(BufferedReader br) throws Exception {
        return Arrays.stream(br.readLine().split(" ")).mapToInt(Integer::parseInt).toArray();
    }
}

E. Add Modulo 10

You are given an array of n integers $a_1, a_2, \dots, a_n$

You can apply the following operation an arbitrary number of times:

  • select an index $i (1 \le i \le n)$ and replace the value of the element $a_i$ with the value $a_i + (a_i \bmod 10)$, where $a_i \bmod 10$ is the remainder of the integer dividing $a_i$ by $10$.

For a single index (value $i$), this operation can be applied multiple times. If the operation is applied repeatedly to the same index, then the current value of $a_i$ is taken into account each time. For example, if $a_i=47$ then after the first operation we get $a_i=47+7=54$, and after the second operation we get $a_i=54+4=58$.

Check if it is possible to make all array elements equal by applying multiple (possibly zero) operations.

For example, you have an array $[6, 11]$.

  • Let’s apply this operation to the first element of the array. Let’s replace $a_1 = 6$ with $a_1 + (a_1 \bmod 10) = 6 + (6 \bmod 10) = 6 + 6 = 12$. We get the array $[12, 11]$.
  • Then apply this operation to the second element of the array. Let’s replace $a_2 = 11$ with $a_2 + (a_2 \bmod 10) = 11 + (11 \bmod 10) = 11 + 1 = 12$. We get the array $[12, 12]$.

Thus, by applying 2 operations, you can make all elements of an array equal.

Input

The first line contains one integer $t (1 \le t \le 10^4)$ — the number of test cases. What follows is a description of each test case.

The first line of each test case contains one integer $n (1 \le n \le 2 \cdot 10^5)$ — the size of the array.

The second line of each test case contains n integers $a_i (0 \le a_i \le 10^9)$ — array elements.

It is guaranteed that the sum of n over all test cases does not exceed $2 \cdot 10^5$.

Output

For each test case print:

  • YES if it is possible to make all array elements equal;
  • NO otherwise.

You can print YES and NO in any case (for example, the strings yEs, yes, Yes and YES will be recognized as a positive answer) .

解法一

找规律,题目大意是给定一组数组,尝试替换$a_i$为$a_i+(a_i \bmod 10)$,问能否是的数组所有元素相等。

我们可以将元素替换序列都列出来,然后找规律,进行分组,具体见代码:

import java.util.*;
import java.io.*;

// AtCoder/AcWing 提交上面部分即可,CF需要将JavaMain移到上面然后提交
public class E {
    public static void main(String[] args) throws Exception{
        // 输入重定向,通过jvm参数判断环境
        if (args.length > 0 && "Resolmi_DEBUG".equals(args[0])) {
            System.setIn(new FileInputStream("./input.txt"));
        }
        new Main().main(args);
    }
}

class Main {

    // 1  2  4  8  16  22  24  28  36  42  44 ...
    // 3  6  12 14  18  26  32  34  38  46  52 ...
    // 5  10 10 10 ....
    // 15 20 20 20 ...
    // 7 14 18 26 ....
    // 9 18
    public static void main(String[] args) throws Exception {
        PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = read(br)[0];
        // 分析发现20为一个循环,我们将20以内的元素进行分组,两组元素序列完全平行,无法相交
        // 所以如果元素 mod 20在同一组中,那么就一定可以转化成一样的,否则一定不行
        // 除此之外,需要额外注意5和0结尾的元素,它们最多只能转化一次,所以如果有5/0结尾的元素,那么所有元素都必须是5/0结尾,且5结尾经过转换后与0结尾相同
        var s1 = new HashSet<>();
        s1.add(1); s1.add(2); s1.add(4); s1.add(8); s1.add(13); s1.add(16); s1.add(17); s1.add(19);
        var s2 = new HashSet<>();
        s2.add(3); s2.add(6); s2.add(7); s2.add(9); s2.add(11); s2.add(12); s2.add(14); s2.add(18); 
        while (T-- > 0) {
            read(br);
            var a = read(br);
            var set5 = new HashSet<Integer>();
            var cnt5 = 0;

            var ins1 = false;
            var ins2 = false;
            for (Integer v : a) {
                if (v%10 == 5 || v%10 == 0) {
                    cnt5++;
                    set5.add(v + v%10);
                }
                if (s1.contains(v%20)) {
                    ins1 = true;
                }
                if (s2.contains(v%20)) {
                    ins2 = true;
                }
            }
            // 既有s1中的元素,又有s2中的元素 || 有5/0结尾的但是不全是 || 全是5/0结尾的,但是转换后不想等
            if ((ins2 && ins1) || (cnt5 != 0 && cnt5 != a.length) || (cnt5 == a.length && set5.size() != 1)) {
                out.println("No");
            } else {
                out.println("Yes");
            }
        }
        out.flush();
    }


    public static int[] read(BufferedReader br) throws Exception {
        return Arrays.stream(br.readLine().split(" ")).mapToInt(Integer::parseInt).toArray();
    }
}

G. Path Prefixes

You are given a rooted tree. It contains $n$ vertices, which are numbered from $1$ to $n$. The root is the vertex $1$.

Each edge has two positive integer values. Thus, two positive integers $a_j$ and $b_j$ are given for each edge.

Output $n-1$ numbers $r_2, r_3, \dots, r_n$, where $r_i is defined as follows.

Consider the path from the root (vertex $1$) to $i (2 \le i \le n)$. Let the sum of the costs of $a_j$ along this path be $A_i$. Then $r_i$ is equal to the length of the maximum prefix of this path such that the sum of $b_j$ along this prefix does not exceed $A_i$.

https://static.imlgw.top/blog/20220807214642.png

… 题目太长,懒得copy,去看原题吧

解法一

dfs栈上二分,如果这题放在D或者E我肯定能写出来,放在后面并且题目这么长,让人有点畏惧,不过说到底还是太菜了。

import java.util.*;
import java.io.*;

// AtCoder/AcWing 提交上面部分即可,CF需要将JavaMain移到上面然后提交
public class G {
    public static void main(String[] args) throws Exception{
        // 输入重定向,通过jvm参数判断环境
        if (args.length > 0 && "Resolmi_DEBUG".equals(args[0])) {
            System.setIn(new FileInputStream("./input.txt"));
        }
        new Main().main(args);
    }
}

class Main {

    static int idx;
    static int[] e, ne, h, wa, wb;
    static int si;
    static long[] stack;
    static int[] res;

    public static void add(int a, int b, int i, int j) {
        e[idx] = b; ne[idx] = h[a];
        wa[idx] = i; wb[idx] = j;
        h[a] = idx++;
    }

    public static void main(String[] args) throws Exception {
        PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = read(br)[0];
        while (T-- > 0) {
            int N = read(br)[0];
            
            idx = 0;   
            h = new int[N+1]; Arrays.fill(h, -1);
            e = new int[N+1]; ne = new int[N+1];
            wa = new int[N+1]; wb = new int[N+1];

            stack = new long[N+1];
            si = 0;

            res = new int[N+1];
            for (int i = 2; i <= N; i++) {
                int[] t = read(br);
                add(t[0], i, t[1], t[2]);
            }
            dfs(1, 0, 0);
            for (int i = 2; i <= N; i++) {
                out.print(res[i] + " ");
            }
            out.println();
        }
        out.flush();
    }

    public static void dfs(int x, long suma, long sumb) {
        stack[si++] = sumb;
        // 二分找suma
        int left = 0, right = si-1;
        while (left <= right) {
            int mid = left + (right-left)/2;
            if (stack[mid] <= suma) {
                res[x] = mid;
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        for (int i = h[x]; i != -1; i = ne[i]) {
            dfs(e[i], suma+wa[i], sumb+wb[i]);
        }
        // 出栈
        si--;
    }


    public static int[] read(BufferedReader br) throws Exception {
        return Arrays.stream(br.readLine().split(" ")).mapToInt(Integer::parseInt).toArray();
    }
}

解法二

倍增的做法,学习下

import java.util.*;
import java.io.*;

// AtCoder/AcWing 提交上面部分即可,CF需要将JavaMain移到上面然后提交
public class G_2 {
    public static void main(String[] args) throws Exception{
        // 输入重定向,通过jvm参数判断环境
        if (args.length > 0 && "Resolmi_DEBUG".equals(args[0])) {
            System.setIn(new FileInputStream("./input.txt"));
        }
        new Main().main(args);
    }
}

class Main {

    static int idx;
    static int[] e, ne, h, wa, wb;
    static int[] res;
    // fa[i][j],节点i向上跳2^j的节点
    static int[][] fa;
    // fsb[i][j],节点i向上跳2^j的节点sumb总和
    static long[][] fsb;
    static int[] dep;
    static long[] suma, sumb;

    public static void add(int a, int b, int i, int j) {
        e[idx] = b; ne[idx] = h[a];
        wa[idx] = i; wb[idx] = j;
        h[a] = idx++;
    }

    // 倍增做法
    public static void main(String[] args) throws Exception {
        PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = read(br)[0];
        while (T-- > 0) {
            int N = read(br)[0];
            idx = 0;   
            h = new int[N+1]; Arrays.fill(h, -1);
            e = new int[N+1]; ne = new int[N+1];
            wa = new int[N+1]; wb = new int[N+1];

            suma = new long[N+1]; sumb = new long[N+1];
            dep = new int[N+1];
            // fa[i][j]: i向上跳2^j的节点
            fa = new int[N+1][31];
            fsb = new long[N+1][31];

            for (int i = 2; i <= N; i++) {
                int[] t = read(br);
                add(t[0], i, t[1], t[2]);
            }
            // 构建倍增数组
            dfs(1);
            for (int i = 2; i <= N; i++) {
                int res = dep[i];
                if (suma[i] < sumb[i]) {
                    // 向上走大于等于t距离的第一个
                    long t = sumb[i]-suma[i];
                    int x = i;
                    for (int k = 30; k >= 0; k--) {
                        if (fsb[x][k] <= t) {
                            t -= fsb[x][k];
                            x = fa[x][k];
                            res = dep[x];
                        }
                    }
                    // 找不到sumb刚好相差t的节点,再往上走一步
                    if (t > 0) res--;
                }
                out.print(res+" ");
            }
            out.println();
        }
        out.flush();
    }

    public static void dfs(int root) {

        for (int i = 1; i < 31; i++) {
            fa[root][i] = fa[fa[root][i-1]][i-1];
            // root跳2^i = root跳到2^(i-1) + 2^(i-1)到2^i
            fsb[root][i] = fsb[root][i-1] + fsb[fa[root][i-1]][i-1];
        }

        for (int i = h[root]; i != -1; i = ne[i]) {
            int t = e[i];
            suma[t] = suma[root] + wa[i];
            sumb[t] = sumb[root] + wb[i];
            dep[t] = dep[root] + 1;
            fa[t][0] = root;
            fsb[t][0] = wb[i];
            dfs(t);
        }
    }


    public static int[] read(BufferedReader br) throws Exception {
        return Arrays.stream(br.readLine().split(" ")).mapToInt(Integer::parseInt).toArray();
    }
}