您的位置:首页 > 其它

【整理】斜率or单调队列优化dp

2015-03-18 21:16 441 查看

【1】HDU2993 MAX Average Problem

题意:求一个序列的子区间满足长度大于k且所有数平均值最大

周源论文里的题。。之前有人说周源讲的是错的 其实应该是没什么问题的 可以O(n)求出
不过这题hdu上的数据不知道怎么了 = = 反正我在网上找的ac代码们全都TLE。。。。
总之……意思明白就好 反正也是入门题……

#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cmath>

using namespace std;

int read()
{
int sign = 1, n = 0; char c = getchar();
while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
return sign*n;
}

int N, K;
int a[100005];
double sum[100005];

namespace queue{
int q[100005], head, tail;

inline void init(){ head = 1; tail = 0; }
inline void push_back(int x){ q[++tail] = x; }
inline void pop_back() { --tail; }
inline void pop_front() { ++head; }
inline int front() { return q[head]; }
inline int second() { return q[head + 1]; }
inline int back() { return q[tail]; }
inline int before() { return q[tail - 1]; }
inline int size() { return tail - head + 1; }
}

inline double get_k(int i, int j){ return (sum[i] - sum[j]) / (i - j); }

int main()
{
while(~scanf("%d%d", &N, &K))
{
for(int i = 1; i <= N; ++i)
{
a[i] = read();
sum[i] = sum[i - 1] + a[i];
}

using namespace queue;
double ans = 1e-100; init();

for(int i = K; i <= N; ++i)
{
int now = i - K;
while (size() > 1 && get_k(back(), before()) >= get_k(now, back()) ) pop_back();
push_back(now);
while (size() > 1 && get_k(front(), i) <= get_k(second(), i)) pop_front();
ans = fmax(ans, get_k(front(), i));
}

printf("%.2f\n", ans);
}
return 0;
}


【2】HDU3480 Division

题意:把一个集合 分成若干个子集合 每个集合的cost为最大值与最小值差的平方 求最大cost的最小值

和之前摩天轮那道题是一样的。。先排个序 很明显每个集合是要取连续的一段区间
那么dp[i][j]表示前i个人分成了j个区间的最大cost的最小值。。于是dp[i][j] = max{ dp[k-1][j-1] + (sum[i] - sum[k]) ^ 2 }
然后展开以后就可以斜率优化了……还有就是第二维可以滚动。。

说实话这题写的我略痛苦。。因为还是不太熟TAT

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

int read()
{
int sign = 1, n = 0; char c = getchar();
while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
return sign*n;
}

const int Nmax = 10005;

int N, M;
int a[Nmax], sqr[Nmax];
int dp[Nmax][2], G[Nmax];

namespace queue{
int q[100005], head, tail;

inline void init(){ head = 1; tail = 0; }
inline void push_back(int x){ q[++tail] = x; }
inline void pop_back() { --tail; }
inline void pop_front() { ++head; }
inline int front() { return q[head]; }
inline int second() { return q[head + 1]; }
inline int back() { return q[tail]; }
inline int before() { return q[tail - 1]; }
inline int size() { return tail - head + 1; }
}

inline double getk(int i, int j)
{
if(a[i] == a[j]) return 1e200;
return 1. * (G[i] - G[j]) / (a[i] - a[j]);
}

int main()
{
for(int T = read(), cas = 0; T--; )
{
N = read(), M = read();
for(int i = 1; i <= N; ++i)
{
a[i] = read();
dp[i][0] = 0x3f3f3f3f;
}

sort(a + 1, a + N + 1);
for(int i = 1; i <= N; ++i) sqr[i] = a[i] * a[i];

using namespace queue;
for(int j = 1; j <= M; ++j)
{
int nw = j & 1, pr = nw ^ 1;
init();
for(int i = j; i <= N; ++i)
{
G[i] = dp[i - 1][pr] + sqr[i];
while(size() > 1 && getk(before(), back()) >= getk(back(), i)) pop_back();
push_back(i);

while(size() > 1 && getk(front(), second()) <= 2. * a[i]) pop_front();
dp[i][nw] = G[front()] + sqr[i] - 2 * a[i] * a[front()];
}
}

printf("Case %d: %d\n", ++cas, dp
[M & 1]);
}
return 0;
}


【3】HDU3530 Subsequence

题意:找出一个数列中的最长的一个子区间 满足最大值和最小值之差在[M, K]之间

开两个单调队列一个维护递增一个维护递减就可以了。。然后每次判断队首合不合法。。。并更新答案。。。
这题我最开始队列初始化为a[1]就一直会挂 = =我也不知道为什么


#include <cstdio>
#include <iostream>
#include <cstdlib>

using namespace std;

int read()
{
int sign = 1, n = 0; char c = getchar();
while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
return sign*n;
}

int N, M, K;
int a[100005];

struct queue{
int Q[100005], head, tail;

inline void init(){ head = 1; tail = 0; }
inline void push_back(int x){ Q[++tail] = x; }
inline void pop_back() { --tail; }
inline void pop_front() { ++head; }
inline int front() { return Q[head]; }
inline int back() { return Q[tail]; }
inline int size() { return tail - head + 1; }
}q[2];

