1. 程式人生 > 其它 >Docker的日誌清理與全域性配置

Docker的日誌清理與全域性配置

題目內容:給定一個字串\(s\),求其中最長的迴文子串的長度。
資料範圍:\(s\)的長度不超過\(2*10^5\)
時間限制:\(1000ms\)
傳統的動態規劃的做法時間複雜度\(O(n^2)\),會\(TLE\).此處不多作介紹.
以下給出兩種優化的做法:
解法一:
1.當迴文子串的長度為奇數時:掃描陣列,列舉每一個\(s[i]\),並以之作為迴文子串的中心,向兩邊二分搜尋求取從該中心出發向兩邊擴散的最大長度\(L\), 此時迴文子串的長度為\(2*L+1\)
2.當迴文子串的長度為偶數時:中間有兩個相鄰的\(s[i]\)\(s[i+1]\),它們兩個相等,可以把它們看成是一個新的中心,向左右兩個方向擴散求最大擴散長度\(L\)

,此時迴文子串長度為\(2*L+2\)

進行二分搜尋時,對於\(1\),\(L\)的分佈範圍是\([0,min(i - 1, n - i)]\);對於\(2\),\(L\)的分佈範圍是\([0, min(n - i - 1,i - 1)]\).
程式碼:

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5+2;
char s[N];

bool check(int x, int medi, int kind)//x為迴文子串的中心,kind為二分判斷的型別。
{
	if(kind == 0)//對應第一種
	{
		for(int k = 1;k <= medi; k++)
    	{
		if(s[x - k] != s[x + k])return false;
    	}
    	
    	return true;
	}
	
	if(kind == 1)//對應第二種
	{
		for(int k = 1;k <= medi; k++)
		{
			if(s[x - k] != s[x + 1 + k])return false;
		}
		
		return true;
	}
}
int main()
{
	cin>>(s + 1);

	int n = strlen(s+1);
	
	int ans = 1; 
	
	//第一種情況列舉中心:跳過了s[1]以及s[n],因為以它們為中心時L均為0 
	for(int i = 2;i <= n - 1; i++)
	{
		int l = 0, r = min(n - i, i - 1);
		
		while(l < r)
		{
		   int medi = (l + r + 1)>>1;
		   if(check(i, medi, 0))l = medi;
		   else r = medi - 1;	
		}
		ans = max(ans, 2*l + 1);
	}
	
	for(int i = 1;i <= n - 1; i++)
	{
		if(s[i] == s[i + 1])
		{
		int l = 0, r = min(n - i - 1, i - 1);
		while(l < r)
		{
			int medi = (l + r + 1)>>1;
			if(check(i, medi, 1))l = medi;
			else r = medi - 1;
		}
		
		ans = max(ans, 2*l + 2);
	}
	 } 
	 
	 cout<<ans;
	 return 0;
	
}

解法二
解法一在輸入字串只有一種字母且長度很大時會退化到\(O(n^2)\)(當然資料水一點一般不會出現這種情況),它最大的不足在於每一次二分進行判斷(呼叫\(check\)函式時)代價都是線性的。
解法二跟解法一的思路差不多,是解法一的強化版。只不過藉助於雜湊,我們將每一次\(check\)的代價降低為常數時間。
雜湊的目的是將任意一段區間上的字串唯一對映到一個值(這裡用\(unsigned\) \(long\) \(long\)作為對映值)

類比某個\(base\)的進位制數,上圖左側區間的雜湊值記作:
\(a_{n}*base^{n-1}+a_{n-1}*base^{n-2}+...+a_{1}\)


右側區間雜湊值記作:
\(b_{n}*base^{n-1}+b_{n-1}*base^{n-2}+...+b_{1}\)
那麼我們只需要直接判斷這兩個雜湊值是否相等就可以知道這兩個區間上的字串是否為映象對稱。
\(f_{i} = f_{i-1}*base + s_{i} -'a' +1\)
(\(s_{i}-'a'+1\)將每一個字母與字母\(a\)進行參照轉化為整數)
\(g_{i} = g_{i+1}*base + s_{i} - 'a'+1\)
\(P_{i} = base^{i}\)
那麼左側區間型別的原字串陣列\([i,j]\)對應的雜湊值為:\(f_{j} - f_{i-1}*P_{j - i + 1}\)
右側區間型別的原字串陣列\([i,j]\)對應的雜湊值為:\(g_{i} - g_{j+1}*P_{j - i + 1}\)
這兩個式子的推導:
\(g_{n} = s_{n}-'a'+1\)
\(g_{n-1} = (s_{n}-'a'+1)*base+s_{n-1}-'a'+1\)
...
\(g_{j+1} = (s_{n}-'a'+1)*base^{n-j-1}+...s_{j+1}-'a'+1\)
...
\(g_{i} = (s_{n}-'a'+1)*base^{n-i}+...(s_{j+1}-'a'+1)*base^{j+1-i}+...+s_{i}-'a'+1\)
區間\([i,j]\)雜湊值為\(g_{i}-g_{j+1}*base^{j+1-i}=g_{i} - g_{j+1}*P_{j - i + 1}\)
\(f\)的式子也是一樣的,這裡省去推導。

#include<bits/stdc++.h> 
using namespace std;

#define base 137
typedef unsigned long long int ull; 
const int N = 200001;
char s[N];
ull f[N], g[N], P[N];

bool check(int l1, int l2, int l3, int l4)//分別為f對應於區間[l1,l2], g對應於區間[l3, l4] 
{
	ull t1 = f[l2] - f[l1 - 1]*P[l2 - l1 + 1];
	ull t2 = g[l3] - g[l4 + 1]*P[l4 - l3 + 1];
	return t1 == t2; //相等則為映象對稱
}

void init(int n)
{
	P[0] = 1;
	for(int i = 1;i <= n; i++)P[i] = P[i - 1]*base;
}

int main()
{
	cin>>(s + 1);
	
	int n = strlen(s + 1);
	
	init(n);//初始化P陣列
	
	for(int i = 1;i <= n; i++)
		f[i] = f[i - 1]*base + s[i] - 'a' + 1;
		
	for(int i = n; i >= 1; i--)
	    g[i] = g[i + 1]*base + s[i] - 'a' + 1;
	
	
	int ans = 0;
	for(int i = 1;i <= n; i++)
	{
		//區間長度為奇數: 
		int l = 0, r = min(n - i, i - 1);
		while(l < r)
		{
			int medi = (l + r + 1)>>1;
			if(check(i-medi, i - 1, i + 1, i + medi))l = medi;
			else r = medi - 1;
		 } 
		ans = max(ans, l*2 + 1);
		
		
		//區間長度為偶數,將s[i]與s[i+1]看成新的中心,但是二者必須相等 
		if(s[i] == s[i + 1])
		{
			l = 0,r = min(n - i - 1, i - 1);
			
			while(l < r)
			{
				int medi = (l + r + 1)>>1;
				if(check(i - medi,i - 1, i + 2, i + medi + 1))l = medi;
				else r = medi - 1;
			 } 
			
			ans = max(ans, 2*l + 2);
		}
		
		
	}
	
	cout<<ans;
	return 0;
}

解法二雖然能夠確保任何情況下時間複雜度為\(O(nlogn)\),由於解法一,但是也付出了更大的空間代價。