[Bzoj2004][Hnoi2010]Bus 公交线路(状压dp&&矩阵加速)
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2004
看了很多大佬的博客才理解了这道题,菜到安详QAQ
在不考虑优化的情况下,先推$dp$式子,设$dp[i][j]$为最慢的公交车走到了第$i$站,$[i,i+p-1]$站的状态为$j$时的方案数。$i$到$i+p-1$的范围内有且仅有$k$辆车,则状态$j$应该为$p$长度的二进制串,其中有且仅有$k$个$1$(表示$k$辆车)并且第$1$位一定为$1$(第$1$位对应了当前的位置)。则初始态为$dp[0][111(k个1)…000(p-k个0)]$,结束态为$dp[n-k][111(k个1)…000(p-k个0)]$。判断状态$w$是否可以转移到状态$e$,则判断$w$的第$2$位到第$p+1$位($p+1$位补零)是否与$e$的第$1$位到$p$位只有一位不同,是则可以转移$dp[i][e]+=dp[i-1][w]$;
这时候考虑优化,$n<=1e9$就注定要矩阵快速幂加速,则先处理出所有状态之间的关系并构建矩阵$d[i][j]$,$d[i][j]$为$1$表示第一次第$i$个状态可以转移到第$j$个状态。我们要求的是第$n-k$次后初始态到结束态的方案数,根据矩阵乘法的定义$d[i][j]=\sum_{k=1}^{n}d[i][k]*d[k][j]$,则我们只要将矩阵连乘$(n-k)$次,d[结束态][初始态]就是我们所求的。而初始态和结束态实际上是一样的。
当n较小时可以状压dp
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int mod = 30031; 5 int check(int x) {//判断x状态中1的个数 6 int sum = 0; 7 while (x) { 8 sum++; 9 x -= (x&(-x)); 10 } 11 return sum; 12 } 13 int dp[12][1 << 12]; 14 int main() { 15 int n, k, p; 16 while (scanf("%d%d%d", &n, &k, &p) != EOF) { 17 memset(dp, 0, sizeof(dp)); 18 int End; 19 for (int i = (1 << (p - 1)); i < (1 << p); i++) { 20 if (i == (1 << p) - 1 - ((1 << (p - k)) - 1)) 21 End = i; 22 } 23 dp[0][End] = 1; 24 for (int i = 1; i <= n - k; i++) { 25 for (int j = (1 << (p - 1)); j < (1 << p); j++) { 26 for (int w = (1 << (p - 1)); w < (1 << p); w++) { 27 if (check(j) == check(w) && check(j) == k) { 28 int q = (w - (1 << (p - 1))) << 1; 29 int t = (q^j); 30 if (t == (t&(-t))) { 31 dp[i][j] = (dp[i][j] + dp[i - 1][w]) % mod; 32 } 33 } 34 } 35 } 36 } 37 printf("%d\n", dp[n - k][End]); 38 } 39 }
本题正解:
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int mod = 30031; 5 int check(int x) {//判断x中1的个数 6 int sum = 0; 7 while (x) { 8 sum++; 9 x -= (x&(-x)); 10 } 11 return sum; 12 } 13 struct martix { 14 int tmp[150][150]; 15 int num; 16 martix operator *(const martix &b)const { 17 martix ans; 18 ans.num = num; 19 for (int i = 1; i <= num; i++) { 20 for (int j = 1; j <= num; j++) { 21 ans.tmp[i][j] = 0; 22 for (int k = 1; k <= num; k++) { 23 ans.tmp[i][j] += tmp[i][k] * b.tmp[k][j]; 24 ans.tmp[i][j] %= mod; 25 } 26 } 27 } 28 return ans; 29 } 30 }; 31 martix qpow(martix a, int x) { 32 martix ans; 33 ans.num = a.num; 34 memset(ans.tmp, 0, sizeof(ans.tmp)); 35 for (int i = 1; i <= ans.num; i++) 36 ans.tmp[i][i] = 1; 37 while (x) { 38 if (x & 1) 39 ans = ans * a; 40 a = a * a; 41 x /= 2; 42 } 43 return ans; 44 } 45 int statu[1 << 10]; 46 int main() { 47 int n, k, p; 48 while (cin >> n >> k >> p) { 49 martix a; 50 int len = 0, End; 51 for (int i = (1 << (p - 1)); i < (1 << p); i++) { 52 if (check(i) == k) { 53 statu[++len] = i; 54 if (i == (1 << p) - 1 - ((1 << (p - k)) - 1)) 55 End = len; 56 } 57 } 58 a.num = len; 59 memset(a.tmp, 0, sizeof(a.tmp)); 60 for (int i = 1; i <= len; i++) { 61 for (int j = 1; j <= len; j++) { 62 int q = (statu[j] - (1 << (p - 1))) << 1; 63 int t = (q^statu[i]); 64 if (t == (t&(-t))) 65 a.tmp[i][j] = 1; 66 } 67 } 68 a = qpow(a, n - k); 69 printf("%d\n", a.tmp[End][End]); 70 } 71 }