[71] (多校聯訓) A層衝刺NOIP2024模擬賽24

HaneDaniko發表於2024-11-19

byd T3 放道這種題有什麼深意嗎

flowchart TB A(選取字串) style A color:#ffffff,fill:#00c0c0,stroke:#ffffff

確實是籤,但是一直在想組合意義,最後因為沒提前處理逆元遺憾離場了,賽後看題解發現的確是往樹上轉化更簡單點

賽時的組合意義程式碼

沒過

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=998244353,num=233;
int k;
string s;
int pi[1000002];
int fact[1000002];
int tp[1000002],ct[1000002];
vector<int>v;
int power(int a,int t){
    int base=a,ans=1;
    while(t){
        if(t&1){
            ans=ans*base%p;
        }
        base=base*base%p;
        t>>=1;
    }
    return ans;
}
int C(int n,int m){
    return fact[n]*power(fact[n-m]*fact[m]%p,p-2)%p;
}
signed main(){
    // freopen("sam/T1/ex.in","r",stdin);
    freopen("string.in","r",stdin);
    freopen("string.out","w",stdout);
    cin>>k>>s;
    s=" "+s;
    basenum[0]=1;
    fact[0]=1;
    for(int i=1;i<=(int)s.length();++i){
        if(i!=(int)s.length()) h[i]=h[i-1]*num+s[i];
        if(i!=(int)s.length()) basenum[i]=basenum[i-1]*num;
        fact[i]=fact[i-1]*i%p;
    }
    for(int i=2;i<=(int)s.length()-1;++i){
        int j=pi[i-1];
        while(j>0 and s[j+1]!=s[i]) j=pi[j];
        if(s[j+1]==s[i]) j++;
        pi[i]=j;
    }
    for(int i=1;i<=(int)s.length()-1;++i){
        int j=i;
        while(j>0){
            tp[j]++;
            ct[i]++;
            j=pi[j];
        }
    }
    int ans=C((int)s.length(),k);
    for(int i=1;i<=(int)s.length()-1;++i){
        ans=(ans+C(tp[i],k)*((ct[i]+1)*(ct[i]+1)-ct[i]*ct[i])%p)%p;
    }
    cout<<ans<<endl;
}

組合意義的大體思路是先處理出所有 border,然後列舉每個字首,然後再列舉以這個字首為公共前字尾(不一定非得是 border)的字首數量(這句話有點繞,形式化地說,設 \(t=b(s)\) 表示 \(t\) 既是 \(s\) 的字首也是 \(s\) 的字尾,我們要統計的就是,對於給定的 \(i\),滿足 \(s_{[1,i]}=b(s_{[1,j]})\)\(j\) 的數量 \(a\),不難發現這個 \(i\) 對答案的貢獻就是 \(C^{k}_{a}\) 乘一個什麼係數),我們組合意義要求的就是這個係數,透過手摸可以發現,如果滿足 \(s_{[1,k]}=b(s_{[1,i]})\)\(k\)\(c\) 個,那麼這個係數是 \(c^2-(c-1)^2=2c-1\),所以直接做就行了

然後是題解給的樹上意義的解法

如果將 \(i\)\(p_i\) 連邊(\(p_i\)\(s_{[1,i]}\) 的 border 長度),發現可以連成一顆樹,選 \(k\) 個字串找公共前字尾的實質是在樹上選 \(k\) 個點求 LCA,對答案的貢獻是 LCA 的深度的平方

這個轉化還挺有意思的

