hdu 6447 YJJ's Salesman(离散化+树状数组优化dp)
题意
有一天,你想从城市A走到城市B,假设城市A在正方形$(0,0)$点,而城市B在正方形$(10^9,10^9)$点,你是能向东,向南或者向东南走,也就是说,假设你站在$(x,y)$点,你只能向$(x+1,y),(x,y+1)和(x+1,y+1)$这三个方向走。在这个矩阵的地图上,还散落着一些小村庄,村庄k坐落的位置为$(x_k,y,_k)$,如果你能从$(x_k-1,x_y-1)$的位置走到村庄k,那么你就可以得到$v_k$的金钱,问你可以得到的最大的金钱数量为多少?
输入
$T$组输入数据,范围$(1≤N≤10)$
$N$个村庄,范围$(1≤N≤10^5) $
接下来有$N$行,每行有三个数为$x_k,y_k,v_k(1≤v_k≤10^3)$
输出
可以得到的最大的金钱数
思路
首先我们可以明显的看到这是一个dp,状态转移方程为$dp[i][j]=max(dp[i-1][j-1]+v[i][j],dp[i-1][j],dp[i][j-1])$其中$dp[i][j]$表示的是在$(i,j)$位置得到的最大金钱数量。但是同时也会发现,这个地图有$(10^9,10^9)$那么大,直接用上面的状态转移方程根本不可能啊!转念一想,我们可以利用滚动数组,将二维的变为一维的,此时的状态转移方程为$dp[j]=max(dp[k])+v[i][j](1≤k≤j-1)$,其中$dp[j]$表示在第$j$列时得到的最大金钱数量,但是此时还是$10^9$的数组。
再定睛一看,虽然地图很大,但是村庄很少,十分稀疏,最多只有$10^5$个,所以将横坐标离散化,这样数据范围就大大的降低。
对于找$j-1$个$dp$的最大,可以利用树状数组维护前缀最大值,我们知道树状数组$tree[x]$放置的是从$x$开始的前$lowbit(x)$个数的和,那么我们可以将$tree[x]$变为从$x$开始的前$lowbit(x)$个数的最大值是多少,这时查找最大值的时间复杂度为$log^n$,整体算法的时间复杂度为$nlog^n$
代码
#include <bits/stdc++.h> #define ll long long using namespace std; const int N = 1e5+10; const int inf = 0x3f3f3f3f; int T; int n; int num = 0; int tree[N]; struct node { int x,y; int v; }data[N]; bool cmp(node a,node b) { if(a.x<b.x) return true; else if(a.x==b.x) { if(a.y>b.y) return true; else return false; } else return false; } int lowbit(int x) { return x&-x; } void add(int x,int k) { while(x<=num) { tree[x] = max(tree[x],k); x = x + lowbit(x); } } int getmax(int x) { int maxx = -inf; while(x>=1) { maxx = max(maxx,tree[x]); x = x - lowbit(x); } return maxx; } int main() { cin>>T; while(T--) { cin>>n; memset(tree,0,sizeof(tree)); vector <int> v; for(int i=0;i<n;i++) { scanf("%d%d%d",&data[i].x,&data[i].y,&data[i].v); v.push_back(data[i].y); } sort(v.begin(),v.end()); v.erase(unique(v.begin(),v.end()),v.end()); sort(data,data+n,cmp); int ans = -inf; num = v.size(); for(int i=0;i<n;i++) { int j = upper_bound(v.begin(),v.end(),data[i].y)-v.begin(); int w = data[i].v; tree[j] = ( j!=1 ? max(getmax(j-1)+w,tree[j]) : max(tree[j],w)); add(j,tree[j]); ans = max(ans,tree[j]); } cout<<ans<<endl; } return 0; }