7.25
算是正经的出(ban)了一道题。
类似的思路我也想过,然后yjq学长刚好出了一道更难写的题,就直接搬过来了。
题目思路很巧妙,就是具体实现细节过于复杂,所以只能算半道好题。
考场上也没有人过。()有个学军的同学交了以前的标程都没过。)是有点遗憾。
算是积累了一次宝贵的出题经验。以后做的题多了,自己再多思考,就可以出出来更多好题了。原创题还是一个很重要的能力!
造数据也是非常重要的,一道好题一定要配上很强的数据。随机数据不强。一开始我用随机数据对拍,std犯了很大的错误都没有拍出来。还是yjq的数据靠谱!
#include
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<" "<1,tot) cout<" "<" "<" "<" "<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<
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)
数据要认真造