#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;
}
#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 */
#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 */
#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; }
对于某一个灯,需要统计让它开了的方案数。公式是$\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;
}
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;
}
证明: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$
// 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;
}
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;
}

浙公网安备 33010602011771号