文章目录
D - Largest Submatrix of All 1’s( POJ3494 )
题意:
给定n*m
的01矩阵,求全是1的最大子矩阵面积
思路:
单调栈,
对每一行都做一次单调栈求当前高度为最小的区间,(当前高度是从第一行到第i
行连续的有多少个1
,这个1要包括在当前这一行)
具体看代码实现.
然后就是普通的求面积了,只不过求了n次
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define eps 1e-9
#define INF 0x3f3f3f3f
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef unsigned int UI;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> P;
const int N = 2e3 + 10;
int h[N][N], st[N], top;
int a[N][N];
int n, m;
int L[N], R[N];
void get_LR(int i) {
top = 0;
h[i][0] = 0;
st[0] = 0;
for (int j = 1; j <= m; j++) {
while (top > 0 && h[i][j] <= h[i][st[top]]) top--;
L[j] = st[top] + 1;
st[++top] = j;
}
top = 0;
h[i][m + 1] = 0;
st[0] = m + 1;
for (int j = m; j >= 1; j--) {
while (top > 0 && h[i][j] <= h[i][st[top]]) top--;
R[j] = st[top] - 1;
st[++top] = j;
}
}
int main() {
while (~scanf("%d%d", &n, &m)) {
memset(h, 0,sizeof(h));
for (int i = 1; i <= n; i++) {
for (int j = 1, d; j <= m; j++) {
scanf("%d", &d);
h[i][j] = (d == 0 ? 0 : h[i - 1][j] + d);
}
}
// for (int i = 1; i <= n; i++) {
// for (int j = 1; j <= m; j++) {
// printf("%d ", h[i][j]);
// }
// printf("\n");
// }
int ans = 0;
for (int i = 1; i <= n; i++) {
get_LR(i);
for (int j = 1; j <= m; j++) {
ans = max(ans, h[i][j] * (R[j] - L[j] + 1));
}
}
printf("%d\n", ans);
}
return 0;
}
H - The Fewest Coins( POJ3260 ) 这题的鸽笼原理确定上界有点懵
题意:
有n种硬币,每种硬币分别有 c i c_i ci个,要买价值为T的物品,要使得自己支付的硬币数与卖家找回的硬币数之和最少
思路:
多重背包和完全背包
dp_pay[MAXT + MAXV * MAXV];
//付钱为i时的最少硬币数
dp_change[MAXT + MAXV * MAXV];
//找钱为i时的最少硬币数
所以 a n s = m i n ( d p p a y [ T + i ] + d p c h a n g e [ i ] ) ans = min(dp_pay[T + i]+dp_change[i]) ans=min(dppay[T+i]+dpchange[i])
因为找钱的时候硬币无限,所以一定是最优,完全背包
而付钱的时候硬币有限,求最小,用多重背包 (多重背包用二进制分解成01背包)
要用鸽笼原理确定dp上界 (emmmm)
上界: MAX_T + MAX_V * MAX_V
鸽笼原理:
意味着,要凑足(大于等于)价格T的商品且硬币数最少,最多只能多给max_v * max_v的金额(其中max_v为硬币的最大面值),称此上界为W。为什么会有这么紧的上界呢,假设存在一种最优支付方案,给了多于t + max_v * max_v的钱,那么商店就会找回多于max_v * max_v的钱,这些硬币的个数大于max_v。设这些硬币的面值分别为a_i,根据鸽笼原理的应用,硬币序列中存在至少两个子序列,这两个子序列的和分别都能被max_v整除。如果我们直接用长度更小的那个子序列换算为面值为max_v的硬币某整数个,再去替换母序列就能用更少的硬币买到商品,形成矛盾。
其实我还没搞懂。。。。。。只能意会一点点
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define eps 1e-9
#define INF 0x3f3f3f3f
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef unsigned int UI;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> P;
const int N = 110;
const int MAXT = 1e4 + 10;
const int MAXV = 120 + 1;
int max_v;
int n, T;
int v[N], c[N];
int dp_pay[MAXT + MAXV * MAXV]; //付钱为i时的最少硬币数
int dp_change[MAXT + MAXV * MAXV]; //找钱为i时的最少硬币数
//最终答案时min(dp_pay[i + T] + dp_change[i])
void dp_p(int n, int W) { //W是背包容量
memset(dp_pay, INF, sizeof(dp_pay));
dp_pay[0] = 0;
for (int i = 0; i < n; i++) {
int num = c[i]; //当前硬币的最大数量
for (int k = 1; num > 0; k <<= 1) { //二进制分解
int cnt = min(k, num);
for (int j = W; j >= v[i] * cnt; j--) {
dp_pay[j] = min(dp_pay[j], dp_pay[j - v[i] * cnt] + cnt);
}
num -= cnt;
}
}
}
void dp_c(int n, int W) {
memset(dp_change, INF, sizeof(dp_change));
dp_change[0] = 0;
for (int i = 0; i < n; i++) {
for (int j = v[i]; j <= W; j++) {
dp_change[j] = min(dp_change[j], dp_change[j - v[i]] + 1);
}
}
}
int main() {
scanf("%d%d", &n, &T);
max_v = 0;
for (int i = 0; i < n; i++) {
scanf("%d", &v[i]);
max_v = max(max_v, v[i]);
}
for (int i = 0; i < n; i++)
scanf("%d", &c[i]);
//pay 多重背包(转换成01背包)
dp_p(N, T + max_v * max_v);
dp_c(N, T + max_v * max_v);
int ans = INF;
for (int i = max_v * max_v; i >= 0; i--) {
ans = min(ans, dp_change[i] + dp_pay[T + i]);
}
if (ans == INF) {
ans = -1;
}
printf("%d\n", ans);
return 0;
}
Minimal Segment Cover(CF 1175E)
题意:
给定n条[l, r]
的线段,给m次[l, r]
的查询,每次求能覆盖该查询区间的最少线段数
思路:
倍增
dp[i][j]
表示从i
开始跳了
2
j
2^j
2j 次后能到达的最远距离
因为,这个每次跳之间互不干扰,所以能倍增的得到,
从i跳了两次相当于从跳了一次的地方再跳了一次
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define eps 1e-9
#define INF 0x3f3f3f3f
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef unsigned int UI;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> P;
const int N = 5e5 + 10;
int n, m, a[N];
int dp[N][22]; //dp[i][j]表示从i开始跳了2^j步能达到的最远距离
int main() {
scanf("%d%d", &n, &m);
int l, r, mmax;
for (int i = 0; i < n; i++) {
scanf("%d%d", &l, &r);
a[l] = max(a[l], r);
mmax = max(mmax, r);
}
for (int i = 1; i <= mmax; i++) a[i] = max(a[i], a[i - 1]);
for (int i = 0; i <= mmax; i++) dp[i][0] = a[i];
for (int j = 1; j <= 20; j++) {
for (int i = 0; i <= mmax; i++) {
dp[i][j] = dp[dp[i][j - 1]][j - 1]; //倍增,从i跳了两次相当于从跳了一次的地方再跳了一次
}
}
while (m--) {
int ans = 0;
scanf("%d%d", &l, &r);
for (int i = 20; i >= 0; i--) {
if (dp[l][i] < r) ans += 1 << i, l = dp[l][i];
}
if (a[l] >= r) printf("%d\n", ans + 1);
else printf("-1\n");
}
return 0;
}
玩具装箱(LOJ 10188)
题意:
题意
有n个物品,每个物品长度c[i]
将物品分为连续的几组,每组内部每个物品用空间1隔开
一组物品的长度为 i − j + ∑ i i < = j c [ i ] i - j + \sum_{i}^{i <=j}{c[i]} i−j+∑ii<=jc[i]
每组物品的价值为
(
每组物品的长度
−
L
)
2
(\text{每组物品的长度} - L)^2
(每组物品的长度−L)2, L
为常数
思路:
dp + 斜率优化
dp[i]
表示放入了c[i]
后的最优分组的价值
在这里我们总结一下,单调队列斜率优化的步骤:
1.弹队头,就是最左边的点.
2.放直线,算答案,得到当前状态的答案,得到新的待加入的点. (当前斜率>最左端的斜率就弹跳队首)
3.弹队尾,把插入新点之后不合法的点弹掉.最后加入新点就好了. (加入当前这个点后,会破坏凸包的下凹性,就弹出队尾)
(以上只针对本题的dp方程)
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define eps 1e-9
#define INF 0x3f3f3f3f
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef unsigned int UI;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> P;
const int maxn = 50005;
int q[maxn];
double A[maxn], B[maxn], dp[maxn], sum[maxn];
double X(int x) {return B[x]; }
double Y(int x) {return dp[x] + B[x] * B[x]; }
double nk(int a, int b) {
return (Y(a) - Y(b)) / (X(a) - X(b));
}
int main() {
int n, l;
scanf("%d%d", &n, &l);
for (int i = 1; i <= n; i++)
scanf("%lf", &sum[i]);
for (int i = 1; i <= n; i++) {
sum[i] += sum[i - 1];
A[i] = sum[i] + i;
B[i] = sum[i] + i + l + 1;
}
B[0] = l + 1; //B[0] = sum[0] + 0 + l + 1;
int head = 1, tail = 1;
for (int i = 1; i <= n; i++) {
while (head < tail && nk(q[head], q[head + 1]) < 2 * A[i])
head++;
int j = q[head];
dp[i] = dp[j] + (A[i] - B[j]) * (A[i] - B[j]);
while (head < tail && nk(i, q[tail - 1]) <= nk(q[tail - 1], q[tail]))
tail--;
q[++tail] = i;
}
printf("%lld\n", (ll)dp[n]);
return 0;
}
/*
5 4
3
4
2
1
4
*/
/*
1
*/
股票交易(计蒜客T2651)
题意:
T天内,每天的入股价为api, 出股价为bpi,每天至多入股asi,至多出股bsi
手里的钱无限,手中的股票最多为maxp ,求最后一天的最大收益
思路: dp
dp[i][j]
表示第i
天手中有j
张股票的最大收益。
- 第一次买股票 d p [ i ] [ j ] = − j ∗ a p i dp[i][j] = -j * api dp[i][j]=−j∗api (0 <= j <= asi)
- 什么都不干 d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j ] ) dp[i][j] = max(dp[i][j], dp[i - 1][j]) dp[i][j]=max(dp[i][j],dp[i−1][j])
- 买股票 d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − w − 1 ] [ k ] − ( j − k ) ∗ a p i ) dp[i][j] = max(dp[i][j], dp[i - w - 1][k] - (j - k) * api) dp[i][j]=max(dp[i][j],dp[i−w−1][k]−(j−k)∗api) (i > w && j - asi <= k <= j)
- 卖股票 d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − w − 1 ] [ k ] + ( k − j ) ∗ b p i ) dp[i][j] = max(dp[i][j], dp[i - w - 1][k] + (k - j) * bpi) dp[i][j]=max(dp[i][j],dp[i−w−1][k]+(k−j)∗bpi) (i > w && j <= k <= j + bsi)
前两种情况直接dp就行
后两种情况要用单调队列维护 d p [ i − w − 1 ] [ k ] + k ∗ a p [ i ] dp[i - w - 1][k] + k * ap[i] dp[i−w−1][k]+k∗ap[i] 和 d p [ i − w − 1 ] [ k ] + k ∗ b p [ i ] dp[i - w - 1][k] + k * bp[i] dp[i−w−1][k]+k∗bp[i]
(买)根据k的范围从小到大dp (卖)根据k的范围从大到小维护
具体看代码实现
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define eps 1e-9
#define INF 0x3f3f3f3f
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef unsigned int UI;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> P;
const int maxn = 50005;
int q[2001], l, r;
int dp[2001][2001];
int ap, bp, as, bs, mp, n, w, ans = 0;
int main () {
scanf("%d%d%d", &n, &mp, &w);
memset(dp, -INF, sizeof(dp));
for (int i = 1; i <= n; i++) {
scanf("%d%d%d%d", &ap, &bp, &as, &bs);
for (int j = 0; j <= as; j++) //第一次买股票
dp[i][j] = -1 * j * ap;
for (int j = 0; j <= mp; j++) //啥都不干
dp[i][j] = max(dp[i][j], dp[i - 1][j]);
if (i <= w) continue;
l = 1, r = 0; //单调队列清空
for (int j = 0; j <= mp; j++) { //买股票
int down = j - as;
while (l <= r && q[l] < down) l++;
while (l <= r && dp[i - w - 1][q[r]] + q[r] * ap <= dp[i - w - 1][j] + j * ap)
r--;
q[++r] = j;
if (l <= r)
dp[i][j] = max(dp[i][j], dp[i - w - 1][q[l]] + q[l] * ap - j * ap);
}
l = 1, r = 0;
for (int j = mp; j >= 0; j--) {
int up = j + bs;
while (l <= r && q[l] > up) l++;
while (l <= r && dp[i - w - 1][q[r]] + q[r] * bp <= dp[i - w - 1][j] + j * bp)
r--;
q[++r] = j;
if (l <= r)
dp[i][j] = max(dp[i][j], dp[i - w - 1][q[l]] + q[l] * bp - j * bp);
}
}
for (int i = 0; i <= mp; i++)
ans = max(ans, dp[n][i]);
printf("%d\n", ans);
return 0;
}