动态规划题集2
1. 互不侵犯King
题目:在 N × N N×N N×N 的棋盘里面放 K K K 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 8 8 8 个格子。 ( N ≤ 9 , K ≤ N ∗ N ) (N\le 9, K \le N*N) (N≤9,K≤N∗N).
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 15, K = 90, M = (1 << 9) + 10;
ll f[N][M][K];
int n, m;
int cnt[M];
vector<int> head[M];
int calculate(int x)
{
int res = 0;
for(int i = 0; i < n; i++)
{
res += ((x >> i) & 1);
}
return res;
}
bool check(int x)
{
for(int i = 1; i < n; i++)
{
if((x >> (i - 1)) & (x >> i)) return false;
}
return true;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 0; i < (1 << n); i++)
{
cnt[i] = calculate(i);
for(int j = 0; j < (1 << n); j++)
{
if((i & j) == 0 && check(i | j))
{
head[i].push_back(j);
}
}
}
f[0][0][0] = 1;
for(int i = 1; i <= n + 1; i++)
{
for(int j = 0; j < (1 << n); j++)
{
for(int k = 0; k <= m; k++)
{
for(auto p : head[j])
{
if(k >= cnt[p]) f[i][j][k] += f[i - 1][p][k - cnt[p]];
}
}
}
}
printf("%lld\n", f[n + 1][0][m]);
return 0;
}
2. 集合选数
题目:求集合 { 1 , 2 , … , n } \{1,2,…,n\} {1,2,…,n} 的子集个数,且需要满足:若 x x x 在子集中,则 2 x 2x 2x 和 3 x 3x 3x不在子集中。 ( n ≤ 1 0 5 ) (n\le 10^5) (n≤105)
写出如下矩阵
1 3 9 27…
2 6 18 54…
4 12 36 108…
发现最多有11列,最多17 行
我们在其中选取一些数,相邻的不能选择.
然后就可以状压求方案数了,但是5没有出现,同样5的倍数也没有出现,7也如此
应该记录哪些数字出现过,没出现过就作为矩阵的第一个元素,最后把若干个矩阵的方案相乘
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1000000001;
const int N = 100010, M = (1 << 17) + 10;
bool st[N], check[M];
ll f[18][M], ans = 1;
int n;
int main()
{
scanf("%d", &n);
for(int i = 0; i < (1 << 17); i++)
{
check[i] = true;
for(int j = 1; j < 17 && check[j]; j++)
{
if((i >> (j - 1)) & (i >> j)) check[i] = false;
}
}
for(int u = 1; u <= n; u++)
{
f[0][0] = 1;
if(st[u]) continue;
int i, a, lastm = 0;
for(i = 1, a = u; a <= n; a <<= 1, i++)
{
int p = a, m = 0;
while(p <= n)
{
st[p] = true;
p *= 3, m++;
}
for(int j = 0; j < (1 << m); j++)
{
f[i][j] = 0;
for(int k = 0; k < (1 << lastm); k++)
{
if(check[j] && check[k] && (j & k) == 0) f[i][j] = (f[i][j] + f[i - 1][k]) % mod;
}
}
lastm = m;
}
ans = ans * (f[i - 1][0] + f[i - 1][1]) % mod;
}
printf("%lld\n", ans);
return 0;
}
3. POI2004 PRZ
n n n 个人要过桥,桥有承重量 W W W。所以这只队伍过桥时只能分批过,当一组全部过去时,下一组才能接着过。一个队伍的过桥时时间应该算走得最慢的那一个,我们想知道如何分批过桥能使总时间最少
n < = 16 n<=16 n<=16
首先, d p [ i ] dp[i] dp[i] 为状态为 i i i 下,这些队员过桥最少要用的时间,再维护一下每个状态 i i i 的总重量 W W W 以及总时间 T T T (指一次过桥的重量和时间,不管这个状态能否过桥)。
接着,顺序枚举状态 i i i ,并枚举 j j j ( j ∈ i j \in i j∈i ),将 i i i 分为状态 j j j 和状态 i ⊕ j i \oplus j i⊕j ,意思就是 状态 j j j 的一次过桥时间 + + + 状态 i i i 的最优过桥时间,注意状态 j j j 的总重量要小于题目给定的 w w w ,更新 d p [ i ] dp[i] dp[i]
d p [ i ] = min ( d p [ i ] , T [ j ] + d p [ i ⊕ j ] ) ( W [ j ] ≤ w ) dp[i] = \min (dp[i],T[j]+dp[i \oplus j] )(W[j] \le w) dp[i]=min(dp[i],T[j]+dp[i⊕j])(W[j]≤w)
#include<bits/stdc++.h>
using namespace std;
const int N = 20, M = (1 << 17) + 10;
int t[N], w[N], f[M], maxt[M], sumw[M];
int n, W;
int main()
{
scanf("%d%d", &W, &n);
//小心这个地方要从0开始编号.
for(int i = 0; i < n; i++) scanf("%d%d", &t[i], &w[i]);
for(int i = 0; i < (1 << n); i++)
{
for(int j = 0; j < n; j++)
{
if((i >> j) & 1) sumw[i] += w[j], maxt[i] = max(maxt[i], t[j]);
}
}
memset(f, 0x3f, sizeof f);
f[0] = 0;
for(int i = 0; i < (1 << n); i++)
{
for(int j = i; ; j = ((j - 1) & i))
{
if(sumw[j] <= W) f[i] = min(f[i], f[i ^ j] + maxt[j]);
if(!j) break;
}
}
printf("%d\n", f[(1 << n) - 1]);
}
4. 奇怪的道路
题目: n n n 个点,要连接 m m m 条无向边,可以有重边,要保证最后每个点度数为偶数,并且连边的两个点 u , v u,v u,v 需要满足 1 < = ∣ u − v ∣ < = K 1<=|u-v|<=K 1<=∣u−v∣<=K,问方案数。 1 < = n < = 30 , 0 < = m < = 30 , 1 < = K < = 8 1<=n<=30, 0<=m<=30, 1<=K<=8 1<=n<=30,0<=m<=30,1<=K<=8
5. Hero meet devil
给一个长度不超过 15 15 15 的串 S S S ( 仅包含 A C G T ACGT ACGT ),对于所有的 i i i ( 0 ≤ i ≤ ∣ S ∣ 0\le i \le |S| 0≤i≤∣S∣) ,求有多少长度为 M M M 的串 T T T 满足串 T T T 和串 S S S 的最长公共子序列长度为 i i i。 ( M ≤ 1000 ) (M\le 1000) (M≤1000)
6. SCOI2008 奖励关
7. 不要62
[ L , R ] [L,R] [L,R] 间,不出现 “ 62 62 62” 的数的个数。
裸的数位dp
#include<bits/stdc++.h>
using namespace std;
const int N = 25;
int f[N][N][2], a[N];
int A, B;
int dp(int pos, int k, int lim)
{
if(pos == -1) return 1;
int& v = f[pos][k][lim];
if(v != -1) return v;
int ans = 0, up = lim ? a[pos] : 9;
for(int i = 0; i <= up; i++)
{
if(i == 4) continue;
if(k == 6 && i == 2) continue;
ans += dp(pos - 1, i, i == up && lim);
}
return f[pos][k][lim] = ans;
}
int solve(int x)
{
int sz = 0;
while(x)
{
a[sz++] = x % 10;
x /= 10;
}
memset(f, -1, sizeof f);
return dp(sz - 1, 0, 1);
}
int main()
{
ios::sync_with_stdio(0);
while(cin >> A >> B, A)
{
cout << solve(B) - solve(A - 1) << endl;
}
}
8. B-number
B数的定义:能被13整除且本身包含字符串"13"的数。例如:130和2613是B数,但是143和2639不是B数。你的任务是计算1到n之间有多少个数是B数。
小心深搜的地方,千万别少了 else 判断
#include<bits/stdc++.h>
using namespace std;
const int N = 35, mod = 13;
int f[N][15][15][2], a[N];
int dp(int pos, int sum, int k, int flag, int lim)
{
if(pos == -1) return sum == 0 && flag;
int& v = f[pos][sum][k][flag];
if(!lim && v != -1) return v;
int ans = 0, up = lim ? a[pos] : 9;
for(int i = 0; i <= up; i++)
{
if(k == 1 && i == 3) ans += dp(pos - 1, (sum * 10 + i) % mod, i, 1, i == up && lim);
//千万别少了 else!!!
else ans += dp(pos - 1, (sum * 10 + i) % mod, i, flag, i == up && lim);
}
if(!lim) v = ans;
return ans;
}
int main()
{
int x;
while(cin >> x)
{
int sz = 0;
while(x)
{
a[sz++] = x % 10;
x /= 10;
}
memset(f, -1, sizeof f);
printf("%d\n", dp(sz - 1, 0, 0, 0, 1));
}
return 0;
}
9. count 数字计数
给定两个正整数 a a a 和 b b b,求在 [ a , b ] [a,b] [a,b] 中的所有整数中,每个数码 (digit,指 0-9) 各出现了多少次。其中 a ≤ b ≤ 1 0 12 a ≤ b ≤ 10^{12} a≤b≤1012。
首先,我们单独处理这十个数字会简单一些
我们令 f ( i , c n t ) f(i, cnt) f(i,cnt) 表示处理到了第 i i i 个位置,数字 d d d 从高位到低位出现的次数是 c n t cnt cnt 个. 那么处理到最后一位的时候,只需要返回 c n t cnt cnt 就可以了.
#include<bits/stdc++.h>
using namespace std;
const int N = 20;
typedef long long ll;
ll f[N][N][2][2];
int a[N];
//d表示当前的数字,cnt表示从高位到低位放了多少个d了.
ll dp(int pos, int cnt, int d, int flag, int lim)
{
if(pos == -1) return cnt;
ll& v = f[pos][cnt][flag][lim];
if(v != -1) return v;
ll ans = 0;
int up = lim ? a[pos] : 9;
for(int i = 0; i <= up; i++)
{
ans += dp(pos - 1, cnt + (i == d && (d != 0 || !flag)), d, i == 0 && flag, i == up && lim);
}
return v = ans;
}
ll solve(ll x, int d)
{
int sz = 0;
while(x)
{
a[sz++] = x % 10;
x /= 10;
}
memset(f, -1, sizeof f);
return dp(sz - 1, 0, d, 1, 1);
}
int main()
{
ll a, b;
scanf("%lld%lld", &a, &b);
for(int i = 0; i <= 9; i++) printf("%lld%c", solve(b, i) - solve(a - 1, i), i == 9 ? '\n' : ' ');
return 0;
}
10.手机号码
[ L , R ] [L,R] [L,R] 间,不同时出现 8 , 4 8,4 8,4,且必须包含三个相邻且相同的数。 1 0 10 ≤ L ≤ R ≤ 1 0 11 10^{10} \le L \le R \le 10^{11} 1010≤L≤R≤1011.
这个题8个参数的状态全部开,答案会不一样.
#include<bits/stdc++.h>
using namespace std;
const int N = 15;
typedef long long ll;
ll f[N][N][N][2][2][2][2][2];
int a[N];
ll dp(int pos, int flag_4, int flag_8, int consecutive, int k, int cnt, int lead_zero, int lim)
{
if(flag_4 && flag_8) return 0;
if(pos == -1) return consecutive;
ll& v = f[pos][k][cnt][flag_4][flag_8][consecutive][lead_zero][lim];
if(v != -1) return v;
ll ans = 0;
int up = lim ? a[pos] : 9;
for(int i = 0; i <= up; i++)
{
int t = !lead_zero || i != 0;
if(i == k && !lead_zero) t = cnt + 1;
ans += dp(pos - 1, flag_4 || i == 4, flag_8 || i == 8, consecutive || (t >= 3), i, t, i == 0 && lead_zero, i == up && lim);
}
return v = ans;
}
ll solve(ll x)
{
int sz = 0;
while(x)
{
a[sz++] = x % 10;
x /= 10;
}
memset(f, -1, sizeof f);
return dp(sz - 1, 0, 0, 0, 0, 0, 1, 1);
}
int main()
{
ll L, R;
scanf("%lld%lld", &L, &R);
//printf("%lld\n", solve(R));
printf("%lld\n", solve(R) - solve(L - 1));
return 0;
}
11. D. Beautiful numbers
定义 b e a u t i f u l beautiful beautiful 数:这个数能被它的每一位非零数整除。例如 12 12 12 能被 1 1 1、 2 2 2 整除,故 12 12 12 是 b e a u t i f u l beautiful beautiful 数,求区间 [ l , r ] [l,r] [l,r] 内的 b e a u t i f u l beautiful beautiful 数的个数
被每一位整除,即对这个数的每位数字的lcm取模的值为0 。
于是考虑状态中存下所有数位的lcm,以及对于1~9的lcm(为2520)取模的值。
d p [ i ] [ j ] [ k ] [ 0 / 1 ] dp[i][j][k][0/1] dp[i][j][k][0/1]:长为i,所有数位的lcm为j,对2520取模为k,0/1表示是否压着上界。
注意所有可能的lcm数目很小,应当进行离散化。不离散化会 M L E MLE MLE
这个题有多组询问,但是 f f f 数组不用初始化(每次初始化会 T L E TLE TLE)。只要保存 l i m = 0 lim = 0 lim=0 的状态即可。让 f f f 保存的也是 l i m = 0 lim = 0 lim=0 时的结果。
#include<bits/stdc++.h>
using namespace std;
const int N = 19, M = 2525;
typedef long long ll;
ll f[N][M][50];
int a[N];
int Hash[M];
ll dp(int pos, int sum, int lcm, int lim)
{
if(pos == -1) return sum % lcm == 0;
ll& v = f[pos][sum][Hash[lcm]];
if(!lim && v != -1) return v;
int up = lim ? a[pos] : 9;
ll ans = 0;
for(int i = 0; i <= up; i++)
{
ans += dp(pos - 1, (sum * 10 + i) % 2520, i == 0 ? lcm : lcm * i / __gcd(lcm, i), i == up && lim);
}
if(!lim) v = ans;
return ans;
}
ll solve(ll x)
{
int sz = 0;
while(x)
{
a[sz++] = x % 10;
x /= 10;
}
return dp(sz - 1, 0, 1, 1);
}
int main()
{
int id = 0;
for(int i = 1; i <= 2520; i++)
{
if(2520 % i == 0)
{
Hash[i] = ++id;
}
}
memset(f, -1, sizeof f);
int T;
scanf("%d", &T);
while(T--)
{
ll L, R;
cin >> L >> R;
cout << solve(R) - solve(L - 1) << endl;
}
return 0;
}