离散化+dp+线段树

YJJ's Salesman

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 729    Accepted Submission(s): 204


 

Problem Description

YJJ is a salesman who has traveled through western country. YJJ is always on journey. Either is he at the destination, or on the way to destination.
One day, he is going to travel from city A to southeastern city B. Let us assume that A is (0,0) on the rectangle map and B (109,109). YJJ is so busy so he never turn back or go twice the same way, he will only move to east, south or southeast, which means, if YJJ is at (x,y) now (0≤x≤109,0≤y≤109), he will only forward to (x+1,y), (x,y+1) or (x+1,y+1).
On the rectangle map from (0,0) to (109,109), there are several villages scattering on the map. Villagers will do business deals with salesmen from northwestern, but not northern or western. In mathematical language, this means when there is a village k on (xk,yk) (1≤xk≤109,1≤yk≤109), only the one who was from (xk−1,yk−1) to (xk,yk) will be able to earn vk dollars.(YJJ may get different number of dollars from different village.)
YJJ has no time to plan the path, can you help him to find maximum of dollars YJJ can get.

 

 

Input

The first line of the input contains an integer T (1≤T≤10),which is the number of test cases.

In each case, the first line of the input contains an integer N (1≤N≤105).The following N lines, the k-th line contains 3 integers, xk,yk,vk (0≤vk≤103), which indicate that there is a village on (xk,yk) and he can get vk dollars in that village.
The positions of each village is distinct.

 

 

Output

The maximum of dollars YJJ can get.

 

 

Sample Input

 

1

3

1 1 1

1 2 2

3 3 1 

 

Sample Output

3

 

Source

2018中国大学生程序设计竞赛 - 网络选拔赛

 

 

题意+分析:就是起点是(0,0),终点是(1e9,1e9),有n个网格的点有权值,可以向右,上,右上三个方向走,但往只能右上走时才能获得该权值,要使得经过的点权值之和最大,则很容易得到状态转移方程dp[i][j]=max{dp[i-1][j],dp[i][j-1],dp[i-1][j-1]+w[i][j]},但显然这样会内存爆炸,又由于只有n(n<=1e5)个网格有有权值的点,于是我们可以离散化一下,所谓离散化其实就是给那些点的x和y分别从小到大重新编号,使离散的点在整数集上连续起来。

然而离散之后还是发现,还是需要1e5*1e5的空间,显然不行,此时很容易想到滚动数组降维,降一维之后就变成了1e5,状态转移方程为dp[j]=max(dp[j],dp[k]+w[i][j]),{1<=k<=j-1,dp[k]=max(dp[k])},由于dp[k]为区间[1,j-1]的最大值,很容易想到用线段树或树状树组维护一下,总复杂度O(nlongn)刚好

AC code:

#include<bits/stdc++.h>
using namespace std;
#define lson (rt<<1)
#define rson (rt<<1|1)
typedef long long ll;
const int maxn=1e5+2;
struct Node{
int x,y,w;
}node[maxn];
struct Tree{
  int l,r,m;
}tree[maxn<<2];
void pushup(int rt)
{
    tree[rt].m=max(tree[lson].m,tree[rson].m);
}
void buildtree(int l,int r,int rt)
{
    tree[rt].l=l,tree[rt].r=r;
    if(l==r)
    {
        tree[rt].m=0;
        return;
    }
    int mid=(l+r)>>1;
    buildtree(l,mid,lson);
    buildtree(mid+1,r,rson);
    pushup(rt);
}
void update(int p,int rt,int val)
{
    int l=tree[rt].l,r=tree[rt].r;
    if(l==r)
    {
        tree[rt].m=max(val,tree[rt].m);
        return;
    }
    int mid=(l+r)>>1;
    if(p<=mid) update(p,lson,val);
    else update(p,rson,val);
    pushup(rt);
}
ll query(int L,int R,int rt)
{
    int l=tree[rt].l,r=tree[rt].r;
    if(L<=l&&r<=R)
    {
        return tree[rt].m;
    }
    ll ans=0;
    int mid=(l+r)>>1;
    if(L<=mid) ans=max(ans,query(L,R,lson));
    if(mid<R) ans=max(ans,query(L,R,rson));
    return ans;
}
int phash[maxn];
void inithash(int n)
{
    phash[1]=1;
    sort(node+1,node+1+n,[](const Node&a,const Node&b){return a.x<b.x;});
    for(int i=2;i<=n;i++)
      if(node[i].x==node[i-1].x)
          phash[i]=phash[i-1];
      else
         phash[i]=phash[i-1]+1;
    for(int i=1;i<=n;i++)
       node[i].x=phash[i];
    phash[1]=1;
    sort(node+1,node+1+n,[](const Node&a,const Node&b){return a.y<b.y;});
    for(int i=2;i<=n;i++)
        if(node[i].y==node[i-1].y)
         phash[i]=phash[i-1];
        else
         phash[i]=phash[i-1]+1;
    for(int i=1;i<=n;i++)
        node[i].y=phash[i];
    sort(node+1,node+1+n,[](const Node&a,const Node&b){return a.x==b.x?a.y>b.y:a.x<b.x;});///x从小到大排,相等时让从y从大到小排
}                                            ///滚动数组,x相同时,y逆序枚举
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d%d%d",&node[i].x,&node[i].y,&node[i].w);
        inithash(n);///离散化
        buildtree(1,n,1);
        ll sum=0;
        for(int i=1;i<=n;i++){
           ll temp=node[i].w;
           if(node[i].y!=1)
              temp+=query(1,node[i].y-1,1);///求dp[1...j-1]里的最大值
           update(node[i].y,1,temp);
           sum=max(sum,temp);
        }
        printf("%lld\n",sum);
    }
    return 0;
}

最后补充一下滚动数组降维:

其实就是状态转移后前面的数组的空间不用了 ,会造成浪费,而滚动数组就是利用这部分空间

写Dp经常需要大家开高维数组,比如F[t][i][j]。有的时候转移仅需要上一维数组,如F[t-1][i][j],而F[t-2],F[t-3]都不再有用,留着占用大量空间。我们可以用滚动数组的方式节省大量空间。 本来:int F[100][100][100]; 滚动:int F[2][100][100];

使用姿势: 维护两个数字指针Now和Pre,分别表示当前在哪一维,上一维在哪一维。也就是0和1这两个下标,哪个表示的是F[t][i][j],哪个表示的是F[t-1][i][j]。

复制代码

1 //例子:
2 for (int t = 1; t <= T; t++)
3 {
4    swap(Now, Pre);/*注意这个swap用的非常精妙,不用把两个数组的值互换,仅仅互换指针就可以了。*/
5    for (int i = 1; i <= N; i++)
6        for (int j = 1; j <= M; j++)
7             F[Now][i][j] = F[Pre][i…][j…] + …;
8 }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值