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