HDU 3698 Let the light guide us (DP+线段树) #by Plato

http://acm.hdu.edu.cn/showproblem.php?pid=3698


HDU 3698 Let the light guide us (DP+线段树) #by Plato

题意:一个N*M的矩阵,在每行上选一个点(建灯塔),每个点都要各自对应的代价c和灯塔的光亮f。同时相邻两行的点要满足 |j-k|≤f(i,j)+f(i+1,k)。问最小代价是多少。

Idea:

很容易想到DP方程:f[i,j] = min{f[i-1,k]} + c[i,j];

k 的范围:|j-k|≤f(i,j)+f(i+1,k)

直接这样做的话,O(N*M*M),TLE节奏。所以需要优化。

可以看出,k是在一个区间里的最值,单调队列?线段树?

应该是线段树,但是树怎么建,区间怎么确定?这个我也一直想不清楚,后来看题解了好久才明白。(作下几何图试试)

建树要注意的地方比较多了(可能只针对我这样的菜菜),需要lazy标志,初始化,lazy的初值与更新。

建树,1-M的区间,每个结点保存此区间的最小值(上一行能照到这个结点的)。

(要去上课了,详细的下次补全了)

 

 

题目昨天卡了一天,主要是线段树写挫了,调了好久,今早才想起一个错误,改了,终于A了。。。

#include <cstdio>
#include <iostream>
#include <fstream>
#include <cstring>
#include <vector>
using namespace std;

const int INF = (1<<31)-1;
int c[109][5009],f[109][5009];
int g[109][5009];

struct node
{
    int l,r,lazy,min;
}t[5009*4];
int min_value;

void pushdown(int k)
{
	if (t[k].lazy != INF) {
		t[k<<1].lazy = min(t[k<<1].lazy,t[k].lazy);
		t[k<<1|1].lazy = min(t[k<<1|1].lazy,t[k].lazy);
		t[k<<1].min = min(t[k].lazy,t[k<<1].min);
		t[k<<1|1].min = min(t[k].lazy,t[k<<1|1].min);
		t[k].lazy = INF;//t[k].lazy = 0;
	}
}

void construct(int l,int r,int k,int ii)
{
	t[k].l = l;
	t[k].r = r;
	t[k].lazy = INF;
	//t[k].lazy = 0;
	t[k].min = INF;
	if (l == r){
        t[k].min = g[ii][l];
		return;
	}

	int mid = (l+r)>>1;
	construct(l,mid,k<<1,ii);
	construct(mid+1,r,k<<1|1,ii);

	t[k].min = min(t[k<<1].min,t[k<<1|1].min);
}

void updata(int k,int left,int right,int value)
{
    t[k].min = min(value,t[k].min);
	if (t[k].l == left && t[k].r == right){
        //t[k].min = min(value,t[k].min);
		t[k].lazy = min(t[k].lazy,value);
		//t[k].lazy = value;
		return;
	}
	pushdown(k);

	int mid = (t[k].l+t[k].r)>>1;
	if (right <= mid) updata(k<<1,left,right,value);
	else
		if (left > mid) updata(k<<1|1,left,right,value);
		else {
			updata(k<<1,left,mid,value);
			updata(k<<1|1,mid+1,right,value);
		}
}

void query(int k,int left,int right)
{
	if (t[k].l == left && t[k].r == right){
        if (t[k].min < min_value) min_value = t[k].min;
		return;
	}
	pushdown(k);

	int mid = (t[k].l+t[k].r)>>1;
	if (right <= mid) query(k<<1,left,right);
	else
		if (left > mid) query(k<<1|1,left,right);
		else {
			query(k<<1,left,mid);
			query(k<<1|1,mid+1,right);
		}
}

int main()
{
    freopen("test.txt","r",stdin);
    int N,M;
    while (scanf("%d%d",&N,&M),N+M)
    {
        for (int i = 1;i <= N;i++)
            for (int j = 1;j <= M;j++)
                scanf("%d",&c[i][j]);

        for (int i = 1;i <= N;i++)
            for (int j = 1;j <= M;j++)
                scanf("%d",&f[i][j]);

        memset(g,-1,sizeof(g));
        for (int i = 1;i <= M;i++)
            g[1][i] = c[1][i];
        for (int i = 2;i <= N;i++)
        {
            construct(1,M,1,i-1);
            for (int j = 1;j <= M;j++)
            {
                int L = j - f[i-1][j] > 0 ? j - f[i-1][j] : 1;
                int R = j + f[i-1][j] < M ? j + f[i-1][j] : M;
                updata(1,L,R,g[i-1][j]);
            }

            for (int j = 1;j <= M;j++)
            {
                int L = j - f[i][j] > 0 ? j - f[i][j] : 1;
                int R = j + f[i][j] < M ? j + f[i][j] : M;
                min_value = INF;
                query(1,L,R);
                g[i][j] = min_value + c[i][j];
            }
        }

        int minx = INF;
        for (int i = 1;i <= M;i++)
        {
            if (g[N][i] < minx && g[N][i] != -1) minx = g[N][i];
        }
        printf("%d\n",minx);
    }

    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值