题目大意
在二维平面的第一象限和第四象限上有 n 条线段表示 n 堵墙,每堵墙有一个坚固度 wi ,表示只有不小于 wi 的能量才能摧毁并贯穿它。你只能从原点向任意方向发射任意能量,问至少需要发射多少能量才能把所有的墙都摧毁。
解题思路
在考试的时候一直都想偏了,一直在考虑各个墙之间的遮挡关系,甚至画出了拓扑图。
由于这道题的线段都是在二维平面上,这就很烦,所以考虑降维。虽然发射能量的方向是连续的,但是离散为向各个线段的两端点发射能量就能包括所有的情况。所以通过对线段的端点进行极角排序就可以把二位平面上的线段降到一维去。
显然,对于一组相交的线段,摧毁它们的最小代价应该是最坚固的墙的坚固度。
考虑区间 dp ,其中 dp[i][j] 表示摧毁被区间 [i,j] 完全包含的线段,所需的最小能量值。每当合并两个区间时,先找到区间内坚固度最高的线段,因为如果要摧毁这个区间内的所有线段,发射的能量必定等于这条线段的坚固度。在坚固度最高的线段内枚举断点更新 dp 数组。转移如下:
dp[L][R]=min(dp[L,k−1]+dp[k+1][R]+MaxWinrange[L,R])
d
p
[
L
]
[
R
]
=
m
i
n
(
d
p
[
L
,
k
−
1
]
+
d
p
[
k
+
1
]
[
R
]
+
M
a
x
W
i
n
r
a
n
g
e
[
L
,
R
]
)
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=333;
typedef long long ll;
int n,tot;
int h[maxn], l[maxn], r[maxn], w[maxn];
ll dp[maxn*2][maxn*2];
struct vec {
int x,y,id;
vec() {}
vec(int x_,int y_,int i):x(x_),y(y_),id(i) {}
bool operator < (const vec &rhs) const {return 1ll*x*rhs.y-1ll*y*rhs.x<0;}
bool operator == (const vec &rhs) const {return 1ll*x*rhs.y-1ll*y*rhs.x==0;}
}p[maxn*2];
void work() {
scanf("%d",&n);
tot=0;
register int len,i,j,k;
for(i=1;i<=n;++i) {
scanf("%d%d%d%d",&h[i],&l[i],&r[i],&w[i]);
p[++tot]=vec(l[i],h[i],i);
p[++tot]=vec(r[i],h[i],i);
}
sort(p+1,p+1+tot);
tot=unique(p+1,p+1+tot)-(p+1);
for(i=1;i<=n;++i) {
l[i]=lower_bound(p+1,p+1+tot,vec(l[i],h[i],i))-p;
r[i]=lower_bound(p+1,p+1+tot,vec(r[i],h[i],i))-p;
}
for(len=1;len<=tot;++len)
for(i=1;i+len-1<=tot;++i) {
j=i+len-1;
dp[i][j]=(1ll<<60);
int x=0, y=0, maxx=-1;
for(k=1;k<=n;++k) if(i<=l[k] && r[k]<=j && w[k]>maxx) {
maxx=w[k];
x=l[k], y=r[k];
}
if(maxx==-1) {dp[i][j]=0; continue;}
for(k=x;k<=y;++k)
dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+maxx);
}
printf("%I64d\n",dp[1][tot]);
return;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
#endif
int T;
for(scanf("%d",&T);T;T--)
work();
return 0;
}