#include
#include
using namespace std;
const int maxn = 35, maxv = 20010;
int f[maxv], v[maxn], V, N;
int main() {
scanf("%d%d", &V, &N);
for (int i = 1; i <= N; i++) {
scanf("%d", &v[i]);
}
for (int i = 1; i <= N; i++) {
for (int j = V; j >= v[i]; j--) {
f[j] = max(f[j], f[j - v[i]] + v[i]);
}
}
printf("%d\n", V - f[V]);
return 0;
}
#include
#include
#include
using namespace std;
const int maxv = 10010, maxn = 110, V = 10000;
int f[maxv], N, kase;
struct P{
int e, s, l;
bool operator<(const P& rhp)const {
return s * rhp.l < l* rhp.s;
}
}G[maxn];
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d", &N);
memset(f, -0x3f, sizeof f);
f[0] = 0;
for (int i = 0; i < N; i++) {
scanf("%d%d%d", &G[i].s, &G[i].e, &G[i].l);
}
sort(G, G + N);
for (int i = 0; i < N; i++) {
int s = G[i].s, e = G[i].e, l = G[i].l;
for (int j = V; j >= s; j--) {
f[j] = max(f[j], f[j - s] + e - (j - s) * l);
}
}
int ans = 0;
for (int i = 0; i <= V; i++) ans = max(f[i], ans);
printf("Case #%d: %d\n", ++kase, ans);
}
return 0;
}
题意:给定 n n n 种硬币,其中第 i i i 种硬币的面值为 A i A_i Ai,共有 C i C_i Ci 个. 问 1 ∼ M 1 \sim M 1∼M 之间能被凑成的面值有多少个? 1 ≤ n ≤ 100 , 1 ≤ m ≤ 1 0 5 , 1 ≤ A i ≤ 1 0 5 , 1 ≤ C i ≤ 1000 1 \le n \le 100,1 \le m \le 10^5,1\le A_i \le 10^5,1 \le C_i \le 1000 1≤n≤100,1≤m≤105,1≤Ai≤105,1≤Ci≤1000.
设 u s d [ j ] usd[j] usd[j] 表示 f [ j ] f[j] f[j] 在选择第 i i i 种硬币的时候,需要的最少硬币数。那么在 f [ j − a [ i ] ] f[j - a[i]] f[j−a[i]] 已经为 t r u e true true 的时候,如果 f [ j ] f[j] f[j] 为 t r u e true true,就不进行状态转移,并令 u s e d [ j ] = 0 used[j] = 0 used[j]=0. 如果 f [ j ] f[j] f[j] 为 f a l s e false false,就进行 u s e d [ j ] = u s e d [ j − a [ i ] ] + 1 used[j] = used[j - a[i]] + 1 used[j]=used[j−a[i]]+1 的转移.
#include
using namespace std;
const int N = 110, M = 100010;
bitset<M> f;
int used[M], a[N], c[N];
int n, m;
int main()
{
while(cin >> n >> m, n)
{
f.reset();
f[0] = 1;
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 1; i <= n; i++) scanf("%d", &c[i]);
for(int i = 1; i <= n; i++)
{
fill(used + 1, used + m + 1, 0);
for(int j = a[i]; j <= m; j++)
{
if(!f[j] && f[j - a[i]] && used[j - a[i]] < c[i])
{
f[j] = 1, used[j] = used[j - a[i]] + 1;
}
}
}
//把0扣掉
int ans = f.count() - 1;
printf("%d\n", ans);
}
}
#include
#include
using namespace std;
const int maxn = 20;
int w[maxn][maxn], f[maxn][maxn], N, M, ans[maxn];
int main() {
scanf("%d%d", &N, &M);
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
scanf("%d", &w[i][j]);
}
}
for (int i = 1; i <= N; i++) {
for (int j = 0; j <= M; j++) {
f[i][j] = f[i - 1][j];
for (int k = 1; k <= j; k++) {
f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
}
}
}
int j = M;
for (int i = N; i >= 1; i--) {
for (int k = 0; k <= M; k++) {
if (j >= k && f[i][j] == f[i - 1][j - k] + w[i][k]) {
ans[i] = k;
j -= k;
break;
}
}
}
printf("%d\n", f[N][M]);
for (int i = 1; i <= N; i++) {
printf("%d %d\n", i, ans[i]);
}
return 0;
}
#include
#include
#include
using namespace std;
const int maxv = 32010, maxn = 70;
typedef pair<int, int> P;
P master[maxn];
vector<P> servent[maxn];
int f[maxv], N, V;
int main() {
scanf("%d%d", &V, &N);
for (int i = 1; i <= N; i++) {
int v, p, q;
scanf("%d%d%d", &v, &p, &q);
if (!q) master[i] = { v, v * p };
else servent[q].push_back({ v, v * p });
}
for (int i = 1; i <= N; i++) {
if (!master[i].first) continue;
for (int j = V; j >= 0; j--) {
auto& sv = servent[i];
for (int k = 0; k < 1 << sv.size(); k++) {
int v = master[i].first, w = master[i].second;
for (int u = 0; u < sv.size(); u++) {
if (k >> u & 1) {
v += sv[u].first;
w += sv[u].second;
}
}
if (j >= v) f[j] = max(f[j], f[j - v] + w);
}
}
}
printf("%d\n", f[V]);
return 0;
}
#include
#include
#include
using namespace std;
const int maxv = 25010, maxn = 110;
int a[maxn], f[maxv], V, N;
int main() {
int T;
scanf("%d", &T);
while (T--) {
V = 0;
memset(f, 0, sizeof f);
f[0] = 1;
scanf("%d", &N);
for (int i = 1; i <= N; i++) {
scanf("%d", &a[i]);
V = max(V, a[i]);
}
for (int i = 1; i <= N; i++) {
for (int j = a[i]; j <= V; j++) {
f[j] += f[j - a[i]];
}
}
int ans = 0;
for (int i = 1; i <= N; i++) {
ans += (f[a[i]] == 1);
}
printf("%d\n", ans);
}
return 0;
}
设集合 A 有 n 1 n_1 n1 个 a 1 a_1 a1, n 2 n_2 n2 个 a 2 a_2 a2,…, n m n_m nm 个 a m a_m am 问使其相邻两个数不相同的排列有多少种.
多重集合交错排列
如果把这 n 个数分块,块的内部都是同一组的数字,并且分块儿的过程忽视掉快与块之间的顺序,相当于集合的概念。那么,按照容斥原理,答案应该是:n块的排列,减去n-1块的排列(有连续的两个数字是同一组,而在算dp的过程中,dp[n-1] 已经包含了这个情况的所有方案)……然后就是这样的容斥原理。 a n s = d p [ n ] n ! − d p [ n − 1 ] ( n − 1 ) ! + ⋯ + ( − 1 ) ( n − i ) d p [ i ] i ! + . . . − d p [ 1 ] ∗ 1 ! + d p [ 0 ] ∗ 0 ! ans=dp[n]n!−dp[n−1](n−1)!+⋯+(−1)^{(n−i)}dp[i]i!+...-dp[1]*1! + dp[0]*0! ans=dp[n]n!−dp[n−1](n−1)!+⋯+(−1)(n−i)dp[i]i!+...−dp[1]∗1!+dp[0]∗0!
设 d p [ i ] [ k ] dp[i][k] dp[i][k] 表示考虑前 i 种数分成 k 块的个数,那么,
d p [ i ] [ k ] = ∑ j = 1 k d p [ i − 1 ] [ k − j ] C n i − 1 j − 1 ∗ n i ! j ! dp[i][k]=∑_{j=1}^kdp[i−1][k−j]C_{n_i−1}^{j−1}∗\frac{n_i!}{j!} dp[i][k]=∑j=1kdp[i−1][k−j]Cni−1j−1∗j!ni!
for (auto p : tot) {
cnt.push_back(p.second);
}
int sz = tot.size();
dp[0][0] = 1;
for (int i = 1; i <= sz; i++) {
for (int k = 1; k <= N; k++) {
for (int j = 1; j <= min((int)cnt[i - 1], k); j++) {
dp[i][k] = (dp[i][k] + dp[i - 1][k - j] * C(cnt[i - 1] - 1, j - 1LL) % mod
* fact[cnt[i - 1]] % mod * infact[j] % mod) % mod;
}
}
}
ll ans = 0;
for (ll i = N; i >= 0; i--) {
ll f = ((N - i) & 1) ? -1LL : 1LL;
ans += f * dp[sz][i] % mod * fact[i] % mod;
ans = (ans % mod + mod) % mod;
}
printf("%lld\n", ans);
const int maxn = 110;
int f[maxn][maxn], N;
int main() {
memset(f, 0x3f, sizeof f);
scanf("%d", &N);
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
scanf("%d", &f[i][j]);
}
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
if (i == 1 && j == 1) continue;
f[i][j] = min(f[i - 1][j] + f[i][j], f[i][j - 1] + f[i][j]);
}
}
printf("%d\n", f[N][N]);
return 0;
}
#include
using namespace std;
const int maxn = 110;
int a[maxn], f1[maxn], f2[maxn];
int N;
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; i++) scanf("%d", &a[i]);
for (int i = 1; i <= N; i++) {
f1[i] = 1;
for (int j = 1; j <= i; j++) {
if (a[j] < a[i]) f1[i] = max(f1[i], f1[j] + 1);
}
}
for (int i = N; i > 0; i--) {
f2[i] = 1;
for (int j = N; j >= i; j--) {
if (a[i] > a[j]) f2[i] = max(f2[i], f2[j] + 1);
}
}
int res = 0;
for (int i = 1; i <= N; i++) {
res = max(res, f1[i] + f2[i] - 1);
}
printf("%d\n", N - res);
return 0;
}
题意:给定 n n n 个长度不超过 10 10 10 的字符串以及 m m m 次询问,每次询问给出长度不超过 10 10 10 的一个字符串和一个操作次数上限。对于每次询问,请你求出给定的 n n n 个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。每个对字符串进行的单个字符的插入、删除或替换算作一次操作。 1 ≤ n , m ≤ 1000 1\le n,m \le 1000 1≤n,m≤1000.
注意这道题,遇到字符串容易出错。我们这道题向函数传入字符串的时候不可以是 s + 1,因为在计算递推式的时候,下标是从1开始的,如果传入s + 1,那么字符串下标其实是从2开始的。
#include
using namespace std;
const int N = 1010, M = 15;
char str[N][M];
int f[M][M];
int solve(char a[], char b[])
{
int l1 = strlen(a + 1), l2 = strlen(b + 1);
for(int i = 1; i <= l1; i++) f[i][0] = i;
for(int j = 1; j <= l2; j++) f[0][j] = j;
for(int i = 1; i <= l1; i++)
{
for(int j = 1; j <= l2; j++)
{
f[i][j] = min(f[i - 1][j], f[i][j - 1]) + 1;
f[i][j] = min(f[i][j], f[i - 1][j - 1] + (a[i] != b[j]));
}
}
return f[l1][l2];
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%s", str[i] + 1);
}
char s[M];
int limit;
for(int j = 1; j <= m; j++)
{
scanf("%s%d", s + 1, &limit);
int res = 0;
for(int i = 1; i <= n; i++)
{
res += (solve(str[i], s) <= limit);
}
printf("%d\n", res);
}
return 0;
}
给定一个长度为 N 的数组,数组中的第 i i i 个数字表示一个给定股票在第 i i i 天的价格。设计一个算法来计算你所能获取的最大利润,你最多可以完成 k 笔交易。
f ( i , j , k ) f(i, j, k) f(i,j,k):表示第 i i i 天,已经进行了 j 次交易,此时的状态为 k 时的收益最大值。 k = 0 k=0 k=0 表示手中没有股票, k = 1 k=1 k=1表示手中有股票。
两个状态,四条有向边,分别对应四个状态转移:
f ( i , j , 0 ) = m a x { f ( i − 1 , j , 0 ) , f ( i − 1 , j , 1 ) + w [ i ] } f(i, j, 0) = max\{f(i - 1, j, 0), f(i - 1, j , 1) + w[i]\} f(i,j,0)=max{f(i−1,j,0),f(i−1,j,1)+w[i]}
f ( i , j , 1 ) = m a x { f ( i − 1 , j , 1 ) , f ( i − 1 , j − 1 , 0 ) − w [ i ] } f(i, j, 1) = max\{f(i - 1, j, 1), f(i - 1, j - 1, 0) - w[i]\} f(i,j,1)=max{f(i−1,j,1),f(i−1,j−1,0)−w[i]}
关于初始化的问题。如果不初始化为 − I N F -INF −INF,那么交易了 u 次后,也许收益会变成负数,但是状态会从 0 转移过来。 根据实际情况,什么时候收益会是0呢?首先,交易了0次时,收益一定是0,而此时手中一定没有股票。其次,在每一天,都不交易股票,那么收益仍然是0。因此,初始化应是 f ( i , 0 , 0 ) = 0 , 0 ≤ i ≤ N f(i, 0, 0) = 0, 0 \le i \le N f(i,0,0)=0,0≤i≤N.
#include
#include
#include
using namespace std;
const int maxn = 100010, maxm = 110;
int N, M, w[maxn], f[maxn][maxm][2];
int main() {
scanf("%d%d", &N, &M);
//因为状态会有负数,所以要初始化为负无穷。
memset(f, -0x3f, sizeof f);
//这个地方初始化注意!
for (int i = 0; i <= N; i++) f[i][0][0] = 0;
for (int i = 1; i <= N; i++) scanf("%d", &w[i]);
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
f[i][j][0] = max(f[i - 1][j][0], f[i - 1][j][1] + w[i]);
f[i][j][1] = max(f[i - 1][j][1], f[i - 1][j - 1][0] - w[i]);
}
}
int ans = 0;
for (int j = 1; j <= M; j++) ans = max(f[N][j][0], ans);
printf("%d\n", ans);
return 0;
}
给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
于是, f ( i , j ) f(i, j) f(i,j)表示在第 i i i 天,状态为 j j j 时,收益最大值。 j = 0 j=0 j=0 表示手中有货, j = 1 j=1 j=1表示手中无货第一天, j = 2 j=2 j=2 表示手中无货 ≥ 2 \ge2 ≥2 天。
#include
#include
#include
using namespace std;
const int maxn = 100010, INF = 0x3f3f3f3f;
int f[maxn][3], w[maxn], N;
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; i++) {
scanf("%d", &w[i]);
}
memset(f, -0x3f, sizeof f);
//把入口初始化为0
f[0][2] = 0;
for (int i = 1; i <= N; i++) {
f[i][0] = max(f[i - 1][0], f[i - 1][2] - w[i]);
f[i][1] = f[i - 1][0] + w[i];
f[i][2] = max(f[i - 1][2], f[i - 1][1]);
}
printf("%d\n", max(f[N][1], f[N][2]));
return 0;
}
#include
using namespace std;
const int N = 210;
int n;
int f[N][N], a[N];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
a[i + n] = a[i];
}
//长度从3开始枚举,到 n + 1
for(int len = 3; len <= n + 1; len++)
{
for(int l = 1, r = l + len - 1; r <= 2 * n; l++, r++)
{
//转移节点不能取到左右边界
for(int k = l + 1; k < r; k++)
{
f[l][r] = max(f[l][r], f[l][k] + f[k][r] + a[l] * a[k] * a[r]);
}
}
}
int res = 0;
// 答案对应的区间长度是 n + 1.
for(int l = 1, r = l + n; r <= 2 * n; l++, r++)
{
res = max(res, f[l][r]);
}
printf("%d\n", res);
return 0;
}
#include
using namespace std;
const int N = 110;
const __int128 INF = 1e36;
__int128 f[N][N];
int a[N], n;
void Print(__int128 x)
{
if(x < 0)
{
putchar('-');
x = -x;
}
if(x > 9) Print(x / 10);
putchar(x % 10 + '0');
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
a[i + n] = a[i];
}
for(int len = 3; len <= n; len++)
{
for(int l = 1, r = l + len - 1; r <= 2 * n; l++, r++)
{
//在这里初始化f更方便
f[l][r] = INF;
for(int k = l + 1; k < r; k++)
{
f[l][r] = min(f[l][r], f[l][k] + f[k][r] + (__int128)a[l] * a[k] * a[r]);
}
}
}
__int128 res = INF;
for(int l = 1, r = l + n - 1; r <= 2 * n; l++, r++)
{
res = min(res, f[l][r]);
}
Print(res);
return 0;
}
设一个 n n n 个节点的二叉树 tree 的中序遍历为( 1 , 2 , 3 , … , n 1,2,3,…,n 1,2,3,…,n),其中数字 1 , 2 , 3 , … , n 1,2,3,…,n 1,2,3,…,n 为节点编号。每个节点都有一个分数(均为正整数),记第 i i i 个节点的分数为 d i d_i di,tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下:
subtree的左子树的加分 × × × subtree的右子树的加分 + + + subtree的根的分数
**若某个子树为空,规定其加分为 1 1 1。**叶子的加分就是叶节点本身的分数,不考虑它的空子树。试求一棵符合中序遍历为( 1 , 2 , 3 , … , n 1,2,3,…,n 1,2,3,…,n)且加分最高的二叉树 tree。
要求输出:(1)tree的最高加分(2)tree的前序遍历。
f ( l , r ) f(l,r) f(l,r) 表示从中序遍历为 l l l 到 r r r 且加分最大的子树的加分值. 因为不改变中序遍历,因此同一棵子树的结点编号一定是连起来的. 那么我们只需要枚举子树的根节点,即 min l < k < r { f ( l , k − 1 ) ∗ f ( k + 1 , r ) + w [ k ] } \min\limits_{l < k < r}\{f(l, k - 1)*f(k + 1,r) + w[k]\} l<k<rmin{f(l,k−1)∗f(k+1,r)+w[k]}. 不过需要注意边界问题,由于子树可能为空,但是子树为空的权值为1,因此需要特殊处理.
#include
using namespace std;
const int N = 35, INF = 0x3f3f3f3f;
int f[N][N], g[N][N], w[N];
int n;
void dfs(int l, int r)
{
int root = g[l][r];
printf("%d ", root);
if(l <= root - 1) dfs(l, root - 1);
if(root + 1 <= r) dfs(root + 1, r);
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &w[i]);
for(int len = 1; len <= n; len++)
{
for(int l = 1, r = l + len - 1; r <= n; l++, r++)
{
//子树大小为1的时候需要注意
if(l == r)
{
f[l][r] = w[l];
g[l][r] = l;
}
else for(int k = l; k <= r; k++)
{
int left = (k == l) ? 1 : f[l][k - 1];
int right = (k == r) ? 1 : f[k + 1][r];
int score = left * right + w[k];
if(f[l][r] < score)
{
f[l][r] = score;
g[l][r] = k;
}
}
}
}
printf("%d\n", f[1][n]);
dfs(1, n);
return 0;
}
由于 x ‾ \overline{x} x 是定值,因此我们只需要让 ∑ i = 1 n ( x i − x ‾ ) 2 n \frac{\sum\limits_{i=1}^n (x_i - \overline x)^2}{n} ni=1∑n(xi−x)2 最小即可。对于每一个 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2) 的矩形,横着有 x 2 − x 1 − 1 x_2 - x_1 - 1 x2−x1−1 的种方法,可以选择 2 ∗ ( x 2 − x 1 − 1 ) 2 * (x_2 - x_1 - 1) 2∗(x2−x1−1) 种方案(扔掉上面的或者下面的),然后被扔掉的那一块儿通过二位前缀和可以迅速求得. 对于列的切割是同理的.
#include
using namespace std;
const int N = 15, M = 9, INF = 1e9;
double f[M][M][M][M][N];
int sum[M][M], n;
double X;
double get(int x1, int y1, int x2, int y2)
{
//X是浮点数,因此 s 一定不要写成 int
//这里公式没有错,想清楚了
double s = sum[x2][y2] - sum[x2][y1 - 1] - sum[x1 - 1][y2] + sum[x1 - 1][y1 - 1] - X;
return s * s / n;
}
double dp(int x1, int y1, int x2, int y2, int k)
{
double& v = f[x1][y1][x2][y2][k];
if(v >= 0) return v;
if(k == 1) return get(x1, y1, x2, y2);
v = INF;
for(int i = x1; i < x2; i++)
{
v = min(v, dp(x1, y1, i, y2, k - 1) + get(i + 1, y1, x2, y2));
v = min(v, get(x1, y1, i, y2) + dp(i + 1, y1, x2, y2, k - 1));
}
for(int j = y1; j < y2; j++)
{
v = min(v, dp(x1, y1, x2, j, k - 1) + get(x1, j + 1, x2, y2));
v = min(v, get(x1, y1, x2, j) + dp(x1, j + 1, x2, y2, k - 1));
}
return v;
}
int main()
{
scanf("%d", &n);
memset(f, -1, sizeof f);
for(int i = 1; i <= 8; i++)
{
for(int j = 1; j <= 8; j++)
{
scanf("%d", &sum[i][j]);
//求二位前缀和小心别弄错.
sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
}
}
X = (double)sum[8][8] / n;
printf("%.3f\n", sqrt(dp(1, 1, 8, 8, n)));
return 0;
}
#include
using namespace std;
const int N = 50010, M = N * 2;
int f[N];
int h[N], e[M], ne[M], idx;
int n, st[N], ans;
int cnt[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int dfs(int u, int fa)
{
st[u] = true;
int d1 = 0, d2 = 0;
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
int d = dfs(v, u) + 1;
if(d >= d1) d2 = d1, d1 = d;
else if(d > d2) d2 = d;
}
ans = max(ans, d1 + d2);
return d1;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
for(int j = 2 * i; j <= n; j += i)
{
f[j] += i;
}
}
memset(h, -1, sizeof h);
for(int i = 1; i <= n; i++)
{
if(i > f[i])
{
add(i, f[i]);
add(f[i], i);
}
}
for(int i = 1; i <= n; i++)
{
if(!st[i]) dfs(i, -1);
}
printf("%d\n", ans);
return 0;
}
#include
using namespace std;
const int N = 110, M = N * 2;
int h[N], e[M], ne[M], w[M], idx;
int n, v, value[N];
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int f[N][N], sz[N];
int dfs(int u, int fa)
{
sz[u] = 1;
for(int i = h[u]; i != -1; i = ne[i])
{
int son = e[i];
if(son == fa) continue;
value[son] = w[i];
sz[u] += dfs(son, u);
//从大到小循环
for(int j = min(sz[u] - 1, v - 1); j >= 0; j--)
{
for(int k = j; k >= 0; k--)
{
f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);
}
}
}
//一定要从大到小循环
for(int j = v; j >= 1; j--)
{
f[u][j] = f[u][j - 1] + value[u];
}
f[u][0] = 0;
return sz[u];
}
int main()
{
scanf("%d%d", &n, &v);
v++;
memset(h, -1, sizeof h);
memset(f, -0x3f, sizeof f);
for(int i = 1; i <= n; i++) f[i][0] = 0;
for(int i = 1; i < n; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dfs(1, -1);
printf("%d\n", f[1][v]);
return 0;
}
#include
using namespace std;
const int N = 1510, M = N * 2;
int h[N], e[M], ne[M], idx;
int n;
int st[N], f[N][2];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u, int fa)
{
f[u][0] = 0, f[u][1] = 1;
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
dfs(v, u);
//若不选择u,那么每个v都要选,不然会导致有些边的端点没有选到.
f[u][0] += f[v][1];
//选了u之后,v可选可不选.
f[u][1] += min(f[v][0], f[v][1]);
}
}
int main()
{
while(~scanf("%d", &n))
{
memset(h, -1, sizeof h);
idx = 0;
memset(st, 0, sizeof st);
for(int i = 1; i <= n; i++)
{
int id, cnt;
scanf("%d:(%d)", &id, &cnt);
id++;
while(cnt--)
{
int v;
scanf("%d", &v);
v++;
add(id, v), add(v, id);
st[v] = true;
}
}
dfs(1, -1);
printf("%d\n", min(f[1][0], f[1][1]));
}
return 0;
}
#include
#include
#include
#include
using namespace std;
const int maxn = 11;
int f[maxn][10];
//f[i][j] 保存的是 i 位最高位的数字是 j 的满足要求的数字的个数。
//这个答案是准确的,至于前导零怎么考虑,是在dp函数里面利用这个算的。
int dp(int n) {
if (n == 0) return 0; // 这道题的 0 不在考虑范围内
vector<int> nums;
while (n) nums.push_back(n % 10), n /= 10;
int last = -2, res = 0;
for (int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
for (int j = i == nums.size() - 1; j < x; j++) {
if (abs(j - last) >= 2) {
res += f[i + 1][j];
}
}
if (abs(x - last) >= 2) last = x;
else break;
if (i == 0) res++;
}
// 特殊处理有前导零的数
for (int i = 1; i < nums.size(); i++) {
for (int j = 1; j <= 9; j++) {
res += f[i][j];
}
}
return res;
}
int main() {
//预处理
//这里,应该从0开始枚举。尽管0不在计算之内,但是如果不算0的话,有些状态转移不过来
//比如,f[2][2],从f[1][0]转移,f[2][2]应该包含20这个数字,如果f[1][0]=0,这个数就转移不过来了
//但是在算res时,研究dp就会发现,0这个数字是不会算进去的。
for (int j = 0; j <= 9; j++) f[1][j] = 1;
for (int i = 2; i < maxn; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = 0; k <= 9; k++) {
if (abs(j - k) >= 2) {
f[i][j] += f[i - 1][k];
}
}
}
}
int a, b;
cin >> a >> b;
cout << dp(b) - dp(a - 1) << endl;
return 0;
}
深搜代码
#include
using namespace std;
const int N = 35;
int f[N][N][2], a[N];
int A, B;
int dp(int pos, int k, int flag, int lim)
{
if(pos == -1) return 1;
if(!lim && f[pos][k][flag] != -1) return f[pos][k][flag];
int up = lim ? a[pos] : 9;
int ans = 0;
for(int i = 0; i <= up; i++)
{
if(!flag && abs(i - k) >= 2) ans += dp(pos - 1, i, flag, i == up && lim);
if(flag) ans += dp(pos - 1, i, i == 0 && flag, i == up && lim);
}
if(!lim) f[pos][k][flag] = ans;
return 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, 1);
}
int main()
{
scanf("%d%d", &A, &B);
//printf("*** %d\n*** %d\n", solve(A - 1), solve(B));
printf("%d\n", solve(B) - solve(A - 1));
}
#include
#include
#include
#include
using namespace std;
int f[11][10][110];
int P;
int mod(int x, int y) {
return (x % y + y) % y;
}
int dp(int n) {
if (n == 0) return 1;
vector<int> nums;
while (n) {
nums.push_back(n % 10);
n /= 10;
}
int res = 0, last = 0; //last 存左面各分支数字之和。
for (int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
for (int j = 0; j < x; j++) {
res += f[i + 1][j][mod(-last, P)];
}
last += x;
if (i == 0 && (last % P == 0)) res++;
}
return res;
}
void init() {
memset(f, 0, sizeof f);
for (int j = 0; j <= 9; j++) f[1][j][j % P]++;
for (int i = 2; i < 11; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = 0; k < P; k++) {
for (int x = 0; x <= 9; x++) {
f[i][j][k] += f[i - 1][x][mod(k - j, P)];
}
}
}
}
}
int main() {
int l, r;
while (cin >> l >> r >> P) {
init();
cout << dp(r) - dp(l - 1) << endl;
}
}
深搜代码
#include
using namespace std;
const int N = 35, M = 110;
int f[N][M], a[N];
int A, B, mod;
int dp(int pos, int k, int lim)
{
if(pos == -1) return k == 0;
if(!lim && f[pos][k] != -1) return f[pos][k];
int ans = 0, up = lim ? a[pos] : 9;
for(int i = 0; i <= up; i++)
{
ans += dp(pos - 1, (k + i) % mod, i == up && lim);
}
if(!lim) f[pos][k] = ans;
return 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 >> mod)
{
cout << solve(B) - solve(A - 1) << endl;
}
}
#include
#include
#include
#include
typedef long long ll;
const ll mod = 1e9 + 7;
using namespace std;
const int maxn = 5010;
char s1[maxn], s2[maxn];
int M;
int f[maxn][60][60], p[maxn], a[maxn];
int my_mod(int a, int b) {
return (a % b + b) % b;
}
int dp(int pos, int sum, int res, bool limit) {
// lim=1 表示当前贴合上界,lim=0 则不贴合
if (pos == -1) {
return res == 0;
}
if (limit == false && f[pos][sum][res] != -1) {
return f[pos][sum][res];
}
else {
int ans = 0, up = limit ? a[pos] : 9;
for (int i = 0; i <= up; i++) {
int xx = my_mod(res + sum * i - i * p[pos], M);
ans = (ans + dp(pos - 1, my_mod(sum + i, M), xx, (i == up) && limit)) % mod;
}
if (limit == false) { // 不贴合上界的情况有可能会被复用
f[pos][sum][res] = ans;
}
return ans;
}
}
int solve(char s[], int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < M; j++) {
memset(f[i][j], -1, sizeof f[i][j]);
}
}
for (int i = 0; i < n; i++) {
a[i] = s[n - i - 1];
}
//这个方法也是从高数位往低数位填数字
return dp(n - 1, 0, 0, 1);
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%s%s%d", s1, s2, &M);
int n1 = strlen(s1), n2 = strlen(s2);
for (int i = 0; i < n1; i++) s1[i] -= '0';
for (int i = 0; i < n2; i++) s2[i] -= '0';
s1[n1 - 1]--;
for (int i = n1 - 1; i >= 0; i--) {
if (s1[i] < 0) {
s1[i] += 10;
s1[i - 1]--;
}
else break;
}
//for (int i = 0; i < n1; i++) printf("%d", s1[i]);
p[0] = 1;
for (int i = 1; i < maxn; i++) {
p[i] = p[i - 1] * 10 % M;
}
//printf("*** %d %d\n", solve(s2, n2), solve(s1, n1));
printf("%d\n", my_mod(solve(s2, n2) - solve(s1, n1), mod));
}
return 0;
}
再来一道深搜处理数位 d p dp dp 的题目。
题意:Beautiful Numbers定义为这个数能整除它的所有位上非零整数。问[l,r]之间的Beautiful Numbers的个数。
数位 DP: 数位 DP 问题往往都是这样的题型,给定一个闭区间 [ l , r ] [l,r] [l,r],让你求这个区间中满足某种条件的数的总数。我们将问题转化成更加简单的形式。设 a n s i ans_i ansi 表示在区间 中满足条件的数的数量,那么所求的答案就是 a n s r − a n s l − 1 ans_{r} - ans_{l - 1} ansr−ansl−1.
看代码吧,可以发现所有个位数的最小公倍数是2520,dfs(len, sum, lcm, limit) ,len表示迭代的长度,sum为截止当前位的数对2520取余后的值。 lcm为截止当前位的所有数的最小公倍数。limit表示当前数是否可以任意取值(对取值上限进行判断).
代码其实很好懂,但是方法确实不是那么好想。
#include
#include
#include
using namespace std;
const int maxlcm = 2520;
typedef long long ll;
ll dp[20][50][2530];
int Hash[2530];
ll nums[20];
ll gcd(ll a, ll b) {
if (b == 0) return a;
return gcd(b, a % b);
}
ll dfs(ll pos, ll sum, ll lcm, bool limit) {
if (pos == -1) {
return sum % lcm == 0;
}
if (limit == false && dp[pos][Hash[lcm]][sum] != -1) return dp[pos][Hash[lcm]][sum];
ll ans = 0;
int up = limit ? nums[pos] : 9;
for (int i = 0; i <= up; i++) {
ans += dfs(pos - 1, (sum * 10 + i) % maxlcm, i ? i * lcm / gcd((ll)i, lcm) : lcm, limit && i == up);
}
if (limit == 0) dp[pos][Hash[lcm]][sum] = ans;
return ans;
}
ll solve(ll N) {
int p = 0;
while (N) {
nums[p++] = N % 10;
N /= 10;
}
return dfs(p - 1, 0, 1, true);
}
int main() {
int T;
scanf("%d", &T);
memset(dp, -1, sizeof dp);
int cnt = 0;
for (int i = 1; i <= maxlcm; i++) {
if (maxlcm % i == 0) Hash[i] = cnt++;
}
while (T--) {
ll l, r;
cin >> l >> r;
cout << solve(r) - solve(l - 1) << endl;
}
return 0;
}