1. 程式人生 > >PKUWC2018題解

PKUWC2018題解

題面

#2537. 「PKUWC2018」Minimax

線段樹合併神仙題

離散化之後每個點弄一個線段樹,線段樹上第i個葉子節點存的就是當前節點權值是i的概率

對於樹上每一個非葉子節點要合併他的左右兒子,就是線段樹合併了

扔個核心程式碼

il vd upd(int x,int y){
    if(!x)return;
    sum[x]=1ll*sum[x]*y%mod;
    if(~lz[x])lz[x]=1ll*lz[x]*y%mod;
    else lz[x]=y;
}
il vd down(int x){if(~lz[x])upd(ls[x],lz[x]),upd(rs[x],lz[x]),lz[x]=-1;}
il int merge(int x,int y,int l,int r,const int&p,int pre_x=0,int pre_y=0,int suf_x=0,int suf_y=0){
    if(!x&&!y)return 0;
    if(!x){upd(y,(1ll*p*pre_x+1ll*(1+mod-p)*suf_x)%mod);return y;}
    if(!y){upd(x,(1ll*p*pre_y+1ll*(1+mod-p)*suf_y)%mod);return x;}
    down(x),down(y);
    int _sufx=(suf_x+sum[rs[x]])%mod,_sufy=(suf_y+sum[rs[y]])%mod,_prex=(pre_x+sum[ls[x]])%mod,_prey=(pre_y+sum[ls[y]])%mod;
    ls[x]=merge(ls[x],ls[y],l,mid,p,pre_x,pre_y,_sufx,_sufy);
    rs[x]=merge(rs[x],rs[y],mid+1,r,p,_prex,_prey,suf_x,suf_y);
    sum[x]=(sum[ls[x]]+sum[rs[x]])%mod;
    return x;
}

簡單解釋一下,pre_x就是當前ls的\([1,l-1]\)之和,pre_y是rs的;suf_x則是ls的\([r+1,n]\)之和。

合併的時候到了一個區間\([l,r]\)左兒子和右兒子只有一邊的線段樹有值(不妨假設左兒子這一段有值)的時候就可以返回了,但返回之前要先打個區間乘標記

\(f[ls][k]=p\times f[ls][k]\times\sum_{i=1}{l-1}f[rs][i]+(1-p)\times f[ls][k]\times\sum_{i=r+1}{n}f[rs][i] (k\in[l,r])\)

這個也很好理解,就是右兒子的\([l,r]\)沒有值,如果右兒子取\([1,l-1]\)

,那麼應該取得是最大值;否則取最小值。



#include<bits/stdc++.h>
#define il inline
#define vd void
#define mod 998244353
typedef long long ll;
il int gi(){
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x*f;
}
#define maxn 300010
int n,m,fir[maxn],dis[maxn],nxt[maxn],id,p[maxn],fa[maxn],isLeaf[maxn],uni_p[maxn];
il vd link(int a,int b){nxt[++id]=fir[a],fir[a]=id,dis[id]=b;}
int sum[maxn*50],ls[maxn*50],rs[maxn*50],rt[maxn],lz[maxn*50],cnt=0;
#define mid ((l+r)>>1)
il int build(int l,int r,const int&p){
    int x=++cnt;sum[x]=1;lz[x]=-1;
    if(l==r)return x;
    if(p<=mid)return ls[x]=build(l,mid,p),x;
    else return rs[x]=build(mid+1,r,p),x;
}
il vd upd(int x,int y){
    if(!x)return;
    sum[x]=1ll*sum[x]*y%mod;
    if(~lz[x])lz[x]=1ll*lz[x]*y%mod;
    else lz[x]=y;
}
il vd down(int x){if(~lz[x])upd(ls[x],lz[x]),upd(rs[x],lz[x]),lz[x]=-1;}
il int merge(int x,int y,int l,int r,const int&p,int pre_x=0,int pre_y=0,int suf_x=0,int suf_y=0){
    if(!x&&!y)return 0;
    if(!x){upd(y,(1ll*p*pre_x+1ll*(1+mod-p)*suf_x)%mod);return y;}
    if(!y){upd(x,(1ll*p*pre_y+1ll*(1+mod-p)*suf_y)%mod);return x;}
    down(x),down(y);
    int _sufx=(suf_x+sum[rs[x]])%mod,_sufy=(suf_y+sum[rs[y]])%mod,_prex=(pre_x+sum[ls[x]])%mod,_prey=(pre_y+sum[ls[y]])%mod;
    ls[x]=merge(ls[x],ls[y],l,mid,p,pre_x,pre_y,_sufx,_sufy);
    rs[x]=merge(rs[x],rs[y],mid+1,r,p,_prex,_prey,suf_x,suf_y);
    sum[x]=(sum[ls[x]]+sum[rs[x]])%mod;
    return x;
}
int ans=0;
il vd solve(int x,int l,int r){
    if(l==r){ans=(ans+1ll*l*uni_p[l]%mod*sum[x]%mod*sum[x]%mod)%mod;return;}
    down(x),solve(ls[x],l,mid),solve(rs[x],mid+1,r);
}
#undef mid
il vd dp(int x){
    if(isLeaf[x])rt[x]=build(1,m,p[x]);
    else{
        p[x]=1ll*p[x]*796898467%mod;
        for(int i=fir[x];i;i=nxt[i]){
            dp(dis[i]);
            if(rt[x])rt[x]=merge(rt[x],rt[dis[i]],1,m,p[x]);
            else rt[x]=rt[dis[i]];
        }
    }
}
int main(){
    n=gi();
    for(int i=1;i<=n;++i)fa[i]=gi(),link(fa[i],i);
    for(int i=1;i<=n;++i)p[i]=gi();
    for(int i=1;i<=n;++i)isLeaf[i]=1;
    for(int i=1;i<=n;++i)isLeaf[fa[i]]=0;
    for(int i=1;i<=n;++i)if(isLeaf[i])uni_p[++m]=p[i];
    std::sort(uni_p+1,uni_p+m+1);
    for(int i=1;i<=n;++i)if(isLeaf[i])p[i]=std::lower_bound(uni_p+1,uni_p+m+1,p[i])-uni_p;
    dp(1);
    solve(rt[1],1,m);
    printf("%d\n",ans);
    return 0;
}

