zoukankan      html  css  js  c++  java
  • 【题解】PKUWC2018简要题解

    【题解】PKUWC2018简要题解

    Minimax

    定义结点x的权值为:

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

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

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

    [sum i V_iD_i^2 ]

    (nle 3e5)

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

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

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

    考虑一下时间复杂度,就是线段树合并的复杂度(O(nlog 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
    ",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
    ",ans);
    	}
    	return 0;
    }
    
    
    

    斗地主

    随机算法

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

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

    (nle 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
    ",dp[u]);
    	return 0;
    }
    
    

    猎人杀

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

    [dp(S)={w_1over w_{ xin S}+w_1} ]

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

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

    然后用背包优化容斥即可

    我们对着(w_{xin 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
    ",ans);
    	return 0;
    }
    
    
    

    随机游走

    不会

  • 相关阅读:
    联机交易场景持续拓展,巨杉数据库中标吉林省农信
    巨杉数据库与深度操作系统合作共建基础软件技术生态
    SequoiaDB报告创建线程失败的解决办法
    巨杉数据库中标张家口银行、保定银行,华北地区布局再升级
    扩展国产数据库生态,巨杉技术社区与恩墨学院建立全面合作
    巨杉Tech | SequoiaDB虚机镜像正式上线
    跨越数据库发展鸿沟,谈分布式数据库技术趋势
    喜讯 | 国际智慧城市大会巨杉喜获两项大奖
    巨杉数据库再夺“广州独角兽”称号
    巨杉Tech | 使用 SequoiaDB 分布式数据库搭建JIRA流程管理系统
  • 原文地址:https://www.cnblogs.com/winlere/p/12102337.html
Copyright © 2011-2022 走看看