Z 函式(擴充套件KMP)

辜铜星發表於2024-07-18

author: LeoJacob, Marcythm, minghu6

約定:字串下標以 \(0\) 為起點。

定義

對於一個長度為 \(n\) 的字串 \(s\),定義函式 \(z[i]\) 表示 \(s\)\(s[i,n-1]\)(即以 \(s[i]\) 開頭的字尾)的最長公共字首(LCP)的長度,則 \(z\) 被稱為 \(s\)Z 函式。特別地,\(z[0] = 0\)

國外一般將計算該陣列的演算法稱為 Z Algorithm,而國內則稱其為 擴充套件 KMP

這篇文章介紹在 \(O(n)\) 時間複雜度內計算 Z 函式的演算法以及其各種應用。

解釋

下面若干樣例展示了對於不同字串的 Z 函式:

  • \(z(\mathtt{aaaaa}) = [0, 4, 3, 2, 1]\)
  • \(z(\mathtt{aaabaab}) = [0, 2, 1, 0, 2, 1, 0]\)
  • \(z(\mathtt{abacaba}) = [0, 0, 1, 0, 3, 0, 1]\)

樸素演算法

Z 函式的樸素演算法複雜度為 \(O(n^2)\)
C++

vector<int> z_function_trivial(string s) {
	int n = (int)s.length();
	vector<int> z(n);
	for (int i = 1; i < n; ++i)
		while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
	return z;
}

Python

def z_function_trivial(s):
n = len(s)
z = [0] * n
for i in range(1, n):
	while i + z[i] < n and s[z[i]] == s[i + z[i]]:
		z[i] += 1
		return z

線性演算法

如同大多數字符串主題所介紹的演算法,其關鍵在於,運用自動機的思想尋找限制條件下的狀態轉移函式,使得可以藉助之前的狀態來加速計算新的狀態。

在該演算法中,我們從 \(1\)\(n-1\) 順次計算 \(z[i]\) 的值(\(z[0]=0\))。在計算 \(z[i]\) 的過程中,我們會利用已經計算好的 \(z[0],\ldots,z[i-1]\)

對於 \(i\),我們稱區間 \([i,i+z[i]-1]\)\(i\)匹配段,也可以叫 Z-box。

演算法的過程中我們維護右端點最靠右的匹配段。為了方便,記作 \([l,r]\)。根據定義,\(s[l,r]\)\(s\) 的字首。在計算 \(z[i]\) 時我們保證 \(l\le i\)。初始時 \(l=r=0\)

在計算 \(z[i]\) 的過程中:

  • 如果 \(i\le r\),那麼根據 \([l,r]\) 的定義有 \(s[i,r] = s[i-l,r-l]\),因此 \(z[i]\ge \min(z[i-l],r-i+1)\)。這時:
  • \(z[i-l] < r-i+1\),則 \(z[i] = z[i-l]\)
  • 否則 \(z[i-l]\ge r-i+1\),這時我們令 \(z[i] = r-i+1\),然後暴力列舉下一個字元擴充套件 \(z[i]\) 直到不能擴充套件為止。
  • 如果 \(i>r\),那麼我們直接按照樸素演算法,從 \(s[i]\) 開始比較,暴力求出 \(z[i]\)
  • 在求出 \(z[i]\) 後,如果 \(i+z[i]-1>r\),我們就需要更新 \([l,r]\),即令 \(l=i, r=i+z[i]-1\)

可以訪問 這個網站 或者 後面的這個程式碼 來看 Z 函式的模擬過程。
C++

	vector<int> z_function(string s) {
		int n = (int)s.length();
		vector<int> z(n);
		for (int i = 1, l = 0, r = 0; i < n; ++i) {
			if (i <= r && z[i - l] < r - i + 1) {
				z[i] = z[i - l];
			} else {
				z[i] = max(0, r - i + 1);
				while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
			}
			if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
		}
		return z;
	}

Python

def z_function(s):
n = len(s)
z = [0] * n
l, r = 0, 0
for i in range(1, n):
	if i <= r and z[i - l] < r - i + 1:
		z[i] = z[i - l]
		else:
			z[i] = max(0, r - i + 1)
			while i + z[i] < n and s[z[i]] == s[i + z[i]]:
				z[i] += 1
				if i + z[i] - 1 > r:
					l = i
					r = i + z[i] - 1
					return z

複雜度分析

對於內層 while 迴圈,每次執行都會使得 \(r\) 向後移至少 \(1\) 位,而 \(r< n-1\),所以總共只會執行 \(n\) 次。

對於外層迴圈,只有一遍線性遍歷。

總複雜度為 \(O(n)\)

應用

我們現在來考慮在若干具體情況下 Z 函式的應用。

這些應用在很大程度上同 字首函式 的應用類似。

匹配所有子串

為了避免混淆,我們將 \(t\) 稱作 文字,將 \(p\) 稱作 模式。所給出的問題是:尋找在文字 \(t\) 中模式 \(p\) 的所有出現(occurrence)。

