【题解】PKUWC2018简要题解

【题解】PKUWC2018简要题解

Minimax

定义结点x的权值为:

1.若x没有子结点,那么它的权值会在输入里给出,保证这类点中每个结点的权值互不相同。

2.若x有子结点,那么它的权值有p的概率是它的子结点的权值的最大值,有1-p的概率是它的子结点的权值的最小值。

1号点权值第i小的可能性的权值是\(V_i\),它的概率为 ,求:

\[\sum i V_iD_i^2 \]

\(n\le 3e5\)

先离散化一下所有叶子节点的值,然后考虑一个DP

\(dp(i,j)\)表示在\(i\)点权值是第\(j\)小的概率,只有一个儿子的时候直接转移过来,转移很显然就不写了

然后考虑线段树合并维护这个过程,因为转移时是乘上一段后缀/前缀,同时线段树合并可以获得好的效果。

考虑一下时间复杂度,就是线段树合并的复杂度\(O(n\log n)\)

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define mid ((l+r)>>1)
#define pp(pos) {seg[pos].val=MOD(seg[seg[pos].ls].val+seg[seg[pos].rs].val);}

using namespace std;  typedef long long ll;   char __buf[1<<22],*__c=__buf;
inline int qr(){
	int ret=0,f=0,c=*__c++;
	while(!isdigit(c))f|=c==45,c=*__c++;
	while(isdigit(c)) ret=ret*10+c-48,c=*__c++;
	return f?-ret:ret;
}

const int mod=998244353;
const int maxn=3e5+5;
inline int ksm(const int&ba,const int&p){
	int ret=1;
	for(int t=p,b=ba%mod;t;t>>=1,b=1ll*b*b%mod)
		if(t&1) ret=1ll*ret*b%mod;
	return ret;
}
const int baseinv=ksm(1e4,mod-2);

int e[maxn][2];
struct E{
	int ls,rs,val,tag;
	E(const int&a=0,const int&b=0,const int&c=0,const int&d=1):ls(a),rs(b),val(c),tag(d){}
	inline E&operator *=(ll a){return *this=E(ls,rs,val*a%mod,tag*a%mod);}
}*seg;

inline void pd(const int&pos){
	if(seg[pos].tag==1) return;
	seg[seg[pos].ls]*=seg[pos].tag;
	seg[seg[pos].rs]*=seg[pos].tag;
	seg[pos].tag=1;
}
inline int MOD(const int&x){return x>mod?x-mod:x;}
int rt[maxn],n,w[maxn],sav[maxn],ans,cnt,len;
struct vec{
	int data[2];
	inline int&operator[](int x){return data[x];}
};

//p0=ch max p1=ch min
int Merge(vec t,vec pre,vec suf,vec p){
	if(!t[0]||!t[1]){
		register int k=t[1]>0;
		seg[t[k]]*=1ll*p[0]*pre[k^1]%mod+1ll*p[1]*suf[k^1]%mod;
		return t[k];
	}
	pd(t[0]); pd(t[1]);
	vec P=pre,S=suf;
	for(int i=0;i<=1;++i)
		pre[i]=MOD(pre[i]+seg[seg[t[i]].ls].val),suf[i]=MOD(suf[i]+seg[seg[t[i]].rs].val);
	seg[t[0]].ls=Merge({seg[t[0]].ls,seg[t[1]].ls},P,suf,p);
	seg[t[0]].rs=Merge({seg[t[0]].rs,seg[t[1]].rs},pre,S,p);
	pp(t[0]);
	return t[0];
}

void build(int p,int l,int r,int&pos){
	if(p<l||p>r) return;
	if(!pos) seg[pos=++cnt]=E();
	if(l==r){seg[pos].val=1;return;}
	if(p<=mid) build(p,l,mid,seg[pos].ls);
	else build(p,mid+1,r,seg[pos].rs);
	pp(pos);
}

void dfs(const int&now){
	if(!e[now][0]) return build(w[now],1,len,rt[now]);
	if(!e[now][1]) return dfs(e[now][0]),rt[now]=rt[e[now][0]],void();
	dfs(e[now][0]); dfs(e[now][1]);
	rt[now]=Merge({rt[e[now][0]],rt[e[now][1]]},{0,0},{0,0},{w[now],mod-w[now]+1});
}

