LG 題解 P1357 花園
阿新 • • 發佈:2021-10-10
今後無論發生什麼事也好,這個左手上的⋯⋯都是同伴的記號!
目錄
則可以轉移。(其中 \(g(k)\) 表示 \(k\) 中 \(1\) 的個數,\(K\) 表示題目的限制)
前置知識
- 狀壓 DP
- 矩陣乘法
Description
小 L 有一座環形花園,沿花園的順時針方向,他把各個花圃編號為 \(1 \sim n\)。花園 \(1\) 和 \(n\) 是相鄰的。
他的環形花園每天都會換一個新花樣,但他的花園都不外乎一個規則:任意相鄰 \(m\) 個花圃中都只有不超過 \(k\) 個 \(C\) 形的花圃,其餘花圃均為 \(P\) 形的花圃。
例如,若 \(n=10\), \(m=5\), \(k=3\),則
CCPCPPPPCC
是一種不符合規則的花圃。CCPPPPCPCP
請幫小 L 求出符合規則的花園種數對 \(10^9+7\) 取模的結果。
\(n \le 10^15, 2 \le m \le \min (n, 5), 1 \le k < m\)
Solution
這題的 \(80\%\) 資料加上 \(m \le 5\) 讓你感覺很可以 DP,所以你考慮寫部分分。
設 \(f_{i,j}\) 表示考慮到第 \(i\) 位,後 \(m-1\) 位的狀態為 \(j\)。
\[f_{i,j} = \sum_k f_{i-1,k} \]可以列舉前一位的狀態 \(k\) 來轉移,實際只考慮新增一位對前面的影響,如果 \(g(k) < K\)
因為要序列為環形,考慮 多次 DP 來消除影響。
每次以一個合法的 \(f_{m-1,i}\) 開始,一直 DP 到 \(f_{n+m-1,i}\),用一個 \(ans\) 統計 \(f_{n+m-1,i}\) 的和即可。對應著與環上前 \(m-1\) 個位置的狀態相同。
理論有 \(80pts\),但我只得了 \(50pts\),蠻怪的。
你發現每一次轉移所列舉的狀態都是相同的。
於是你考慮用矩陣加速轉移。
初始化矩陣 \(base\)
如果 \(i\) 狀態能向 \(j\) 狀態轉移,那麼 \(base_{i,j} = 1\)
剩下的就是矩陣快速冪板子了。
計算貢獻也可像上面的一樣計算多次。但你發現他實際就是這個矩陣的對角線之和。
Code
狀壓 DP
/*
Work by: Suzt_ilymics
Problem: 不知名屑題
Knowledge: 垃圾演算法
Time: O(能過)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 2e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;
int n, m, K, ans = 0;
int f[MAXN][20];
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
void DP() {
for(int i = m; i <= n + m; ++i) {
for(int j = 0; j < (1 << m - 1); ++j) {
int cnt = 0;
for(int k = 1; k < m; ++k) if(j & (1 << k - 1)) cnt++;
f[i][j >> 1] = (f[i][j >> 1] + f[i - 1][j]) % mod;
if(cnt < K) f[i][(j >> 1) + (1 << m - 2)] = (f[i][(j >> 1) + (1 << m - 2)] + f[i - 1][j]) % mod;
}
}
}
signed main()
{
n = read(), m = read(), K = read();
for(int i = 0; i < (1 << m - 1); ++i) {
int cnt = 0;
for(int j = 1; j < m; ++j) {
if(i & (1 << j - 1)) cnt++;
}
if(cnt > K) continue;
memset(f, false, sizeof f);
f[m - 1][i] = (cnt <= K);
DP();
ans = ans + f[n + m - 1][i];
}
printf("%lld\n", ans);
return 0;
}
矩陣快速冪
/*
Work by: Suzt_ilymics
Problem: 不知名屑題
Knowledge: 垃圾演算法
Time: O(能過)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 2e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;
int n, m, K, Ans = 0;
int f[MAXN][20];
struct Matrix {
int a[17][17];
Matrix () { memset(a, false, sizeof a); }
void Init() { memset(a, false, sizeof a); }
Matrix operator * (Matrix b) {
Matrix res;
for(int i = 0; i < (1 << m - 1); ++i)
for(int j = 0; j < (1 << m - 1); ++j)
for(int k = 0; k < (1 << m - 1); ++k)
res.a[i][j] = (res.a[i][j] + a[i][k] * b.a[k][j]) % mod;
return res;
}
Matrix operator ^ (int p) {
Matrix res, x = *this;
for(int i = 0; i < (1 << m - 1); ++i) res.a[i][i] = 1;
while(p) {
if(p & 1) res = res * x;
x = x * x, p >>= 1;
}
return res;
}
}ans, base;
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
signed main()
{
n = read(), m = read(), K = read();
for(int i = 0; i < (1 << m - 1); ++i) {
int cnt = 0;
for(int j = 1; j < m; ++j) if(i & (1 << j - 1)) cnt++;
base.a[i][i >> 1] = 1;
if(cnt < K) base.a[i][(i >> 1) + (1 << m - 2)] = 1;
}
base = base ^ n;
ans = ans * base;
for(int i = 0; i < (1 << m - 1); ++i) {
int cnt = 0;
for(int j = 1; j < m; ++j) if(i & (1 << j - 1)) cnt++;
if(cnt <= K) {
ans.Init();
ans.a[1][i] = 1;
ans = ans * base;
Ans = (Ans + ans.a[1][i]) % mod;
}
}
printf("%lld\n", Ans);
return 0;
}