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

还有两个题

咕咕咕

posted @ 2018-11-19 09:32  菜狗xzz  阅读(314)  评论(6编辑  收藏  举报