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('inputText').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);
/* 放在元素的上方 */
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);
/* 放在元素的上方 */
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);
/* 确保这里是正确的图片路径 */
background-repeat: no-repeat;
background-size: cover;
box-shadow: 0px 4px 10px rgba(56, 112, 200, 0.16);
overflow: hidden;
/* 保持子元素的圆角效果 */
}
.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;
/* 根据需要调整文本内容的样式 */
}
.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;
/* 透明背景 */
border: none;
/* 去除边框 */
cursor: pointer;
/* 鼠标悬停时显示指针 */
outline: none;
/* 去除焦点轮廓 */
position: absolute;
/* 绝对定位 */
top: 10px;
/* 距顶部的距离 */
right: 10px;
/* 距右边的距离 */
font-size: 24px;
/* 字体大小 */
line-height: 24px;
/* 行高,以确保垂直居中 */
color: #333;
/* 字体颜色 */
font-weight: bold;
/* 字体加粗 */
}
.close-button[data-v-04ce0a75]:hover {
color: #666;
/* 鼠标悬停时的颜色变化 */
}
.line[data-v-04ce0a75], .redline[data-v-04ce0a75] {
font-family: Arial, Helvetica, sans-serif;
/* 使用无衬线字体 */
font-size: 16px;
/* 设置合适的字体大小 */
line-height: 24px;
color: #333;
/* 文字颜色 */
padding: 3px 3px 3px 6px;
/* 内边距 */
margin-top: 5px;
cursor: pointer;
transition: background-color 0.3s, border-left-color 0.3s;
/* 背景色和边框颜色变化的过渡效果 */
border-left: 3px solid #ddd;
/* 设置灰色的左边框 */
}
.redline[data-v-04ce0a75] {
border-left-color: #e53935;
/* 鼠标悬停时边框颜色变为红色 */
background-color: #f5f5f5;
}
.button[data-v-04ce0a75] {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
background-color: #FB4A3E;
/* 一个明亮但不饱和的红色 */
color: white;
/* 文本颜色为白色 */
padding: 5px 10px;
/* 按钮内边距 */
border: none;
/* 无边框 */
border-radius: 5px;
/* 轻微的圆角 */
font-size: 10px;
text-transform: uppercase;
/* 文本大写 */
cursor: pointer;
/* 鼠标悬停时的指针样式 */
transition: background-color 0.3s;
/* 背景颜色变化的过渡效果 */
}
.button[data-v-04ce0a75]:hover {
background-color: #d32f2f;
/* 鼠标悬停时的背景颜色稍暗 */
}
.button[data-v-04ce0a75]:active {
background-color: #c62828;
/* 鼠标点击时的背景颜色更暗 */
}
.button[data-v-04ce0a75]:disabled {
background-color: #ef9a9a;
/* 禁用状态的按钮颜色更亮,更少饱和度 */
cursor: default;
/* 禁用状态的鼠标样式 */
}
.loading-indicator[data-v-04ce0a75] {
/* 添加你的样式,比如居中显示、动画等 */
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;
/* ...其他样式 */
}
.message[data-v-04ce0a75] {
position: absolute;
top: 50%;
/* 定位到父元素的中间 */
left: 50%;
/* 定位到父元素的中间 */
transform: translate(-50%, -50%);
/* 使用 transform 实现精确居中 */
padding: 10px;
background-color: white;
/* 设置背景颜色为白色 */
border: 1px solid blue;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
/* 添加阴影效果 */
text-align: center;
z-index: 100;
/* 确保提示信息在其他元素上方 */
}
.loader[data-v-04ce0a75] {
border: 5px solid #f3f3f3;
/* 浅灰色边框 */
border-top: 5px solid #3498db;
/* 蓝色边框 */
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>