Jeanny
寂兮,寥兮,独立不改,周行而不殆

#2116. 「ABC145E」吃大餐

0.首先这道题先想到枚举哪道菜是最后一道菜,至少剩一分钟它就可以吃。

1.最简单的方式是,转换题目,在整个过程中只能使用一次魔法,使得某一道菜编程1的时间吃完。

#include <bits/stdc++.h>
using namespace std;
int a[4000];
int b[4000];
int dp[4000][4000][2];

int main() {
    int n, t;
    cin >> n >> t;
    for (int i = 1; i <= n; i++) {
        cin >> a[i] >> b[i];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= t; j++) {
            dp[i][j][0] = dp[i - 1][j][0];
            dp[i][j][1] = dp[i - 1][j][1];
            if (j >= a[i]) {
                dp[i][j][0] = max(dp[i][j][0], dp[i - 1][j - a[i]][0] + b[i]);
                dp[i][j][1] = max(dp[i][j][1], dp[i - 1][j - a[i]][1] + b[i]);
            }
            if (j >= 1) {
                dp[i][j][1] = max(dp[i][j][1], dp[i - 1][j - 1][0] + b[i]);
            }
        }
    }
    cout << dp[n][t][1] << endl;
}
Code
2.第二种方法就是基于暴力枚举。进一步分析,可以预处理,用f[]表示前i道菜吃j的时间获得的价值,
用g[]表示后i道菜吃k的时间获得的价值。答案是max(f[i-1][j] + g[i+1][T-1-j] + a[i].v),打擂台
#include <bits/stdc++.h>
using namespace std;
int ans, g[3005][3005], f[3005][3005], n, T;
struct Node {
    int t, v;
} a[3005];
int main() {
    cin >> n >> T;
    for (int i = 1; i <= n; i++) cin >> a[i].t >> a[i].v;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= T; j++) {
            f[i][j] = f[i - 1][j];
            if (j >= a[i].t) {
                f[i][j] = max(f[i][j], f[i - 1][j - a[i].t] + a[i].v);
            }
        }
    }
    g[n + 1][0] = 0;
    for (int i = n; i >= 1; i--) {
        for (int j = 0; j <= T; j++) {
            g[i][j] = g[i + 1][j];
            if (j >= a[i].t) {
                g[i][j] = max(g[i][j], g[i + 1][j - a[i].t] + a[i].v);
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= T - 1; j++) {
            ans = max(ans, f[i - 1][j] + g[i + 1][T - j - 1] + a[i].v);
        }
    }
    cout << ans << endl;
    return 0;
}
/*
3 60
30 20
30 10
30 30
*/
Code
3.第三种方法是可变容量的背包问题,考虑贪心,依然考虑哪个菜是最后一道,对应背包总容量发生变化。
贪心策略:可以选择的菜越多对应的背包总容量尽可能大。
比如:T=8,a:3,4,10如果排序后顺序是10,4,3,则背包纵容量为8+10-1=17时,只能选择一个10道菜。背包总容量是8+4-1=11时,
能从10、4两道菜中选,当背包容量时10+3-1=12时,能从10、4、3三道菜中选。但显然更合理的方式是:背包容量最大为17时,
可以选择前三个菜。因此排序方式为从小到大。
#include <bits/stdc++.h>
using namespace std;
int ans, S, V, dp[3005][6005], n, T, s;
struct Node {
    int t, v;
} a[3005];
bool cmp(Node A, Node B) {
    if (A.t == B.t)
        return A.v > B.v;
    return A.t < B.t;
}
int main() {
    cin >> n >> T;
    for (int i = 1; i <= n; i++) cin >> a[i].t >> a[i].v;
    sort(a + 1, a + n + 1, cmp);
    for (int i = 1; i <= n; i++) {  // dp[i][j] 前i个,最后一次选择时容量不超过
        for (int j = 0; j <= T + a[i].t - 1; j++) {  // 100 3 4
            if (j >= a[i].t)
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - a[i].t] + a[i].v);
            else
                dp[i][j] = dp[i - 1][j];
            ans = max(ans, dp[i][j]);
        }
    }
    cout << ans << endl;
    return 0;
}
/*
4 40
15 9 20 7 10
35
61 - 40 = 21


10
10 1 1
>=1

*/
Code
#2213 开灯,还是开灯
1.DP方法:对于第i个灯,枚举前j个开关按或者不按,使得这个灯泡亮了的方案数,累加这个方案数。
#include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i <= (b); ++i)
#define mst(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;

const int N = 57, P = 1e9 + 7;
int t, n, m, ans, cas, dp[8][N], num, x;
ll sw[N];

void up(int &a, int b) {
    a += b;
    if (a >= P)
        a -= P;
}

