1. 程式人生 > >codeforcs 1063F. String Journey - dp -SAM - 主席樹/線段樹合併 - 子串定位 - 倍增

codeforcs 1063F. String Journey - dp -SAM - 主席樹/線段樹合併 - 子串定位 - 倍增

題目大意:
給你一個長為 n n 的字串 S S ,求最大的 k k

,使得能夠找出 k k 個不重疊的子串 t 1 t k
t_1\dots t_k
,使得 i [ 1 , k
) , t i > t i + 1 \forall i\in[1,k),|t_i|>|t_{i+1}|
,並且 t i + 1 t_{i+1} t i t_i 的子串。 n 5 × 1 0 5 , 5 s n\le5\times 10^5,5s
題解:
首先觀察性質。
顯然和這種“當前串是上一個的子串然後最大化序列長度”之類的,可以證明一定存在一種最優解,使得每個串都是上一個串的字首或者字尾(注意都有可能,並不一定全部都是字尾或者字首,因為證明過程是,例如他不是字首,我想讓他變成字首就把大的串的字首砍掉;但是由於他已經是字尾了可能砍掉後兩個串相等就gg了),並且進一步的說,可以讓相鄰兩個串的長度差恰好是1,再進一步的說,最後最短的串可以讓他長度是1(否則每次切掉那個沒用的位置,做若干輪即可)。很快發現答案不超過根號,於是你可以樸素的設f(i,j)表示第i個位置向後延伸j的長度是否可行,列舉下一個位置是 S [ i , i + j 1 ] S[i,i+j-1] 的字首還是字尾,然後顯然下一次出現越早越好,因此直接找到那個位置,用那個轉移即可。如果實現優秀可以做到不帶log的根號演算法。
但是又會發現,f(i,j)作為一個bool陣列,其一段字首是1,之後是0.因此設f(i)表示i這個位置最遠能夠延伸多少,二分答案做上述過程即可兩個log。
顯然求下一個位置無論如何都優化不掉了(ba),考慮去掉前面的二分答案。
進一步觀察性質,很快發現,假設當前f(i)=j,有兩種情況,第一種是下一個位置取了個字尾,那麼f(i+1)這個位置就和和下一個位置相等,因此 f ( i + 1 ) j 1 f(i+1)\geq j-1 ;第二種情況是下一個位置是字首,看似此時 f ( i + 1 ) j 2 f(i+1)\geq j-2 其實不然,你還是可以選出 j 1 j-1 的答案。因此有 f ( i + 1 ) f ( i ) 1 f(i+1)\geq f(i)-1 恆成立。注意到 f ( n ) = 1 f(n)=1 ,而之前的等價於 f ( i ) f ( i + 1 ) + 1 f(i)\le f(i+1)+1 ,因此用類似倍增陣列求height的方法即可做到均攤線性的列舉答案。
求下一個位置只要先在SAM上倍增做子串定位,然後上線段樹合併/主席樹即可。實測如果實現不好(即你開了倍增陣列、ch陣列開了26),還寫了線段樹合併會MLE。一個優化方法是發現倍增陣列和ch陣列使用時間不重疊可以公用,另一種方法是使用主席樹可以卡掉很大的空間常數。這樣就時空一個log了。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define Rep(i,v) rep(i,0,(int)v.size()-1)
#define lint long long
#define ull unsigned lint
#define db long double
#define pb push_back
#define mp make_pair
#define fir first
#define sec second
#define gc getchar()
#define N 500010
#define SIG 27
#define LOG 20
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
#define S(rt) (((rt)==NULL)?0:((rt)->s))
#define CH(rt,w) (((rt)==NULL)?NULL:((rt)->ch[w]))
using namespace std;
typedef pair<int,int> pii;
typedef set<int>::iterator sit;
struct edges{ int to,pre; };
struct segment{ int s;segment *ch[2]; };
int f[N];
int update(segment* &x,segment* y,int l,int r,int p)
{
	x=new segment,x->s=S(y)+1;int mid=(l+r)>>1;
	x->ch[0]=CH(y,0),x->ch[1]=CH(y,1);if(l==r) return 0;
	if(p<=mid) update(x->ch[0],CH(y,0),l,mid,p);
	if(mid<p) update(x->ch[1],CH(y,1),mid+1,r,p);
	return 0;
}
int query(segment* x,segment* y,int l,int r,int p)
{
	int mid=(l+r)>>1,ans;if(S(x)-S(y)==0||p>r) return 0;if(l==r) return r;
	if((ans=query(CH(x,0),CH(y,0),l,mid,p))) return ans;
	return query(CH(x,1),CH(y,1),mid+1,r,p);
}
struct SAM{
	int node_cnt,rt,las,val[N*2],ch[N*2][SIG],dfc,in[N<<1],rp[N<<1];
	int up[N<<1][LOG],fa[N*2],ps[N<<1],n,h[N<<1],etop,out[N<<1],tms[N<<1];
	struct edges { int to,pre; }e[N<<1];segment *seg[N<<1];
	inline int add_edge(int u,int v) { return e[++etop].to=v,e[etop].pre=h[u],h[u]=etop; }
	SAM() { n=node_cnt=0,rt=las=new_SAM_node(0); }
	inline int new_SAM_node(int v,int x=0) { return val[x=++node_cnt]=v,x; }
	inline int extend(int w)
	{
		int p=las,np=new_SAM_node(val[p]+1);
		while(p&&!ch[p][w]) ch[p][w]=np,p=fa[p];
		if(!p) fa[np]=rt;
		else{
			int q=ch[p][w],v=val[p]+1;
			if(val[q]==v) fa[np]=q;
			else{
				int nq=new_SAM_node(v);
				memcpy(ch[nq],ch[q],sizeof ch[q]);
				fa[nq]=fa[q],fa[q]=fa[np]=nq;
				while(p&&ch[p][w]==q) ch[p][w]=nq,p=fa[p];
			}
		}
		return las=np,ps[++n]=np,rp[np]=n;
	}
	int dfs(int x)
	{
		in[x]=dfc+1;if(rp[x]) tms[++dfc]=rp[x];
		for(int i=h[x];i;i=e[i].pre) dfs(e[i].to);
		return out[x]=dfc;
	}
	inline int build()
	{
		dfc=0,getedge(),getup(),dfs(rt),seg[0]=NULL;
		rep(i,1,n) update(seg[i],seg[i-1],1,n,tms[i]);
		return 0;
	}
	inline int getedge() { rep(i,1,node_cnt) if(fa[i]) add_edge(fa[i],i);return 0; }
	inline int getup()
	{
		rep(i,1,node_cnt) up[i][0]=fa[i];
		rep(j,1,LOG-1) rep(i,1,node_cnt) up[i][j]=up[up[i][j-1]][j-1];
		return 0;
	}
	inline int getnode(int r,int len)
	{
		int x=ps[r];
		for(int i=LOG-1;i>=0;i--)
			if(up[x][i]&&val[up[x][i]]>=len) x=up[x][i];
		return x;
	}
	inline int query_nxt(int l,int r,int t)//s[q,q+len-1]=s[l,r],q>=t
	{
		int len=r-l+1,x=getnode(r,len);
		if(in[x]>out[x]||t+len-1>n) return 0;
		int ans=query(seg[out[x]],seg[in[x]-1],1,n,t+len-1);
		if(ans) return ans-len+1;return 0;
	}
}s;
inline int cant(int l,int x,int t=0)
{
	t=s.query_nxt(l,l+x-2,l+x);if(t&&f[t]>=x-1) return 0;
	t=s.query_nxt(l+1,l+x-1,l+x);if(t&&f[t]>=x-1) return 0;
	return 1;
}
char str[N];
int main(
            
           

相關推薦

Codeforces 666E Forensic Examination SAM+權值線段

namespace return 匹配 roo tchar long bits codeforce XA 第一次做這種$SAM$帶權值線段樹合並的題 然而$zjq$神犇看完題一頓狂碼就做出來了 $Orz$ 首先把所有串當成一個串建$SAM$ 我們對$SAM$上每個點 建一棵

字串(tjoi2016,heoi2016,bzoj4556)(sam(字尾自動機)+線段合併+倍增+二分答案)

佳媛姐姐過生日的時候,她的小夥伴從某東上買了一個生日禮物。生日禮物放在一個神奇的箱子中。箱子外邊寫了 一個長為\(n\)的字串\(s\),和\(m\)個問題。佳媛姐姐必須正確回答這\(m\)個問題,才能開啟箱子拿到禮物,升職加薪,出任CE O,嫁給高富帥,走上人生巔峰。每個問題均有\(a,b,c,d\)四個引

2018黑龍江省賽 A Sequence Game 主席+st表 / 主席+線段 / st表+莫隊+離散化

7218: A Sequence Game 時間限制: 1 Sec  記憶體限制: 128 MB 提交: 128  解決: 32 [提交] [狀態] [討論版] [命題人:admin] 題目描述 One da

bzoj 1901 ZOJ 2112 Dynamic Rankings [狀陣列套主席] [線段套平衡]

Dynamic Rankings Time Limit: 10 Sec Memory Limit: 128 MB Submit: 6900 Solved: 2870 Description 給定一個含有n個數的序列a[1],a[2],a[3]……a[

動態區間第k小(主席+線段狀陣列)

靜態區間第k小問題,是給你一個序列,每次詢問序列中的一個區間中的第k小數,這個問題用普通的主席樹就可以解決。動態區間第k小問題就是在靜態的基礎上加上了修改操作,也就是每次除了詢問區間第k小之外,還可以修改序列中的某個數。因為這裡涉及到了修改操作,我們用只用主席樹

[SPOJ COT3] SG函數 Trie 線段合並

swa 線段 std line etc 分析 eight sin stdout 題目    分析       實現 #include <cstdio> #include <cstring> #include <cstdlib>

bzoj3685普通van Emde Boas 線段

while 個數 最大 time push 線段 != submit using 3685: 普通van Emde Boas樹Time Limit: 9 Sec Memory Limit: 128 MBSubmit: 1932 Solved: 626[Submit][S

bzoj3196 二逼平衡——線段套平衡

online truct str clu turn fin lan modify .com 題目:https://www.lydsy.com/JudgeOnline/problem.php?id=3196 人生中第一棵樹套樹! 寫了一個晚上,成功卡時 9000ms+ 過了!

摧毀圖狀 - 線段 - 倍增

題目大意: 給你一棵無權樹,對於每個k=1…n求: 每次你可以給一個點x到其k級祖先的路徑上的所有點打上刪除標記。問最少多少次可以把所有點打上刪除標記。 n ≤

jzoj5943. 【NOIP2018模擬11.01】(線段)

5943. 【NOIP2018模擬11.01】樹 Description Input 第一行一個整數 n 表示序列長度, 接下來一行 n 個整數描述這個序列. 第三行一個整數 q 表示操作次數, 接下來 q 行每行一次操作, 格式同題目描述. Output 輸

bzoj3196 Tyvj 1730 二逼平衡 線段套splay

Description 您需要寫一種資料結構(可參考題目標題),來維護一個有序數列,其中需要提供以下操作: 1.查詢k在區間內的排名 2.查詢區間內排名為k的值 3.修改某一位值上的數值 4.查詢k在區間內的前驅(前驅定義為小於x,且最大的數) 5.查詢k在區

poj 2155 matrix 二維線段 線段線段

題意 一個$n*n$矩陣,初始全為0,每次翻轉一個子矩陣,然後單點查詢 題解 任意一種能維護二維平面的資料結構都可以 我這裡寫的是二維線段樹,因為四分樹的寫法複雜度可能會退化,因此考慮用樹套樹實現二維線段樹 簡單來說就是每個點都維護了一顆線段樹... 因為二維線段樹難以實現pushdown,而他的

【BZOJ3600】沒有人的算術 - 替罪羊+線段

題意:   題解: Orz vfleaking……真·神題 做法大概是先把題意中定義的“數”都賦一個實數權值,用平衡樹來維護整個從大到小排序過的序列,再用線段樹查詢最值; 這樣做為什麼是對的?考慮插入一個數$x$,我們已經知道了$x_L$和$x_R$在序列中的位置,就可以直接每

bzoj3682 Phorni 字尾平衡+線段

Description 給定一個字串和n個pos,要求資瓷: 開頭插入一個字元c 將第x個pos改為y 查詢第l到r個pos中所代表的最大字尾 強制線上 Solution 久違的串串題。感覺字尾平衡樹好像挺好寫的,線上還能logn。

bzoj 3730: 震波 (點分+線段)

最近敲這種一言不合程式碼就150+的點分樹題已經麻木了QAQ 對於每個重心我們開兩顆線段樹(動態開): 一顆線段樹中,對點分樹中以u為根的子樹中的節點v,我們以他距u的距離為下標,點權為值插入線段樹 另一顆裡,對點分治中以u為根的子樹中的節點v,我們以他距u的父親節點距離為下標,點權為值插入線段

藍橋杯 校外的 線段

#include <iostream> #include <stdio.h> #define N 10010 #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 using namespace std;

資料結構----線段----線段的定義與構造

一、什麼是線段樹? 1、線段樹是一棵二叉樹,樹中的每一個結點表示了一個區間[a,b]。 2、每一個葉子節點表示的是一個單位區間。 3、根節點表示的是“整體”的區間。 4、對於每一個非葉結點所表示的區間

bzoj 3196 && luogu 3380 JoyOI 1730 二逼平衡 (線段套Treap)

put clas 結構 online input 維護 amp bbs 查詢 鏈接:https://www.lydsy.com/JudgeOnline/problem.php?id=3196 題面; 3196: Tyvj 1730 二逼平衡樹 Time Limit

[CF1063F]String Journey[字尾陣列+線段]

題意 在 \(S\) 中找出 \(t\) 個子串滿足 \(t_{i+1}\) 是 \(t_{i}\) 的子串,要讓 \(t\) 最大。 \(|S| \leq 5\times 10^5\). 分析 定義狀態 \(f_{i}\) 表示從 \(i\) 出發能夠得到的最長的 \(journey\) .