{ }codelper
栈64 位整数P0400

在成组书写的串珠图样里数相邻的两种颜色

有一份把珠子串成一排的图样。
珠子的颜色用一个英文小写字母表示。
图样由一个或多个部件不留空格地连着写成。
部件要么是一颗珠子,要么是一组。
一组写成 k(图样) 的形式,意思是把括号里的图样重复串 k 次。
例如按 a2(bc) 串出来是 abcbc,按 2(a2(b)) 串出来是 abbabb。
每份图样都带着两个字母 xy。
求按图样串完的一排里,x 色珠子后面紧跟着 y 色珠子的位置有多少处。
第一行给出图样的份数 t。
接下来 t 行,每行给出两个字母 xy 和一份图样,用一个空格隔开。
对每份图样,各用一行输出位置的数量。

输入
1 ≤ t ≤ 20。
x 和 y 是英文小写字母,可以相同。
图样长度之和不超过 100,000。
k 是 1 到 10^9 的整数,不以 0 开头。
括号里的图样不为空。
组的嵌套深度不超过 3,000。
串完的一排不超过 10^15 颗珠子
输出
对每份图样,各用一行输出 x 后面紧跟 y 的位置的数量
样例 1 · stdin
5
ba 3(ab)
ab a2(b)
ab 2(ca)b
ba 2(3(a)b)
aa 12(a)
stdout
2
1
1
1
11
样例 2 · stdin
2
ab 1000000(1000000(ab))
xy 3(x2(y))
stdout
1000000000000
3
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

串完的一排最多有 10^15 颗珠子,所以把图样真的展开再数,用哪种语言都算不完。
办法是每一组只记三样东西:组内数到的位置数、第一颗珠子、最后一颗珠子。
遇到 ( 就把读到现在的内容压进栈里重新开始,遇到 ) 就从栈里取出来合并。
里面的组重复 k 次时位置数变成 k 倍,如果最后一颗珠子是 x、第一颗珠子是 y,还要加上重复之间的 k - 1 处接缝。
栈的写法:Python 在列表上 append、pop 元组,JS 在数组上 push、pop。
TS 里 stack.pop() 的类型是 Frame | undefined,直接用会报类型错误(TS18048),必须用 as Frame 告诉它。
Java 用 ArrayDeque<long[]> 的 push、pop,C# 用装着元组的 Stack<...> 的 Push、Pop。
C 用结构体数组和栈顶下标 top 自己做。
C++ 的 std::stack 的 pop() 不返回值,所以先用 top() 读,再 pop()。
Craykon 用 添加 往四个列表里压,读出最后一个值,再用 按位置移除 去掉。
答案可能接近 10^15,所以 Java、C# 用 long,C、C++ 用 long long。
JS、TS 的 number 和 Craykon 的数在这个范围内是精确的,Python 的整数没有大小限制。
陷阱一:不能漏掉重复之间的接缝。
ba 3(ab) 是 ababab,所以是 2,漏掉就得到 0。
陷阱二:组前面紧挨着的珠子和组的第一颗珠子也是相邻的。
ab a2(b) 是 abb,所以是 1,不数就得到 0。
陷阱三:一组结束后,最后一颗珠子就是这一组的最后一颗珠子。
不更新的话,ab 2(ca)b(cacab)会得到 0 而不是 1。
陷阱四:一组以另一组开头时,里面那组的第一颗珠子就是外面那组的第一颗珠子。
不传上去的话,ba 2(3(a)b)(aaabaaab)会得到 0 而不是 1。
陷阱五:次数可以是多位数。
只留最后一位数字的话,aa 12(a) 会得到 1 而不是 11。
陷阱六:在 Java 里把位置数放进 int,第二个示例的第一行会变成 -727379968 而不是 1000000000000。
陷阱七:在 Python 里不用栈而是每遇到一组就再调用一次函数,遇到嵌套 3,000 层的图样会因 RecursionError 而终止。
示例里看不出来。
陷阱八:在 Craykon 里把一组真的按次数重复读,会在第二个示例的第一行带着“耗时太长”的提示停止。