int main() {
    t = 1;
    while (t--) {
        scanf("%d%d", &n, &m), mst(sw, 0), ans = 0;
        F(i, 1, m) {
            scanf("%d", &num);
            F(j, 1, num) scanf("%d", &x), sw[i] |= 1ll << x;
        }
        F(i, 1, n) {
            mst(dp, 0);
            dp[0][0] = 1;
            F(l, 0, m - 1) F(u, 0, 1) if (dp[u][l]) {
                if (sw[l + 1] & (1ll << i))
                    up(dp[u ^ 1][l + 1], dp[u][l]);  //按了
                else
                    up(dp[u][l + 1], dp[u][l]);  //按了
                up(dp[u][l + 1], dp[u][l]);      //没按
            }
            up(ans, dp[1][m]);
        }
        printf("%d\n", ans);
    }
    return 0;
}
Code

对于某一个灯,需要统计让它开了的方案数。公式是$\sum x_i$,即$X$,则为$\sum (x_1+x_2+...x_n)$ =>> $\sum x_1 + \sum x_2 + ... + \sum x_n$,也就是让$x_1、x_2、...x_n$亮。

#2236 开灯,还是开灯.对于$X^3$,则为$\sum (x_1+x_2+...x_n) * (x_1+x_2+...x_n) *(x_1+x_2+...x_n)$ =>> $\sum x_1*x_1*x_1、\sum x_1*x_1*x_2、\sum x_1*x_1*x_3、...\sum x_1*x_2*x_1 ... $,相当于我需要让$x_1*x_1*x_1、x_1*x_1*x_2、x_1*x_1*x_3、...x_1*x_2*x_1 ... $亮。这实际上是

for(i = 1; i <= n; i++)
    for(int j = 1; j <= n; j++)
        for(int k = 1; k <= n; k++)
查看代码
 #include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i <= (b); ++i)
#define mst(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;

const int N = 57, P = 1e9 + 7;
int t, n, m, ans, cas, dp[8][N], num, x;
ll sw[N];

void up(int &a, int b) {
    a += b;
    if (a >= P)
        a -= P;
}

int main() {
    t = 1;
    while (t--) {
        scanf("%d%d", &n, &m), mst(sw, 0), ans = 0;
        F(i, 1, m) {
            scanf("%d", &num);
            F(j, 1, num) scanf("%d", &x), sw[i] |= 1ll << x;
        }
        F(i, 1, n) F(j, 1, n) F(k, 1, n) {
            mst(dp, 0);
            dp[0][0] = 1;
            F(l, 0, m - 1) F(u, 0, 7) if (dp[u][l]) {
                int nxt = u;
                if (sw[l + 1] & (1ll << i))
                    nxt ^= 1;
                if (sw[l + 1] & (1ll << j))
                    nxt ^= 2;
                if (sw[l + 1] & (1ll << k))
                    nxt ^= 4;
                up(dp[nxt][l + 1], dp[u][l]);  //开
                up(dp[u][l + 1], dp[u][l]);    //关
            }
            up(ans, dp[7][m]);
        }
        printf("%d\n", ans);
    }
    return 0;
}

 

2.数学方法1: 预处理统计每一个灯,有几个开关控制它,那么从这些开关(x个)中选择一个、三个、五个都可以把它点亮,剩下的不控制它的开关

状态可开可关无所谓。预处理$C_x^p$(杨辉三角)。答案是ans += $ \sum C_x^p \times 2^{(m-x)} $ (p是奇数)

#include <bits/stdc++.h>

using namespace std;

int n, m, a[55], c[55][55];

int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int k;
        cin >> k;
        while (k--) {
            int x;
            cin >> x;
            a[x]++;
        }
    }
    for (int i = 0; i <= 50; i++)
        for (int j = 0; j <= i; j++)
            if (j == 0)
                c[i][0] = 1;
            else
                c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % int(1e9 + 7);
    int ans = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= a[i]; j += 2)
            ans = (ans + 1ll * c[a[i]][j] * ((1ll << m - a[i]) % int(1e9 + 7))) % int(1e9 + 7);
    cout << ans;
    return 0;
}
Code

 3.数学方法2: 变形这个公式$\sum C_x^p \times 2^{(m-x)} $ = $2^{(m-x)} \times (C_x^1 + C_x^2+ C_x^3...)$

= $2^{(m-x)} \times (1+1)^x $ =  $2^{(m-x)} \times 2^x$ = $2^m$。

只包含奇数,则答案是$2^{m - 1}$.

