蓝桥杯真题(DP)

DP

蓝桥骑士

一眼看出是最长上升子序列,但是普通的写法是n方的,会超时,所以要用单调队列优化

单调队列优化思想:维护一个单调的队列,新来的元素通过二分找到适当位置,并进行置换

这里维护的是单调递增的队列,队列中的每个元素都严格大于它的前一个元素

当有新元素x时,找到最大的比它严格小的元素q[i]

为了维持队列的单调性,i+1为x的正确放置位置,即:q[i + 1] = x

如果q[i + 1] == x,则令q[i + 1] = x并没有任何问题

如果q[i + 1] > x,分两种情况

如果q[i]是队列的最后一个元素,则q[i + 1] = x相当于新元素入队,使队列长度加一。

如果q[i]不是队列的最后一个元素,也就是说q[i + 1]也是队列中的元素,那么对于之后的元素x',如果x' > q[i + 1] 则肯定也有 x' > x。因此将q[i + 1]换成x不会对队列有影响。

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]

n, = read()
arr = read()

q = [0] * (n + 1)

ans = 0
for x in arr:
    l = 0; r = ans
    # 二分查找最大的小于x的数
    while l < r:
        mid = l + r + 1 >> 1
        if q[mid] < x: l = mid
        else: r = mid - 1
    q[r + 1] = x
    ans = max(ans, r + 1)
# 注意q[0]没有被使用过
print(ans)

排列数

把题目要求的东西一般化,用 f[i][j] 表示考虑前i个数,并且有j个折点的所有方案的种数

我们要在1~i-1的基础上加入一个数i,看加入后折点数量的变化,开始画图找规律。

发现加入一个数i后折点数量只能+0或+1或+2。

进一步发现,有两种方案可以使折点数量加1(穷举左右两边四种情况,都是两种方案)

然后可以发现,如果有j个折点,则有j+1种方案可以使折点数量不变(山谷和山峰是交错的,可以从一个山峰开始枚举,向右相间加入山谷和山峰,能够发现该结论,根据对称性,从山谷开始枚举结论也成立,所以该结论成立)。

即:f[i-1][j] -> f[i][j] 有j+1种方案,f[i-1][j-1] -> f[i][j] 有2种方案

因为i-1个点有i个空位,所以对于f[i-1][j-2] -> f[i][j]i - ((j-2)+1) - (2) = i-j-1 种转移方案。

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : a <= b and range(a, b + 1) or range(a, b - 1, -1)

n, m = read()

f = alloc(n, m)

f[1][0] = 1
f[2][0] = 2

for i in bet(3, n):
  f[i][0] = 2
  for j in bet(1, m - 1):
    f[i][j] = f[i - 1][j] * (j + 1) + f[i - 1][j - 1] * 2
    if j >= 2: f[i][j] += f[i - 1][j - 2] * (i - j - 1)
    f[i][j] %= 123456
  
print(f[n][m - 1] % 123456)

装饰珠

解题思路:

根据题意,6件装备这个信息没有用,因为最终是按照所有装备中装饰物的总数来算的。

看数据范围,等级只有1、2、3、4,并且1级的可以任意放,2级的只能放2、3、4级,3级的只能放3、4级,4级的只能放4级。

所以这是一个分组背包问题。按照等级对装饰物分组。

背包问题的体积在这里就是可以放置的槽位的总数。

f[i][j]表示放了i种装饰物,总共放了j个装饰物的最大总价值。

这里与背包问题不一样,第二维不表示“体积不超过j”,而表示“体积为j”,因为递推需要保证填满j,不然无法递推。

若放t个第i种装饰物的价值为w[t],则 f[i][j] 可以由 max(f[i - 1][j - t] + w[t]) 推得。

因为这是一个分组背包问题,所以降维方法也一样。

考虑到等级的限制,需要从较高等级的装饰物开始枚举。

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : a <= b and range(a, b + 1) or range(a, b - 1, -1)

