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

【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[N][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[N] << 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[N] << endl;
	}

	return 0;
}

【6】HDU 3669 Cross the Wall


题解:http://blog.csdn.net/qq_21841245/article/details/44461527


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值