int main()
{
while(~scanf("%d%d%d", &N, &M, &K))
{
for(int i = 1; i <= N; ++i) a[i] = read();

int left = 0, ans = 0; q[0].init(); q[1].init();
for(int i = 1; i <= N; ++i)
{
while(q[0].size() && a[q[0].back()] <= a[i]) q[0].pop_back();
q[0].push_back(i);
while(q[1].size() && a[q[1].back()] >= a[i]) q[1].pop_back();
q[1].push_back(i);

while(a[q[0].front()] - a[q[1].front()] > K)
{
if(q[0].front() < q[1].front()){ left = q[0].front(); q[0].pop_front(); }
else { left = q[1].front(); q[1].pop_front(); }
}

if(a[q[0].front()] - a[q[1].front()] >= M) ans = max(ans, i - left);
}
printf("%d\n", ans);
}

return 0;
}

【4】HYSBZ1911 特别行动队

题意:……中文题就不写了……

然后这题的公式还是蛮好推的。。但是因为特别长所以要注意细节。。。
A是小于0的 我们要求的是最大值 那么就维护一个上凸包(其实也可以转化成下凸包。。都一样)
f[i] = max( f[j] + a*(s[i]-s[j])^2 + b*(s[i]-s[j]) + c) 
     = ( f[j] + a*s[j]^2  - b*s[j] ) + ( a*s[i]^2 +b*s[i]+c )  - ( 2*a*s[i]*s[j] )
分三块。。很明显是不是。。

#include <cstdio>
#include <iostream>

using namespace std;

typedef long long LL;

const int Nmax = 1000005;
int N;
LL a[Nmax], sum[Nmax], sqr[Nmax], A, B, C;
LL dp[Nmax], F[Nmax];

namespace queue{
int q[Nmax], head, tail;

inline void init(){ head = 1; tail = 0; }
inline void push_back(int x){ q[++tail] = x; }
inline void pop_back() { --tail; }
inline void pop_front() { ++head; }
inline int front() { return q[head]; }
inline int second() { return q[head + 1]; }
inline int back() { return q[tail]; }
inline int before() { return q[tail - 1]; }
inline int size() { return tail - head + 1; }
}

double get_k(int i, int j)
{
if(sum[i] == sum[j]) return 1e200;
return 1. * (F[i] - F[j]) / (sum[i] - sum[j]);
}

int main()
{
ios::sync_with_stdio(false);
cin >> N >> A >> B >> C;
for(int i = 1; i <= N; ++i)
{
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
sqr[i] = sum[i] * sum[i];
dp[i] = A * sqr[i] + B * sum[i] + C;
}

using namespace queue; init();
for(int i = 1; i <= N; ++i)
{
int K = 2 * A * sum[i];
while(size() > 1 && get_k(front(), second()) >= K) pop_front();
dp[i] = max(dp[i], F[front()] + dp[i] - 2 * A * sum[front()] * sum[i]);
F[i] = dp[i] + A * sqr[i] - B * sum[i];
while(size() > 1 && get_k(before(), back()) <= get_k(back(), i)) pop_back();
push_back(i);
}

cout << dp
<< endl;

return 0;
}

【5】HDU 3507 Print Article

题意:把N个字母分成若干行 每个字母有个ci 然后每行的cost为sigma(ci) + M 求总cost的最小值

和上面那题差不多 而且公式还简单些 = =

#include <cstdio>
#include <iostream>

using namespace std;

typedef long long LL;

const int Nmax = 500005;
int N, M;
LL a[Nmax], sum[Nmax], sqr[Nmax];
LL dp[Nmax], F[Nmax];

namespace queue{
int q[Nmax], head, tail;

inline void init(){ head = 1; tail = 0; q[head] = 0;}
inline void push_back(int x){ q[++tail] = x; }
inline void pop_back() { --tail; }
inline void pop_front() { ++head; }
inline int front() { return q[head]; }
inline int second() { return q[head + 1]; }
inline int back() { return q[tail]; }
inline int before() { return q[tail - 1]; }
inline int size() { return tail - head + 1; }
}

double get_k(int i, int j)
{
if(sum[i] == sum[j]) return 1e200;
return 1. * (F[i] - F[j]) / (sum[i] - sum[j]);
}

int main()
{
ios::sync_with_stdio(false);
while(cin >> N >> M)
{
for(int i = 1; i <= N; ++i)
{
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
sqr[i] = sum[i] * sum[i];
dp[i] = sqr[i] + M;
}

using namespace queue; init();
for(int i = 1; i <= N; ++i)
{
int K = 2 * sum[i];
while(size() > 1 && get_k(front(), second()) <= K) pop_front();
dp[i] = min(dp[i], F[front()] + dp[i] - 2 * sum[front()] * sum[i]);
F[i] = dp[i] + sqr[i];
while(size() > 1 && get_k(before(), back()) >= get_k(back(), i)) pop_back();
push_back(i);
}

cout << dp
<< endl;
}

return 0;
}

【6】HDU 3669 Cross the Wall

题解:http://blog.csdn.net/qq_21841245/article/details/44461527
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: