「アルゴリズム競技・クイック300問」 1日1問「前置圧縮技術」

アルゴリズム コンペティション: クイック 300 問」は 2024 年に出版される予定で、「アルゴリズム コンペティション」の補助問題集です。
すべての質問は、自作の OJ New Online Judgeに配置されます。
コードは C/C++、Java、Python の 3 つの言語で提供されており、トピックは主に中レベルから低レベルのトピックであり、初級レベルから上級レベルの学生に適しています。


プレフィックス圧縮テクノロジー 」、リンク: http://oj.ecustacm.cn/problem.php?id=2140

質問の説明

[問題の説明] Web サイトには現在 n 人のユーザーがいます。各ユーザー名は長さ L の文字列です。すべてのユーザー名を保存するには、n*L 文字を保存する必要があります。
   メモリを圧縮する方法が追加されました。各ユーザーの完全なユーザー名を保存する代わりに、他のユーザー名に同じプレフィックスがない限り、そのユーザー名のプレフィックスのみを保存します。
   たとえば、名前が james と jacob だけの場合、jam と jac のみが保存され、両方とも認識されます。
   この圧縮技術を使用すると、n 個のユーザー名を保存するには何文字が必要になりますか?
[入力形式] 1行目は正の整数n、L、2≦n≦10000、1≦L≦1000です。入力は n * L ≤ 1000000 であることが保証されます。
   次の n 行には、各行に小文字のみを含む長さ L の文字列が含まれており、すべての文字列は互いに異なります。
[出力形式]答えを表す整数を出力します。
【入力サンプル】

样例12 5
james
jacob

样例24 4
xxxx
yxxx
xxyx
yxxy

【出力サンプル】

样例16

样例214

答え

   シミュレーションと辞書ツリーの 2 つの方法を使用して解決します。
【ポイント】辞書ツリー。

C++ コード-1

   一つ目の方法はシミュレーションです。この質問では、異なるプレフィックスがいくつあるかを数える必要があります。手順は次のとおりです:
   (1) すべての文字列を並べ替えます。
   (2) 2 つの隣接する文字列のプレフィックスを継続的に比較します。異なる文字が見つかったら停止し、プレフィックスの長さを一度記録します。次に、次の 2 つの隣接する文字列の比較を続けます。

#include<bits/stdc++.h>
using namespace std;
string s[10001];
int ans[10001];
int main(){
    
    
    int n, L;    cin >> n >> L;
    for(int i = 0; i < n; i++)  cin >> s[i];
    sort(s, s + n);                   //字符串排序
    int sum = 0;
    for(int i = 0; i < n - 1; i++){
    
             //连续比较字符串s[i] 和 s[i+1]
        for(int j = 0; j < L; j++){
    
              
            ans[i]   = max(ans[i],   j+1);
            ans[i+1] = max(ans[i+1], j+1);
            if(s[i][j] != s[i+1][j]) break;  //第j个字符不同
        }
        sum += ans[i];     //统计第i个字符串的前缀:它的前j个字符和其他字符都不同
    }
    cout<<sum + ans[n-1];
    return 0;
}

C++ コード-2

   以下では、辞書ツリー エンコーディングが使用されています (辞書ツリー テンプレート、清華大学出版局、Luo Yongjun、Guo Weibin、「アルゴリズム コンペティション」、562 ページを参照)。
   まず辞書ツリーtree[]を構築します。Tree[u].son[v] は、u 番号のノードの次の文字が v であるノードの番号を表します。
   次に、Insert() を使用して、すべての文字列を辞書ツリーに挿入します。辞書ツリーのルートノードからこの文字列の文字が出現するかどうかを調べ、出現する場合、このプレフィックスの番号は num++ です。num=1 の場合、このプレフィックスは 1 回だけ出現し、一意であることを意味します。
   最後に、Find() を使用して、すべての異なるプレフィックスの合計長をカウントします。つまり、num=1 でプレフィックスをクエリし、その長さを累積します。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
