#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;
}
还有两个题
咕咕咕