【题解】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;
}

随机游走

不会

【题解】PKUWC2018简要题解的更多相关文章

  1. Noip 2014酱油记+简要题解

    好吧,day2T1把d默认为1也是醉了,现在只能期待数据弱然后怒卡一等线吧QAQ Day0 第一次下午出发啊真是不错,才2小时左右就到了233,在车上把sao和fate补掉就到了= = 然后到宾馆之后 ...

  2. Tsinghua 2018 DSA PA2简要题解

    反正没时间写,先把简要题解(嘴巴A题)都给他写了记录一下. upd:任务倒是完成了,我也自闭了. CST2018 2-1 Meteorites: 乘法版的石子合并,堆 + 高精度. 写起来有点烦貌似. ...

  3. Codeforces 863 简要题解

    文章目录 A题 B题 C题 D题 E题 F题 G题 传送门 简要题解?因为最后一题太毒不想写了所以其实是部分题解... A题 传送门 题意简述:给你一个数,问你能不能通过加前导000使其成为一个回文数 ...

  4. HNOI2018简要题解

    HNOI2018简要题解 D1T1 寻宝游戏 题意 某大学每年都会有一次 Mystery Hunt 的活动,玩家需要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍可以获得这一年出题的机会. 作为 ...

  5. JXOI2018简要题解

    JXOI2018简要题解 T1 排序问题 题意 九条可怜是一个热爱思考的女孩子. 九条可怜最近正在研究各种排序的性质,她发现了一种很有趣的排序方法: Gobo sort ! Gobo sort 的算法 ...

  6. BJOI2018简要题解

    BJOI2018简要题解 D1T1 二进制 题意 pupil 发现对于一个十进制数,无论怎么将其的数字重新排列,均不影响其是不是 \(3\) 的倍数.他想研究对于二进制,是否也有类似的性质. 于是他生 ...

  7. CQOI2018简要题解

    CQOI2018简要题解 D1T1 破解 D-H 协议 题意 Diffie-Hellman 密钥交换协议是一种简单有效的密钥交换方法.它可以让通讯双方在没有事先约定密钥(密码)的情况下,通过不安全的信 ...

  8. AtCoder ExaWizards 2019 简要题解

    AtCoder ExaWizards 2019 简要题解 Tags:题解 link:https://atcoder.jp/contests/exawizards2019 很水的一场ARC啊,随随便便就 ...

  9. Comet OJ - Contest #2 简要题解

    Comet OJ - Contest #2 简要题解 cometoj A 模拟,复杂度是对数级的. code B 易知\(p\in[l,r]\),且最终的利润关于\(p\)的表达式为\(\frac{( ...

随机推荐

  1. POLARDB 2.0 重磅升级,分别支持Oracle与PostgreSQL

    点击订阅新品发布会! 新产品.新版本.新技术.新功能.价格调整,评论在下方,下期更新!关注更多内容,了解更多 最新发布 POLARDB 2.0 重磅升级 2019年6月19日15时,阿里云 POLAR ...

  2. Javascript用正则表达式replace替换父串中所有符合条件的子串

    这样用,只会替换匹配到的第一个子串 str = 'I hava a pen ,I hava an apple,apple pen, pen apple' str = str.replace('appl ...

  3. 9-1进程,进程池和socketserver

    一 进程: # 什么是进程 : 运行中的程序,计算机中最小的资源分配单位# 程序开始执行就会产生一个主进程# python中主进程里面启动一个进程 —— 子进程# 同时主进程也被称为父进程# 父子进程 ...

  4. 17-1 djanjo进阶-路由,视图,模板

    一 路由系统进阶(urls.py) 动态路由 urls.py中通过正则表达式的分组匹配,捕获用户访问的url中的值,传递给视图函数1 分组匹配(通过圆括号): 相当于给视图函数传递 位置参数 例子: ...

  5. canvas+js实现验证码功能

    转载自:https://blog.csdn.net/qq_42463851/article/details/90755734<!DOCTYPE html> <html> < ...

  6. PL/SQL语言的学习笔记

    一.PL/SQL简介1.什么是PL/SQL程序?(PL/SQL是对SQL语言的一个扩展,从而形成的一个语言) 2.PL/SQL语言的特点(操作Orcale数据库效率最高的就是PL/SQL语言,而不是C ...

  7. Activiti6-快速开始

    下载 https://www.activiti.org/download-links 快速开始 https://www.activiti.org/quick-start 用户指南 https://ww ...

  8. HOSt ip is not allowed to connect to this MySql server, MYSQL添加远程用户或允许远程访问三种方法

    HOSt ip is not allowed to connect to this MySql server 报错:1130-host ... is not allowed to connect to ...

  9. TCP和UDP的联系和用途

    一.区别        二者都是有用的和常用的,如果纯粹从概念上区分二者就比较费解了,我们直接从功能上进行区分,简单明了:        这两种传输协议也就是合于适配不同的业务和不同的硬件终端.    ...

  10. The Preliminary Contest for ICPC Asia Nanjing 2019ICPC南京网络赛

    B.super_log (欧拉降幂) •题意 定一个一个运算log*,迭代表达式为 给定一个a,b计算直到迭代结果>=b时,最小的x,输出对m取余后的值 •思路 $log*_{a}(1)=1+l ...