void que(int l,int r,int pos){
	if(l==r){ans=(ans+1ll*seg[pos].val*seg[pos].val%mod*l%mod*sav[l]%mod)%mod;return;}
	pd(pos);
	que(l,mid,seg[pos].ls),que(mid+1,r,seg[pos].rs);
}

int main(){
	fread(__c=__buf,1,1<<22,stdin);
	n=qr(); qr();
	seg=(E*)malloc(sizeof(E)*n*18.2);
	for(int t=2,a;t<=n;++t)
		a=qr(),(e[a][0]?e[a][1]:e[a][0])=t;
	for(int t=1;t<=n;++t){
		w[t]=qr();
		if(e[t][0]) w[t]=1ll*w[t]*baseinv%mod;
		else sav[++len]=w[t];
	}
	sort(sav+1,sav+len+1);
	for(int t=1;t<=n;++t)
		if(!e[t][0])
			w[t]=lower_bound(sav+1,sav+len+1,w[t])-sav;
	dfs(1);
	que(1,len,1);
	printf("%d\n",ans);
	return 0;
}


Slay the Spire

你有\(n\)个攻击牌\(n\)个强化牌,都有权值,一种攻击牌的效果是进行面值攻击,一张强化牌的效果是将其他所有攻击牌的面值乘上他的面值\(>1\)。问你随意选出\(m\)张牌,可以打出\(k\)张牌,问你所有选出\(m\)张牌的方案的最大伤害的和是多少。

由于强化牌\(>1\)所以能打强化牌就打强化牌,最后剩下一个打攻击牌。很显然打攻击牌是打那些选出\(m\)个最大的那几个,那么将攻击牌序列排序,可能打的攻击牌是一个子序列。那么设\(dp(i,j)\)表示钦定\(i\)选中,选了\(j\)个的权值和,转移显然,要记前缀和。

那么如何处理强化牌呢?可以考虑到,强化牌最多可以打\(k-1\)张,再多是没有意义的,同样排序后,设\(f(i,j)\)表示选了钦定选择了\(i\)号牌,总共选择了\(j\)个牌的强化权值,(要记前缀和)。为了满足上面这个\(\le k-1\)的限制,我们钦定当强化牌数量\(> k-1\)的时候,接下来加入强化牌的权值都为1。这样,多选的强化牌就不会造成伤害的贡献,但是仍然可以贡献方案。

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<assert.h>

using namespace std;  typedef long long ll;
int qr(){
	int ret=0,f=0,c=getchar();
	while(!isdigit(c))f|=c==45,c=getchar();
	while(isdigit(c)) ret=ret*10+c-48,c=getchar();
	return f?-ret:ret;
}

const int mod=998244353;
const int maxn=3e3+15;
int st[maxn],w[maxn];
int dp[maxn][maxn];
int f[maxn][maxn];
int c[maxn][maxn];
int n,m,k,ans;

int MOD(const int&x){return x>=mod?x-mod:x;}
int MOD(const int&x,const int&y){return 1ll*x*y%mod;}

int main(){
	n=3e3;
	c[0][0]=1;
	for(int t=1;t<=n;++t){
		c[t][0]=1;
		for(int i=1;i<=t;++i){
			c[t][i]=MOD(c[t-1][i]+c[t-1][i-1]);
		}
	}
	int T=qr();
	while(T--){
		n=qr(); m=qr(); k=qr();
		for(int t=1;t<=n;++t) st[t]=qr();
		for(int t=1;t<=n;++t) w[t]=qr();
		sort(st+1,st+n+1,greater<int>());
		sort(w+1,w+n+1,greater<int>());
		ans=0;
		f[0][0]=1;
		for(int t=1;t<=n;++t){
			dp[t][0]=dp[t-1][0];
			f[t][0]=f[t-1][0];
			for(int i=t;i;--i)
				dp[t][i]=MOD(dp[t-1][i]+MOD(dp[t-1][i-1]+MOD(c[t-1][i-1],w[t])));
			for(int i=1;i<=t;++i)
				f[t][i]=MOD(f[t-1][i]+MOD(f[t-1][i-1],i<k?st[t]:1));
		}
		for(int t=min(n,m);~t;--t){
			int s=0,Cash=m-t,Pay=max(k-t,1);
			for(int i=t;i<=n;++i) s=MOD(s+f[i][t]-(i?f[i-1][t]:0)+mod);
			if(Cash>n||!s) continue;
			for(int i=1;i<=n;++i)
				if(n-i>=Cash-Pay)
					ans=MOD(ans+MOD(s,MOD(c[n-i][Cash-Pay],dp[i][Pay]-dp[i-1][Pay]+mod)));
		}
		printf("%d\n",ans);
	}
	return 0;
}


