• 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 imes f[ls][k] imessum_{i=1}{l-1}f[rs][i]+(1-p) imes f[ls][k] imessum_{i=r+1}{n}f[rs][i] (kin[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
    ",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] imes G[m-x][k-x]$

    如果$xgeq k$,答案就是$F[x][k-1] imes 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] imessum{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_^$种方案

    最后通过$f$求$F$,可以枚举打出牌的最小值来求,(F[x][y]=sum={i=1}{n}C[n-i][x-y] imes 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
    ",ans);
    	}
    	return 0;
    }
    

    #2540. 「PKUWC2018」随机算法

    简单状压dp,,,我都不想贴代码了

    #2542. 「PKUWC2018」随机游走

    Min-Max容斥,就是一个集合$S$每个数都有被取到的时间,$S$中取到第一个数的时间记为$Min(S)$,取完$S$的时间记为$Max(S)$。然后有

    (Max(S)=sum_{Tsubset S,T eq 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
    ",S[s]);
    	}
    	return 0;
    }
    

    还有两个题

    咕咕咕

  • 相关阅读:
    LiveNVS实现摄像头RTSP无插件播放,并集中化管理
    liveplayer免费网页直播_点播播放器-页面动态多播放器添加代码示例
    JavaScript之图片滚动
    JavaScript之图片轮换
    DOS命令
    jquery之音乐均衡器
    JavaScript之可运行按钮
    jQuery之点击弹出图标环形菜单
    Android之View方法
    Android之所有权限
  • 原文地址:https://www.cnblogs.com/xzz_233/p/9981439.html
Copyright © 2020-2023  润新知