luogu-P3344
这个题面,这个数据范围,完完全全就是网络流宗教。。然而我发现建不出来。
很多一眼网络流最后却不是网络流而往往是 d p dp dp 的题目都有一个特性:一个点可以流出多条流量,流入流量却只能为 1 1 1。
我曾尝试过对点加两条边,一条流量为 1 1 1 带花费,另一条流量无穷不带花费,然后强制先流特殊边,这就必须要用费用流来做到。
但题目所求往往相反,也就是我强制先流要用负花费,求出的是最大费用,而题目却要最小费用。
首先我们把所有不能被任何一个网络架点覆盖的景点删去,就解决了第一小问。
问题转化成了将所有点全覆盖的最小花费。
此题需要寻找一个结论:景点按 x x x 排序后,每个网络架点覆盖的点一定是景点的一段连续区间。
由 所有网络架的圆心都在矩形外 以及 所有网络架的半径均相同 这两个性质确保该结论的正确性。
注意这个结论是 所有圆心均在矩形上方 / 所有圆心均在矩形下方 分类后才成立。
于是我们就设 f ( i , j , k ) : f(i,j,k): f(i,j,k): 考虑前 i i i 个景点,最后一次使用圆心在矩阵上方的网络架为 j j j,最后一次使用圆心在矩阵下方的网络架为 k k k。
i i i 景点能被圆心在矩阵上方的第 j j j 个网络架覆盖,则再枚举一下矩阵上方的网络架上一次用的是哪个:
f ( i , j , k ) = min { f ( i − 1 , o , k ) } + c ( j ) f(i,j,k)=\min\{f(i-1,o,k)\}+c(j) f(i,j,k)=min{f(i−1,o,k)}+c(j)。
圆心在矩阵下方的转移同理。
时间复杂度就是非常朴素的 O ( n 4 ) O(n^4) O(n4)。
注意:我们的 f f f 状态设计不再是常见到,使用前 j / k j/k j/k 个圆心在矩阵上 / 下方的网络架,而是直接最近一次使用的编号。
所以实际上这个网络架的使用编号是反复变化跳跃的。
看似并不是一个圆会覆盖一段连续点的方案。
但实际上这些不合法的方案一定不会成为最优解,因为多次计算了网络架点的搭建花费。
而一个圆覆盖一段连续点的最优方案也统计进了,所以最后答案是不会受到影响的。
#include
using namespace std;
#define int long long
#define maxn 105
int n, m, R, cnt, cntd, cntu;
int f[maxn][maxn][maxn];
struct NB { int x, y; }s[maxn], g[maxn];
struct DK { int x, y, c; }p[maxn], d[maxn], u[maxn];
bool check( DK o, NB w ) {
return (o.x - w.x) * (o.x - w.x) + (o.y - w.y) * (o.y - w.y) <= R * R;
}
signed main() {
scanf( "%lld %lld %lld", &n, &m, &R );
for( int i = 1;i <= n;i ++ ) scanf( "%lld %lld", &s[i].x, &s[i].y );
for( int i = 1;i <= m;i ++ ) scanf( "%lld %lld %lld", &p[i].x, &p[i].y, &p[i].c );
for( int i = 1;i <= n;i ++ ) {
for( int j = 1;j <= m;j ++ )
if( check( p[j], s[i] ) ) {
g[++ cnt] = s[i];
break;
}
}
sort( g + 1, g + cnt + 1, []( NB a, NB b ) { return a.x < b.x; } );
for( int i = 1;i <= m;i ++ )
if( p[i].y < 0 ) d[++ cntd] = p[i];
else u[++ cntu] = p[i];
memset( f, 0x3f, sizeof( f ) );
f[0][0][0] = 0;
for( int i = 1;i <= cnt;i ++ )
for( int j = 0;j <= cntu;j ++ )
for( int k = 0;k <= cntd;k ++ ) {
if( j and check( u[j], g[i] ) ) {
f[i][j][k] = f[i - 1][j][k];
for( int o = 0;o <= cntu;o ++ )
f[i][j][k] = min( f[i][j][k], f[i - 1][o][k] + u[j].c );
}
if( k and check( d[k], g[i] ) ) {
f[i][j][k] = f[i - 1][j][k];
for( int o = 0;o <= cntd;o ++ )
f[i][j][k] = min( f[i][j][k], f[i - 1][j][o] + d[k].c );
}
}
int ans = 1e18;
for( int i = 0;i <= cntu;i ++ )
for( int j = 0;j <= cntd;j ++ )
ans = min( ans, f[cnt][i][j] );
printf( "%lld\n%lld\n", cnt, ans );
return 0;
}