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;
}
博客保留所有权利,谢绝学步园、码迷等不在文首明显处显著标明转载来源的任何个人或组织进行转载!其他文明转载授权且欢迎!