為了解決該問題,我們構造一個新的字串 \(s = p + \diamond + t\),也即我們將 \(p\)\(t\) 連線在一起,但是在中間放置了一個分割字元 \(\diamond\)(我們將如此選取 \(\diamond\) 使得其必定不出現在 \(p\)\(t\) 中)。

首先計算 \(s\) 的 Z 函式。接下來,對於在區間 \([0,|t| - 1]\) 中的任意 \(i\),我們考慮以 \(t[i]\) 為開頭的字尾在 \(s\) 中的 Z 函式值 \(k = z[i + |p| + 1]\)。如果 \(k = |p|\),那麼我們知道有一個 \(p\) 的出現位於 \(t\) 的第 \(i\) 個位置,否則沒有 \(p\) 的出現位於 \(t\) 的第 \(i\) 個位置。

其時間複雜度(同時也是其空間複雜度)為 \(O(|t| + |p|)\)

本質不同子串數

給定一個長度為 \(n\) 的字串 \(s\),計算 \(s\) 的本質不同子串的數目。

考慮計算增量,即在知道當前 \(s\) 的本質不同子串數的情況下,計算出在 \(s\) 末尾新增一個字元後的本質不同子串數。

\(k\) 為當前 \(s\) 的本質不同子串數。我們新增一個新的字元 \(c\)\(s\) 的末尾。顯然,會出現一些以 \(c\) 結尾的新的子串(以 \(c\) 結尾且之前未出現過的子串)。

設串 \(t\)\(s + c\) 的反串(反串指將原字串的字元倒序排列形成的字串)。我們的任務是計算有多少 \(t\) 的字首未在 \(t\) 的其他地方出現。考慮計算 \(t\) 的 Z 函式並找到其最大值 \(z_{\max}\)。則 \(t\) 的長度小於等於 \(z_{\max}\) 的字首的反串在 \(s\) 中是已經出現過的以 \(c\) 結尾的子串。

所以,將字元 \(c\) 新增至 \(s\) 後新出現的子串數目為 \(|t| - z_{\max}\)

演算法時間複雜度為 \(O(n^2)\)

值得注意的是,我們可以用同樣的方法在 \(O(n)\) 時間內,重新計算在端點處新增一個字元或者刪除一個字元(從尾或者頭)後的本質不同子串數目。

字串整週期

給定一個長度為 \(n\) 的字串 \(s\),找到其最短的整週期,即尋找一個最短的字串 \(t\),使得 \(s\) 可以被若干個 \(t\) 拼接而成的字串表示。

考慮計算 \(s\) 的 Z 函式,則其整週期的長度為最小的 \(n\) 的因數 \(i\),滿足 \(i+z[i]=n\)

該事實的證明同應用 字首函式 的證明一樣。

練習題目

  • CF126B Password
  • UVa # 455 Periodic Strings
  • UVa # 11022 String Factoring
  • UVa 11475 - Extend to Palindrome
  • LA 6439 - Pasti Pas!
  • Codechef - Chef and Strings
  • Codeforces - Prefixes and Suffixes
  • Leetcode 2223 - Sum of Scores of Built Strings

本頁面主要譯自博文 Z-функция строки и её вычисление 與其英文翻譯版 Z-function and its calculation。其中俄文版版權協議為 Public Domain + Leave a Link;英文版版權協議為 CC-BY-SA 4.0。

模擬程式

