Codeforces Round 940 (Div. 2) and CodeCraft-23补题题解(A-D)
比赛传送门
Dashboard - Codeforces Round 940 (Div. 2) and CodeCraft-23 - Codeforces
c,d详解,a,b略解
A
题目很简单,细节见代码。注:代码省略了 F a s t I O FastIO FastIO 的部分
def solve():
n = I()
a = LI()
dic = {}
for i in range(n):
dic[a[i]] = dic.get(a[i], 0) + 1
ans = 0
for v in dic.values():
ans += v // 3
print1(ans)
for _ in range(I()):
solve()
B
我们注意到数组的和是有上限的,那么我们可以怎么设置 O R OR OR 运算的每一位呢,其实仔细想一想就可以想到当且仅当 k > = 2 x + 1 − 1 k>=2^{x+1}-1 k>=2x+1−1 时,我们能得到 1 1 1 ~ x x x 所有位上的 1。所以我们输出 2 x + 1 − 1 , k − ( 2 x + 1 − 1 ) , 0 , . . . 2^{x+1}-1,k-(2^{x+1}-1),0,... 2x+1−1,k−(2x+1−1),0,... 即可具体实现见代码:
p2 = [1]
for i in range(32):
p2.append(2*p2[-1])
def solve():
n,k = MI()
ans = []
for i in range(31):
if p2[i+1]-1 > k:
ans.append(p2[i]-1)
ans.append(k - p2[i]+1)
break
if len(ans) < n:
ans = ans + [0]*(n-len(ans))
if len(ans) > n:
while len(ans) > n:
tp = ans.pop()
ans[-1] += tp
print_arr(ans)
for _ in range(I()):
solve()
C
这题有两种理解和解题思路。
解法一:排列组合
- 根据题意我们发现可以忽略车在初始配置中的确切位置。只有空闲行列的数量才是最重要的,我们需要求解方案数。
- 那么我们每下一步棋会发生什么呢?假设剩余 m m m 行和列没被占用,我们可以下在形如 ( x , x ) (x,x) (x,x) 的位置, x x x 为坐标,我们的 m m m 会减少 1 1 1。当然, 我们也可以下在形如 ( x , y ) , x ≠ y (x,y),x\not = y (x,y),x=y 的位置,这样电脑会下在 ( y , x ) (y,x) (y,x), m m m 减少 2 2 2。
- 这样我们就可以枚举经过 k k k 步以后到结束,下在形如 ( x , x ) (x,x) (x,x) 位置的旗子数量的所有可能,统计每个数量对答案的贡献。
- 我们假设剩余 m m m 个位置,我们下在形如 ( x , x ) (x,x) (x,x) 位置上的个数为 i i i 个( m − i m-i m−i为偶数才行 ),那么对答案的贡献为 ( i m ) × 2 m − i 2 × ( m − i − 1 ) ! ! \tbinom{i}{m}\times2^{\frac{m-i}{2}}\times (m-i-1)!! (mi)×22m−i×(m−i−1)!! 。其中 ( i m ) \tbinom{i}{m} (mi) 表示从 m m m 中选出 i i i 个。后面的 2 m − i 2 × ( m − i − 1 ) ! ! 2^{\frac{m-i}{2}}\times (m-i-1)!! 22m−i×(m−i−1)!! 是 m − i m-i m−i 个形如 ( x , y ) , x ≠ y (x,y),x\not = y (x,y),x=y 的位置的贡献,两个相乘即是对答案的总贡献。
- 关于后面式子 2 m − i 2 × ( m − i − 1 ) ! ! 2^{\frac{m-i}{2}}\times (m-i-1)!! 22m−i×(m−i−1)!! 的解释如下:其中 ( m − i − 1 ) ! ! (m-i-1)!! (m−i−1)!! 为 m − i m-i m−i 个两两配对的方案数,推导:我们考虑一种递归的推导,设 f ( n ) f(n) f(n) 表示 n n n 个人两两配对的方案数,那么可以先随便取出 1 1 1 人和一个配对,再将剩下的 n − 2 n-2 n−2 人继续配对,即: f ( n ) = ( n − 1 ) f ( n − 2 ) f(n) = (n-1)f(n-2) f(n)=(n−1)f(n−2) ,最后算出答案为 f ( n ) = ( n − 1 ) ! ! f(n) = (n-1)!! f(n)=(n−1)!! 即 ( n − 1 ) (n-1) (n−1) 的双阶乘。 2 m − i 2 2^{\frac{m-i}{2}} 22m−i 是指每一对,都有两种下法。
- 启发:其实我们发现解这道题的关键是这些性质和式子的推导,要有意识的训练自己推式子,划分子问题的能力QAQ。
ac 代码:
mod = 10**9+7
ls = [1,1]
for i in range(2,300005):
ls.append((ls[-1]*i) % mod)
ss = [1,1,2,3]
for i in range(4,300005):
ss.append((ss[-2]*i) % mod)
p2 = [1,2]
for i in range(150005):
p2.append(2*p2[-1]%mod)
inv = lambda x:pow(x,mod-2,mod)
def solve():
n,k = MI()
vis = [0]*(n+1)
for _ in range(k):
x,y = MI()
vis[x] = vis[y] = 1
ans = 1
tmp = n - sum(vis)
if tmp%2 == 1:
for i in range(1,tmp,2): # 多少个下在 (x,x) 上
ans = (ans + (p2[(tmp-i)//2]*ls[tmp]*inv(ls[i])%mod)*inv(ls[tmp-i])*ss[tmp-i-1] % mod ) % mod
else:
for i in range(0,tmp,2): # 多少个下在 (x,x) 上
ans = (ans + (ls[tmp] * inv(ls[i]) * inv(ls[tmp - i]) % mod) * p2[(tmp-i)//2] * ss[tmp - i - 1] % mod) % mod
print1(ans)
for _ in range(I()):
solve()
解法二:DP
可参考官方题解,状态转移的方程的推导有点基于排列组合的意思。
D
题目解析
- 考虑异或的性质 x ⊕ x = 0 x\oplus x = 0 x⊕x=0,我们可以转换式子: f ( x , y ) ⊕ f ( y , z ) > f ( x , z ) f(x,y)\oplus f(y,z)>f(x,z) f(x,y)⊕f(y,z)>f(x,z) 等价于 f ( x , z ) ⊕ a y > f ( x , z ) f(x,z)\oplus a_y>f(x,z) f(x,z)⊕ay>f(x,z)。
- 所以我们要找对于每一个 y y y 使得包含 a y a_y ay 的数组异或后变大,计算这样的数组的个数。
- 考虑异或的性质,异或后变大变小,仅需考虑最高位 1,我们现在的问题转化为,对于每一个 a y a_y ay 和其最高位 m m m 我们如何快速找到包含它的所有区间 [ x , z ] [x,z] [x,z],使得 f ( x , z ) & ( 1 < < m ) = 0 f(x,z) \& (1<<m)=0 f(x,z)&(1<<m)=0.
- 对于求区间的一种累加性质,并且满足可逆性,我们考虑前缀和的形式,处理出 “异或前缀和”: s u su su, f ( x , z ) = s u [ z ] ⊕ s u [ x − 1 ] f(x,z)=su[z]\oplus su[x-1] f(x,z)=su[z]⊕su[x−1]。
- 对于 m m m 位为 0 0 0 ,考虑两种情况, s u [ x − 1 ] 和 s u [ z ] 的 m 位同为 1 或同为 0 su[x-1]和su[z]的m位同为1或同为0 su[x−1]和su[z]的m位同为1或同为0,根据这一性质如何快速找出满足条件的区间数?
- 想到预处理出前缀的数量即可,具体实现见代码。
启发: 涉及异或的题目我们可以多考虑异或的性质,其中异或后变大变小,仅需考虑最高位 1。对于区间的一些累加性质的维护,我们要有想到用前缀和处理的思维灵敏性。
代码:
def solve():
def maxbit(x):
tmp = -1
while x:
x>>=1
tmp += 1
return tmp
n = I()
a = [0]+LI()
su = [0] # a 的异或前缀和
for i in range(1,n+1):
su.append(su[-1]^a[i])
c1,c0 = [[0]*31 for _ in range(n+1)], [[0]*31 for _ in range(n+1)]
# 从0到i,j位为1的前缀的个数,从0到i,j位为0的前缀的个数
for i in range(n+1):
if i != 0:
c1[i],c0[i] = [c1[i-1][j] for j in range(31)],[c0[i-1][j] for j in range(31)]
for j in range(31):
if su[i] & (1<<j): c1[i][j] += 1
else: c0[i][j] += 1
ans = 0
for i in range(1,n+1):
y = a[i]
m = maxbit(y)
ans += c1[i - 1][m] * (c1[n][m] - c1[i - 1][m])
# 对于su[i]前的所有m位为1,i之后所有m位为1,两两组合会使得 f(x,z)=0
ans += c0[i - 1][m] * (c0[n][m] - c0[i - 1][m])
# 对于su[i]前的所有m位为0,i之后所有m位为0,两两组合会使得 f(x,z)=0
print1(ans)
for _ in range(I()):
solve()