CSP-S2019题解

格雷码

€€£:我不抄自己辣!JOJO!

这题比那个SCOI的炒鸡格雷码好多了,甚至告诉你构造方法,所以...

void wk(uLL kk)
{
	int j=0;
	for(uLL i=n-1;~i;--i)
	{
		if(kk>>i&1) printf("%d",j^1),j^=j==0;
		else printf("%d",j),j^=j==1;
	}
}

括号树

考虑朴素做法,即dfs整棵树,然后记录到根的括号序列,每个点的贡献为祖先贡献+以自己这个位置为右端点的合法括号序列数量,可以\(O(n^2)\)

考虑优化,我们要统计的括号序列是一段后缀,所以把)看做\(1\),(看做\(-1\),那么一个后缀为合法括号序列当且仅当这一段的权值和为0,且这个后缀没有包含一段和\(<0\)的后缀(也就是有一些(一定匹配不上)),那么线段树每个位置维护对应后缀的和,以及再维护区间权值0个数(就是维护区间最小值以及其个数),每次进入一个点先对所有后缀\(+1/-1\),然后线段树上二分找出权值\(<0\)的最靠右位置\(p\),那么以这个点为最右端的合法括号序列个数就是\([p+1,dep_x]\)内权值0个数

M_sea:你个[],这不是\(O(n)\)傻[]题吗

其实线段树是多余的,考虑对种和开一个桶记录这种权值的数出现次数,然后每次加入一个点就看这个点导致的整个权值变化,再用当前权值意义下为0的桶更新答案,不过要注意在之前当前权值意义下为-1的最后一个数不能计入答案,所以在更新桶的时候注意一下(ruaaa)

code

树上的数

wtcl,又被这种题送退役了

做法显然是从小到大枚举每个数,然后枚举这个数最终走到的点,如果合法就让它走过去,现在问题是怎么判断合法

考虑一个数从点\(x\)走到\(y\),依次经过的边为\(e_1,e_2...e_m\),首先\(e_1\)必须比其他和\(x\)相连的边先选(如果前面已经有一些必须在最前面的边,就把\(e_1\)放在前面那些边的最后面),否则这个数会走到别的地方去,并且走不回来,然后中间的\(e_i(1<i\le m)\),一定是比\(e_{i-1}\)后选,并且对于和\(e_{i-1}\)\(e_i\)公共点相连的其他边,都不能在\(e_{i-1}\)\(e_i\)之间选.最后是\(e_m\),必须在其他和\(y\)相连的边选完后再选(如果后面已经有一些必须在最后面的边,就把\(e_m\)放在后面那些边的最前面),不然这个数会跑到别的地方去

这里面比较麻烦的是中间那个其他边不能在两条边之间选,但如果我们只考虑一个点以及它连出去的边(相当于菊花图),这个时候我们发现其他不连接这个点的边是不会影响到这个点上的数的,也就是一个点连出去的菊花是和其他菊花互不影响的,那么可以先单独考虑最后放在一起看.对于一个菊花,如果要使得其他边不能在两条边之间选,那么这两条边必须选完前面一条后马上选另一条.那么上述限制可以抽象成:一条边必须最先选,一条边必须最后选,两条边必须选完一条后马上选另一条.那么可以对每个点建一个链表,维护这些边操作的相对顺序,一条边必须在前面就建一个表示最前面的虚点,然后这条边接在这个虚点链尾后面;一条边必须在最后类似;一条边必须在另一条边后操作,那就把这两条边所在链接在一起.至于一些矛盾关系,这里简单列举一下:

  • 接完以后会出现环(无向图意义上的环)或者链上有分叉
  • 最前面虚点必须为链首,最后面虚点必须为链尾
  • 接完以后使得最前面虚点为链首,最后面虚点为链尾,但是还有其他不在这一条链上的一些链

code

Emiya 家今天的饭

这里的方案计算只有某一列被选次数不超过\(\lfloor\frac{k}{2}\rfloor\)这个限制不太好直接做,所以考虑容斥,即总方案-不合法方案.总方案为\(\prod_{i=1}^{n}(1+\sum_{j=1}^{m}a_{i,j})\)

如果有一列被选次数\(>\lfloor\frac{k}{2}\rfloor\),那么有且仅有这一列会超出限制,所以不合法方案只有一列不满足限制,那就枚举超出限制的为哪一列,然后设\(f_{i,j,k}\)表示考虑前\(i\)行,选了\(j\)次,指定的不合法列选\(k\)次的方案,转移背包转移即可,那么这一列的贡献为\(\sum_{j=1}^{n}\sum_{k=\lfloor\frac{j}{2}\rfloor+1}^{j} f_{n,j,k}\),复杂度\(O(n^3m)\)

考虑优化,如果我们把上述状态的\(j-k\)\(k\)做差,那么一个不合法方案,它的这个差一定\(<0\),所以上述状态改为\(f_{i,j}\)表示前\(i\)行,其他列出现次数-指定列出现次数为\(j\)的方案,然后转移完后用\(j<0\)的统计,即可做到\(O(n^2m)\).实现时注意负下标的处理

code

划分

先考虑朴素dp,设\(f_{i,j}\)表示上一次选择的段为\([j,i]\)的最小值,复杂度\(O(n^3)\),然后注意到一个\(f_{i,j}\),从前面一个位置\(k\)转移过来\((k=j-1)\),那么\(f_k\)的合法第二维一定是在\([x,k]\)之间,其中\(x\)为最大的能转移到\(f_{i,j}\)的位置,那么记一下\(f_{i,j}\)关于\(j\)的后缀最小值,即可做到\(O(n^2)\)

然后先丢一个证明链接,再不加证明的给出两个引理

  • 一个\(f_{i,j}\)往后转移时,一定是用合法的\(j\)最大的\(f_{i,j}\)的转移到后面,后面记这个最大的\(j\)\(p_i\)
  • 对于一个\(i\),记能从前面转移过来的位置集合为\({x}\),那么最优的转移位置一定是集合最大值\(k\),即用\(f_{k,p_k}\)转移到\(f_{i,k+1}\)

你问我为什么不给证明,因为这是打表得到的

这样对于每个\(i\),二分出它最先可以贡献的后面的位置(即最小的\(a\)满足\(sum_a-sum_i\ge sum_i-sum_{p_i-1}\)),把\(i\)丢进对应的决策集合,然后在做到某个位置时用这个位置的决策更新最优决策,可以做到\(O(nlogn)\)

但这个二分是可以去掉的,我们维护一个单调队列,维护还没加入的决策\(i\),这些\(i\)以对应的\(sum_i-sum_{p_i-1}\)为关键字,每次加入一个决策进队列就把队尾的一定比这个决策差的决策弹掉(设队尾对应元素为\(t\)\(sum_i-sum_{p_i-1}<=sum_t-sum_{p_t-1}-(sum_i-sum_t)\)时弹队尾),然后压进队尾,每次用队首能加入的决策更新当前决策

同时你还需要实现一个高精,所以请注意常数问题.我这里用的是4个\(\text{int}\)表示\(32\)位整数

code

树的重心

初赛时认为完全二叉树就是满二叉树,然后这道题以为完美二叉树不是满二叉树,就GG QAQ

考虑朴素做法,枚举鸽的是哪条边,然后对于剩下两个子树分别求重心,具体操作是分别以两个点为根,然后从根出发,走到第一个点满足最大的儿子子树大小\(\le \lfloor\frac{size}{2}\rfloor\).如果有两个重心,则说明当前走到的点最大的儿子子树大小\(=\lfloor\frac{size}{2}\rfloor\),那么另一个重心为对应子树大小最大的儿子

如果套用以上做法,然后随便设一个点为根,那么可以统计出鸽掉每条边后,靠下面的点(记为\(y\))的子树的重心贡献,在往儿子走的时候加一个倍增优化即可.然后还有被鸽边上面那个点(记为\(x\))的贡献,那么可以看做是把\(x\)作为根,然后不考虑\(y\)的子树,然后往儿子走的过程,所以可以考虑换根dfs,因为在dfs到某个点的时候,只有这个点到根路径上的点的父子关系变化了,所以每次枚举这个点的儿子\(y\)时,\(x\)为根时的儿子实际上是\(x\)在原树中的其他儿子和\(x\)的父亲,那么倍增数组只要用这些\(x\)为根时的儿子预处理就好了.具体实现的时候,如果\(y\)为子树大小最大的儿子,那么\(x\)下一步走的是父亲或者子树大小次大儿子,否则是父亲或者子树大小最大儿子

code

posted @ 2019-11-21 21:09  ✡smy✡  阅读(307)  评论(4编辑  收藏  举报