count = [0] * 5
for i in range(6): 
  for t in read()[1:]:
    count[t] += 1

m = sum(count)

n, = read()
w = [[] for _ in range(5)]
for i in bet(1, n):
    a, b, *c = read()
    w[a].append(c)

f = alloc(m) 
# f[i][j] 表示按照从高等级开始放入珠子,放了i个,并且总共放了j个装饰物的最大价值。
# 这里按照背包问题的降维方法,去掉了i对应的那一维

able = 0
for level in bet(4, 1):
    able += count[level] # j可以达到的最大值
    for vec in w[level]:
        for j in bet(able, 0): # 为了降维,j从大开始枚举
            for i, v in enumerate(vec):
                if j - i - 1 < 0: break
                # 这里不能用f[j]=max(f[j],f[j-i-1]+v),否则可能会超时
                if f[j] < f[j - i - 1] + v:
                    f[j] = f[j - i - 1] + v

print(max(f[:]))

子串分值

起初想要用f[i] 表示前i个字母所有子串的f(S)的和,但是发现递推的时候还需要用到以第i个字母结尾的所有子串的f(S)的和。所以还不如只求后者。结果就是sum(f)。

这题如果画图就会很容易发现加入一个字符后如何从f[i-1]递推f[i]。

s[r][i] 表示r到i这段子串。并且考虑i固定,r变化,可以称为以i结尾的后缀。

下面是根据画图结果发现的从f[i-1]f[i] 的转移方式:

last[c] 表示上一次c出现的位置,则s[last[c] + 1, i - 1] 中没有c。

因此对于r in [last[c] + 1, i - 1]f(s[r][i]) = f(s[r][i-1]) + 1

last2[c] 表示上上一次c出现的位置,则s[last2[c] + 1,i - 1] 中有1个c,s[0, i - 1] 中有至少2个c。

因此对于r in [last2[c] + 1, last[c]]f(s[r][i]) = f(s[r][i-1]) - 1

对于r in [0, last2[c]]f(s[r][i]) = f(s[r][i-1])

并且显然还有f(s[i][i]) = 1

结论就是:f[i] = f[i - 1] + 1 + (i - 1 - last[c]) - (last[c] - last2[c])

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : a <= b and range(a, b + 1) or range(a, b - 1, -1)
from collections import defaultdict

s = readline().strip()
n = len(s)
last = defaultdict(int)
last2 = defaultdict(int)
# last[c] 表示上一次c出现的位置
# last2[c] 表示上上一次c出现的位置
# 使用defauldict,认为在下标0处有26个字母

f = alloc(n)
# f[i] 表示以i结尾的所有子串的f(S)的和

for i in bet(1, n):
  c = s[i - 1]
  # 递推公式可以画图推导
  f[i] = f[i - 1] + 1 + (i - 1 - last[c]) - (last[c] - last2[c])
  last2[c] = last[c]
  last[c] = i
print(sum(f))

子串分值和

解题思路:

这题和子串分值那题基本一样,只是改成了f(S)=len(set(S))

做法同样是画图,可以很容易看出递推公式。

s[r][i] 表示r到i这段子串。

last[c] 表示上一次c出现的位置,则s[last[c] + 1, i - 1] 中没有c。

因此对于r in [last[c] + 1, i - 1]f(s[r][i]) = f(s[r][i-1]) + 1

对于r in [0, last[c]]f(s[r][i]) = f(s[r][i-1])

心得:对于序列问题的dp,通常可以用f[i]表示以i结尾的所有后缀组成的集合,集合的值视问题而定,比如这题就是集合中所有元素对应的f(S)的和。

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : a <= b and range(a, b + 1) or range(a, b - 1, -1)
from collections import defaultdict
s = readline().strip()
n = len(s)
f = alloc(n)
last = defaultdict(int)
for i in bet(1, n):
    c = s[i - 1]
    f[i] = f[i - 1] + 1 + i - 1 - last[c]
    last[c] = i

print(sum(f))

