【题解】CodeM美团点评编程大赛复赛 做题感悟&题解
【T1】
【简要题意】
长度为N的括号序列,随机确定括号的方向;对于一个已确定的序列,每次消除相邻的左右括号(右左不行),消除后可以进一步合并和消除直到不能消为止。求剩下的括号的期望。\(N \leq 2000\),保留三位小数。
【做题过程】
看上去一道傻逼题,又让我涨姿势了。
直接按长度DP咯?仔细一想,要记录一直延续至今的待消左括号个数,所以起码是二维。所以就是 \(F_{i,j}\) 表示处理了前i个点,目前有j个待消的左括号。写了一半我才发现一个问题, $F_{i,j} $ 本身意义不明:到底是到这一个状态的概率(<1),还是到这个状态的期望消除数量?讲道理,单记一个肯定没法转移,而“经验”告诉我以前好像没有一起转移的。所以就GG咯?
然后强行加一维变成 \(F_{i,j,k}\) ,k表示目前消除的对数,这样就直接按概率转移即可。现在是\(O(N^3)\)“经验”又告诉我,这种肯定是瞎逼卡精度题。j维可以小一些(很长待命左括号不现实),k维也可以小一些_但是搞着搞着,发现本身F的精度就不够。跑 \(N=500\) 的时候F每一个值都是0了。
那还是找规律吧。打表发现,答案可以表示成 \(\frac {P_i}{Q_i}\)的形式,其中 \(P_i\) 是这个数列 \(Q_i\)是\(2^ {i-1}\)。根据OIES的 \(P_i\)的5阶线性递推(我把\(Q_i\)也整理到递推里)写了三行代码。。
woc。。为啥 \(N=2000\) 又爆精度了?仔细一看,N稍微大一些,每一个答案 \(A_i\) 就会有误差,然后误差越来越大……理论上可以直接对 \(P_i\) 进行高精运算(因为是整数没有精度丢失),然后高精除2的幂次算答案,懒得找板子了。(所以理论上此题可以加强,换成求精确值233)
【题解】
直接按我最初的想法做即可,正常转移概率和期望数组。
【UPD】
一位大佬有一个更简洁霸气的 \(O(N)\) 做法。我们考虑之前的\(2*P_i\) 构成的数列,它是A1=2,6,16,38,88,196,432......。
考虑后面一位减去前面一位的两倍,得到序列A2=2,4,6,12,20,40......
注意到这个序列实际上是和A3=A000984密切相关的,具体地,A2=A3[1],A3[1]2,A3[2],A3[2]2,A3[3],A3[3]*2......。
把组合数和要除的2的幂次整理在递推式里,就能\(O(N)\)递推了,实测无精度问题>_<。
【T2】
【简要题意】
给定一棵带点权的树,Q个询问。每次给出向根路径上的两个点\(x,y\)和一个数值\(v\),考虑从 \(x\) 向 \(y\) 爬,每到一个点时若其点权大于 \(v\) 就更新 \(v\) 。每次询问更新的次数。\(N,Q\leq100000\),可以离线。
【做题过程】
没仔细想离线,就强上了。若询问不同的 \(x\) 只有一个,可以维护从根到 \(x\) 一个单调递减的序列,每次二分 \(y\) 的位置,看看序列里的点个数即可。 \(x\) 不同的话,因为树上不能均摊,所以可以对每一个点维护一个可持久化的数组。每次继承父亲的数组,然后二分找到替换的位置并将后面的数字删除。具体实现的时候,可以用可持久化线段树来维护这个操作。不得不说,细节有点萎,而且卡内存常数。
【题解】
又是一个精妙的“傻逼”题!首先为了方便可以在询问的x点下面挂一个点,权值就是 \(v\) ,这样全在树上的节点上进行操作。离线一下,可以构出一棵类似于虚树的树,只是每一个点的父亲为原树上第一个权值比它大的祖先。然后倍增跳一跳即可,NOIP即视感。(所以又可以加强233)
【T3】
【简要题意】
给出一个N个点的基环树,在给定一个质数模域。每一个点有一个待确定的值,对于每一条边\((x,y)\),给出一个限制\(A_i*x+B_i*y=C_i( \mod P)\) 求每一个点的值,保证解唯一。
【做题过程&题解】
傻逼题。找到环然后走一圈解一个方程,再往树枝外面拓展即可。
【T4】
【简要题意】
二维坐标里有N个点 \(A_i\) (保证所有的横坐标集合和纵坐标集合都是一个1~N的排列)。随机生成一个排列Pi,并执行以下过程:
now=A[p[1]];
for (i=2;i<=n;i++)
if (A[p[i]].x>=now.x&&A[p[i]].y>=now.y) now=A[p[i]];
return now;
分别对于每一个点,求出最终now是它的排列的个数。\(N\leq100000\)
【题解】
原题题解说的有点迷。
考虑一个特定序列\(B_i\)(记录了某排列让now发生变化的所有点的序号),现在计算这个\(B_i\)对应了多少的排列:首先,显然\(B_1\)必须放在排列的第一位,现在总排列数有\((n-1)!\);然后对于一对\(B_i\)和\(B_{i+1}\),它们在排列的位置之间不能插入任何横纵坐标都大于\(B_i\)的点;再转化一下,所有横纵坐标都大于\(B_i\)的点必须出现在\(B_{i+1}\)之后。
具体地说,假设比\(B_1\)横纵坐标都大的点有\(T_1\)个,那么这时的限制就是对于一个确定的\(T_1\)个点的集合,某一个点(即\(B_2\))必须出现在集合的最前面。因为在所有排列中,\(T_1\)个点谁在最前面几率均等,所以$\frac {(n-1)!}{T_1} $ 就是目前符合条件数。对于所有的 \(B_i\) 和 \(B_{i+1}\) 我们都要满足这样的限制。而且很关键的一点:每次我们强制把\(B_{i+1}\)放在比\(B_i\)大的点的最前面后,剩下的点在什么位置还是都有可能的,不会对进一步的计算造成一定的限制。所以,对于这个序列\(B_i\),满足条件的P的数量就是 \(\frac {(n-1)!}{\prod_{i=1}^{|B|-1} T_i}\)
现在就是怎么求答案了。对于一个点i,若它是now,那么它就是B序列中的最后一个点。我们枚举它之前的那个B是什么(即枚举横纵坐标都小于它的点)并对所有方案数求和。因此我们知道,对于每一个点我们要维护一个\(F_i\),它是由比它小的点求和转移过来的,而且最后它还要除掉比它大的点的个数。对于结束点i,不必再除了,最后再乘上\((n-1)!\)即可。以上过程可简单地用数据结构维护。