ACM出題日記

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)
數據要認真造

這裏寫圖片描述
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章