然後需要做的就只有列舉每個點作為公共 LCA 的情況,容易發現只有在子樹裡選才行,方案為 \(C_{size}^{k}\),注意要減去所有點都出現在同一顆子樹裡的情況

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=998244353;
int k,ans;
int pi[1000002];
string s;
int fact[1000002],inv[1000002],invfact[1000002];
vector<int>e[1000001];
inline int C(int n,int m){
    if(n<m) return 0;
    return fact[n]*invfact[m]%p*invfact[n-m]%p;
}
int sizen[1000001];
void dfs(int now,int deep){
    sizen[now]=1;
    int tot=0;
    for(int i:e[now]){
        dfs(i,deep+1);
        sizen[now]+=sizen[i];
        tot=(tot+C(sizen[i],k))%p;
    }
    ans=(ans+(C(sizen[now],k)%p-tot+p)%p*(deep*deep)%p)%p;
}
signed main(){
    freopen("string.in","r",stdin);
    freopen("string.out","w",stdout);
    cin>>k>>s;
    s=" "+s;
    for(int i=2;i<=(int)s.length()-1;++i){
        int j=pi[i-1];
        while(j and s[j+1]!=s[i]) j=pi[j];
        if(s[j+1]==s[i]) j++;
        pi[i]=j;
    }
    for(int i=1;i<=(int)s.length()-1;++i){
        e[pi[i]].push_back(i);
    }
    inv[1]=1;
    for(int i=2;i<=1000001;++i){
        inv[i]=inv[p%i]*(p-p/i)%p;
    }
    fact[0]=invfact[0]=1;
    for(int i=1;i<=1000001;++i){
        fact[i]=fact[i-1]*i%p,invfact[i]=invfact[i-1]*inv[i]%p;
    }
    dfs(0,1);
    cout<<ans;
}
flowchart TB A(取石子) style A color:#ffffff,fill:#00c0c0,stroke:#ffffff

tba

flowchart TB A(均衡區間) style A color:#ffffff,fill:#00c0c0,stroke:#ffffff

考慮直接欽定一個定點(題目裡也是讓這麼求的),然後列舉區間另一個端點的所有可能情況

發現這個東西完全沒有單調性,這很不好,於是考慮做 trick

怎麼才能套上 trick,考慮到如果你只欽定左端點 \(l\) 滿足條件(即找符合條件的 \(r\ge l\),滿足 \(a_l\neq\max\limits_{l\le i\le r}a_i,a_l\neq\min\limits_{l\le i\le r}a_i\),其實就是把題目裡對於右端點的限制撤了),你會發現這個玩意是有單調性的,對於所有的 \(r\) 大於某個定值都成立,因此可以二分去找這個值

對右端點,你用同樣的情況去找滿足條件的 \(l\),然後嘗試列舉所有的左右端點,嘗試從中間合併它倆,如果能合併就合法(合併的條件是,左端點的合法區間包含右端點,右端點的合法區間包含左端點,這樣在區間裡左右端點就一定都符合條件了)

設對左端點 \(l\),其合法區間是 \([a_l,n]\),對右端點 \(r\),其合法區間是 \([1,b_r]\),其實現在我們要找的就是滿足 \(r\ge a_l,l\le b_r\)\((l,r)\) 的數量

這東西複雜度太高,考慮直接上個啥資料結構維護一下

上權值樹狀陣列(線段樹亦可,但是比較危險),按 \(a_i\) 從大到小列舉所有 \(a_i\),然後將 \(j\in[a_l,n]\)\(j\) 動態地插入樹狀陣列內,現在只需要查這些插進去的數里有幾個是包含 \(i\) 的,貪心地想,插入的時候可以把每個區間插到其右端點上,查詢的時候,只要右端點在 \(i\) 右側的均包含 \(i\)(因為原區間是 \([1,r]\)),因此直接查 \([i,n]\) 內點的數量即可

另一邊也是同理做

