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;
}

posted @ 2022-10-01 22:07  谁是鸽王  阅读(57)  评论(0编辑  收藏  举报