Pythonmain.py · 46 行
import sys  def count_pairs(x, y, design):    """다 꿴 줄에서 x 바로 다음에 y가 오는 자리의 수"""    stack = []                       # 바깥 묶음들: (자리 수, 첫 구슬, 끝 구슬, 되풀이 횟수)    count, first, last = 0, "", ""   # 지금 읽고 있는 묶음. 구슬이 아직 없으면 ""    times = 0    for ch in design:        if ch.isdigit():            times = times * 10 + int(ch)        elif ch == "(":            stack.append((count, first, last, times))            count, first, last, times = 0, "", "", 0        elif ch == ")":            in_count, in_first, in_last = count, first, last            count, first, last, k = stack.pop()            total = in_count * k            if in_last == x and in_first == y:     # 되풀이 사이의 이음매 k - 1곳                total += k - 1            if last == x and in_first == y:        # 묶음 앞 구슬과 묶음의 첫 구슬 사이                total += 1            count += total            if first == "":                first = in_first            last = in_last        else:            if last == x and ch == y:                count += 1            if first == "":                first = ch            last = ch    return count  def main():    lines = sys.stdin.read().split("\n")    t = int(lines[0])    out = []    for line in lines[1:t + 1]:        pair, design = line.split()        out.append(str(count_pairs(pair[0], pair[1], design)))    print("\n".join(out))  main()
JavaScriptmain.js · 40 行
const lines = require("fs").readFileSync(0, "utf8").split("\n"); // 다 꿴 줄에서 x 바로 다음에 y가 오는 자리의 수function countPairs(x, y, design) {  const stack = [];                    // 바깥 묶음들: [자리 수, 첫 구슬, 끝 구슬, 되풀이 횟수]  let count = 0, first = "", last = ""; // 지금 읽고 있는 묶음. 구슬이 아직 없으면 ""  let times = 0;  for (const ch of design) {    if (ch >= "0" && ch <= "9") {      times = times * 10 + Number(ch);    } else if (ch === "(") {      stack.push([count, first, last, times]);      count = 0; first = ""; last = ""; times = 0;    } else if (ch === ")") {      const inCount = count, inFirst = first, inLast = last;      const outer = stack.pop();      count = outer[0]; first = outer[1]; last = outer[2];      const k = outer[3];      let total = inCount * k;                           // 10^15 이하라서 number로 정확하다      if (inLast === x && inFirst === y) total += k - 1; // 되풀이 사이의 이음매 k - 1곳      if (last === x && inFirst === y) total += 1;       // 묶음 앞 구슬과 묶음의 첫 구슬 사이      count += total;      if (first === "") first = inFirst;      last = inLast;    } else {      if (last === x && ch === y) count += 1;      if (first === "") first = ch;      last = ch;    }  }  return count;} const t = Number(lines[0]);const out = [];for (let i = 1; i <= t; i++) {  const [pair, design] = lines[i].trim().split(" ");  out.push(countPairs(pair[0], pair[1], design));}console.log(out.join("\n"));
TypeScriptmain.ts · 49 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n"); // 바깥 묶음 하나: 자리 수, 첫 구슬, 끝 구슬, 되풀이 횟수interface Frame {  count: number;  first: string;  last: string;  times: number;} // 다 꿴 줄에서 x 바로 다음에 y가 오는 자리의 수function countPairs(x: string, y: string, design: string): number {  const stack: Frame[] = [];  let count = 0, first = "", last = "";   // 지금 읽고 있는 묶음. 구슬이 아직 없으면 ""  let times = 0;  for (const ch of design) {    if (ch >= "0" && ch <= "9") {      times = times * 10 + Number(ch);    } else if (ch === "(") {      stack.push({ count, first, last, times });      count = 0; first = ""; last = ""; times = 0;    } else if (ch === ")") {      const inCount = count, inFirst = first, inLast = last;      const outer = stack.pop() as Frame;   // 도안이 올바르므로 스택은 비어 있지 않다      count = outer.count; first = outer.first; last = outer.last;      const k = outer.times;      let total = inCount * k;                           // 10^15 이하라서 number로 정확하다      if (inLast === x && inFirst === y) total += k - 1; // 되풀이 사이의 이음매 k - 1곳      if (last === x && inFirst === y) total += 1;       // 묶음 앞 구슬과 묶음의 첫 구슬 사이      count += total;      if (first === "") first = inFirst;      last = inLast;    } else {      if (last === x && ch === y) count += 1;      if (first === "") first = ch;      last = ch;    }  }  return count;} const t = Number(lines[0]);const out: number[] = [];for (let i = 1; i <= t; i++) {  const [pair, design] = lines[i].trim().split(" ");  out.push(countPairs(pair[0], pair[1], design));}console.log(out.join("\n"));
JavaMain.java · 49 行
import java.io.*;import java.util.*; public class Main {    // 다 꿴 줄에서 x 바로 다음에 y가 오는 자리의 수    static long countPairs(char x, char y, String design) {        // 바깥 묶음들을 쌓아 두는 스택. 구슬이 아직 없으면 첫 구슬과 끝 구슬은 0        ArrayDeque<long[]> stack = new ArrayDeque<>();   // {자리 수, 첫 구슬, 끝 구슬, 되풀이 횟수}        long count = 0, times = 0;        char first = 0, last = 0;        for (int i = 0; i < design.length(); i++) {            char ch = design.charAt(i);            if (Character.isDigit(ch)) {                times = times * 10 + (ch - '0');            } else if (ch == '(') {                stack.push(new long[]{count, first, last, times});                count = 0; first = 0; last = 0; times = 0;            } else if (ch == ')') {                long inCount = count;                char inFirst = first, inLast = last;                long[] outer = stack.pop();                count = outer[0]; first = (char) outer[1]; last = (char) outer[2];                long k = outer[3];                long total = inCount * k;                           // int로는 넘친다                if (inLast == x && inFirst == y) total += k - 1;    // 되풀이 사이의 이음매 k - 1곳                if (last == x && inFirst == y) total += 1;          // 묶음 앞 구슬과 묶음의 첫 구슬 사이                count += total;                if (first == 0) first = inFirst;                last = inLast;            } else {                if (last == x && ch == y) count += 1;                if (first == 0) first = ch;                last = ch;            }        }        return count;    }     public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        int t = Integer.parseInt(br.readLine().trim());        StringBuilder sb = new StringBuilder();        for (int i = 0; i < t; i++) {            String[] parts = br.readLine().trim().split(" ");            sb.append(countPairs(parts[0].charAt(0), parts[0].charAt(1), parts[1])).append('\n');        }        System.out.print(sb);    }}
C#Main.cs · 59 行
using System;using System.Collections.Generic;using System.Text; class Program{    // 다 꿴 줄에서 x 바로 다음에 y가 오는 자리의 수    static long CountPairs(char x, char y, string design)    {        // 바깥 묶음들을 쌓아 두는 스택. 구슬이 아직 없으면 첫 구슬과 끝 구슬은 '\0'        var stack = new Stack<(long count, char first, char last, long times)>();        long count = 0, times = 0;        char first = '\0', last = '\0';        foreach (char ch in design)        {            if (char.IsDigit(ch))            {                times = times * 10 + (ch - '0');            }            else if (ch == '(')            {                stack.Push((count, first, last, times));                count = 0; first = '\0'; last = '\0'; times = 0;            }            else if (ch == ')')            {                long inCount = count;                char inFirst = first, inLast = last;                long k;                (count, first, last, k) = stack.Pop();                long total = inCount * k;                           // int로는 넘친다                if (inLast == x && inFirst == y) total += k - 1;    // 되풀이 사이의 이음매 k - 1곳                if (last == x && inFirst == y) total += 1;          // 묶음 앞 구슬과 묶음의 첫 구슬 사이                count += total;                if (first == '\0') first = inFirst;                last = inLast;            }            else            {                if (last == x && ch == y) count += 1;                if (first == '\0') first = ch;                last = ch;            }        }        return count;    }     static void Main()    {        int t = int.Parse(Console.ReadLine()!.Trim());        var sb = new StringBuilder();        for (int i = 0; i < t; i++)        {            string[] parts = Console.ReadLine()!.Trim().Split(' ');            sb.Append(CountPairs(parts[0][0], parts[0][1], parts[1])).Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 57 行
#include <cctype>#include <iostream>#include <stack>#include <string> // 바깥 묶음 하나. 구슬이 아직 없으면 first와 last는 0struct Frame {    long long count;    char first, last;    long long times;}; // 다 꿴 줄에서 x 바로 다음에 y가 오는 자리의 수long long countPairs(char x, char y, const std::string& design) {    std::stack<Frame> stack;    long long count = 0, times = 0;    char first = 0, last = 0;    for (char ch : design) {        if (std::isdigit(static_cast<unsigned char>(ch))) {            times = times * 10 + (ch - '0');        } else if (ch == '(') {            stack.push({count, first, last, times});            count = 0; first = 0; last = 0; times = 0;        } else if (ch == ')') {            long long inCount = count;            char inFirst = first, inLast = last;            Frame outer = stack.top();   // top()은 보기만 하고, 꺼내는 것은 pop()이다            stack.pop();            count = outer.count; first = outer.first; last = outer.last;            long long k = outer.times;            long long total = inCount * k;                          // int로는 넘친다            if (inLast == x && inFirst == y) total += k - 1;        // 되풀이 사이의 이음매 k - 1곳            if (last == x && inFirst == y) total += 1;              // 묶음 앞 구슬과 묶음의 첫 구슬 사이            count += total;            if (first == 0) first = inFirst;            last = inLast;        } else {            if (last == x && ch == y) count += 1;            if (first == 0) first = ch;            last = ch;        }    }    return count;} int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    int t;    std::cin >> t;    for (int i = 0; i < t; i++) {        std::string pair, design;        std::cin >> pair >> design;        std::cout << countPairs(pair[0], pair[1], design) << "\n";    }    return 0;}
Cmain.c · 59 行
#include <stdio.h>#include <ctype.h> #define MAX_LEN 100000#define MAX_DEPTH 3000 /* 바깥 묶음 하나. 구슬이 아직 없으면 first와 last는 0 */typedef struct {    long long count;    char first, last;    long long times;} Frame; static char design[MAX_LEN + 2];static Frame stack[MAX_DEPTH]; /* 다 꿴 줄에서 x 바로 다음에 y가 오는 자리의 수 */static long long count_pairs(char x, char y, const char *s) {    int top = 0;    long long count = 0, times = 0;    char first = 0, last = 0;    for (int i = 0; s[i] != '\0'; i++) {        char ch = s[i];        if (isdigit((unsigned char)ch)) {            times = times * 10 + (ch - '0');        } else if (ch == '(') {            stack[top++] = (Frame){count, first, last, times};            count = 0; first = 0; last = 0; times = 0;        } else if (ch == ')') {            long long in_count = count;            char in_first = first, in_last = last;            Frame outer = stack[--top];            count = outer.count; first = outer.first; last = outer.last;            long long k = outer.times;            long long total = in_count * k;                         /* int로는 넘친다 */            if (in_last == x && in_first == y) total += k - 1;      /* 되풀이 사이의 이음매 k - 1곳 */            if (last == x && in_first == y) total += 1;             /* 묶음 앞 구슬과 묶음의 첫 구슬 사이 */            count += total;            if (first == 0) first = in_first;            last = in_last;        } else {            if (last == x && ch == y) count += 1;            if (first == 0) first = ch;            last = ch;        }    }    return count;} int main(void) {    int t;    char pair[4];    if (scanf("%d", &t) != 1) return 1;    for (int i = 0; i < t; i++) {        if (scanf("%3s %100000s", pair, design) != 2) return 1;        printf("%lld\n", count_pairs(pair[0], pair[1], design));    }    return 0;}
Craykonmain.cray · 81 行
// 다 꿴 줄에서 x 바로 다음에 y가 오는 자리의 수자리세기(x, y, 도안){    // 바깥 묶음들을 쌓아 두는 스택. 묶음마다 자리 수, 첫 구슬, 끝 구슬, 되풀이 횟수    쌓은수 = [];    쌓은첫 = [];    쌓은끝 = [];    쌓은횟수 = [];    수 = 0; // 지금 읽고 있는 묶음. 구슬이 아직 없으면 첫과 끝은 ""    첫 = "";    끝 = "";    횟수 = 0;    遍历 (ch 在 도안)    {        如果 (ch >= "0" && ch <= "9")        {            횟수 = 횟수 * 10 + ch.转数字();        }        否则 如果 (ch == "(")        {            쌓은수.添加(수);            쌓은첫.添加(첫);            쌓은끝.添加(끝);            쌓은횟수.添加(횟수);            수 = 0;            첫 = "";            끝 = "";            횟수 = 0;        }        否则 如果 (ch == ")")        {            안수 = 수;            안첫 = 첫;            안끝 = 끝;            위 = 쌓은수.数量 - 1; // 스택의 맨 위에서 바깥 묶음을 꺼낸다            수 = 쌓은수[위];            첫 = 쌓은첫[위];            끝 = 쌓은끝[위];            k = 쌓은횟수[위];            쌓은수.按位置移除(위);            쌓은첫.按位置移除(위);            쌓은끝.按位置移除(위);            쌓은횟수.按位置移除(위);            모두 = 안수 * k;            如果 (안끝 == x && 안첫 == y)            {                모두 += k - 1;            }            如果 (끝 == x && 안첫 == y)            {                모두 += 1;            }            수 += 모두;            如果 (첫 == "")            {                첫 = 안첫;            }            끝 = 안끝;        }        否则        {            如果 (끝 == x && ch == y)            {                수 += 1;            }            如果 (첫 == "")            {                첫 = ch;            }            끝 = ch;        }    }    返回 수;} t = 控制台.读行().转数字();循环 (i = 0; i < t; i++){    parts = 控制台.读行().去空白().分割(" ");    控制台.输出(자리세기(parts[0][0], parts[0][1], parts[1]));}