T1 xj中学的诈骗犯
题面
n个数,两个人轮流选操作。每一次操作选择一个最大的数分裂。分裂一个数x就是选择一个小于x的非负整数y。若y=0则这个数消失,否则,对于y的每个因子,有z/y的概率生成一个数z。若某回合开始时没有数了,则这回合操作的人失败。问先手获胜的概率。(ai<=100,n<=100,精确到1e-9,SPJ).
思路
这是一道 诈骗题 啊啊啊啊啊啊啊啊
考虑最大值个数的奇偶性。最终状态:最大值个数(1个)是奇数。
若最大值个数是偶数,操作后一定使最大值个数为奇数。
若最大值个数为奇数,考虑两种情况:
-
个数>1,使y等于任意一非负数,最大值的个数变为偶数。
-
个数=1,若次大值有偶数个,则y<次大值;否则y=次大值。所以当最大值个数是奇数时,一定能使最大值个数变成偶数。
所以,最大值个数是奇数为必胜,是偶数时必败。
T4 又是树树树树树树树树树树树树树树树树树树
题面
有一个有n个点的树,以1为根。第i条树边的长度为Li。在一号点有m把钥匙,编号一次为1~m.每个点有一个价值为vi的宝藏,需要若干把钥匙,编号依次为b1,b2……。lq从根节点出发,走树上的路径。不能走回头路。他会选择性地打开某些宝藏,除第一个打开的宝藏外,打开宝藏的钥匙一定全部在之前打开宝藏时用过,且连续两次打开的宝藏距离不能超过K。每一个方案有一个价值,定义是所有打开的宝藏的价值和。求总方案的价值和。
思路
-
两个数组f记录选当前点的方案数,g记录选当前点的总价值。把mp(dis[x],x)放进堆(每个节点都有一个堆)中。深搜,回溯时计算。用sumf,数sumg组记录每一个状态的f,g的总和。当搜完整棵子树后,把子树的堆和当前堆合并,取出堆里距离小于limit的点,更新sumf和sumg。枚举所有是当前点子集的状态,用sumf,sumg把f,g更新。时间复杂度O(n2^m) 这个算法用f,g更新sumf,sumg是O(1),用sumf,sumg更新f,g是O(2^m) 的,T瓜。
-
把状态分成两半。sumf,sumg都是二维数组,第一位记录前一半的子集,第二位记录后一半的超集。这样两次更新都是O(2^{m/2})的,效率高得飞起。
注意
如何求子树里的sumf和sumg?提前把f,g减掉。
先上码(标程)
template<typename Ta,typename Tb>inline void inc(Ta &x,Tb y){
x=(x+y>=Mod) ? x+y-Mod:x+y;
}//加
template<typename Ta,typename Tb>inline void dec(Ta &x,Tb y){
x=(x-y>=0) ? x-y:x-y+Mod;
}//减
inline void update(int x){
int S=(key[x]>>9),T=(key[x]&511);
for(int i=S;;i=(i+1)|S){// 前半段枚举超集
inc(SF[i][T],f[x]);//更新SF
inc(SG[i][T],g[x]);
if(i==511) break;
}
}
inline void dfs(int x,int fa){
int S=(key[x]>>9),T=(key[x]511);//前后半段分开
for(int i=T;;i=(i-1)&T){//后半段枚举子集
dec(f[x],SF[S][i]);//把f[x]减去SF[S][i],因为后面要把SF加回来
dec(g[x],SG[S][i]);//同理
if(i==0) break;
}
q[x].push(mp(dis[x],x));
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(y==fa) continue;
dis[y]=dis[x]+len[i];
dfs(y,x);
q[x].join(q[y]);//合并两个堆
}
while(!q[x].empty() && q[x].top().first-dis[x]>=limit)
update(q[x].top().second),q[x].pop();
for(int i=T;;i=(i-1)&T){
inc(f[x],SF[S][i]);
inc(g[x],SG[S][i]);
if(i==0) break;
}
inc(f[x],1);//它本身
inc(g[x],(ll)f[x]*val[x]%Mod);//它的方案数*它的价值
inc(ans,g[x]);
}