url: https://www.luogu.com.cn/problem/P2851

tag:
动态规划,背包DP,进制,USACO,2006

思路:
可以用背包来做。用 f[i] 表示john付i块钱时最少的硬币数,用 g[i] 表示店主找零i块钱时最少的硬币数,对于f来说,因为硬币数量有限所以需要用多重背包来完成。而对于g来说因为硬币无限多,所以可以用完全背包。对于体积上限取多少可以参考: https://www.luogu.com.cn/article/sd0bx6un

代码:

#include <iostream>  
#include <cstring>  
#include <cstdio>  
#include <algorithm>  
using namespace std;  
typedef long long LL;  
const int N = 10010, M = 150;  
int c[M], v[M];  
int f[N + M * M], g[N + M * M];  
int n, t, mx, sum;  
int main()  
{  
    cin >> n >> t;  
    for (int i = 1; i <= n; i ++)  
    {  
        cin >> v[i];  
        mx = max(mx, v[i] * v[i]);  
    }  
    for (int i = 1; i <= n; i ++)  
    {  
        cin >> c[i];  
        sum += v[i] * c[i];  
    }  
    if (sum < t)  
    {  
        cout << -1 << endl;  
        return 0;  
    }  
    memset(f, 0x3f, sizeof f);  
    memset(g, 0x3f, sizeof g);  
    g[0] = 0;  
    f[0] = 0;  
    for (int i = 1; i <= n; i ++)  
        for (int j = v[i]; j <= mx; j ++)  
            g[j] = min(g[j], g[j - v[i]] + 1);  
    for (int i = 1; i <= n; i ++)  
    {  
        for (int j = 1; j <= c[i]; j *= 2)  
        {  
            for (int k = mx + t; k >= j * v[i]; k --)  
                f[k] = min(f[k], f[k - j * v[i]] + j);  
            c[i] -= j;  
        }  
        if (c[i])  
            for (int k = mx + t; k >= c[i] * v[i]; k --)  
                f[k] = min(f[k], f[k - c[i] * v[i]] + c[i]);  
    }  
    int res = 0x3f3f3f3f;  
    for (int i = t; i <= t + mx; i ++)  
        res = min(res, f[i] + g[i - t]);  
    cout << (res == 0x3f3f3f3f ? -1 : res) << endl;  
    return 0;  
}

url: https://www.luogu.com.cn/problem/P4158

tag:
动态规划,递推,枚举,背包DP

思路:
f[i][j] 表示前i块木板粉刷j次最多的正确次数。用 g[i][j][k] 表示第i块木板粉刷j次粉刷前k个格子时最多的正确次数。用 sum[i][j] 表示第i块木板,前j个格子中需要涂成蓝色的有几个。
所以可以知道状态转移方程为 f[i][j] = max(f[i][j], f[i - 1][j - k] + g[i][k][m])
g[i][j][k] = max(g[i][j][k], g[i][j - 1][q] + max(sum[i][k] - sum[i][q], k - q - sum[i][k] + sum[i][q])) 最后遍历不同种粉刷次数中最多的粉刷格子数。

代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
int f[51][2550], sum[51][2550];
int g[51][2550][51];
int n, m, t;
char s[100];
int main()
{
    cin >> n >> m >> t;
    for (int i = 1; i <= n; i ++)
    {
        cin >> s;
        for (int j = 1; j <= m; j ++)
        {
            if (s[j - 1] == '1') sum[i][j] = sum[i][j - 1] + 1;
            else sum[i][j] = sum[i][j - 1];
        }
    }
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= m; j ++)
            for (int k = 1; k <= m; k ++)
                for (int q = j - 1; q < k; q ++)
                    g[i][j][k] = max(g[i][j][k], g[i][j - 1][q] + max(sum[i][k] - sum[i][q], k - q - sum[i][k] + sum[i][q]));
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= t; j ++)
            for (int k = 0; k <= min(j, m); k ++)
                f[i][j] = max(f[i][j], f[i - 1][j - k] + g[i][k][m]);
    int res = 0;
    for (int i = 1; i <= t; i ++) res = max(res, f[n][i]);
    cout << res << endl;

    return 0;
}

url: https://www.luogu.com.cn/problem/CF149D

tag:
搜索,记忆化搜索,区间DP

思路:
使用 f[l][r][i][j] 表示区间lr之间li色,rj色时的染色方案数。有两种情况,当 l 和 r 配对时,可以由 l + 1 到 r - 1更新过来。如果不配对,由 l 到 第一个配对的括号这段区间和由那个括号后面的括号到 r 这段区间的方案数相乘。注意如果相邻的括号都有染色且颜色一样需要跳过。初始化是当 l + 1 == r 时,此时区间只有两个括号,对应的四个情况都只有一种方案。最后dfs之后将所有可能的答案相加并取模,然后输出即可。

代码:

#include <iostream>  
#include <cstdio>  
#include <cstring>  
#include <algorithm>  
#include <stack>  
using namespace std;  
const int N = 800;  
const int mod = 1000000007;  
char s[N];  
stack<int> stk;  
int rightt[N];  
int f[N][N][5][5];  
void dfs(int l, int r)  
{  
    if (l + 1 == r)  
    {  
        f[l][r][0][1] = f[l][r][0][2] = f[l][r][1][0] = f[l][r][2][0] = 1;  
        return;  
    }  
    if (r == rightt[l])  
    {  
        dfs(l + 1, r - 1);  
        for (int i = 0; i <= 2; i ++)  
            for (int j = 0; j <= 2; j ++)  
            {  
                if (j != 1) f[l][r][0][1] += f[l + 1][r - 1][i][j], f[l][r][0][1] %= mod;  
                if (j != 2) f[l][r][0][2] += f[l + 1][r - 1][i][j], f[l][r][0][2] %= mod;  
                if (i != 1) f[l][r][1][0] += f[l + 1][r - 1][i][j], f[l][r][1][0] %= mod;  
                if (i != 2) f[l][r][2][0] += f[l + 1][r - 1][i][j], f[l][r][2][0] %= mod;  
            }  
    }  
    else  
    {  
        dfs(l, rightt[l]), dfs(rightt[l] + 1, r);  
        for (int i = 0; i <= 2; i ++)  
            for (int j = 0; j <= 2; j ++)  
                for (int p = 0; p <= 2; p ++)  
                    for (int q= 0; q <= 2; q ++)  
                    {  
                        if (j == 1 && p == 1 || j == 2 && p == 2) continue;  
                        f[l][r][i][q] += (f[l][rightt[l]][i][j] * f[rightt[l] + 1][r][p][q] % mod);  
                        f[l][r][i][q] %= mod;  
                    }  
    }  
}  
int main() {  
    scanf("%s", s + 1);  
    int n = strlen(s + 1);  
    for (int i = 1; i <= n; i++)  
    {  
        if (s[i] == '(') stk.push(i);  
        else  
        {  
            rightt[i] = stk.top();  
            rightt[stk.top()] = i;  
            stk.pop();  
        }  
    }  
    dfs(1, n);  
    int res = 0;  
    for (int i = 0; i <= 2; i ++)  
        for (int j = 0; j <= 2; j ++)  
            res += f[1][n][i][j], res %= mod;  
    cout << res << endl;  
    return 0;  
}

问题描述

小R有nn部电脑,每部电脑的电池容量分别为aiai​。她可以使用两种不同的充电方式来给电脑充电:

  1. 普通充电:每单位时间为电脑充电xx单位的电量。
  2. 闪充:每单位时间为电脑充电4x4x单位的电量。

现在,所有电脑的电量都为零。小R希望使用闪充给所有电脑充满电,计算她需要的总充电时间。请保留结果的小数点后两位。


测试样例

样例1:

输入:n = 4 ,x = 1 ,a = [2, 3, 4, 5]
输出:'3.50'

样例2:

输入:n = 3 ,x = 2 ,a = [4, 6, 8]
输出:'2.25'

样例3:

输入:n = 2 ,x = 1 ,a = [10, 5]
输出:'3.75'

代码:

string solution(int n, int x, vector<int> a) {
    int res = 0;
    for (int i = 0; i < n; i ++) res += a[i];
    double ans = 1.0 * res / (x * 4); 
    ostringstream oss;
    oss << fixed << setprecision(2) << ans;
    return oss.str();
}

解释:

  1. 返回值问题:使用 ostringstream 将浮点数格式化为保留两位小数的字符串,并返回该字符串。
  2. 浮点数格式化:使用 iomanip 库中的 fixed 和 setprecision 来格式化浮点数。

url: https://www.luogu.com.cn/problem/P1453

tag:
动态规划,图论,基环树,树形DP

思路:
基环树是一种特殊的图结构,可以看作是在一棵树的基础上加入一条额外的边,从而使图中恰好出现一个环,也称作单环图唯一环图(unicyclic graph)。其主要特点包括:

  1. 连通性:基环树是连通图,任意两个顶点之间都有路径相连。
  2. 唯一的环:整个图中仅存在一个简单环,其他部分均保持树的结构(即无环)。
  3. 分解思想:可以将基环树看作由一个环和附着在环上各个顶点上的若干棵树构成。对于很多算法(例如树形 DP),通常先对环进行特殊处理(比如“断环”),再处理附着的树结构。

这种结构在算法竞赛中经常出现,处理时通常需要先找出环,然后考虑两种或多种情况(例如环上某些节点选或不选),从而在保证整体方案最优的前提下完成状态转移和求解。

这道题目其实和求树的最小覆盖问题类似,我们用 f[u][0/1] 表示节点u为根且选或者不选时的总共人流量。只是因为有基环树,所以需要先断边,然后分别用那一边的两个点为根且不选(方便另外一个点可选)时各跑一遍dfs,求答案,最后两个答案求一个最大值并乘上k,注意,对于不同的情况来说需要先将f数组清空(因为可选的点不同了)。

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int h[N], e[N * 2], ne[N * 2], idx;
int p[N];
bool st[N];
double k;
int n, s1, s2, flag;
int f[N][2];
void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++;
}
void dfs1(int u, int fa)
{
    st[u] = 1;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int v = e[i];
        if (v == fa) continue;
        if (st[v])
        {
            s1 = u, s2 = v;
            flag = true;
            return;
        }
        dfs1(v, u);
        if (flag) return ;
    }
    st[u] = 0;
}
void dfs2(int u, int fa)
{
    f[u][1] = p[u];
    for (int i = h[u]; ~i; i = ne[i])
    {
        int v = e[i];
        if (v == fa) continue;
        if ((u == s1 && v == s2) || (u == s2 && v == s1)) continue;
        dfs2(v, u);
        f[u][1] += f[v][0];
        f[u][0] += max(f[v][0], f[v][1]);
    }
}
int main()
{
    memset(h, -1, sizeof h);
    cin >> n;
    for (int i = 0; i < n; i ++) cin >> p[i];
    int a, b;
    for (int i = 0; i < n; i ++)
    {
        cin >> a >> b;
        add(a, b), add(b, a);
    }
    cin >> k;
    dfs1(0, 0);
    dfs2(s1, s1);
    int res1 = f[s1][0];
    memset(f, 0, sizeof f);
    dfs2(s2, s2);
    int res2 = f[s2][0];
    printf("%.1f", max(res1, res2) * k);
    return 0;
}