#2538. 「PKUWC2018」Slay the Spire

dp神仙題

因為強化牌點數大於1所以打強化牌肯定比打攻擊牌好,所以最優方案肯定是從大到小打強化牌,如果強化牌打完了再從大到小打攻擊牌;如果強化牌打不完就打\(k-1\)張,最後打最大的攻擊牌

所以可以把兩種牌從大到小排序

\(F[i][j]\)表示抽上來i張強化牌,從大到小打出了j張的所有方案的乘積之和;攻擊牌同理,設\(G[i][j]\)表示抽上來i張攻擊牌,從大到小打出了j張的所有方案的傷害和之和。

那麼如果抽上來了x張強化牌

如果\(x<k\),答案就是\(F[x][x]\times G[m-x][k-x]\)

如果\(x\geq k\),答案就是\(F[x][k-1]\times G[m-x][1]\)

現在就是要求F,G,發現很不好求,然後dalao說,

\(f[i][j]\)表示打了\(i\)張強化牌,其中最小的是\(j\)的積之和。\(g[i][j]\)表示攻擊牌同理。

可以知道\(f[i][j]\)就是選了\(j\),再在\([1,j-1]\)中選擇\(i-1\)個,所以可以列舉第二小的,\(f[i][j]=W[j]\times\sum{k=1}{j-1}f[i-1][k]\)

g[i][j]也是一樣的,\(g[i][j]=W[j]*C_{j-1}^{i-1}+\sum{k=1}{j-1}f[i-1][k]\),乘的組合數是因為要在\([1,j-1]\)中選擇\(i-1\)張牌打出來,所以一共有\(C_{j-1}^{i-1}\)種方案

最後通過\(f\)\(F\),可以列舉打出牌的最小值來求,\(F[x][y]=\sum={i=1}{n}C[n-i][x-y]\times f[y][i]\),很好理解,就是最小值是i,因為打了y張所以在\([i+1,n]\)中選\(x-y\)個不要,在\([1,i]\)中選y個最小值為i。

g也同理,這題就沒了。