游园安排

解题思路:

分析算法类型:一眼看出是最长上升子序列问题,序列的元素是字符串,不过直接用python自带的比较运算符就可以了。

分析数据范围:因为这题元素数最大为1e6,所以必须用单调队列优化的算法。

分析题目要求:这里求的不是最长上升子序列的长度,而是要求最长上升子序列。一般这种问题可以使用链表法,记录每个元素的前驱,然后从后向前遍历链表即可获得序列。

具体到最长上升子序列问题,因为用单调队列优化后,每个元素都会找到单调队列中第一个大于等于它的元素进行置换,保持队列的单调性,因此在最长上升子序列中这个元素的前驱就是它在单调队列中的前驱。并且根据我们的置换规则,最终的单调队列就是字典序最小的。

这题让我收获最多的是自己手写的二分没有Python自带的二分快,手写过不了,用自带的就能过。。。

心得:

时间苛刻的情况下,能用Python自带的二分就用Python自带的二分。

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : a <= b and range(a, b + 1) or range(a, b - 1, -1)
import bisect

s = readline().strip()
names = []
name = ''
for c in s:
  if str.isupper(c):
    if name != '': names.append(name)
    name = c
  else:
    name = name + c
if name != '': names.append(name)

n = len(names)
q = alloc(n)
len_ = 0
pre = [-1] * (n + 10)
q[0] = -1
sq = pre[:]
sq[0] = ""
for i, name in enumerate(names):
  r = bisect.bisect_left(sq, name, 0, len_ + 1) - 1

  q[r + 1] = i
  sq[r + 1] = name
  pre[i] = q[r]
  if r + 1 > len_:
    len_ = r + 1

ans = []
t = q[len_]
while pre[t] != -1:
  ans.append(names[t])
  t = pre[t]
ans.append(names[t])

print(''.join(reversed(ans)))

括号序列

解题思路:

我们放括号的时候肯定不能放一个(),因为这样直接删去这对括号不会有任何影响。

