7.25
算是正經的出(ban)了一道題。
類似的思路我也想過,然後yjq學長剛好出了一道更難寫的題,就直接搬過來了。
題目思路很巧妙,就是具體實現細節過於複雜,所以只能算半道好題。
考場上也沒有人過。()有個學軍的同學交了以前的標程都沒過。)是有點遺憾。
算是積累了一次寶貴的出題經驗。以後做的題多了,自己再多思考,就可以出出來更多好題了。原創題還是一個很重要的能力!
造數據也是非常重要的,一道好題一定要配上很強的數據。隨機數據不強。一開始我用隨機數據對拍,std犯了很大的錯誤都沒有拍出來。還是yjq的數據靠譜!
附上題面:
#include<bits/stdc++.h>
using namespace std;
#define maxn 200020
#define rep(i,l,r) for(register int i = l ; i <= r ; i++)
#define repd(i,r,l) for(register int i = r ; i >= l ; i--)
typedef long long ll;
struct node{
int next,to;
};
int n,T,q;
char ch[maxn];
struct SAM{
int next[maxn][10],pnt[maxn],val[maxn],id[maxn],rec[maxn],jump[20][maxn];
int last,tot;
node e[maxn];
int head[maxn],cnt;
int rt[maxn],ls[maxn << 5],rs[maxn << 5],lm[maxn << 5],rm[maxn << 5],num;
ll sum[maxn << 5],sum2[maxn << 5];
void clear(){
rep(i,0,tot) memset(next[i],0,sizeof(next[i])) , rec[i] = head[i] = rt[i] = pnt[i] = val[i] = id[i] = 0;
rep(i,0,num) ls[i] = rs[i] = lm[i] = rm[i] = 0 , sum[i] = sum2[i] = 0;
last = tot = num = cnt = 0;
}
inline void adde(int x,int y){
e[++cnt].to = y;
e[cnt].next = head[x];
head[x] = cnt;
}
void insert(int x){
int p = last , np = ++tot;
val[np] = val[p] + 1 , id[np] = val[np] , rec[val[np]] = tot;
while ( p && !next[p][x] ) next[p][x] = np , p = pnt[p];
int q = next[p][x];
if ( !q ) next[p][x] = np , pnt[np] = p;
else if ( q && val[p] + 1== val[q] ) pnt[np] = q;
else{
int nq = ++tot;
val[nq] = val[p] + 1;
pnt[nq] = pnt[q];
pnt[q] = pnt[np] = nq;
memcpy(next[nq],next[q],sizeof(next[q]));
while ( p && next[p][x] == q ) next[p][x] = nq , p = pnt[p];
if ( next[p][x] == q ) next[p][x] = nq;
}
last = np;
}
inline void update(int x){
sum[x] = sum[ls[x]] + sum[rs[x]];
sum2[x] = sum2[ls[x]] + sum2[rs[x]] + (ll)lm[rs[x]] * rm[ls[x]];
lm[x] = lm[ls[x]] ? lm[ls[x]] : lm[rs[x]];
rm[x] = rm[rs[x]] ? rm[rs[x]] : rm[ls[x]];
}
inline void copy(int cur,int x){
ls[cur] = ls[x] , rs[cur] = rs[x];
sum[cur] = sum[x] , sum2[cur] = sum2[x];
lm[cur] = lm[x] , rm[cur] = rm[x];
}
int Merge(int x,int y){
if ( !x && !y ) return 0;
int cur = ++num; //合併線段樹的時候因爲每個點的信息都要記錄,所以節點必須新建。空間複雜度O(nlogn * 2)
if ( !x ){ copy(cur,y); return cur; }
if ( !y ){ copy(cur,x); return cur; }
ls[cur] = Merge(ls[x],ls[y]);
rs[cur] = Merge(rs[x],rs[y]);
update(cur);
return cur;
}
void insert(int &x,int l,int r,int id){
if ( !x ) x = ++num;
if ( l == r ){ lm[x] = rm[x] = id , sum[x] = (ll)id * id; return; }
int mid = (l + r) >> 1;
if ( id <= mid ) insert(ls[x],l,mid,id);
else insert(rs[x],mid + 1,r,id);
update(x);
}
void dfs(int x){
if ( id[x] ) insert(rt[x],1,n,id[x]);
for (int i = head[x] ; i ; i = e[i].next){
dfs(e[i].to);
rt[x] = Merge(rt[x],rt[e[i].to]);
}
}
void print(){
rep(i,1,tot) cout<<i<<" "<<pnt[i]<<endl;
cout<<endl;
rep(i,1,tot) cout<<i<<" "<<lm[rt[i]]<<" "<<rm[rt[i]]<<" "<<sum[rt[i]]<<" "<<sum2[rt[i]]<<endl;
}
void init(){
rep(i,1,tot) adde(pnt[i],i) , jump[0][i] = pnt[i];
dfs(0);
rep(i,1,18)
rep(j,1,tot)
jump[i][j] = jump[i - 1][jump[i - 1][j]];
}
ll query(int x,int l,int r,int L,int R){ //查詢和,注意合併的時候要把左右區間的相鄰位置的和更新一下
if ( L > R ) return 0;
if ( !x ) return 0;
if ( L <= l && R >= r ) return sum[x] - sum2[x];
ll res = 0; int mid = (l + r) >> 1;
if ( L <= mid ) res += query(ls[x],l,mid,L,R);
if ( R > mid ) res += query(rs[x],mid + 1,r,L,R);
if ( L <= rm[ls[x]] && lm[rs[x]] <= R ) res -= (ll)rm[ls[x]] * lm[rs[x]];
return res;
}
int queryL(int x,int l,int r,int d){ //查詢一個位置的前驅
if ( d < 1 ) return 0;
if ( !x ) return 0;
if ( l == r ) return l;
int mid = (l + r) >> 1;
if ( d <= mid || !rs[x] ) return queryL(ls[x],l,mid,d);
int id = queryL(rs[x],mid + 1,r,d);
if ( !id ) return rm[ls[x]];
return id;
}
int queryR(int x,int l,int r,int d){ //查詢一個位置的後繼
if ( d > n ) return 0;
if ( !x ) return 0;
if ( l == r ) return l;
int mid = (l + r) >> 1;
if ( d > mid || !ls[x] ) return queryR(rs[x],mid + 1,r,d);
int id = queryR(ls[x],l,mid,d);
if ( !id ) return lm[rs[x]];
return id;
}
ll query(int x,int len){ //統計不合法情況
int l = rm[x] - len + 1 , r = min(lm[x] + len - 2,n);
if ( rm[x] == lm[x] ){ //如果只有一個位置
return (ll)(len - 1) * (2 * n - len - 2) / 2;
}
//兩個位置且互不相交
if ( queryR(x,1,n,lm[x] + 1) == rm[x] && rm[x] - lm[x] >= len ) return (ll)(len - 1) * (len - 1);
int fir = queryL(x,1,n,l - 1);
int last = queryR(x,1,n,r + 1);
ll csum = 0;
//如果沒有相交的部分直接返回0
if ( last && last <= fir ) return 0;
// cout<<l<<" "<<r<<" "<<fir<<" "<<last<<endl;
if ( !fir ) fir = len , csum = query(x,1,n,l,r) - (ll)lm[x] * len;
else csum = query(x,1,n,fir,r) - (ll)fir * fir; //要把上一個位置的貢獻減掉
if ( last ){ //判一下最後一個合法位置是否是最後一次在原串中出現的位置
int rid = queryL(x,1,n,last - 1);
csum -= (ll)(rm[x] - len + 1) * (r - fir + 1);
csum += (ll)(r - rid + 1) * last;
}
else{
//按照推的式子算貢獻,後面是個等差數列求和
csum -= (ll)(rm[x] - len + 1) * (rm[x] - fir);
csum += max(0ll,(ll)(lm[x] - rm[x] + len - 1) * (2 * n - lm[x] - rm[x] + len - 2) / 2);
}
return csum;
}
void solve(int l,int r){
int cur = rec[r],len = r - l + 1;
repd(i,18,0) if ( val[jump[i][cur]] >= len ) cur = jump[i][cur]; //倍增定位一個串對應的節點
ll ans = (ll)(n - 2) * (n - 1) / 2 - query(rt[cur],len);
printf("%lld\n",ans);
}
}sam;
int main(){
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
scanf("%d",&T);
// T = 1;
while ( T-- ){
sam.clear();
scanf("%d %d",&n,&q);
scanf("%s",ch + 1);
rep(i,1,n) sam.insert(ch[i] - '0');
sam.init();//sam.print();
while ( q-- ){
int l,r;
scanf("%d %d",&l,&r);
sam.solve(l,r);
}
}
return 0;
}
怎麼上傳題解啊QAQ
大概就是維護每個串的所有出現位置(pnt樹上合併線段樹)
然後計算一下貢獻。細節非常多(見7.22文件夾)
附上第一場搬的sb題留作紀(jing)念(xing)
數據要認真造