struct node{
    
    
    int son[26];  //26个字母
    int num;      //这个前缀出现的次数
}t[N];            //字典树tree[u].son[v]:编号为u的节点的下一个字母为v的节点的编号
int cnt = 1;      //当前分配的存储位置。把cnt=0留给根节点
void Insert(char *s) {
    
                    //往字典树中插入字符串s
    int now = 0;                      //从根节点开始找插入前缀的位置
    for(int i = 0; s[i]; i++) {
    
    
        int ch = s[i]-'a';
        if(t[now].son[ch]==0)         //这个字符还没有存储过
            t[now].son[ch] = cnt++;   //把cnt位置分配给这个字符
        now = t[now].son[ch];         //沿着字典树往下走
        t[now].num++;                 //统计这个前缀出现多少次
    }
}
int Find(int u, int len){
    
    
    if(t[u].num == 1)  return len;   //这个前缀只出现了1次,说明是唯一的前缀
    int ans = 0;
    for(int i = 0; i < 26; i++)      //遍历整个字典树,统计只出现一次的前缀
        if(t[u].son[i])
            ans += Find(t[u].son[i], len+1);
    return ans;
}
char s[1010];
int main(){
    
    
    int n, L;  scanf("%d%d", &n, &L);
    while(n--){
    
    
        scanf("%s", s);
        Insert(s);
    }
    cout<<Find(0, 0)<<endl;  //从根节点0开始找,此时字符长度为0
    return 0;
}

Java コード-1

シミュレーション

 import java.util.Arrays;
import java.util.Scanner;
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int L = scanner.nextInt();
        String[] s = new String[n];
        for (int i = 0; i < n; i++)    s[i] = scanner.next();
        Arrays.sort(s); // 字符串排序
        int[] ans = new int[n];
        int sum = 0;
        for (int i = 0; i < n - 1; i++) {
    
     // 连续比较字符串s[i] 和 s[i+1]
            for (int j = 0; j < L; j++) {
    
    
                ans[i]   = Math.max(ans[i],  j+1);
                ans[i+1] = Math.max(ans[i+1],j+1);
                if (s[i].charAt(j) != s[i + 1].charAt(j))  break; // 第j个字符不同
            }
            sum += ans[i]; // 统计第i个字符串的前缀:它的前j个字符和其他字符都不同
        }
        System.out.println(sum + ans[n - 1]);
    }
}

Java コード-2

辞書ツリー

import java.util.*;
public class Main {
    
    
    static class Node {
    
    
        int[] son = new int[26];
        int num;
    }
    static Node[] t = new Node[1000010];
    static int cnt = 1;
    public static void main(String[] args) {
    
    
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int L = input.nextInt();
        for (int i = 0; i < t.length; i++) 
            t[i] = new Node(); 
        for (int i = 0; i < n; i++) {
    
    
            String s = input.next();
            Insert(s);
        }
        System.out.println(Find(0, 0));
    }
    static void Insert(String s) {
    
    
        int now = 0;
        for (int i = 0; i < s.length(); i++) {
    
    
            int ch = s.charAt(i) - 'a';
            if (t[now].son[ch] == 0) {
    
    
                t[now].son[ch] = cnt++;
            }
            now = t[now].son[ch];
            t[now].num++;
        }
    }
    static int Find(int u, int len) {
    
    
        if (t[u].num == 1) return len;
        int ans = 0;
        for (int i = 0; i < 26; i++) 
            if (t[u].son[i] != 0) 
                ans += Find(t[u].son[i], len + 1);
        return ans;
    }
}

Python コード-1

   シミュレーション

n, L = map(int, input().split())
s = []
for i in range(n):    s.append(input())
s.sort()                     # 字符串排序
ans = [0] * n
sum = 0
for i in range(n - 1):    # 连续比较字符串s[i] 和 s[i+1]
    for j in range(L):
        ans[i]   = max(ans[i], j + 1)
        ans[i+1] = max(ans[i + 1], j + 1)
        if s[i][j] != s[i+1][j]:  break     # 第j个字符不同            
    sum += ans[i]  # 统计第i个字符串的前缀:它的前j个字符和其他字符都不同
print(sum + ans[n - 1])

Python コード-2

   辞書ツリー

import sys
sys.setrecursionlimit(1000000)
class Node:
    def __init__(self):
        self.son = [0] * 26
        self.num = 0
t = [Node() for _ in range(1000010)]
cnt = 1
def Insert(s):
    global cnt
    now = 0
    for c in s:
        ch = ord(c) - ord('a')
        if t[now].son[ch] == 0:
            t[now].son[ch] = cnt
            cnt += 1
        now = t[now].son[ch]
        t[now].num += 1
def Find(u, length):
    if t[u].num == 1:  return length
    ans = 0
    for i in range(26):
        if t[u].son[i] != 0:
            ans += Find(t[u].son[i], length + 1)
    return ans
n, L = map(int, input().split())
for _ in range(n):
    s = input().strip()
    Insert(s)
print(Find(0, 0))

おすすめ

転載: blog.csdn.net/weixin_43914593/article/details/132405803