[ZJOI2015]幻想乡 Wi-Fi 搭建计划(dp + 结论)

problem

luogu-P3344

solution

这个题面,这个数据范围,完完全全就是网络流宗教。。然而我发现建不出来。

很多一眼网络流最后却不是网络流而往往是 d p dp dp 的题目都有一个特性:一个点可以流出多条流量,流入流量却只能为 1 1 1

我曾尝试过对点加两条边,一条流量为 1 1 1 带花费,另一条流量无穷不带花费,然后强制先流特殊边,这就必须要用费用流来做到。

但题目所求往往相反,也就是我强制先流要用负花费,求出的是最大费用,而题目却要最小费用。


首先我们把所有不能被任何一个网络架点覆盖的景点删去,就解决了第一小问。

问题转化成了将所有点全覆盖的最小花费。

此题需要寻找一个结论:景点按 x x x 排序后,每个网络架点覆盖的点一定是景点的一段连续区间

[ZJOI2015]幻想乡 Wi-Fi 搭建计划(dp + 结论)_第1张图片

由 所有网络架的圆心都在矩形外 以及 所有网络架的半径均相同 这两个性质确保该结论的正确性。

注意这个结论是 所有圆心均在矩形上方 / 所有圆心均在矩形下方 分类后才成立。

于是我们就设 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(i1,o,k)}+c(j)

圆心在矩阵下方的转移同理。

时间复杂度就是非常朴素的 O ( n 4 ) O(n^4) O(n4)

注意:我们的 f f f 状态设计不再是常见到,使用前 j / k j/k j/k 个圆心在矩阵上 / 下方的网络架,而是直接最近一次使用的编号。

所以实际上这个网络架的使用编号是反复变化跳跃的。

看似并不是一个圆会覆盖一段连续点的方案。

但实际上这些不合法的方案一定不会成为最优解,因为多次计算了网络架点的搭建花费。

而一个圆覆盖一段连续点的最优方案也统计进了,所以最后答案是不会受到影响的。

code

#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;
}

你可能感兴趣的:(DP,结论和构造,动态规划,算法,图论)