[bzoj3716]Muzeum

题目大意

商场是一个平面直角坐标系。
lihua想要来偷珠宝,一共有 n n 个珠宝,分布在不同的位置,第i个珠宝在 (axi,ayi) ( a x i , a y i ) 有价值为 avi a v i
但是有 m m 个保安,第i个保安在 (bxi,byi) ( b x i , b y i ) ,lihua贿赂他需要 bvi b v i 的代价。
每个保安都有一个相同的视野角度θ。
如图:
这里写图片描述
lihua可以花费代价来贿赂保安。
如果一个珠宝不在任何一个未被贿赂保安视野范围内,lihua可以偷走它获得对应价值。
求lihua最大收益。

最小割

首先显然可以最小割。
我们要最大化收益,那么可以求珠宝的价值总和,然后考虑最小化无法偷走的珠宝价值和以及需要贿赂的保安代价和。
对每个保安和珠宝都建立一个点。
如果一个保安点属于S集合,规定其未被贿赂。
如果一个珠宝点属于S集合,规定其无法偷走。
根据这两条规定,容易建图,保安点放左侧,源点向保安点连代价。珠宝点放右侧,珠宝点向汇点连价值。
因为一个保安如果没被贿赂,那么其能看到的珠宝则无法偷走。
因此每个保安点向能看到的珠宝点连正无穷的边,表示保安点属于S集合(未被贿赂),则能看到的珠宝点也应属于S集合(无法偷走)。

贪心

最小割是等于最大流的。
我们考虑模型转换:
保安点 i i 是一个喷水的,能喷bvi的水。
珠宝点 i i 是一个储水的,最多储avi的水。
一个喷水的可以向视野内的储水装置喷水。
这确实是网络流。
现在要知道最多喷多少水。
不妨先拉伸坐标系使得视野呈直角。
然后再45度旋转:
这里写图片描述
可以发现这样改变坐标系后,视野范围更好定义。
现在我们将珠宝和保安放在一起,按 x x 坐标从大到小做。
插入一个保安时,需要决策喷水。
由于我们按x坐标有序做,因此只需要考虑 y y 坐标即可。
既然如此,我们可以贪心的想,先喷y坐标最小的,喷满了继续喷第二小的,直至没有可以喷的或者喷完了水。
用set即可操作。

#include<cstdio>
#include<algorithm>
#include<set>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
typedef double db;
const int maxn=400000+10;
const ll inf=1000000000000000000;
const db eps=1e-10;
db x[maxn],y[maxn];
int v[maxn],id[maxn];
int i,j,k,l,t,n,m;
db W,H,nx,ny;
ll ans;
struct compare {
    bool operator () (int i, int j) {
        return y[i] < y[j] || (y[i] == y[j] && i < j);
    }
};
set<int,compare> s;
set<int,compare>::iterator it,ut;
bool cmp(int a,int b){
    return x[a]<x[b];
}
int main(){
    scanf("%d%d",&n,&m);
    scanf("%lf%lf",&W,&H);
    fo(i,1,n+m){
        scanf("%lf%lf%d",&x[i],&y[i],&v[i]);
        if (H<W) x[i]*=H/W;
        else y[i]*=W/H;
        nx=x[i]+y[i];ny=y[i]-x[i];
        x[i]=nx;y[i]=ny;
        if (i>n) x[i]+=eps,y[i]+=eps;
    }
    fo(i,1,n+m) id[i]=i;
    sort(id+1,id+n+m+1,cmp);
    fo(i,1,n) ans+=(ll)v[i];
    y[n+m+1]=inf;
    s.insert(n+m+1);
    y[0]=-inf;
    s.insert(0);
    fo(i,1,n+m){
        j=id[i];
        if (j<=n) s.insert(j);
        else{
            it=s.upper_bound(j);
            for (--it;*it;it=ut) {
                ut=it,--ut;
                k=min(v[*it],v[j]);
                v[*it]-=k;
                v[j]-=k;
                ans-=(ll)k;
                if (!v[*it]) s.erase(it);
                else break;
            }
        }
    }
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WerKeyTom_FTD

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值