#include<bits/stdc++.h>
#define il inline
#define vd void
#define mod 998244353
typedef long long ll;
il int gi(){
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int n,m,k,f[3010][3010],g[3010][3010];
int C[3010][3010],W[3010];
il int F(int x,int y){
    if(x>n)return 0;if(x<y)return 0;if(!y)return C[n][x];
    int ret=0,lim=std::min(n,n+1-x+y);
    for(int i=y;i<=lim;++i)ret=(ret+1ll*C[n-i][x-y]*f[y][i])%mod;
    return ret;
}
il int G(int x,int y){
    if(x>n)return 0;if(x<y)return 0;
    int ret=0,lim=std::min(n,n+1-x+y);
    for(int i=y;i<=lim;++i)ret=(ret+1ll*C[n-i][x-y]*g[y][i])%mod;
    return ret;
}
int main(){
#ifndef ONLINE_JUDGE
    freopen("2538.in","r",stdin);
    freopen("2538.out","w",stdout);
#endif
    int T=gi();
    C[0][0]=1;
    for(int i=1;i<3001;++i){
        C[i][0]=1;
        for(int j=1;j<=i;++j)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    }
    while(T--){
        n=gi(),m=gi(),k=gi();
        for(int i=1;i<=n;++i)W[i]=-gi();
        std::sort(W+1,W+n+1);
        f[0][0]=1;for(int i=1;i<=n;++i)f[1][i]=W[i]=-W[i];
        for(int i=2;i<=n;++i){
            for(int j=i;j<=n;++j)f[i][j]=(f[i][j-1]+f[i-1][j-1])%mod;
            for(int j=i;j<=n;++j)f[i][j]=1ll*f[i][j]*W[j]%mod;
        }
        for(int i=1;i<=n;++i)W[i]=-gi();
        std::sort(W+1,W+n+1);
        for(int i=1;i<=n;++i)W[i]=g[1][i]=-W[i];
        for(int i=2;i<=n;++i){
            for(int j=i;j<=n;++j)g[i][j]=(g[i][j-1]+g[i-1][j-1])%mod;
            for(int j=i;j<=n;++j)g[i][j]=(g[i][j]+1ll*W[j]*C[j-1][i-1])%mod;
        }
        int ans=0;
        for(int i=0;i<m;++i)
            if(i<k)ans=(ans+1ll*F(i,i)*G(m-i,k-i))%mod;
            else ans=(ans+1ll*F(i,k-1)*G(m-i,1))%mod;
        printf("%d\n",ans);
    }
    return 0;
}

#2540. 「PKUWC2018」隨機演算法

簡單狀壓dp,,,我都不想貼程式碼了

#2542. 「PKUWC2018」隨機遊走

Min-Max容斥,就是一個集合\(S\)每個數都有被取到的時間,\(S\)中取到第一個數的時間記為\(Min(S)\),取完\(S\)的時間記為\(Max(S)\)。然後有

\(Max(S)=\sum_{T\subset S,T\neq \varnothing}(-1)^{|T|+1}Min(T)\)

我也不知道為什麼

然後只要求Min就行了

賊簡單,爺穩穩部落格



#include<bits/stdc++.h>
#define il inline
#define vd void
#define mod 998244353
typedef long long ll;
il int gi(){
    int x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int n,Q,rt,fir[20],dis[40],nxt[40],id,d[20];
il vd link(int a,int b){nxt[++id]=fir[a],fir[a]=id,dis[id]=b;}
il int inv(int x){
    int y=mod-2,ret=1;
    while(y){
        if(y&1)ret=1ll*ret*x%mod;
        x=1ll*x*x%mod;y>>=1;
    }return ret;
}
int A[19],B[19],state,S[1<<18],cnt[1<<18];
il vd inc(int&a,int b){a+=b;if(a>=mod)a-=mod;}
il vd dfs(int x,int fa=-1){
    if((state>>x-1)&1){A[x]=B[x]=0;return;}
    if(d[x]==1&&x!=rt){A[x]=B[x]=1;return;}
    int _A=mod-d[x],_B=d[x];
    for(int i=fir[x];i;i=nxt[i]){
        if(dis[i]==fa)continue;
        dfs(dis[i],x);
        inc(_A,A[dis[i]]),inc(_B,B[dis[i]]);
    }
    A[x]=_A=inv(mod-_A),B[x]=1ll*_B*_A%mod;
}
int main(){
    n=gi(),Q=gi(),rt=gi();
    int a,b;
    for(int i=1;i<n;++i)a=gi(),b=gi(),link(a,b),link(b,a),++d[a],++d[b];
    for(int i=1;i<1<<n;++i){
        state=i;dfs(rt);
        cnt[i]=cnt[i^(i&-i)]+1;
        S[i]=B[rt];if(!(cnt[i]&1))S[i]=(mod-S[i])%mod;
    }
    for(int i=1;i<1<<n;i<<=1)
        for(int j=1;j<1<<n;++j)
            if(i&j)inc(S[j],S[j^i]);
    while(Q--){
        int s=0,t=gi();
        while(t--)s|=1<<gi()-1;
        printf("%d\n",S[s]);
    }
    return 0;
}