2022 牛客多校題解

2022 牛客多校題解

Contest 1

J

Contest 2

J

Contest 3

H Hack(SAM)

枚舉B中的右端點,查詢最長在A串中向右可以延伸多長。考慮對A串建立一個SAM,然後對於B串從左到右跑SAM的DAG,如果fail了就跳fa,將clen=Len[fa[now]]。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;

long long pre[maxn],seg[maxn];
char a[maxn],temp[maxn];
int n,m,k;

void add(int p,long long val){
        while(p>0)
                seg[p]=max(val,seg[p]),p-=p&-p;
}

long long segque(int p){
        long long ret=-1e18;
        while(p<=m)
                ret=max(ret,seg[p]),p+=p&-p;
        return ret;
}

int trans[26][maxn],fa[maxn],Len[maxn],cnt=1,last=1;

void extend(int c){
        int u=++cnt,v=last;
        Len[u]=Len[v]+1;
        while(v&&!trans[c][v]) trans[c][v]=u,v=fa[v];
        if(!v) fa[u]=1;
        else{
                int x=trans[c][v];
                if(Len[x]==Len[v]+1) fa[u]=x;
                else{
                        int y=++cnt;
                        for(int t=0;t<26;++t)
                                trans[t][y]=trans[t][x];
                        fa[y]=fa[x]; fa[x]=fa[u]=y; Len[y]=Len[v]+1;
                        while(v&&trans[c][v]==x) trans[c][v]=y,v=fa[v];
                }
        }
        last=u;
}

int main(){
        scanf("%d%d%d",&n,&m,&k);
        scanf("%s",a+1);
        for(int t=1;t<=n;++t)
                extend(a[t]-'a');
        for(int t=1;t<=m;++t)
                scanf("%lld",pre+t),pre[t]+=pre[t-1];
        for(int t=1;t<=k;++t){
                scanf("%s",temp+1);
                int now=1;
                int clen=0;
                for(int i=1;i<=m;++i) seg[i]=-1e17;
                add(1,0);
                long long ans=0;
                for(int i=1;i<=m;++i){
                        while(fa[now]&&!trans[temp[i]-'a'][now])
                                clen=Len[fa[now]],now=fa[now];
                        if(trans[temp[i]-'a'][now])
                                now=trans[temp[i]-'a'][now],++clen;
                        if(clen){
                                if(clen<0)
                                        cerr<<clen<<endl,exit(0);
                                ans=max(ans,segque(i-clen+1)+pre[i]);
                        }
                        add(i+1,-pre[i]);
                }
                //cerr<<endl;
                printf("%lld\n",ans);
        }
        return 0;
}

D Directed (隨機遊走)

給定一棵樹和一個起點,1號節點爲終點,隨機選其中K條邊變成指向終點的單向邊,在樹上隨機遊走,求到達終點的期望步數
https://www.cnblogs.com/winlere/p/11852977.html

假設所有邊都是無向邊
\(e_i\)表示從\(i\)點開始隨機遊走第一次到達的自己父親的期望步數。根據這個鏈接的口胡,\({e_k}\)滿足

\[e_n=\begin{cases} 0&n=1\\ 1 &n is leave\\ {1\over \deg} \sum (e_u +e_n +1 ) +{1\over \deg} &\text{u is son},n\neq 1 \end{cases} \]

答案是從s一直走父親走到1

化簡發現,一個子樹內的邊貢獻爲2,父邊貢獻爲1,\(e_n=2 \text{siz[n]}-1\)