斗地主

随机算法

给出一个最大独立集的算法,问你这个东西的正确率:

  • 随机一个排列
  • 按照这个排列依次试图将点加入独立集

\(n\le 20\)

\(dp(S)\)表示\(S\)集合是排列中前\(|S|\)个元素时,按上述算法在所有可能的顺序下能够算出独立集的大小\(=Max(S)\)的概率,Max(S)字面意思,所有可能中最大的独立集大小。那么考虑,枚举排列第一个元素是谁设为u,然后访问去除所有和\(u\)相连的点 的状态\(S'\),那么贡献就是\(dp(S')\over popcnt(S)\) 。注意只能从所有\(Max(S')+1=Max(S)\)的状态转移来。

钦定统计第一个!!!!太有技巧性了

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lb(t) ((t)&-(t))
using namespace std;  typedef long long ll;
inline int qr(){
	int ret=0,f=0,c=getchar();
	while(!isdigit(c))f|=c==45,c=getchar();
	while(isdigit(c)) ret=ret*10+c-48,c=getchar();
	return f?-ret:ret;
}
const int maxn=20;
const int mod=998244353;
int e[maxn],n,m,u;
int dp[1<<maxn],Max[1<<maxn];
int id[1<<maxn],num[1<<maxn],invc[21],ans;

inline int ksm(const int&ba,const int&p){
	int ret=1;
	for(int t=p,b=ba;t;t>>=1,b=1ll*b*b%mod)
		if(t&1) ret=1ll*ret*b%mod;
	return ret;
}
inline int MOD(const int&x){return x>=mod?x-mod:x;}

int main(){
	
	n=qr(); m=qr(); u=(1<<n)-1;
	for(int t=1;t<=n;++t) invc[t]=ksm(t,mod-2);
	for(int t=1,a,b;t<=m;++t)
		a=qr()-1,b=qr()-1,e[a]|=1<<b,e[b]|=1<<a;
	for(int t=0;t<n;++t) id[1<<t]=t;
	for(int t=1;t<=u;++t) num[t]=num[t^lb(t)]+1;
	dp[0]=1;Max[0]=0;
	for(int t=1;t<=u;++t){
		int temp=t;
		while(temp){
			int f=lb(temp),g=id[f],k=e[g]^u,s=(t&k)^f;
			temp^=f;
			if(Max[s]+1>Max[t]) dp[t]=dp[s],Max[t]=Max[s]+1;
			else if(Max[s]+1==Max[t]) dp[t]=MOD(dp[t]+dp[s]);
		}
		dp[t]=1ll*dp[t]*invc[num[t]]%mod;
	}
	printf("%d\n",dp[u]);
	return 0;
}

猎人杀

\(dp(S)\)表示钦定\(S\)中的人在\(1\)号猎人之后死亡,其他人生死我们不管。可以知道在任何时刻,死掉\(S\)中的任何一个人的概率始终是死掉\(1\)号猎人的\(w_{ x\in S}\over w_1\)倍。注意\(1\not \in S\) 那么

\[dp(S)={w_1\over w_{ x\in S}+w_1} \]

那么考虑题目给我们的条件,是要求\(1\)是最后一个死亡的概率,也就是不存在有人在他后面死亡。由于\(dp(S)\)是钦定,考虑容斥,答案就是

\[\sum_{S\subseteq U-1} (-1)^{|S|+1} dp(S) \]

然后用背包优化容斥即可

我们对着\(w_{x\in S}\)带容斥系数进行背包,设\(dp(i)\)表示\(w_{x \in S}=i\)的方案数,根据\(|S|\)的不同带符号,那么他的生成函数是

\[ [x^i]\prod (1-x^{w_j}) \]

由于多项式乘法满足结合律也满足交换律,所以直接二进制合并即可,复杂度\(O(n \log^2 n)\)

猫猫说这做到的是最优复杂度。

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<assert.h>
#include<cmath>

using namespace std;  typedef long long ll;
inline int qr(){
	int ret=0,f=0,c=getchar();
	while(!isdigit(c))f|=c==45,c=getchar();
	while(isdigit(c)) ret=ret*10+c-48,c=getchar();
	return f?-ret:ret;
}
const int maxn=1e5+5;
const int mod=998244353;
int w[maxn],inv[maxn],n,s,ans;
inline int MOD(const int&x){return x>=mod?x-mod:x;}
inline int MOD(const int&x,const int&y){return 1ll*x*y%mod;}
inline int ksm(const int&ba,const int&p){
	int ret=1;
	for(int t=p,b=ba%mod;t;t>>=1,b=1ll*b*b%mod)
		if(t&1) ret=1ll*ret*b%mod;
	return ret;
}

namespace poly{
	typedef vector<int> poly;
	const int maxn=1<<18;
	const int g=3;
	const int gi=ksm(g,mod-2);
	int r[maxn];
	inline void getr(const int&len){
		static int L=0;
		if(L==len) return;
		L=len;
		for(int t=0;t<len;++t)
			r[t]=r[t>>1]>>1|(t&1?L>>1:0);
	}
	inline void NTT(poly&a,const int&len,const int&tag){
		getr(len);
		for(int t=0;t<len;++t) 
			if(t<r[t]) swap(a[t],a[r[t]]);
		int s=tag==1?g:gi;
		for(int t=1,wn;t<len;t<<=1){
			wn=ksm(s,(mod-1)/(t<<1));
			for(int i=0;i<len;i+=t<<1)
				for(int j=0,w=1,k;j<t;++j,w=1ll*wn*w%mod)
					k=1ll*a[t+i+j]*w%mod,a[t+i+j]=MOD(a[i+j]-k+mod),a[i+j]=MOD(a[i+j]+k);
		}
		if(tag!=1)
			for(int t=0,i=ksm(len,mod-2);t<len;++t)
				a[t]=1ll*a[t]*i%mod;
	}
	inline int foo(const int&len){
		int ret=1;
		while(ret<len) ret<<=1;
		return ret;
	}
	queue<int> q;
	inline poly DIVD_NTT(vector<poly>a){
		for(auto&t:a) t.resize(foo(t.size()));
		for(int i=0,ed=a.size();i<ed;++i) q.push(i);
		while(q.size()>1){
			int t1=q.front();
			q.pop();
			int t2=q.front();
			q.pop();
			int g=a[t1].size()+a[t2].size(),k=foo(a[t1].size()+a[t2].size()-1);
			a[t1].resize(k); a[t2].resize(k);
			NTT(a[t1],k,1); NTT(a[t2],k,1);
			for(int t=0;t<k;++t) a[t1][t]=1ll*a[t1][t]*a[t2][t]%mod;
			NTT(a[t1],k,0);
			a[t1].resize(g);
			q.push(t1);
		}
		return a[q.front()];
	}
}

vector<int> dp;
vector<poly::poly> a;
int main(){
	n=qr();
	for(int t=1;t<=n;++t) w[t]=qr(),s+=w[t];
	sort(w+2,w+n+1);
	for(int t=2;t<=n;++t)
		a.push_back(vector<int>(w[t]+1)),a.back()[0]=1,a.back()[w[t]]=mod-1;
	dp=poly::DIVD_NTT(a);
	inv[1]=1;
	for(int t=2;t<=s;++t) inv[t]=1ll*(mod-mod/t)*inv[mod%t]%mod;
	for(int t=0,ed=dp.size();t<ed;++t) ans=MOD(ans+1ll*dp[t]*w[1]%mod*inv[w[1]+t]%mod);
	printf("%d\n",ans);
	return 0;
}


随机游走

不会

posted @ 2019-12-26 15:33  谁是鸽王  阅读(181)  评论(0编辑  收藏  举报