所以在一个间隙放括号时,肯定是类似这样的:))(((

因此放左括号和放右括号是相互独立的,可以分别求出对应的放法,任何乘起来即为答案。

考虑放左括号。因为放右括号只需要对称做即可。

因为这种求方案的题,很有可能是DP题;数据范围最大5e3,因此最多两重循环。

还是老套路,第一重循环应该是遍历序列,第二重循环可以是放左括号的数量,但是不能递推。

这里有一个技巧,就是找第一重循环中变化的量。发现遍历序列时,左右括号数的差值会变化。

因此用f[i][j] 表示考虑前i个括号,并且添加若干括号后,左括号比右括号多j个的方案数。

这里的状态转移是借助放左括号实现的,因为放左括号是为了匹配右括号,因此考虑在右括号之前放左括号。

如果 s[i] == '(' ,则有 f[i][j] = f[i - 1][j - 1]

如果 s[i] == ')' ,则有 f[i][j] = f[i - 1][j + 1] + f[i - 1][j] + ... + f[i - 1][0]

类似完全背包的优化思路,f[i][j] = f[i - 1][j + 1] + f[i][j - 1]

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : a <= b and range(a, b + 1) or range(a, b - 1, -1)

s = readline().strip()
n = len(s)
MOD = int(1e9 + 7)

def dp(s):
    f = alloc(n, n)

    f[0][0] = 1
    for i in bet(1, n):
        c = s[i - 1]

        if c == '(':
            for j in bet(1, n):
                f[i][j] = f[i - 1][j - 1]
                
        else:
            f[i][0] = (f[i - 1][1] + f[i - 1][0]) % MOD
            for j in bet(1, n):
                f[i][j] = (f[i - 1][j + 1] + f[i][j - 1]) % MOD

    for j in bet(0, n):
        if f[n][j]: return f[n][j] 
        

s1 = list(s)
s2 = s1[::-1]
for i, v in enumerate(s2):
    if v == ')': s2[i] = '('
    else: s2[i] = ')'

print(dp(s1) * dp(s2) % MOD)

序列计数

解题思路:

首先可以尝试写个dfs,并用记忆化搜索进行优化剪枝。

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : a <= b and range(a, b + 1) or range(a, b - 1, -1)

n, = read()
MOD = 10000

f = alloc(n, n) # 记忆化搜索:f[i][j] 即为 dfs(i, j)

def dfs(x, d): # 以x结尾,差值为d
    if f[x][d]: return f[x][d]
    res = 1
    for i in range(1, d):
        td = abs(i - x)
        if f[i][td]: res += f[i][td]
        else:
            f[i][td] = dfs(i, td) % MOD
            res = (res + f[i][td]) % MOD
    f[x][d] = res
    return res

ans = 0
for i in bet(1, n):
    ans = (ans + dfs(i, n - i)) % MOD

print(ans)

但是这样还是会超时,主要是dfs的那一层for循环。

注意到决定循环次数的是d,那么如果将d改成d-1,循环会少一次,那么就会想到用完全背包问题的优化思路了:f[x][d] = f[x][d - 1] + f[d - 1][abs(x - (d - 1))]

心得:可以先写一个暴力的版本,再逐步进行优化。

优化后的代码:

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : a <= b and range(a, b + 1) or range(a, b - 1, -1)

n, = read()
MOD = 10000

f = alloc(n, n) # 记忆化搜索:f[i][j] 即为 dfs(i, j)

def dfs(x, d): # 以x结尾,差值为d
    if f[x][d]: return f[x][d]
    if d <= 1: return 1
    f[x][d] = (dfs(x, d - 1) + dfs(d - 1, abs(x - (d - 1)))) % MOD
    return f[x][d]

ans = 0
for i in bet(1, n):
    ans = (ans + dfs(i, n - i)) % MOD

print(ans)

波动数列

解题思路:

首先先把这种数列的和进行符号化。

设数列首项为x,并且第i项是在第i-1项基础上加y[i],y[i] in {a, -b}

则数列的和s = x + (x + y[1]) + (x + y[1] + y[2]) +...+ (x + y[1] + y[2] +...+ y[n-1])

合并后:s = nx + (n - 1)y[1] + (n - 2)y[2] + ... + y[n-1]

两边同时对n取模:s % n = (-y[1] -2y[2] - ... - (n-1)y[n-1]) % n

同时取负:(-s) % n = (y[1] + 2y[2] + ... + (n-1)y[n-1]) % n

因此我们可以用f[i][j] 表示考虑y[1] 到 y[i],且和模n等于j的所有方案数。显然f[0][0] = 1

心得:有时候取模是一个化繁为简的操作 。

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : a <= b and range(a, b + 1) or range(a, b - 1, -1)

MOD = int(1e8 + 7)

n, s, a, b = read()

f = alloc(n, n)
f[0][0] = 1

for i in bet(1, n - 1):
    for k in bet(0, n - 1):
        # + i * a
        f[i][k] = (f[i][k] + f[i - 1][(k - a * i) % n]) % MOD
        # - i * b
        f[i][k] = (f[i][k] + f[i - 1][(k + b * i) % n]) % MOD

print(f[n - 1][(-s) % n])

二进制问题

因为N最大取到1e18,所以只能用数位DP。log2(1e18) < 60。

数位DP能够求解0到N中所有满足一定条件的数的个数。

数位DP的通常套路是:从高数位向低数位依次填入数字,根据给定数N在相应数位的数字来对能够填入的数字进行分类讨论。

对于本题,读入的N即为给定的数,因为K >= 1,所以不需要考虑0。

将N拆分为二进制,用bits存储各个数位。

我们用last存储已经填入的1的个数。

  1. 如果 bits[i] == 1 ,则我们可以在第i个数位填入0或1。

    1. 如果填入0,则剩下的数位可以随便填,也就是从剩下i个数位中选择K-last个数位填1。

    2. 如果填入1,则令last+=1。如果last已经超过了K,则不能继续向下分类了。

  2. 如果 bits[i] == 0 ,因为我们前面填入的数都和bits中对应数位的数一样,所以这种情况下只能填0。

最后要判断最右的分支是否合法,也就是last == K,如果合法,要加上该分支。

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : a <= b and range(a, b + 1) or range(a, b - 1, -1)
import math
N, K = read()
n = int(math.log2(N)) + 1
C = alloc(n, n)

for i in bet(0, n):
    for j in bet(0, i):
        if j == 0: C[i][j] = 1
        else: C[i][j] = C[i - 1][j - 1] + C[i - 1][j]

bits = []
while N:
    bits.append(N & 1)
    N >>= 1

ans = 0
last = 0
for i in bet(len(bits) - 1, 0):
    bit = bits[i]
    if bit == 1:
        # 填0
        ans += C[i][K - last]
        # 填1
        last += 1
        if last > K: break
    if i == 0 and last == K: ans += 1 # 加上最右侧的分支

print(ans)

包子凑数

给定若干数,问有多少数不能被凑出来。也就是a1 * x1 + a2 * x2 + ... + an * xn 不能表示的数的个数。

这种形式可以联想到裴蜀定理。如果这n个数的最大公因数为d,则所有能表示的数都一定为d的倍数,所以如果d大于1,则不能被d整除的数都无法被表示,答案为INF。

如果d等于1,根据裴蜀定理,两个数a和b互质,最大的不能被凑出的数为(a - 1)(b - 1) 。根据数据范围,该数小于10000。如果考虑更多数a, b, c ...,最大的不能被凑出的数应该更小,因为考虑的数更多了。

因此剩下就是一个完全背包问题,用f[i][j] 表示从前i个数中选,每个数可以选任意个,并且总和为j,f[i][j] = 1 表示存在这种方案,f[i][j] = 0 表示不存在这种方案,也就是说j不能被凑出。

根据选择第i个数的个数:

f[i][j] = f[i - 1][j] | f[i - 1][j - xi] | f[i - 1][j - xi * 2] | ...

和完全背包问题一样的优化方法:

f[i][j] = f[i - 1][j] | f[i][j - xi]

这里同样可以使用完全背包问题的降维方法。

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : a <= b and range(a, b + 1) or range(a, b - 1, -1)

n, = read()
arr = [None] + [read()[0] for _ in range(n)]

gcd = lambda a, b : b == 0 and a or gcd(b, a % b)

d = arr[1]
for x in arr[2:]: d = gcd(d, x)

# f[i][j] : 前i个数,能凑出j
M = 10000
# f = alloc(n, M)
f = alloc(M)
# f[0][0] = 1
f[0] = 1
if d > 1: print('INF')
else:
    for i in bet(1, n):
        x = arr[i]
        # for j in bet(0, M):
        #     f[i][j] = f[i - 1][j]
        #     if j - x >= 0: f[i][j] |= f[i][j - x]
        for j in bet(x, M):
            f[j] |= f[j - x]
    ans = 0
    for i in bet(0, M):
        # if not f[n][i]: ans += 1
        if not f[i]: ans += 1
    print(ans)

生命之树

S其实是一个连通分量。

因为这是一个树上的问题,所以可以使用树形DP。树形DP的通常思路是根据子节点的值递推父节点的值。

这里用f[u] 表示以u为根节点的树中,所有包含u的连通分量的最大值。

对于子树v,如果f[v] > 0 ,则加上f[v] 一定会让f[u] 更大。如果f[v] <= 0 ,可以不用选择该子树加入连通分量。

因此 f[u] = w[u] + max(0, f[v1]) + max(0, f[v2]) + ...

这题有点问题,明明说了S是非空子集,但是必须要考虑S为空才能过。

# 树形DP

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
alloc2 = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [-1] * int(s[0] + 2)

n, = read()
arr = [None] + read()
e = alloc(n * 2); ne = alloc(n * 2); h = alloc2(n * 2); idx = 0

def add(a, b):
    global idx
    e[idx] = b; ne[idx] = h[a]; h[a] = idx; idx += 1

for _ in range(n - 1):
    a, b = read()
    add(a, b); add(b, a)
    
f = alloc(n)
# def dfs(u, fa):
#     f[u] = arr[u]
#     t = h[u]
#     while t != -1:
#         j = e[t]
#         if j != fa:
#             f[u] += max(0, dfs(j, u))
#         t = ne[t]
#     return f[u]
def dfs(u, fa):
    stk = []
    stk.append((u, fa))
    vst = __import__('collections').defaultdict(bool)
    
    while stk:
        if not vst[stk[-1]]:
            vst[stk[-1]] = True
            u, fa = stk[-1]
            t = h[u]
            while t != -1:
                j = e[t]
                if j != fa:
                    stk.append((j, u))
                t = ne[t]
        else:
            u, fa = stk.pop()
            f[u] = arr[u]
            t = h[u]
            while t != -1:
                j = e[t]
                if j != fa:
                    f[u] += max(0, f[j])
                t = ne[t]

dfs(1, -1)
print(max(f[:n+1]))

垒骰子

如果n比较小,我们可以直接用f[i][j] 表示考虑前i个骰子,并且第i个骰子最上面的骰子点数为j的方案数,不考虑周围的四个面。状态转移方程可以根据骰子的互斥关系和对面关系来推。但是因为n最大为1e9,所以显然会超时。

注意到我们的状态转移方程可以表示为:

f[i][j] = A[j][0] * f[i - 1][0] + A[j][1] * f[i - 1][1] + ... + A[j][5] * f[i - 1][5]

所以我们可以把状态转移表示成矩阵的形式:f[i] = A * f[i - 1]

如果没有互斥关系,矩阵A为全一矩阵。考虑互斥关系后,我们只需要把相应位改为0即可。

因此f[n] = A ** (n - 1) * f[1]。我们可以使用快速幂来把时间复杂度降到O(logn)。

最后,每个骰子都可以旋转,有4种方案,还需要乘上4**n。

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
alloc = lambda *s: len(s) != 1 and [alloc(*s[1:]) for i in range(int(s[0]) + 2)] or [0] * int(s[0] + 2)
bet = lambda a, b : range(a, b + 1)
rev = lambda a, b : range(a, b - 1, -1)

n, m = read()
MOD = int(1e9 + 7)
rep = [set() for _ in range(7)]
opp = {1:4, 2:5, 3:6, 4:1, 5:2, 6:3}
trans_mat = [[1] * 6 for _ in range(6)]
for _ in range(m):
    a, b = read()
    trans_mat[a - 1][opp[b] - 1] = 0
    trans_mat[b - 1][opp[a] - 1] = 0

f = alloc(0, 6)

for j in bet(1, 6): f[1][j] = 1

def matmul(A, B):
    C = [[0] * 6 for _ in range(6)]
    for i in range(6):
        for j in range(6):
            for k in range(6):
                C[i][j] += A[i][k] * B[k][j]
                C[i][j] %= MOD
    return C

def qpow_mat(a, b):
    res = a; b -= 1
    while b:
        if b & 1: res = matmul(res, a)
        b >>= 1
        a = matmul(a, a)
    return res
    
def qpow(a, b):
    res = 1
    while b:
        if b & 1: res = (res * a) % MOD
        b >>= 1
        a = (a * a) % MOD
    return res

vec1 = [[1, 0, 0, 0, 0, 0] for _ in range(6)]
mat = qpow_mat(trans_mat, n - 1)
vecn = matmul(mat, vec1)

ans = 0
for i in range(6): ans = (ans + vecn[i][0]) % MOD
ans = ans * qpow(4, n) % MOD

print(ans)
posted @ 2023-01-04 20:28  iku-iku-iku  阅读(102)  评论(0编辑  收藏  举报