#include<bits/stdc++.h>
using namespace std;
int n,id;
int a[1000001],lg2[1000001];
int stmaxn[20][1000001],stminn[20][1000001];
inline int qmax(int l,int r){
    int tmp=lg2[r-l+1];
    return max(stmaxn[tmp][l],stmaxn[tmp][r-(1<<tmp)+1]);
}
inline int qmin(int l,int r){
    int tmp=lg2[r-l+1];
    return min(stminn[tmp][l],stminn[tmp][r-(1<<tmp)+1]);
}
struct stree{
    struct tree{
        int sum;
    }t[1000001*4];
    #define tol (id*2)
    #define tor (id*2+1)
    #define mid(l,r) mid=((l)+(r))/2
    void change(int id,int l,int r,int pos){
        if(l==r){
            t[id].sum++;
            return;
        }
        int mid(l,r);
        if(pos<=mid) change(tol,l,mid,pos);
        else change(tor,mid+1,r,pos);
        t[id].sum=t[tol].sum+t[tor].sum;
    }
    int ask(int id,int l,int r,int L,int R){
        if(L<=l and r<=R){
            return t[id].sum;
        } 
        int mid(l,r);
        if(R<=mid) return ask(tol,l,mid,L,R);
        else if(L>=mid+1) return ask(tor,mid+1,r,L,R);
        return ask(tol,l,mid,L,mid)+ask(tor,mid+1,r,mid+1,R);
    }
}A,B;
int la[1000001],rb[1000001];
struct la_t{
    int la,id;
    inline bool operator<(const la_t&A)const{
        return la>A.la;
    }
}lat[1000001];
struct rb_t{
    int rb,id;
    inline bool operator<(const rb_t&A)const{
        return rb<A.rb;
    }
}rbt[1000001];
int ansla[1000001],ansrb[1000001];
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>id;
    for(int i=1;i<=n;++i){
        cin>>a[i];
        if(i!=1) lg2[i]=lg2[i/2]+1;
        stmaxn[0][i]=stminn[0][i]=a[i];
    }
    for(int i=1;i<=19;++i){
        for(int j=1;j<=n;++j){
            stmaxn[i][j]=max(stmaxn[i-1][j],stmaxn[i-1][j+(1<<(i-1))]);
            stminn[i][j]=min(stminn[i-1][j],stminn[i-1][j+(1<<(i-1))]);
        }
    }
    for(int i=1;i<=n;++i){
        int l=i+1,r=n,ans=n+1;
        while(l<=r){
            int mid=(l+r)/2;
            if(qmax(i,mid)!=a[i] and qmin(i,mid)!=a[i]){
                r=mid-1;
                ans=mid;
            }
            else l=mid+1;
        }
        lat[i].la=ans;
        lat[i].id=i;
        la[i]=ans;
        // cout<<i<<" ["<<ans<<" "<<n<<"]"<<endl;
    }
    // cout<<endl;
    for(int i=1;i<=n;++i){
        int l=1,r=i-1,ans=0;
        while(l<=r){
            int mid=(l+r)/2;
            if(qmax(mid,i)!=a[i] and qmin(mid,i)!=a[i]){
                l=mid+1;
                ans=mid;
            }
            else r=mid-1;
        }
        rbt[i].rb=ans;
        rbt[i].id=i;
        rb[i]=ans;
        // cout<<i<<" ["<<1<<" "<<ans<<"]"<<endl;
    }
    sort(lat+1,lat+n+1);
    sort(rbt+1,rbt+n+1);
    int j=n+1;
    for(int i=1;i<=n;++i){
        if(lat[i].la==n+1) continue;
        while(j!=lat[i].la){
            j--;
            if(rb[j]!=0) B.change(1,1,n,rb[j]);
        }
        ansla[lat[i].id]=B.ask(1,1,n,lat[i].id,n);
    }
    j=0;
    for(int i=1;i<=n;++i){
        if(rbt[i].rb==0) continue;
        while(j!=rbt[i].rb){
            j++;
            if(la[j]!=n+1) A.change(1,1,n,la[j]);
        }
        ansrb[rbt[i].id]=A.ask(1,1,n,1,rbt[i].id);
    }
    for(int i=1;i<=n;++i){
        cout<<ansla[i]<<' ';
    }
    cout<<endl;
    for(int i=1;i<=n;++i){
        cout<<ansrb[i]<<' ';
    }
    cout<<endl;
}

相關文章