序列奇偶
题目描述:
有 n 个数字,第 i 个数字 vi 的范围在 [xi,yi] ,有 m 个限制,第 i 个限制要求 (i=l∑rvi)mod2=s 。
求有多少合法序列以及最小字典序合法序列。
n≤10,m≤40,0≤xi,yi≤109 。
暴力可以获得 10% 的分数。
我们发现 m 个条件都与 mod2 有关,于是我们考虑之前确定每一个数字的奇偶性,并计算每一个数字为偶数的方案数 ai,0 ,奇数的方案数 ai,1 ,计算公式如下:
ai,1ai,0=⌊2(yi−xi+1)+(ximod2)⌋=(yi−xi+1)−ai,1
于是原问题被转化为一个 01 序列,要求一段区间的异或值为定值,求方案数和转化为原序列之后的最小字典序序列。
直接枚举可以获得 50% 的分数。
然后我们可以作一遍前缀异或和,记 Vi=(j=1∑ivj)mod2 。
然后限制条件就变为 Vli−1⊕Vri=si 。这个时候我们可以考虑将 li−1 向 ri 连边权值为 si 。之后用带权并查集维护 vali 表示 i 和 i 所在集合的根的奇偶性相同/不同。
带权并查集:
如果设 wi 表示 i 和并查集中 i 的父亲 fi 的奇偶性的异或情况,那么 vali 应该为 i 到根的边的异或值。
那么执行 getf 函数时,设当前 getf 的参数为 x ,则应该有 vali=valfi⊕wi 。时时刻刻维护这个值就行。
实际实现时可以偷懒,因为合并时必然是 x 和 y 之间直接执行 fy=x ,所以我们可以把 w 数组和 val 数组合并为一个数组。
注意每次调用 vali 前要先用 getf ,不然可能使用的是一个未更新的值。
1 2 3 4 5 6
| int getf(int x){ if(f[f[x]]==f[x])return f[x]; int t=getf(f[x]); val[x]^=val[f[x]]; return f[x]=t; }
|
然后是合并函数 add(x,y,s) 。
不妨设 x,y 的根为 rtx,rty ,表示 x 和 y 的奇偶性的异或为 s 。
首先如果说 rtx 和 rty 不相等,此时只需要知晓 rtx 和 rty 之间的关系就行。容易发现 wrty=s⊕valx⊕valy 。
然后就是 rtx 和 rty 不相等。此时需要判断有没有矛盾出现。若 valx⊕vals 则没有矛盾,反之亦然。
1 2 3 4 5 6 7 8 9 10 11 12
| void add(int x,int y,int S){ int X=getf(x),Y=getf(y); if(X!=Y){ val[Y]=S^val[x]^val[y]; sz[X]+=sz[Y]; f[Y]=X; } else{ if(val[x]^val[y]^S) flag=1; } }
|
此时如果出现了矛盾,可以直接判断出无解。
接着考虑如何计数。
注意到所有的点分为两类:
- 没有点与这个点有关系,这是自由点。
- 不是自由点,即连通块大小至少为 2 。
注意到第一种完全独立,很好处理;第二种连通块的个数不超过 2n ,即 20 。此时可以暴力枚举所有可能性,然后 DP 第一种即可。
具体的,设 fi,j 表示前 i 个点, Vi=j 。则:
- 若 i 属于第一种,则枚举 k ,有 fi−1,k×ai,j⊕k→fi,j 。
- 否则,设 i 必须选 j ,则只能枚举 k ,有 fi−1,k×ai,j⊕k→fi,j 。且 fi,j⊕1=0 。
然后此时最终答案就是 fn,0+fn,1 ,边界条件 f0,0=1 。
第一问就做完了。
第二问比较简单。你可以从前往后贪心选择,容易发现每个点只会有两个选择,即 xi 和 xi+1 。
每次先选 xi ,然后使用带权并查集看是否有矛盾。如果有则 xi ,否则 xi+1 。
这样就做完了。