A:超级楼梯
题意:上楼梯,每次上一阶或两阶,问有多少种方案走到第n阶
思路:用表示走到第 i 阶楼梯时有多少种走法,状态转移方程为
,
即为所求
时间复杂度:
int n;
int dp[N];
void solve() {
cin >> n;
fill(dp, dp + n + 1, 0);
dp[1] = 1;
for (int i = 2; i <= n; i++) dp[i] = dp[i - 1] + dp[i - 2];
cout << dp[n] << '\n';
}
B:最长上升子序列
题意:求原序列单调递增子序列的最大长度
思路:用表示以i结尾的上升子序列的最大长度,状态转移方程为
,结果为
时间复杂度:
int n;
int a[N], dp[N];
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
dp[i] = 1;
for (int j = 1; j < i; j++) {
if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1);
}
}
int ans = 0;
for (int i = 1; i <= n; i++) ans = max(ans, dp[i]);
cout << ans << '\n';
}
C:骨头收藏家
题意:不同的骨头有不同的体积和价值。这个收藏家有一个体积为 V 的背包,请计算他可以收藏的最大价值。
思路:01背包问题,用表示只选前 i 个物品,体积为 j 时可收藏的最大价值, 状态转移方程为
,可以用滚动数组优化为
,最终
即为所求
时间复杂度:
int n, m;
int w[N], v[N], dp[N];
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> w[i];
for (int i = 1; i <= n; i++) cin >> v[i];
fill(dp, dp + n + 1, 0);
for (int i = 1; i <= n; i++) {
for (int j = m; j >= v[i]; j--) {
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
cout << dp[m] << '\n';
}
D:完全背包问题
题意:有n种重量和价值分别为的物品。从这些物品中挑选总重量不超过 W 的物品,求出挑选物品价值总和的最大值。在这里,每种物品可以挑选任意多件
思路:用表示只选前 i 个物品,体积为 j 时可挑选的最大价值,状态转移方程为
,同理可用滚动数组优化得
,观察
两项可发现
,最终
即为所求
时间复杂度:
int n, m;
int w[N], v[N], dp[N];
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
cin >> m;
fill(dp, dp + n + 1, 0);
for (int i = 1; i <= n; i++) {
for (int j = v[i]; j <= m; j++) {
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
cout << dp[m] << '\n';
}
E:最短Hamilton路径
题意:给定一张无向图,求起点 0 到终点 n-1 的最短Hamilton路径,Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。
思路:用表示已经经过的点的集合为state,且最后位于点 j 的路径的最小值,则状态转移方程为
,其中state可以考虑用状态压缩表示为二进制数, state二进制第 i 位为1时表示已经经过第 i 个点,为0表示未经过第 i 个点,即
,最终
即为所求
时间复杂度:
int n, m;
int a[N][N];
int dp[1 << 21][21];
void solve() {
cin >> n;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> a[i][j];
}
}
memset(dp, 0x3f, sizeof dp);
dp[1][0] = 0;
for (int i = 1; i < 1 << n; i++) {
for (int j = 0; j < n; j++) {
if (i & (1 << j) == 0) {
for (int k = 0; k < n; k++) {
if (i >> k & 1) {
dp[i | (1 << j)][j] = min(dp[i | (1 << j)][j], dp[i][k] + a[j][k]);
}
}
}
}
}
cout << dp[(1 << n) - 1][n - 1] << '\n';
}
F:石子合并
题意:有n堆石子,每次将相邻的两堆合成一堆,新的一堆石子数为合并的体力花费,求将n堆石子合并成一堆的最小花费
思路:用表示将区间
内的石子合成一堆所需的最小花费,则状态转移方程为
,最终
即为所求
时间复杂度:
int n;
int a[N], sa[N];
int dp[N][N];
int find(int l, int r) {
if (l == r) return 0;
if (dp[l][r]) return dp[l][r];
dp[l][r] = 1e9;
for (int k = l; k < r; k++) {
dp[l][r] = min(dp[l][r], find(l, k) + find(k + 1, r) + sa[r] - sa[l - 1]);
}
return dp[l][r];
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], sa[i] = sa[i - 1] + a[i];
cout << find(1, n) << '\n';
}
G:没有上司的舞会
题意:一所大学,职员的关系就像一棵以校长为根的树,每个职员有一个快乐指数。现在要召开舞会,没有职员愿意和直接上司一起参会,求所有参会职员的快乐指数总和最大值
思路:用表示在 u 未参加舞会时,以 u 为根的子树的快乐指数总和的最大值,用
表示在 u 参加舞会时,以 u 为根的子树的快乐指数总和的最大值,则状态转移方程为
,
,其中 v 是 u 的子节点,最终结果为
。
时间复杂度:
struct node {
int fa, ha;
vector<int> adj;
}tr[N];
int dp[N][2];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> tr[i].ha, tr[i].fa = -1;
for (int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
tr[v].adj.push_back(u);
tr[u].fa = v;
}
int rt;
for (int i = 1; i <= n; i++) if (tr[i].fa == -1) rt = i;
function<void(int)> dfs = [&](int u) {
dp[u][1] = tr[u].ha;
for (auto v : tr[u].adj) {
dfs(v);
dp[u][0] += max(dp[v][0], dp[v][1]);
dp[u][1] += dp[v][0];
}
};
dfs(rt);
cout << max(dp[rt][0], dp[rt][1]) << '\n';
}
H:到处都是0
题意:求1到N之间恰好包含K个非0数字的整数的数目
思路:用表示一共有 i 位,且最高位是 j ,一共有 k 位非零的可能方案数,则状态转移方程为
,用cnt表示已经出现过的非零位置的个数,考虑最高的k个不为零的位置,对于位置 i ,分别枚举该位置可能出现的数,
即为对应位置为 j 时的总方案数,求和即为最终答案
时间复杂度:
string s;
int k;
int dp[N][20][10];
void solve() {
for (int j = 0; j < 10; j++) dp[0][j][!!j] = 1;
for (int i = 0; i <= 100; i++) {
for (int j = 0; j < 10; j++) {
for (int k = 0; k < 10; k++) {
for (int l = 0; l < 4; l++) {
dp[i + 1][k][l + (!!k)] += dp[i][j][l];
}
}
}
}
cin >> s >> k;
int cnt = 0, ans = 0;
for (int i = 0; i < s.size(); i++) {
for (int j = 0; j < s[i] - '0'; j++) ans += dp[s.size() - 1 - i][j][k - cnt];
if (s[i] - '0') {
if (++cnt > k) break;
}
if (i == s.size() - 1 && cnt == k) ans++;
}
cout << ans << '\n';
}
I:绿豆蛙的归宿
题意:给一个有向无环连通图,求从结点1走到结点n的所经过的路径总长度期望是多少,从每一个顶点离开时走每条路的概率相同
思路:概率dp,用表示走到结点 u 的概率,则对于结点 u 和其子节点 v ,经过结点 v 的概率应该加上经过结点 u 的概率乘u的儿子个数的倒数,对应的期望应该增加这个概率乘边权,最终将所有点的期望相加即可。
时间复杂度:
int n, m;
double dp[N], e[N];
void solve() {
cin >> n >> m;
vector<vector<pair<int, int>>> ad(n + 1);
for (int i = 1; i <= m; i++) {
int a, b, c; cin >> a >> b >> c;
ad[a].push_back({b, c});
}
//bfs
queue<int> q;
q.push(1);
dp[1] = 1;
vector<int> vis(n + 1, 0);
while (q.size()) {
auto u = q.front(); q.pop();
if (vis[u]) continue;
else vis[u] = 1;
for (auto [v, len] : ad[u]) {
double p = dp[u] * (1.0 / ad[u].size());
dp[v] += p;
e[v] += p * len;
q.push(v);
}
}
double res = 0;
for (int i = 1; i <= n; i++) res += e[i];
printf("%.2lf\n", res);
}
J:Fibonacci
题意:求斐波那契数列第n项mod1e4的值
思路:将斐波那契数列的递推公式写成矩阵形式,,那么
就是
第一行第一列的元素,可以用矩阵快速幂求解。
时间复杂度:
struct mar {
int a[3][3];
mar operator * (const mar &s) const {
mar res;
memset(res.a, 0, sizeof res.a);
for (int i = 1; i <= 2; i++) {
for (int j = 1; j <= 2; j++) {
for (int k = 1; k <= 2; k++) {
res.a[i][j] = (res.a[i][j] + a[i][k] * s.a[k][j]) % mod;
}
}
}
return res;
}
}ans, base;
int n;
void init() {
base.a[1][1] = base.a[1][2] = base.a[2][1] = 1, base.a[2][2] = 0;
ans.a[1][1] = ans.a[1][2] = 1, ans.a[2][1] = ans.a[2][2] = 0;
}
void qpow(int b) {
while (b) {
if (b & 1) ans = ans * base;
base = base * base;
b >>= 1;
}
}
void solve() {
while (cin >> n, n != -1) {
if (n < 3) cout << !!n << '\n';
else {
init();
qpow(n - 2);
cout << ans.a[1][1] % mod << '\n';
}
}
}