Codeforces 15D Map 单调队列+构造

36 篇文章 0 订阅
11 篇文章 0 订阅

题目链接:http://codeforces.com/problemset/problem/15/D

题意:给定n*m的矩阵,每个点都是一个地基,上面的数字表示该地基的高度。

再给定a*b的小房子,要把a*b放在这个矩阵上,显然建房子要保证选取的地基高度一致。

若不一致,则要把选取的a*b大的矩阵中所有地基都挖低使得和其中一块最矮的地基高度一样,花费是挖的高度和

操作:

选取当前花费最小的一块矩阵,在上面建房子,若有多个方案花费一样,则优先选左上角的。

问:

建的房子的左上角坐标和建该房子的花费

思路:

显然我们要先求出选取每个点时,这个点的地基。用单调队列求一次行的,再在这基础上求一次列的就能得到这个点的地基。

然后把所有点都扔到堆里,跑出解即可。


双端队列的简单用法:

deque<int>q;

q.empty();

q.pop_front(); q.pop_back();

q.push_back(i); q.push_front(i);

单调队列:队尾->队首,队列中存的不是数组的元素,而是数组的下标

单调队列求最小就用递增,对于某一个数要求出这个数u到u+len-1的长度内的最小值:

对于i点,则要求出i点的最小值,我们不能直接求[ i, i+len-1]的值,而是求出[i-len+1, i]的最小值,然后转移到上述的 [u,u+len-1] 上

继续讨论对于i点在单调队列中的求解

1、则队尾元素必须是 [i-len+1,i]区间上的,所以 q.back()>=i-len+1,即->当{ q.back()+len-1<i } 时要把q.back()弹掉

2、要保持单调递增,所以当把这个元素x[i]加入队列时, q.front()必须<=x[i], 所以当 { q.front()>x[i] } 时要把q.front()弹掉

3、此时加入x[i],则此队列的数都在 [i-len+1,i]区间上且元素都是单调递增的,最小的元素是 x[q.back()]

4、显然x[i]的最小值就是 x[q.back()] ,到此计算出x[i]的最小值了

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<set>
#include<vector>
#include<map>
#include<math.h>
#include<queue>
#include<string>
#include<stdlib.h>
#include<algorithm>
using namespace std;
#define N 1015
#define LL __int64
#define ll int
ll x[N], dou[N];
void work(ll len, ll top){ 
	deque<ll>q;// q.front() -> q.back()
	for(ll i = 1; i <= top; i++) {
		while(!q.empty() && q.front() + len <= i)q.pop_front();
		while(!q.empty() && x[i] <= x[q.back()]) q.pop_back();
		q.push_back(i);
		dou[i] = x[q.front()];
	}
}
ll n, m, a, b;
struct node{
	ll x,y;
	LL val;
	node(ll a1=0,ll a2=0,LL a3=0):x(a1),y(a2),val(a3){}
	bool operator<(const node& Node) const{
		if(Node.val==val)
		{
			if(Node.x==x) return Node.y<y;
			else return Node.x<x;
		}
		else return Node.val<val;
	}
};
priority_queue<node>q;
vector<node>ans;
bool use[N][N];
void go(){
	ans.clear();
	while(!q.empty()){
		node u =q.top(); q.pop();
		if(use[u.x][u.y] || use[u.x+a-1][u.y+b-1] || use[u.x][u.y+b-1] || use[u.x+a-1][u.y])continue;
		ans.push_back(u);
		for(ll i = u.x; i < u.x+a; i++)
			for(ll j = u.y; j < u.y+b; j++)
				use[i][j] = 1;
	}
	ll siz = ans.size();
	printf("%d\n",siz);
	for(ll i = 0; i < siz; i++) printf("%d %d %I64d\n",ans[i].x,ans[i].y,ans[i].val);
}
ll val[N][N], mp[N][N];
LL sum[N][N];
int main(){
	ll i,j;
	while(~scanf("%d %d %d %d",&n,&m,&a,&b)) {
		for(i=1;i<=n;i++)for(j=1;j<=m;j++)
		{
			scanf("%d",&mp[i][j]);
			sum[i][j] = (LL)mp[i][j] + sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
		}
		for(i=1;i<=n;i++) {
			memcpy(x, mp[i], sizeof x);
			work(b, m);
			for(j=1;j<=m;j++) val[i][j] = dou[j];
		}
		for(i=b;i<=m;i++) {
			for(j=1;j<=n;j++) x[j] = val[j][i];
			work(a, n);
			for(j=1;j<=n;j++) val[j][i] = dou[j];
		}
		for(i=a; i<=n; i++)
			for(j=b;j<=m;j++)
				q.push(node(i-a+1,j-b+1,sum[i][j]-sum[i-a][j]-sum[i][j-b]+sum[i-a][j-b]-(LL)a*(LL)b*(LL)val[i][j]));
		go();
	}
	return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值