题目描述
小A最近迷上了在上课时玩《黄金矿工》这款游戏。为了避免被老师发现,他必须小心翼翼,因此他总是输。在输掉自己所有的金币后,他向你求助。每个黄金可以看做一个点(没有体积)。现在给出你N个黄金的坐标,挖到它们所需要的时间以及它们的价值。有些黄金在同一条直线上,这时候你必须按顺序挖。你可以瞬间把钩子转到任意角度。请你帮助小A算出在时间T内他最多可以得到多少价值的金子。
输入输出格式
输入格式:
第一行,两个整数N和T,表示黄金的个数和总时间。接下来N行,每行四个整数x,y,t,v分别表示黄金的坐标,挖到这个黄金的时间,以及这个黄金的价值。(0≤|x|≤200,0<y≤200,0<t≤200,0≤v≤200)
输出格式:
一个整数,表示你可以在T时间内得到的最大价值。
输入输出样例
输入样例#1:
3 10
1 1 1 1
2 2 2 2
1 3 15 9
输出样例#1:
3
输入样例#2:
3 10
1 1 13 1
2 2 2 2
1 3 4 7
输出样例#2:
7
说明
30%的数据,0 < T ≤ 4000
如果在同一条直线上要先选第一个,那不就可以连接成一条链,几条链连起来不就是树形dp吗
于是我果断开始打树形dp,其实这也没有问题
但是树形dp打到一半,发现根本就不需要树形dp,因为只有更节点才有多个子树
其他的最多只有一个孩子,于是我就把这些子树变成一条链
每一条链用一个一维数组来存,并记录一下这条链中到达第i个的时间花费和价值,再用dp一次解决吧
下面解释一下如何判断是否在一条直线上
我记得有一个关于直线的公式 y = k x + b
然而这里是从原点出发所以没有b 也就是 y = k x
其中k是斜率,如果斜率相同就是在同一条直线上(都是整数点所以不考虑精度问题)
k = y / x , 于是我们把每一个坐标的k取出来,并且排序一下,就可以很轻松的找出来啦
参考代码(可以加很多优化)
// luogu-judger-enable-o2
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std ;
const int N = 2e2 + 10 ;
struct node {
int x , y , t , v ;
double k ;
} a[N] ; int n , T ;
inline bool cmp ( node n1 , node n2 ) {
if ( n1.k != n2.k ) return n1.k < n2.k ;
return n1.y < n2.y ;
}
int tot , g[N][N][2] ;
int dp[N*N] ;
//g[][][0] 表示 时间
//g[][][1] 表示 价值
int main() {
cin >> n >> T ;
for ( int i = 1 ; i <= n ; i ++ ) {
cin >> a[i].x >> a[i].y >> a[i].t >> a[i].v ;
a[i].k = double( a[i].y ) / a[i].x ;
}
sort ( a + 1 , a + n + 1 , cmp ) ;
tot = 1 ; g[1][1][0] = a[1].t ;
g[1][1][1] = a[1].v ; g[1][0][0] = 1 ;
for ( int i = 2 ; i <= n ; i ++ ) {
if ( a[i].k != a[i-1].k ) {
tot ++ ; g[tot][1][0] = a[i].t ;
g[tot][1][1] = a[i].v ; g[tot][0][0] = 1 ;
continue ;
}
int tt = ++ g[tot][0][0] ;
g[tot][tt][0] = g[tot][tt-1][0] + a[i].t ;
g[tot][tt][1] = g[tot][tt-1][1] + a[i].v ;
}
for ( int i = 1 ; i <= tot ; i ++ )
for ( int j = T ; j >= 0 ; j -- )
for ( int k = 1 ; k <= g[i][0][0] ; k ++ ) {
if ( j >= g[i][k][0] ) dp[j] = max ( dp[j] , dp[j-g[i][k][0]] + g[i][k][1] ) ;
else break ;
}
cout << dp[T] << endl ; return 0 ;
}