而單向邊的影響是將自己祖先的siz[n]減少,對期望的貢獻爲\(-2siz[u]\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
const int mod=998244353;
vector<int>e[maxn];


void add(int fr,int to){
        e[fr].push_back(to);
        e[to].push_back(fr);
}

int inv[maxn],jc[maxn],invi[maxn],fsum[maxn];

int MOD(const int&ba){return ba>=mod?ba-mod:ba;}
int MOD(const int&a,const int&b){return 1ll*a*b%mod;}

int ksm(const int&ba,const int&p){
        int ret=1;
        for(int t=p,b=ba;t;t>>=1,b=MOD(b,b))
                if(t&1) ret=MOD(ret,b);
        return ret;
}

void pre(const int&n){
        jc[0]=inv[0]=1;
        for(int t=1;t<=n;++t) jc[t]=MOD(jc[t-1],t);
        inv[n]=ksm(jc[n],mod-2);
        for(int t=n-1;t;--t) inv[t]=MOD(inv[t+1],t+1);
        for(int t=1;t<=n;++t) invi[t]=MOD(jc[t-1],invi[t]);
}

int C(int n,int m){
        if(n<m||m<0) return 0;
        return MOD(MOD(jc[n],inv[m]),inv[n-m]);
}

int n,k,s;
int fa[maxn],siz[maxn],dep[maxn],path[maxn];

void dfs0(int x,int y){
        fa[x]=y; siz[x]=1; dep[x]=dep[y]+1;
        for(auto t:e[x])
                if(t^y)
                        dfs0(t,x),siz[x]+=siz[t];
}

int sav;
void dfs1(int x,int d0){
        //dep[x]-d0 ~ dep[x]-1
        if(dep[x]>d0){
                sav=(sav+2ll*siz[x]*(fsum[dep[x]-2]-fsum[dep[x]-d0-1]+mod) )%mod;
                //cerr<<"qwwq"<<2ll*siz[x]*(fsum[dep[x]-2]-fsum[dep[x]-d0-1]+mod)%mod<<endl;
        }
        for(auto t:e[x])
                if(fa[t]==x&&!path[t])
                        dfs1(t,d0);
}

int main(){
        pre(1e6);
        cin>>n>>k>>s;
        for(int t=1,x,y;t<n;++t){
                cin>>x>>y;
                add(x,y);
        }
        if(k>0){

                for(int t=1;n-1-t>=k-1;++t)
                        fsum[t]=C(n-1-t,k-1);
                for(int t=1;t<=n;++t)
                        fsum[t]=MOD(fsum[t-1]+fsum[t]);
        }
        dfs0(1,0);
        if(s==1) return puts("0"),0;
        //if(k==0) return cout<<2*siz[s]-1<<endl,0;

        int ans=0,x=s;
        while(x!=1){
                dfs1(x,dep[x]);
                ans=(ans+C(n-1,k)*(2ll*siz[x]-1))%mod;
                path[x]=1;

                sav=(sav+fsum[dep[x]-2]*2ll*siz[x])%mod;
                //cerr<<fsum[dep[x]-1]*2ll*siz[x]<<' '<<sav<<endl;

                x=fa[x];
        }

        ans=1ll*(ans-sav+mod)*ksm(C(n-1,k),mod-2)%mod;

        cout<<ans<<endl;
        return 0;
}

Boss(模擬費用流)

需要把N個人派遣到K個城市,每個城市需要的人數是固定的。把不同的人派遣到不同城市,代價都是不同的,求最小代價。
考慮trival的費用流,圖總共分爲三層,S-N個人-K個城市-T。
考慮複雜度瓶頸在於SPFA的複雜度,主要是邊太多了。但是我們考慮,直接維護\(O(K^2)\)種轉移,也就是說,對於人當他第一次被加入到某個城市的時候,我們直接預處理出他\(O(K^2)\)種反邊然後加入的到一個只有\(K\)個點的圖。然後我們只需要在這個K個點的圖上跑SPFA即可。並且用數據結構(set,priority_queue)維護住。
複雜度\(O(N\times \text{ spfa}(K^2)+NK^2 \log (NK))\),題解的複雜度分析好像錯了。
然後我寫的set,卡死了,後面換成懶惰堆就能過了。記住,set的erase操作特別慢!

#include<bits/stdc++.h>
using namespace std;

int qr(){
        int ret=0,c=getchar();
        while(!isdigit(c)) c=getchar();
        while( isdigit(c)) ret=ret*10+c-48,c=getchar();
        return ret;
}
const int maxn=1e5+5;
const int inf=1e9;
typedef priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> heap ;
heap e[11][11];
int cost[maxn][11],fl[11],N,K,d[11],last[11],in[11];
int belong[maxn];
typedef long long ll;

long long que(heap&e,int i){
        while(e.size()&&belong[e.top().second]!=i)
                e.pop();
        if(e.empty()) return 1e15;
        return e.top().first;
}

queue<int>q;
long long solve(){
        for(int t=0;t<=K;++t) d[t]=inf;
        for(int t=0;t<=K;++t) last[t]=0;
        d[0]=0;
        q.push(0);
        while(q.size()){
                int t=q.front();
                q.pop(); in[t]=0;
                for(int i=1;i<=K;++i)
                        if(i^t && que(e[t][i],t)+d[t]<d[i]){
                                d[i]=que(e[t][i],t)+d[t];
                                last[i]=t;
                                if(!in[i]) q.push(i),in[i]=1;
                        }
        }
        long long ret=inf; int i=0;
        for(int t=1;t<=K;++t)
                if(fl[t]&&d[t]<ret)
                        ret=d[t],i=t;
        fl[i]--;
        while(i){
                int L=last[i];
                int id=e[L][i].top().second;
                belong[id]=i;
                for(int t=1;t<=K;++t)
                        if(t!=i)
                                e[i][t].push({cost[id][t]-cost[id][i],id});
                i=L;
        }
        return ret;
}

int main(){
        N=qr(); K=qr();
        for(int t=1;t<=K;++t)
                fl[t]=qr();
        for(int t=1;t<=N;++t)
                for(int i=1;i<=K;++i){
                        cost[t][i]=qr();
                        e[0][i].push({cost[t][i],t});
                }

        long long ans=0;
        for(int t0=N;t0;--t0)
                ans+=solve();
        cout<<ans<<endl;
        return 0;
}

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