jzoj3578 【CEOI2013】Adriatic 巧妙dp

题意

平面上若干个点,每个点(x,y)可以到达满足 x0<x,y0<yx0>x,y0>y 的点(x0,y0),问对于每一个点,所有点到它的最小步数和. (保证点两两互达)
3<=N<=250 000,0<坐标<=2500

分析

考虑到一个点将会把图分成四份区域。其中两个区域(左下,右上)是可以直接到达x的,那这个贡献就好算. 我们要算的只是另外两个区域.
两个区域差不多,只讲一个好了(左上)

这里写图片描述
画个图发现,左上的点必然会先跑进左下,右上,然后才能进点x.
于是我们设f[i][j]表示,一个以(i,j)为右下角的左上区域,他里面的所有点跑进左下,右上的最小代价。
如何更新?
找到两个蓝色区域的严格更新线,也就是说,在图中两条虚线的左上角那个框框(x0,y0)里的所有点,都不能直接走到蓝色区域里.
然后,f[x][y]=f[x0][y0]+sum(x,y),sum(x,y)表示左上角白色区域iii里的点数.
因为虚线划分出来的白色区域需要再走一步才能走到黄色区域里(保证有解),所以这一区域里的点要算走出来的方案f[x0][y0]走到黄色区域,然后再走一步走到蓝色区域里.
不难发现用严格更新线构成的矩阵更新是最优的 (黄色区域里的一步肯定最优,小白色区域必须先走到黄色区域,再走出来,那就满足最优子结构)

左上右下都做一次,答案很好算

#include <cstdio>
#include <iostream>
#include <cstring>
#define can(x,y) ((zx[x]<zx[y] && zy[x]<zy[y]) || (zx[x]>zx[y] && zy[x]>zy[y]))
using namespace std;
const int N = 250010,M = 2510;
int zx[N],zy[N];
int n,lx,ly;
int Q[N],head,tail,dis[N],bz[N];
int minh[M],minl[M],maxh[M],maxl[M];
int f[M][M],f2[M][M],cnt[M][M];
int sum(int lx,int ly,int rx,int ry) {
    return cnt[rx][ly] - cnt[rx][ry-1] - cnt[lx-1][ly] + cnt[lx-1][ry-1];
}
int main() {
    freopen("3578.in","r",stdin);freopen("3578.out","w",stdout);
    cin>>n;
    memset(minh,127,sizeof minh);
    memset(minl,127,sizeof minl);
    for (int i=1; i<=n; i++) {
        scanf("%d %d",&zx[i],&zy[i]);
        cnt[zx[i]][zy[i]]++;
        lx=max(lx,zx[i]),ly=max(ly,zy[i]);
        minh[zx[i]]=min(minh[zx[i]],zy[i]);
        maxh[zx[i]]=max(maxh[zx[i]],zy[i]);
        minl[zy[i]]=min(minl[zy[i]],zx[i]);
        maxl[zy[i]]=max(maxl[zy[i]],zx[i]);
    }
    for (int i=1; i<=lx; i++) for (int j=1; j<=ly; j++)
        cnt[i][j]+=cnt[i-1][j] + cnt[i][j-1] - cnt[i-1][j-1];

    for (int i=lx; i; i--) maxh[i]=max(maxh[i],maxh[i+1]);
    for (int i=ly; i; i--) maxl[i]=max(maxl[i],maxl[i+1]);
    minl[0]=lx+1, minh[0]=ly+1;
    for (int i=1; i<=lx; i++) minh[i]=min(minh[i],minh[i-1]);
    for (int i=1; i<=ly; i++) minl[i]=min(minl[i],minl[i-1]);

    for (int i=ly; i; i--) for (int j=1; j<=lx; j++) {
        int topy=max(i,maxh[j+1]),topx=min(j,minl[i-1]);
        f[j][i]=f[topx][topy] + sum(1,ly,j,i);
    }
    for (int i=1; i<=ly; i++) for (int j=lx; j; j--) {
        int topy=min(i,minh[j-1]),topx=max(j,maxl[i+1]);
        f2[j][i]=f2[topx][topy] + sum(j,i,lx,1);
    }
    for (int i=1; i<=n; i++) {
        printf("%d\n",f[zx[i]][zy[i]] + f2[zx[i]][zy[i]] + n - 3);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值