<!-- Hello -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252"><style>body {transition: opacity ease-in 0.2s; } 
body[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } 
</style><script src="chrome-extension://memhacajcfhmibggbgilihlmiiddeggo/assets/main-world.ts-4ed993c7.js" type="module"></script>
	<meta name="Author" content="John Yundt-Pacheco">
	<meta name="Keywords" content="Animation, Pattern Matching, Algorithms">
	<meta name="Description" content="Dr. Bereg, CS 6333 Algorithms for Computational Biology">
	<title>Z Algorithm (JavaScript Demo)</title>
	<!--link rel="stylesheet" type="text/css" href="/styles.css"-->

	<style>
		body { margin:0; }
		body, td { font-family:verdana; font-size:13px; }
		table.fullsize { margin:0; padding:0; width:100%; height:100%; }
		h1 { margin-top:0px; font-family:verdana; font-size:16px; font-style:italic; }
		h2 { margin-top:0px; font-family:verdana; font-size:14px; font-weight:bold; }
		div.divMenuItem a:hover, div#menuBar a:hover { color:moccasin; }
		div.divMenuItem { position:absolute; display:none; padding:5px 8px 5px 8px; background-color:#6699CC; }
		#menuTd, #menuTd a { text-decoration:none; color:white; font-size:10px; font-weight:bold; }
		div.fh-tabs { display:none; }
		TD.content { border-width:0px; font-family:verdana; font-size:12px; }
		TH.content { border-width:0px; font-family:verdana; font-size:90%; }
		TR.header2 { height:30px; background-color:lightskyblue; }
		TR.header3 { background-color:#D9E9F9; }
		TR.closing { height:20px; background-color:#E5E5E5; border-width:0px; }
		TR.list { background-color:#EEEEEE; border-width:0px; }
		TT.link { color:blue; text-decoration:underline; clear:none; }
		TT.blue { color:blue; font-weight:bold; font-size:12px; font-family:verdana; }
		TT.gold { color:gold; font-weight:bold; font-size:12px; font-family:verdana; }
		TT.hand { text-decoration:none; font-size:12px; font-family:verdana; }
	</style>
</head>

<body>

<table class="fullsize" cellpadding="5" cellspacing="0" border="0">

<tbody><tr height="*">
	<td colspan="3" valign="top" height="100%" style="padding:10px">
<!-- this is the main section -->

<script>
function ce(elName,parent,elId){
	var el = document.createElement(elName);
	if (elId) el.id = elId;
	if (parent) parent.appendChild(el);
	return el;
}

function ctn(text,parent){
	var tn = document.createTextNode(text);
	if (parent) parent.appendChild(tn);
	return tn;
}

function ge(id){
	return document.getElementById(id);
}

function removeAllChildren(el){
	while (el.firstChild)
		el.removeChild(el.firstChild);
}

function sprn(text,d){
    ctn(text,ce('p',d));  
}

function buildTable(t, el){
	var tbody = ce('tbody', table = ce('table', el));
	table.border = 1;
	table.style.border = 0;
	table.cellSpacing = 1;
    var i,j,td,o;
	for (i=0; i<t.length; i++){
	    tr = ce('tr', tbody);
		for (j=0; j<t[i].length; j++){
		    td = ce('td', tr);
			o = t[i][j];

			if (o.text != undefined){
				if (o.color) td.style.color = o.color;
				if (o.colSpan) td.colSpan = o.colSpan;
				if (o.align) td.align= o.align;
				if (o.width) td.style.width = o.width;
				if (o.bgColor) td.style.backgroundColor = o.bgColor;
				td.innerHTML = o.text;
			} else {
				ctn(o, td);
			}
		}
	}
}


var onkeypressListeners = new Array();
document.onkeypress = function(e){ for (var i=0; i<onkeypressListeners.length; i++) onkeypressListeners[i](e); }

function pw(s,w){ var ret = ''+s; while (ret.length<w) ret = ' '+ret; return ret; }
function div3(x){ return Math.floor(x/3); }
function leq1(a1,a2, b1,b2){ return a1 < b1 || (a1 == b1 && a2 <= b2); }
function leq2(a1,a2,a3, b1,b2,b3){ return a1 < b1 || (a1 == b1 && leq1(a2,a3, b2,b3)); }

function bld_table(text,n,colors,table){
    var i;
    for(i=0;i<n;i++){
        if(i< text.length)
            table.push({align:'center',width:25, text:text.charAt(i), bgColor:colors[i] });
       else
            table.push({align:'center',width:25, text:'', bgColor:'' });

    }
}
function animate(text){
	var div = ce('div');
	div.style.backgroundColor = '#DDDDCC';
	div.style.padding = 14;

    var match = false;
    var matches = 0;
	var nt = text.length;
    var table = new Array(); table[0] = new Array(); table[1] = new Array(); table[2] = new Array();
	table[3] = new Array(); 
    var tcolors = new Array(); pcolors = new Array(); zcolors = new Array();
    table[0].push({text:'Index:', align:'right'});
	table[1].push({text:'Text:', align:'right'});
    table[2].push({text:'Text:', align:'right'});
	table[3].push('Z values:');
    table[3].push('');
    var z = new Array(nt);
    var i,j,k,l;
    for(i=0;i<nt;i++){
        tcolors[i]='';
        pcolors[i]='';
		zcolors[i]='';
    }
    for(i=0;i<nt;i++){
		table[0].push({text:i, align:'center'});    
    }
    bld_table(text,nt,tcolors,table[1]);
    bld_table(text,nt,tcolors,table[2]);
	buildTable(table, div);
    sprn('To illustrate the comparisons taking place, the text has been duplicated. Green boxes indicate matches, red a mismatch.',div);
	sprn('In the Z algorithm, k is the index being considered, l is the left side of the Z-box, and r is the right side of the Z-box,',div);
	sprn('The current Z box (bounded by l and r) is indicated on the Index row using pale green.',div);
    sprn('The Z algorithm starts with k=1, r=0, l=0.',div);
    sprn('Press x to generate Z values for the text using the Z Algorithm.',div);

	snapshots.push(div.cloneNode(true));
    var r = 0, zk=0, si=0, kOld, zOld, b, zc ;
    var cmp = 0;
    for(k=1;k<nt;k++){
        for(i=0;i<nt;i++){
           tcolors[i]='';
           pcolors[i]='';
		   zcolors[i]='';
        }
		for(i=l;i<=r;i++){
			zcolors[i] = 'palegreen';
		}

        sprn('_______________________________________________________________________________________',div);
        sprn('Evaluating k='+k+'.',div);
        if(k>r){
            sprn('Current index (k='+k+') is past end of last Z-box (r='+r+'), so Z('+k+') must be computed explicitly.',div);
            zk = 0;
            for(si=0;si<nt;si++){
                cmp++;           
                if((si+k<nt)&&(text.charAt(si) == text.charAt(si+k))){
                    pcolors[si+k] = tcolors[si] = 'green';
                    continue;
                }else{
                    if(si+k<nt){
                      pcolors[si+k]=tcolors[si] = 'red';
                    }
                    break;
                }
            }
            if(si>0){
              zk = si
              r = zk + k - 1
              l = k          
            }
        }else{

             sprn('Current index (k='+k+') is in a previously discovered substring (r='+r+').',div);
             kOld = k - l
             zOld = z[kOld]
             b = r - k + 1
             if (zOld < b){
                zk = zOld
                sprn('The previously discovered substring starts at k-l ('+k+'-'+l+'), with Z('+(k-l)+')='+zk+' which is < the remaining substring S['+k+'..'+r+'], so Z('+k+')=Z('+(k-l)+')='+zk+'.',div);
                sprn('No additional comparisons needed.',div);
                for(var zc=-0;zc<zk;zc++){
                   pcolors[zc+k] = tcolors[zc] = 'green';
                }
                
             }else{
             sprn('The previously discovered substring starts at k-l ('+k+'-'+l+'), with Z('+(k-l)+')='+zk+' which is not < the remaining substring S['+k+'..'+r+'], so additional comparisons are needed.',div);
                zk = b
                for(zc=0;zc<zk;zc++){
                   pcolors[zc+k] = tcolors[zc] = 'green';
                }

                for(si=b;si<nt;si++){
                    cmp++;
                    if((k + si < nt) &&(text.charAt(si) == text.charAt(k+si))){
                        pcolors[si+k] = tcolors[si] = 'green';
                        continue;
                    }else{
                       if(si+k<nt){
                          pcolors[si+k]=tcolors[si] = 'red';
                       }   
                       break;                  
                    }   
                }
                zk = si;
                r = zk + k - 1;
                l = k;
             }
        }
        z[k] = zk;
        for(i=0;i<nt;i++){
           table[0][i+1]= {align:'center',width:25, text:i,bgColor:zcolors[i] }		
           table[1][i+1]= {align:'center',width:25, text:text.charAt(i), bgColor:tcolors[i] }
           table[2][i+1]= {align:'center',width:25, text:text.charAt(i), bgColor:pcolors[i] }
        }
        for(i=0;i<k;i++){
           table[3][i+2]= {align:'center',width:25, text:z[i+1], bgColor:''}
        }
  	    buildTable(table, div);
        sprn('Z('+k+') = '+z[k]+', l='+l+', r='+r+', '+cmp+' comparisons so far.',div);
 	    snapshots.push(div.cloneNode(true));     
    }
    sprn('_______________________________________________________________________________________',div);
    sprn('The Z algorithm has completed with '+cmp+' comparisons on a text of size '+nt+'.',div);
    table = new Array(); table[0] = new Array(); table[1] = new Array(); table[2] = new Array();
    table[0].push({text:'Index:', align:'right'});
	table[1].push({text:'Text:', align:'right'});
	table[2].push('Z values:');
    table[2].push('');
    for(i=0;i<nt;i++){
		table[0].push({text:i, align:'center'});    
        table[1][i+1]= {align:'center',width:25, text:text.charAt(i), bgColor:''}
    }
    for(i=0;i<nt-1;i++){
        table[2][i+2]= {align:'center',width:25, text:z[i+1], bgColor:''}
    }
    buildTable(table, div);
    snapshots.push(div.cloneNode(true));     
}

function setViewer(i){
	var viewer = ge('viewer');
	removeAllChildren(viewer);

	ctn('Step #' + i + ", next press 'x', previous press 'z'", ce('h2', viewer));
	viewer.appendChild(snapshots[i]);
}

function generate(text){
	ge('inputText').value = text;
    snapshots = new Array();
    animate(text);
	setViewer(sindex=0);
}

var sindex = 0;
var snapshots = new Array();
var ord = new Array(), chr = new Array();
for (var i=0; i<256; i++){
	ord[String.fromCharCode(i)] = i;
	chr[i] = String.fromCharCode(i);
}

function prevSnap(){
	if (sindex>0) setViewer(--sindex);
}

function nextSnap(){
	if (sindex+1<snapshots.length) setViewer(++sindex);
}

onkeypressListeners.push(function(e){
	var keycode = e? e.which : event.keyCode;
	switch (keycode){
		case ord['x'] : nextSnap(); break;
		case ord['z'] : prevSnap(); break;
	}
});

</script>
<h1>Z Algorithm</h1>

<p>Input your own the text and click Generate Z values to animate the Z Algorithm from Dan Gusfield's <i>Algorithms on Strings, Trees and Sequences</i> book.</p>
<p>See the <a href="https://personal.utdallas.edu/~besp/demo/John2010/z-match.htm">Z Algorithm Exact Pattern Match animation</a> for details on using Z values for pattern matching.</p>
<table>
 <tbody><tr><td>Text:</td>
    <td><input type="text" id="inputText" size="50" value="aabcaabxaaaz"></td></tr>
</tbody></table>
<input type="button" onclick="generate(ge(&#39;inputText&#39;).value)" value="Generate Z values">

<div id="viewer"><h2>Step #0, next press 'x', previous press 'z'</h2><div style="background-color: rgb(221, 221, 204); padding: 14px;"><table border="1" cellspacing="1" style="border: 0px;"><tbody><tr><td align="right">Index:</td></tr><tr><td align="right">Text:</td></tr><tr><td align="right">Text:</td></tr><tr><td>Z values:</td><td></td></tr></tbody></table><p>To illustrate the comparisons taking place, the text has been duplicated. Green boxes indicate matches, red a mismatch.</p><p>In the Z algorithm, k is the index being considered, l is the left side of the Z-box, and r is the right side of the Z-box,</p><p>The current Z box (bounded by l and r) is indicated on the Index row using pale green.</p><p>The Z algorithm starts with k=1, r=0, l=0.</p><p>Press x to generate Z values for the text using the Z Algorithm.</p></div></div>

<p>This animation was prepared for Dr. Bereg's CS 6333 Algorithms for Computational Biology class by John Yundt-Pacheco (jcy031000 _at_ utdallas.edu).<br> More information can be found at Dr. Bereg's CS 6333 site : <a href="http://www.utdallas.edu/~besp/6333">http://www.utdallas.edu/~besp/6333</a>.</p>

<p>The framework from Felix Halim's Suffix Array Demonstration was used to construct this animation: <a href="http://felix-halim.net/pg/suffix-array">http://felix-halim.net/pg/suffix-array</a>.</p>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
	</td>
</tr>
</tbody></table>
<hr>


<webchatgpt-custom-element-bedf2056-73cd-407d-ba28-ca1f3aaabe34 id="webchatgpt-snackbar" style="color: rgb(255, 255, 255);"><template shadowrootmode="open"><style id="webchatgpt-snackbar-container-emotion-style"></style><div id="webchatgpt-snackbar-container" style="z-index: 2147483647;"></div></template></webchatgpt-custom-element-bedf2056-73cd-407d-ba28-ca1f3aaabe34><yd-mg-icon style="position: fixed; z-index: 2147483647;"><template shadowrootmode="open"><style>
.item {
    display: flex;
    justify-content: center;
    align-items: center;
}
.all {
    direction: ltr;
}
.all > * {
    direction: rtl;
}
.hidden {
    display: none;
    transition: width 0.3s linear;
}
.container:hover .hidden {
    width: var(--131b0bc3);
    height: var(--367772ca);
    display: flex;
}
.container {
    position: fixed;
    top:var(--8b7ed71a);
    right:var(--2c4e45ba);
    width: var(--b2ad46e2);
    height: var(--14b33b7e);
    background-color: #fff;
    border-radius: var(--e55f886a);
    box-shadow: 0 0 10px #b3b5b8;
    transition: width 0.3s linear;
    display: flex;
    justify-content: center;
    align-items: center;
}
.container:hover {
    width: var(--301156d1);
    border-radius: var(--e55f886a);
    justify-content: space-around;
}
.icon {
    width: var(--131b0bc3);
    height: var(--367772ca);
    cursor: pointer;
    user-select: none;
}
.icon:hover {
}
.yd-translate-loader {
    border: 2px solid #f3f3f3;
    border-top: 2px solid #3498db;
    border-radius: 50%;
    height: var(--367772ca);
    width:  var(--131b0bc3);
    animation: spin 0.5s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg);
}
100% { transform: rotate(360deg);
}
}
@keyframes append-animate {
from {
        opacity: 0;
}
to {
        opacity: 1;
}
}
.popup {
  animation: popup 0.3s forwards;
}
@keyframes popup {
0% {
    opacity: 0;
    transform: scale(0);
}
100% {
    opacity: 1;
    transform: scale(1);
}
}
</style><div class="all" style="position: fixed; z-index: 2147483647; --131b0bc3: 20px; --367772ca: 20px; --8b7ed71a: 117.80000495910645px; --2c4e45ba: 270.1187324523926px; --b2ad46e2: 24px; --14b33b7e: 24px; --e55f886a: 12px; --301156d1: 72px;"><!----></div></template></yd-mg-icon><yd-mg-block-icon><template shadowrootmode="open"><style>@charset "UTF-8";
.disabled-element[data-v-d3135d60] {
  cursor: not-allowed;
}
.all[data-v-d3135d60] {
  position: absolute;
  z-index: 1;
}
.all .container[data-v-d3135d60] {
  width: var(--5a3ced10);
  height: var(--58dc89c6);
  display: flex;
  justify-content: center;
  align-items: center;
}
.all .container .yd-line[data-v-d3135d60] {
  display: block;
  position: absolute;
  height: var(--c03ec7f0);
  top: var(--7808ebd8);
  left: var(--891c21b0);
}
.all .container .yd-line .orignal[data-v-d3135d60] {
  padding: 1px;
  background-color: #E4E7F3;
  height: var(--5934d8d6);
}
.all .container .yd-line .yd[data-v-d3135d60] {
  padding: 1px;
  background-color: #FF939E;
  height: var(--4d327ada);
}
.all .container .yd-line .llm[data-v-d3135d60] {
  padding: 1px;
  background-color: #2485FF;
  height: var(--18678bd3);
}
.all .container .tooltip-container[data-v-d3135d60] {
  display: inline-block;
  position: absolute;
  top: var(--147b79a2);
  left: var(--7afb4d26);
}
.all .container .tooltip-container .icon[data-v-d3135d60] {
  width: var(--5a3ced10);
  height: var(--58dc89c6);
  background-image: var(--4a440a69);
  border-radius: 5px;
  cursor: pointer;
}
.all .container .tooltip-container .icon[data-v-d3135d60]::before {
  content: var(--4809e853);
  display: none;
}
.all .container .tooltip-container .icon[data-v-d3135d60]:hover {
  box-shadow: 0px 4px 10px rgba(56, 112, 200, 0.16);
  transition: width 0.3s linear;
  background-image: var(--4809e853);
  cursor: pointer;
}
.all .container .tooltip-container .llmIcon[data-v-d3135d60] {
  width: var(--5a3ced10);
  height: var(--58dc89c6);
  background-image: var(--4a440a69);
  border-radius: 5px;
}
.all .container .tooltip-container .llmIcon[data-v-d3135d60]:hover {
  width: var(--5a3ced10);
  height: 48px;
  background-image: var(--11da4116);
  border-radius: 5px;
}
.all .container .tooltip-container .tooltip[data-v-d3135d60] {
  position: absolute;
  top: var(--523390c5);
  /* &#25918;&#22312;&#20803;&#32032;&#30340;&#19978;&#26041; */
  left: var(--178a82d2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background-color: #F9FBFF;
  padding: 8px;
  width: 150px;
  height: 40px;
  border-radius: 8px;
  box-sizing: border-box;
  border: 1px solid rgba(216, 217, 219, 0.5);
  box-shadow: 0px 1.2px 3.6px 0px rgba(0, 0, 0, 0.1), 0px 2px 20px 0px rgba(27, 19, 98, 0.08);
  font-family: PingFang SC;
  font-size: 16px;
  font-weight: normal;
  line-height: 150%;
  text-align: center;
  letter-spacing: 0em;
  user-select: none;
  color: #2A2B2E;
}
.all .container .tooltip-container .llmTooltip[data-v-d3135d60] {
  position: absolute;
  top: var(--523390c5);
  /* &#25918;&#22312;&#20803;&#32032;&#30340;&#19978;&#26041; */
  left: var(--178a82d2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background-color: #F9FBFF;
  padding: 8px;
  width: 200px;
  height: 40px;
  border-radius: 8px;
  box-sizing: border-box;
  border: 1px solid rgba(216, 217, 219, 0.5);
  box-shadow: 0px 1.2px 3.6px 0px rgba(0, 0, 0, 0.1), 0px 2px 20px 0px rgba(27, 19, 98, 0.08);
  font-family: PingFang SC;
  font-size: 16px;
  font-weight: normal;
  line-height: 150%;
  text-align: center;
  letter-spacing: 0em;
  user-select: none;
  color: #2A2B2E;
}
.all .container .yd-translate-loader-block[data-v-d3135d60] {
  position: absolute;
  top: var(--147b79a2);
  left: var(--7afb4d26);
  width: var(--5a3ced10);
  height: var(--5a3ced10);
  background-image: var(--3fa19693);
  /* &#30830;&#20445;&#36825;&#37324;&#26159;&#27491;&#30830;&#30340;&#22270;&#29255;&#36335;&#24452; */
  background-repeat: no-repeat;
  background-size: cover;
  box-shadow: 0px 4px 10px rgba(56, 112, 200, 0.16);
  overflow: hidden;
  /* &#20445;&#25345;&#23376;&#20803;&#32032;&#30340;&#22278;&#35282;&#25928;&#26524; */
}
.all .container .yd-translate-loader-block[data-v-d3135d60]::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  border-radius: 5px;
  border: 1px solid transparent;
  background: linear-gradient(to left bottom, rgba(38, 132, 255, 0.6), rgba(120, 85, 250, 0.6));
  -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}
.all .container .tip[data-v-d3135d60] {
  background-color: #F9FBFF;
  padding: 8px;
  border-radius: 8px;
  height: 400px;
  border: 1px solid #D8D9DB;
}</style><div data-v-d3135d60="" class="all notranslate" style="--5a3ced10: 24px; --58dc89c6: 24px; --c03ec7f0: 0px; --7808ebd8: 0px; --891c21b0: 0px; --5934d8d6: 0px; --4d327ada: 0px; --18678bd3: 0px; --147b79a2: -16px; --7afb4d26: -44px; --4a440a69: url(chrome-extension://memhacajcfhmibggbgilihlmiiddeggo/block.svg); --4809e853: url(chrome-extension://memhacajcfhmibggbgilihlmiiddeggo/block-h.svg); --11da4116: url(chrome-extension://memhacajcfhmibggbgilihlmiiddeggo/block-h-llm.svg); --523390c5: undefined; --178a82d2: undefined; --3fa19693: url(chrome-extension://memhacajcfhmibggbgilihlmiiddeggo/block-l.gif);"><!----></div></template></yd-mg-block-icon><yd-image-ocr style="z-index: 2147483647;"><template shadowrootmode="open"><style>@charset "UTF-8";
.modal-overlay[data-v-04ce0a75] {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}
.modal-content[data-v-04ce0a75] {
  background-color: white;
  padding: 20px;
  border-radius: 10px;
  display: flex;
  gap: 20px;
  width: 800px;
  height: 500px;
  flex-direction: column;
  position: relative;
}
.modal-header[data-v-04ce0a75] {
  display: flex;
  justify-content: flex-end;
  height: 24px;
}
.modal-body[data-v-04ce0a75] {
  display: flex;
  flex-grow: 1;
  flex-direction: row;
}
.modal-body .imageContainer[data-v-04ce0a75] {
  flex: 2;
  justify-content: center;
}
.modal-body .img[data-v-04ce0a75] {
  position: relative;
  background-size: contain;
  background-position: center;
  height: 600px;
  background-repeat: no-repeat;
}
.modal-body .img .box[data-v-04ce0a75] {
  position: absolute;
  border: #333 3px solid;
}
.modal-body .text-content[data-v-04ce0a75] {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  /* &#26681;&#25454;&#38656;&#35201;&#35843;&#25972;&#25991;&#26412;&#20869;&#23481;&#30340;&#26679;&#24335; */
}
.modal-body .text-content .text[data-v-04ce0a75] {
  overflow-y: auto;
  height: 450px;
}
.modal-body .text-content .toolbar[data-v-04ce0a75] {
  height: 36px;
  display: flex;
  justify-content: space-around;
  flex-direction: row;
  flex-wrap: wrap;
}
.close-button[data-v-04ce0a75] {
  background-color: transparent;
  /* &#36879;&#26126;&#32972;&#26223; */
  border: none;
  /* &#21435;&#38500;&#36793;&#26694; */
  cursor: pointer;
  /* &#40736;&#26631;&#24748;&#20572;&#26102;&#26174;&#31034;&#25351;&#38024; */
  outline: none;
  /* &#21435;&#38500;&#28966;&#28857;&#36718;&#24275; */
  position: absolute;
  /* &#32477;&#23545;&#23450;&#20301; */
  top: 10px;
  /* &#36317;&#39030;&#37096;&#30340;&#36317;&#31163; */
  right: 10px;
  /* &#36317;&#21491;&#36793;&#30340;&#36317;&#31163; */
  font-size: 24px;
  /* &#23383;&#20307;&#22823;&#23567; */
  line-height: 24px;
  /* &#34892;&#39640;&#65292;&#20197;&#30830;&#20445;&#22402;&#30452;&#23621;&#20013; */
  color: #333;
  /* &#23383;&#20307;&#39068;&#33394; */
  font-weight: bold;
  /* &#23383;&#20307;&#21152;&#31895; */
}
.close-button[data-v-04ce0a75]:hover {
  color: #666;
  /* &#40736;&#26631;&#24748;&#20572;&#26102;&#30340;&#39068;&#33394;&#21464;&#21270; */
}
.line[data-v-04ce0a75], .redline[data-v-04ce0a75] {
  font-family: Arial, Helvetica, sans-serif;
  /* &#20351;&#29992;&#26080;&#34924;&#32447;&#23383;&#20307; */
  font-size: 16px;
  /* &#35774;&#32622;&#21512;&#36866;&#30340;&#23383;&#20307;&#22823;&#23567; */
  line-height: 24px;
  color: #333;
  /* &#25991;&#23383;&#39068;&#33394; */
  padding: 3px 3px 3px 6px;
  /* &#20869;&#36793;&#36317; */
  margin-top: 5px;
  cursor: pointer;
  transition: background-color 0.3s, border-left-color 0.3s;
  /* &#32972;&#26223;&#33394;&#21644;&#36793;&#26694;&#39068;&#33394;&#21464;&#21270;&#30340;&#36807;&#28193;&#25928;&#26524; */
  border-left: 3px solid #ddd;
  /* &#35774;&#32622;&#28784;&#33394;&#30340;&#24038;&#36793;&#26694; */
}
.redline[data-v-04ce0a75] {
  border-left-color: #e53935;
  /* &#40736;&#26631;&#24748;&#20572;&#26102;&#36793;&#26694;&#39068;&#33394;&#21464;&#20026;&#32418;&#33394; */
  background-color: #f5f5f5;
}
.button[data-v-04ce0a75] {
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background-color: #FB4A3E;
  /* &#19968;&#20010;&#26126;&#20142;&#20294;&#19981;&#39281;&#21644;&#30340;&#32418;&#33394; */
  color: white;
  /* &#25991;&#26412;&#39068;&#33394;&#20026;&#30333;&#33394; */
  padding: 5px 10px;
  /* &#25353;&#38062;&#20869;&#36793;&#36317; */
  border: none;
  /* &#26080;&#36793;&#26694; */
  border-radius: 5px;
  /* &#36731;&#24494;&#30340;&#22278;&#35282; */
  font-size: 10px;
  text-transform: uppercase;
  /* &#25991;&#26412;&#22823;&#20889; */
  cursor: pointer;
  /* &#40736;&#26631;&#24748;&#20572;&#26102;&#30340;&#25351;&#38024;&#26679;&#24335; */
  transition: background-color 0.3s;
  /* &#32972;&#26223;&#39068;&#33394;&#21464;&#21270;&#30340;&#36807;&#28193;&#25928;&#26524; */
}
.button[data-v-04ce0a75]:hover {
  background-color: #d32f2f;
  /* &#40736;&#26631;&#24748;&#20572;&#26102;&#30340;&#32972;&#26223;&#39068;&#33394;&#31245;&#26263; */
}
.button[data-v-04ce0a75]:active {
  background-color: #c62828;
  /* &#40736;&#26631;&#28857;&#20987;&#26102;&#30340;&#32972;&#26223;&#39068;&#33394;&#26356;&#26263; */
}
.button[data-v-04ce0a75]:disabled {
  background-color: #ef9a9a;
  /* &#31105;&#29992;&#29366;&#24577;&#30340;&#25353;&#38062;&#39068;&#33394;&#26356;&#20142;&#65292;&#26356;&#23569;&#39281;&#21644;&#24230; */
  cursor: default;
  /* &#31105;&#29992;&#29366;&#24577;&#30340;&#40736;&#26631;&#26679;&#24335; */
}
.loading-indicator[data-v-04ce0a75] {
  /* &#28155;&#21152;&#20320;&#30340;&#26679;&#24335;&#65292;&#27604;&#22914;&#23621;&#20013;&#26174;&#31034;&#12289;&#21160;&#30011;&#31561; */
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 1.5em;
  /* ...&#20854;&#20182;&#26679;&#24335; */
}
.message[data-v-04ce0a75] {
  position: absolute;
  top: 50%;
  /* &#23450;&#20301;&#21040;&#29238;&#20803;&#32032;&#30340;&#20013;&#38388; */
  left: 50%;
  /* &#23450;&#20301;&#21040;&#29238;&#20803;&#32032;&#30340;&#20013;&#38388; */
  transform: translate(-50%, -50%);
  /* &#20351;&#29992; transform &#23454;&#29616;&#31934;&#30830;&#23621;&#20013; */
  padding: 10px;
  background-color: white;
  /* &#35774;&#32622;&#32972;&#26223;&#39068;&#33394;&#20026;&#30333;&#33394; */
  border: 1px solid blue;
  border-radius: 5px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  /* &#28155;&#21152;&#38452;&#24433;&#25928;&#26524; */
  text-align: center;
  z-index: 100;
  /* &#30830;&#20445;&#25552;&#31034;&#20449;&#24687;&#22312;&#20854;&#20182;&#20803;&#32032;&#19978;&#26041; */
}
.loader[data-v-04ce0a75] {
  border: 5px solid #f3f3f3;
  /* &#27973;&#28784;&#33394;&#36793;&#26694; */
  border-top: 5px solid #3498db;
  /* &#34013;&#33394;&#36793;&#26694; */
  border-radius: 50%;
  width: 40px;
  height: 40px;
  animation: spin-04ce0a75 2s linear infinite;
  margin-bottom: 20px;
}
@keyframes spin-04ce0a75 {
0% {
    transform: rotate(0deg);
}
100% {
    transform: rotate(360deg);
}
}</style><!----></template></yd-image-ocr></body></html>

相關文章