#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
int a[55];
int main() {
    int n, m, sum = 1;
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int x;
        cin >> x;
        int p;
        while (x--) cin >> p, a[p]++;
    }
    for (int i = 1; i < m; i++) {
        sum *= 2;
        sum %= mod;
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        if (a[i]) {
            ans += sum;
            ans %= mod;
        }
    }
    cout << ans << endl;
    return 0;
}
Code

 证明:n个数中选奇数个数的方案数是,$2^{(n-1)}$

$(1 + 1)^n = \sum_{k=0}^{n} C(n,k)$

$(1 - 1)^n = \sum_{k=0}^{n} C(n,k) (-1)^k$

如果n是偶数,两式相减, 保留下来(-1)^k,即k为奇数的项:
$ 2  \times {(C(n,1) + C(n,3) + C(n,5) + \ldots + C(n,n-1))} = 2^n$
 
#2147「ABC54D」配方比例
1.如何处理比例这个问题?a元素总和、b元素总和不到400,可以枚举。凡是可以拼成这个质量的,转移成功,否则依然是初值。
2.答案继续枚举a元素质量,比例已知,算出b元素,dp值非初值时,打擂台找代价最小的。
其中有一段优秀的代码模板,对于dp[i][]  <<=  dp[i-1]转移方式的处理:
// if (j >= v[i].a && k >= v[i].b)
//   dp[i][j][k] = min(dp[i - 1][j][k], dp[i - 1][j - v[i].a][k - v[i].b] + v[i].c);
// else
//   dp[i][j][k] = dp[i - 1][j][k];

dp[i][j][k] = dp[i-1][j][k];
if (j >= v[i].a && k >= v[i].b)
  dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j - v[i].a][k - v[i].b] + v[i].c);
查看代码
#include <bitsstdc++.h>
using namespace std;
typedef long long ll;
struct Node {
    int a, b, c;
} v[45];
int n, ma, mb, dp[45][405][405], ans = (1 << 30), A, B;
int main() {
    cin >> n >> ma >> mb;
    for (int i = 1; i <= n; i++) {
        cin >> v[i].a >> v[i].b >> v[i].c;
        A += v[i].a;
        B += v[i].b;
    }
    memset(dp, 0x3f, sizeof dp);
    dp[0][0][0] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= A; j++) {
            for (int k = 0; k <= B; k++) {
                // if (j >= v[i].a && k >= v[i].b)
                //     dp[i][j][k] = min(dp[i - 1][j][k], dp[i - 1][j - v[i].a][k - v[i].b] + v[i].c);
                // else
                //     dp[i][j][k] = dp[i - 1][j][k];
                dp[i][j][k] = dp[i-1][j][k];
                if (j >= v[i].a && k >= v[i].b)  
                    dp[i][j][k] = min(dp[i][j][k], dp[i - 1][j - v[i].a][k - v[i].b] + v[i].c);
            }
        }
    }
    for (int i = 1; i <= A; i++) {
        if ((i * mb) % ma == 0) {
            int j = (i * mb) / ma;
            ans = min(dp[n][i][j], ans);
        }
    }
    if (ans > 4000)
        cout << -1 << endl;
    else
        cout << ans << endl;
    return 0;
}
 
#1132土豆先生

1.30%的数据,可以dfs,这些袋土豆选或者不选可以达到L袋,打擂台求最优值。

2.分析题目可以写出公式:

$\frac{\sum c_i}{\sum a_i} \times \frac{C -\sum c_i}{A - \sum a_i}$
其中C表示土豆总价值,A表示土豆总数量。

两个未知数的情况下,可以枚举之一,发现$a_i$更小,作为首选。
此时想让分母应该尽可能小,通过数学分析发现应该让$\sum c_i$尽可能大或者小。
外加一条件:$\sum a_i$正好$L$袋。可以做动态规划:

dp[i][j][k]表示前i袋选择了j袋,正好凑成k个土豆的最小/大价值。

答案:枚举k,当dp[i][j][k]合法时,打擂台求最小:
$dp[i][j][k]/k \times (C-dp[i][j][k])/(A-k)$

时间复杂度:$100\times 100\times 500$

