题意
题目描述
对于一个字符串S ,我们定义$|S|$ 表示S 的长度。
接着,我们定义$S_i$ 表示S 中第i 个字符,$S_{L,R}$ 表示由S 中从左往右数,第L 个字符到第R 个字符依次连接形成的字符串。特别的,如果L > R ,或者$L \notin [1, |S|]$, 或者$R \notin [1, |S|]$ 我们可以认为$S_{L,R} $为空串。
给定一个长度为n 的仅由数字构成的字符串S ,现在有q 次询问,第k 次询问会 给出S 的一个字符串$S_{l,r}$ ,请你求出有多少对(i, j),满足1 <= i < j <= n,i + 1 < j,且$S_{l,r}$ 出现在$S_{1,i}$ 中或$S_{i+1,j-1}$中或$S_{j,n}$ 中。
输入输出格式
输入格式:从文件cutting.in 中读入数据。
输入的第一行包含两个整数n, q。
第二行包含一个长度为n 的仅由数字构成的字符串S 。
接下来q 行,每行两个正整数l 和r,表示此次询问的子串是$S_{l,r}$。
输出格式:输出到文件cutting.out 中。
对于每个询问,输出一个整数表示合法的数对个数。
输入输出样例
说明
分析
参照cz_xuyixuan和TS_Hugh的题解。
问题跟所有子串有关,考虑后缀自动机。
问题转化
题目中两个“或”已经说明了正面求很难做。正难则反,计算\(i,j\)分布使得三段都不包含所有出现的串的方案数。显然\((i,i+1),(j-1,j)\)这两个空隙要切断所有的串。所以问题转化到了空隙上面,把方案数求出来用\(\binom{n-1}2\)减去它就是答案了。
下面的论述为了方便,以右端点代替空隙,即用\(i\)来代替\((i-1,i)\)这个空隙,显然\(i,j\in[2,n],i<j\)。
只询问一个串
首先,一个询问的答案只和询问串的在主串中所有出现的位置有关。因此要找出询问串在后缀自动机上的位置。
定位一个询问的串可以在后缀自动机parent树上倍增在\(O(\log n)\)的时间内完成。如果能预处理出right集合那么所有出现的位置就找到了。
考虑用线段树合并来做这件事。现在我们有了一棵维护着所有询问串出现位置的右端点的线段树,考虑如何得到答案。
考虑较靠前的断点切断了哪些字符串。我们需要求出的即是:
\[ \sum_{k=ql}^qr询问串第一次出现和第k次出现的交∗询问串最后一次出现和第(k+1)次出现的交 \]
其中\([ql,qr]\)为\(k\)可能的取值范围。\(ql\)即第\(k+1\)个串与后面的串有交集时,\(k\)的可取最小值;类似的,\(qr\)即第\(k\)和前面的串有交集时,\(k\)的可取最大值。它们可以在线段树上二分得到。
记询问串第\(k\)次出现的左端点和右端点分别为\(L_k\)和\(R_k\)
对于询问串第一次出现和第\(k\)次出现的交,它在大部分的情况下为\(L_{k+1}-L_k\),也即\(R_{k+1}-R_k\),只有在\(k=qr\)时,它有可能为\(R_1−L_i+1\)。
对于询问串最后一次出现和第\(k+1\)次出现的交,它在大部分情况下为\(R_{k+1}−L_{last}+1\)。
还需要考虑一些特殊情况,可能可以用\(i,j\)中的一个切断所有串。为了不重不漏,对\(i\)来讨论,分为两种情况。
- \(i\)不切断任意的字符串,那么它一定在询问串第一次出现之前,并且\(j\)要恰好切断所有字符串,也即\(j\)的取值范围是询问串所有出现位置的并。
只有在\(k=ql\)时,有可能对应这种情况,此时\(ql=0\)。 - \(i\)切断了所有的字符串,那么\(j\)只需要满足在较靠前的断点之后即可,因此在这种情况下可能的方案数是一个等差数列的各项之和。
只有在\(k=qr\)时有可能出现这种情况,此时\(qr=last\)
那么,我们可以对\(k=ql\),和\(k=qr\)时特殊处理。对于剩下的情况,也即\(k\in(ql,qr)\)时,我们需要求出
\[ \sum^{qr−1}_{k=ql+1}(R_{k+1}−R_k)∗(R_{k+1}−L_{last}+1) \\ =\sum^{qr−1}_{k=ql+1}(R_{k+1}−R_k)∗R_{k+1}−(L_{last}−1)(R_{qr}−R_{ql+1}) \]
对于求和部分,是关于两个相邻位置的信息,我们可以在线段树上额外维护这个信息。那么一个串就做完了。
询问多个串
由于使用线段树合并要求在线的话,合并的时候必须新建节点,所以空间复杂度就错了。所以把询问离线挂在parent树上,询问的时候在parent按从叶子到根的拓扑序来做。这样线段树合并的时候直接用原来的节点就行了。合并时均摊分析,最坏复杂度\(O(n\log^2 n)\)。
然后这道题就做完了。时间复杂度\(O(n\log^2+q\log n)\)。
代码
这题给的数字串……写的字母串调了好久。
#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0;rg char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-') data=-data;
for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0';
return data;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std;
co int N=2e5;
int n,m;
char s[N];
ll ans[300001];
// Interval Tree
struct node{int min,max;ll sum;};
node operator+(co node&a,co node&b){
node c=(node){a.min,b.max,a.sum+b.sum};
if(!c.min) c.min=b.min;
if(!c.max) c.max=a.max;
if(a.max&&b.min) c.sum+=(ll)b.min*(b.min-a.max);
return c;
}
namespace T{
node t[N*17];
int tot,lc[N*17],rc[N*17];
void insert(int&x,int l,int r,int p){
if(!x) x=++tot;
if(l==r) return t[x]=(node){l,l,0},void();
int mid=l+r>>1;
if(p<=mid) insert(lc[x],l,mid,p);
else insert(rc[x],mid+1,r,p);
t[x]=t[lc[x]]+t[rc[x]];
}
int merge(int x,int y){
if(!x||!y) return x+y;
lc[x]=merge(lc[x],lc[y]),rc[x]=merge(rc[x],rc[y]);
t[x]=t[lc[x]]+t[rc[x]];
return x;
}
int lower(int x,int l,int r,int p){ // first >=
if(t[x].min>=p) return t[x].min;
int mid=l+r>>1;
if(t[lc[x]].max&&t[lc[x]].max>=p) return lower(lc[x],l,mid,p);
else return lower(rc[x],mid+1,r,p);
}
int upper(int x,int l,int r,int p){ // last <=
if(t[x].max<=p) return t[x].max;
int mid=l+r>>1;
if(t[rc[x]].min&&t[rc[x]].min<=p) return upper(rc[x],mid+1,r,p);
else return upper(lc[x],l,mid,p);
}
node query(int x,int l,int r,int ql,int qr){
if(ql>qr) return (node){0,0,0};
if(ql<=l&&r<=qr) return t[x];
int mid=l+r>>1;
if(qr<=mid) return query(lc[x],l,mid,ql,qr);
if(ql>mid) return query(rc[x],mid+1,r,ql,qr);
return query(lc[x],l,mid,ql,qr)+query(rc[x],mid+1,r,ql,qr);
}
ll query(int x,int len){ // represent gap with right vertice
if(len<=0) return 0;
int r1=t[x].min,l1=r1-len+1;
int rn=t[x].max,ln=rn-len+1;
int ql=lower(x,1,n,ln);
if(ql!=r1) ql=upper(x,1,n,ql-1);
else ql=0;
int qr=upper(x,1,n,r1+len-1);
if(ql>qr) return 0;
ll ans=0;
if(ql==qr){
assert(0<ql&&ql<rn);
ans+=(ll) (min(r1,lower(x,1,n,ql+1)-len)-(ql-len+1)+1) * (lower(x,1,n,ql+1)-ln+1);
}
else{
node tmp=query(x,1,n,ql+1,qr);
ans+=tmp.sum-(ll) (ln-1) * (tmp.max-tmp.min);
if(ql==0) ans+=(ll) (l1-2) * (r1-ln+1);
else ans+=(ll) (min(r1,lower(x,1,n,ql+1)-len)-(ql-len+1)+1) * (lower(x,1,n,ql+1)-ln+1);
if(qr==rn) ans+=(ll) (n-r1+n-ln) * (r1-ln+1) / 2;
else ans+=(ll) (min(r1,lower(x,1,n,qr+1)-len)-(qr-len+1)+1) * (lower(x,1,n,qr+1)-ln+1);
}
return ans;
}
}
// Suffix Automaton
namespace SAM{
int last=1,tot=1;
int ch[N][10],fa[N],len[N],pos[N]; // pos:out->in
int root[N]; // for Interval Tree
void extend(int c,int po){
int p=last,cur=last=++tot;
len[cur]=len[p]+1,pos[po]=cur;
T::insert(root[cur],1,n,po);
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
if(!p) fa[cur]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[cur]=q;
else{
int clone=++tot;
memcpy(ch[clone],ch[q],sizeof ch[q]);
fa[clone]=fa[q],len[clone]=len[p]+1;
fa[cur]=fa[q]=clone;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
}
}
}
int anc[N][19];
vector<int> e[N];
vector<pair<int,int> > q[N];
void init(){
for(int i=1;i<=n;++i) extend(s[i]-'0',i);
for(int i=1;i<=tot;++i) anc[i][0]=fa[i],e[fa[i]].push_back(i);
for(int k=1;k<=18;++k)
for(int i=1;i<=tot;++i) anc[i][k]=anc[anc[i][k-1]][k-1];
}
void storequery(int l,int r,int id){
int len=r-l+1,p=pos[r];
for(int i=18;i>=0;--i)
if(SAM::len[anc[p][i]]>=len) p=anc[p][i];
q[p].push_back(make_pair(len,id));
}
void work(int p){
for(int i=0;i<e[p].size();++i)
work(e[p][i]),root[p]=T::merge(root[p],root[e[p][i]]);
for(int i=0;i<q[p].size();++i)
ans[q[p][i].second]=(ll)(n-1)*(n-2)/2-T::query(root[p],q[p][i].first-1); // available len for cutting
}
}
int main(){
read(n),read(m),scanf("%s",s+1);
SAM::init();
for(int l,r,i=1;i<=m;++i){
read(l),read(r);
SAM::storequery(l,r,i);
}
SAM::work(1);
for(int i=1;i<=m;++i) printf("%lld\n",ans[i]);
return 0;
}
强烈推荐cz_xuyixuan的代码,他竟然把码风维护到这种题上。当初我早就放弃封装了。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int MAXP = 200005;
const int MAXQ = 300005;
const int MAXS = 3e6 + 5;
const int MAXLOG = 20;
const int MAXC = 10;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct query {int l, r, home; };
struct info {int Min, Max; long long sum; };
info operator + (info a, info b) {
info ans = (info) {a.Min, b.Max, a.sum + b.sum};
if (ans.Min == 0) ans.Min = b.Min;
if (ans.Max == 0) ans.Max = a.Max;
if (a.Max && b.Min) ans.sum += 1ll * b.Min * (b.Min - a.Max);
return ans;
}
struct SegmentTree {
struct Node {
int lc, rc;
info val;
} a[MAXS];
int size, n;
void init(int x) {
n = x;
size = 0;
}
void update(int root) {
a[root].val = a[a[root].lc].val + a[a[root].rc].val;
}
void insert(int &root, int l, int r, int pos) {
if (root == 0) root = ++size;
if (l == r) {
a[root].val = (info) {l, l, 0};
return;
}
int mid = (l + r) / 2;
if (mid >= pos) insert(a[root].lc, l, mid, pos);
else insert(a[root].rc, mid + 1, r, pos);
update(root);
}
void insert(int &root, int val) {
insert(root, 1, n, val);
}
int merge(int x, int y) {
if (x == 0 || y == 0) return x + y;
a[x].lc = merge(a[x].lc, a[y].lc);
a[x].rc = merge(a[x].rc, a[y].rc);
update(x);
return x;
}
void join(int &x, int y) {
x = merge(x, y);
}
int lower(int root, int l, int r, int val) {
if (a[root].val.Min >= val) return a[root].val.Min;
int mid = (l + r) / 2;
if (a[a[root].lc].val.Max != 0 && a[a[root].lc].val.Max >= val) return lower(a[root].lc, l, mid, val);
else return lower(a[root].rc, mid + 1, r, val);
}
int upper(int root, int l, int r, int val) {
if (a[root].val.Max <= val) return a[root].val.Max;
int mid = (l + r) / 2;
if (a[a[root].rc].val.Min != 0 && a[a[root].rc].val.Min <= val) return upper(a[root].rc, mid + 1, r, val);
else return upper(a[root].lc, l, mid, val);
}
info query(int root, int l, int r, int ql, int qr) {
if (ql > qr) return (info) {0, 0, 0};
if (l == ql && r == qr) return a[root].val;
int mid = (l + r) / 2;
if (mid >= qr) return query(a[root].lc, l, mid, ql, qr);
else if (mid + 1 <= ql) return query(a[root].rc, mid + 1, r, ql, qr);
else return query(a[root].lc, l, mid, ql, mid) + query(a[root].rc, mid + 1, r, mid + 1, qr);
}
long long sum(int l, int r) {
if (l > r) swap(l, r);
return (l + r) * (r - l + 1ll) / 2;
}
long long query(int root, int l, int r) {
if (l == r) return 0;
int len = r - l;
int r1 = a[root].val.Min, l1 = r1 - len + 1;
int rn = a[root].val.Max, ln = rn - len + 1;
int ql = lower(root, 1, n, ln);
if (ql != r1) ql = upper(root, 1, n, ql - 1);
else ql = 0;
int qr = upper(root, 1, n, r1 + len - 1);
long long ans = 0;
if (ql > qr) return 0;
if (ql == qr) {
if (ql == 0) ans += (l1 - 2ll) * (r1 - ln + 1ll);
else if (qr == rn) ans += sum(n - r1, n - ln);
else ans += (min(r1, lower(root, 1, n, ql + 1) - len) - (ql - len + 1) + 1ll) * (lower(root, 1, n, ql + 1) - ln + 1ll);
} else {
info tmp = query(root, 1, n, ql + 1, qr);
ans += tmp.sum - (ln - 1ll) * (tmp.Max - tmp.Min);
if (ql == 0) ans += (l1 - 2ll) * (r1 - ln + 1ll);
else ans += (min(r1, lower(root, 1, n, ql + 1) - len) - (ql - len + 1) + 1ll) * (lower(root, 1, n, ql + 1) - ln + 1ll);
if (qr == rn) ans += sum(n - r1, n - ln);
else ans += (min(r1, lower(root, 1, n, qr + 1) - len) - (qr - len + 1) + 1ll) * (lower(root, 1, n, qr + 1) - ln + 1ll);
}
return ans;
}
} ST;
struct SuffixAutomaton {
int root, size, last, len;
int child[MAXP][MAXC], home[MAXN];
int father[MAXP][MAXLOG], sroot[MAXP];
int fail[MAXP], depth[MAXP], cnt[MAXP];
vector <int> a[MAXP];
vector <query> q[MAXP];
long long ans[MAXQ];
int newnode(int dep) {
fail[size] = 0;
depth[size] = dep;
memset(child[size], 0, sizeof(child[size]));
return size++;
}
void extend(int ch, int from) {
int p = last, np = newnode(depth[last] + 1);
while (child[p][ch] == 0) {
child[p][ch] = np;
p = fail[p];
}
if (child[p][ch] == np) fail[np] = root;
else {
int q = child[p][ch];
if (depth[q] == depth[p] + 1) fail[np] = q;
else {
int nq = newnode(depth[p] + 1);
fail[nq] = fail[q];
fail[q] = fail[np] = nq;
memcpy(child[nq], child[q], sizeof(child[q]));
while (child[p][ch] == q) {
child[p][ch] = nq;
p = fail[p];
}
}
}
cnt[last = np]++;
ST.insert(sroot[np], from);
home[from] = np;
}
void init(char *s) {
size = 0;
root = last = newnode(0);
len = strlen(s + 1);
for (int i = 1; i <= len; i++)
extend(s[i] - '0', i);
for (int i = 1; i < size; i++) {
father[i][0] = fail[i];
a[fail[i]].push_back(i);
}
for (int p = 1; p < MAXLOG; p++)
for (int i = 0; i < size; i++)
father[i][p] = father[father[i][p - 1]][p - 1];
}
void storequery(int l, int r, int from) {
int len = r - l + 1, pos = home[r];
for (int i = MAXLOG - 1; i >= 0; i--)
if (depth[father[pos][i]] >= len) pos = father[pos][i];
q[pos].push_back((query) {l, r, from});
}
void work(int pos) {
for (unsigned i = 0; i < a[pos].size(); i++) {
work(a[pos][i]);
ST.join(sroot[pos], sroot[a[pos][i]]);
}
for (unsigned i = 0; i < q[pos].size(); i++)
ans[q[pos][i].home] = (len - 1ll) * (len - 2ll) / 2 - ST.query(sroot[pos], q[pos][i].l, q[pos][i].r);
}
void solve(int m) {
work(0);
for (int i = 1; i <= m; i++)
printf("%lld\n", ans[i]);
}
} SAM;
int n, m;
char s[MAXN];
int main() {
read(n), read(m);
ST.init(n);
scanf("%s", s + 1);
SAM.init(s);
for (int i = 1; i <= m; i++) {
int l, r; read(l), read(r);
SAM.storequery(l, r, i);
}
SAM.solve(m);
return 0;
}