查看代码
#include <bits/stdc++.h>
// #define int long long
#define ll long long
using namespace std;
int n, L, a[105], c[105], suma, sumc, f[105][105][505], g[105][105][505];
ll mn(ll x, ll y) {
    if (y == -1)
        return x;
    else
        return min(x, y);
}
int main() {
    cin >> n >> L;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        suma += a[i];
    }
    for (int i = 1; i <= n; i++) {
        cin >> c[i];
        sumc += c[i];
    }
    memset(f, 0xff, sizeof f);
    for (int i = 0; i <= n; i++) f[i][0][0] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= L; j++) {
            for (int k = 1; k <= suma; k++) {
                if (k < a[i] || f[i - 1][j - 1][k - a[i]] == -1)
                    f[i][j][k] = f[i - 1][j][k];
                else
                    f[i][j][k] = max(f[i - 1][j - 1][k - a[i]] + c[i], f[i - 1][j][k]);  //要 or buyao
            }
        }
    }
    memset(g, 0xff, sizeof g);
    for (int i = 0; i <= n; i++) g[i][0][0] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= L; j++) {
            for (int k = 1; k <= suma; k++) {
                if (g[i - 1][j - 1][k - a[i]] == -1 || a[i] > k)
                    g[i][j][k] = g[i - 1][j][k];
                else
                    g[i][j][k] = mn(g[i - 1][j - 1][k - a[i]] + c[i], g[i - 1][j][k]);
            }
        }
    }
    // cout<<"?? "<<f[n][1][3]<<endl;
    double ans = 2e17;
    for (int x = 1; x <= suma; x++) {
        if (f[n][L][x] != -1) {
            // cout<<"ddd: "<<x<<" "<<f[n][L][x]<<" "<<endl;
            ans = min(ans, f[n][L][x] * (double)1.0 / x * (sumc - f[n][L][x]) / (suma - x));
        }
        if (g[n][L][x] != -1)
            ans = min(ans, g[n][L][x] * (double)1.0 / x * (sumc - g[n][L][x]) / (suma - x));
    }
    printf("%0.3lf\n", ans);
    return 0;
}

#2057 猫猫的CPU缓存

题目简述:给你 $m$ 个 $1$ 到 $n$ 之间的整数,你要找到若干个大小为固定的 $k$ 的闭区间,使得所有这些数都在你找到的某个区间内。你需要最小化这些区间的并集的大小,并输出此大小。本题里区间/区间并集的大小,被定义为,这个区间/区间并集里连续整数的个数。例如,区间是[3,4,7],3、4、7在一个区间,这个区间大小是5(3、4、5、6、7).

题目分析:

我们发现最终,这些整数的分布是这样的: [o o  o] [o   o o  ] [o  o o] [o o    ] 

当a[j]~a[i]的区间长度大于k的时候,可以由若干个重合的闭区间合成并集,区间大小是a[i]-a[j]+1

小于k的时候, [o o    ] ,区间大小依然是k

dp方程为:

if (a[i] - a[j + 1] + 1 >= K) dp[i] = min(dp[j] + (a[i] - a[j+1] + 1), dp[i]);
else dp[i] = min(dp[j] + K, dp[i]);

暴力代码:

查看代码
 #include <bits/stdc++.h>
using namespace std;
int n, m, K, a[300005], dp[300005];
int main() {
    int n, K, m;
    cin >> n >> K >> m;
    for (int i = 1; i <= m; i++) {
        scanf("%d", &a[i]);
    }
    sort(a + 1, a + m + 1);
    memset(dp, 0x3f, sizeof dp);
    dp[0] = 0;
    for (int i = 1; i <= m; i++) {
        for (int j = 0; j <= i; j++) {
            if (a[i] - a[j + 1] + 1 >= K)
                dp[i] = min(dp[j] + a[i] - a[j + 1] + 1, dp[i]);
            else
                dp[i] = min(dp[j] + K, dp[i]);
        }
    }
    cout << dp[m] << endl;
    return 0;
}

观察 dp[i] = min(dp[j] + (a[i] - a[j+1] + 1), dp[i]) 

变形:

if (a[i] - a[j + 1] + 1 >= K) dp[i] = (dp[j] - a[j+1] )+ (a[i] +1)

dp[i]和dp[j]相关,可以把a[i]+1看做常数

随着i变大,a[j+1] 到a[i]的距离扩大,那么dp[j] -a[j+1] = D[j],可以在这段变化的过程中求最小值,因此可以尺取。

 

查看代码
 #include <bits/stdc++.h>
#define M 300005
using namespace std;
int a[M], dp[M];
int main() {
    // freopen("cpu.in","r",stdin);
    // freopen("cpu.out","w",stdout);
    int n, K, m;
    cin >> n >> K >> m;
    for (int i = 1; i <= m; i++) {
        scanf("%d", &a[i]);
        dp[i] = 1e9;
    }
    sort(a + 1, a + m + 1);
    dp[0] = 0;
    int j = 0, mn = 1e9;
    for (int i = 1; i <= m; i++) {
        while (a[i] - a[j + 1] + 1 > K && j < i) {
            mn = min(mn, dp[j] - a[j + 1]);
            j++;
        }
        dp[i] = min(mn + a[i] + 1, dp[j] + K);
    }
    printf("%d\n", dp[m]);
    return 0;
}
posted on 2025-04-29 10:13  Jeanny  阅读(35)  评论(0)    收藏  举报