过河

题目大意

一条宽为W的河。由y=0和y=W两条边界组成。
一些二维点,还有一些圆。每个圆都有半径和使用一次的代价。
你可以在每个二维点上放置一个圆或不放置,代价为该圆使用代价。
现在从y=0走到y=W,两个圆相交或相切则互相联通,与y=0相交或相切的圆可以通过,y=W同理。
求最小代价。

最短路

设(i,j)表示在第i个圆上放第j种圆,用一个点表示它。
对于(i,j)和(k,l),如果 r[j]+r[l]>=(xixj)2+(yiyj)2 ,那么(i,j)向(k,l)连c[l]的单向边。
建立源点S和汇点T,S对那些可以联通y=0的点连边,可以联通y=W的点向T连边,求S到T的最短路即可。

优化连边

点的数量是减不了了,考虑优化连边。
首先如果存在一个圆半径为x费用为a,另一个圆半径为y费用为b,如果满足x>=y但a<=b,那么(y,b)这个圆无意义。
于是按照r排序,c也是单调的。
对于一个(i,j)向k的m个点连边,找到最小的l可以让i与k联通,那么(i,j)向(k,l)连c[l]的单向边。
对于一个点i,(i,j-1)向(i,j)连c[j]-c[j-1]
易知原图的等价性没有改变!
新图是n^3的边数,跑最短路即可。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
typedef double db;
const int mx=250+10,mx2=mx*mx,mx3=mx*mx*mx,inf=500000000;
struct dong{
    int x,id;
    friend bool operator <(dong a,dong b){
        return a.x<b.x||a.x==b.x&&a.id<b.id;
    }
} zlt;
multiset<dong> heap;
int f[mx2],h[mx2],go[mx3],next[mx3],dis[mx3],b[mx][mx],tance[mx][mx];
int x[mx],y[mx],r[mx],c[mx],id[mx],p[mx];
bool bz[mx2];
int i,j,k,l,now,s,t,n,m,w,ca,tot,top;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
bool cmp(int x,int y){
    return r[x]<r[y]||r[x]==r[y]&&c[x]>c[y];
}
db sqr(db x){
    return x*x;
}
void add(int x,int y,int z){
    go[++tot]=y;
    dis[tot]=z;
    next[tot]=h[x];
    h[x]=tot;
}
int main(){
    freopen("river.in","r",stdin);freopen("river.out","w",stdout);
    ca=read();
    while (ca--){
        n=read();m=read();w=read();
        tot=0;
        fo(i,1,n) x[i]=read(),y[i]=read();
        fo(i,1,n)
            fo(j,1,n)
                tance[i][j]=ceil(sqrt(sqr(x[i]-x[j])+sqr(y[i]-y[j])));
        fo(i,1,m) r[i]=read(),c[i]=read(),id[i]=i;
        sort(id+1,id+m+1,cmp);
        fo(i,1,m) p[i]=r[id[i]];
        fo(i,1,m) r[i]=p[i];
        fo(i,1,m) p[i]=c[id[i]];
        fo(i,1,m) c[i]=p[i];
        l=inf;
        top=0;
        fd(i,m,1){
            if (c[i]<l){
                id[++top]=i;
                l=c[i];
            }
        }
        fo(i,1,top/2) swap(id[i],id[top-i+1]);
        m=top;
        fo(i,1,m) p[i]=r[id[i]];
        fo(i,1,m) r[i]=p[i];
        fo(i,1,m) p[i]=c[id[i]];
        fo(i,1,m) c[i]=p[i];
        top=0;
        s=++top;
        fo(i,1,n) 
            fo(j,1,m)
                b[i][j]=++top;
        t=++top;
        fo(i,1,top) h[i]=0;
        fo(i,1,n)
            fo(k,1,n){
                fo(j,1,m){
                    if (r[m]+r[j]<tance[i][k]) continue;
                    l=lower_bound(r+1,r+m+1,tance[i][k]-r[j])-r;
                    add(b[i][j],b[k][l],c[l]);
                }
            }
        fo(i,1,n)
            fo(j,2,m) 
                add(b[i][j-1],b[i][j],c[j]-c[j-1]);
        fo(i,1,n)
            fo(j,1,m)
                if (r[j]>=y[i]) add(s,b[i][j],c[j]);
        fo(i,1,n)
            fo(j,1,m)
                if (y[i]+r[j]>=w) add(b[i][j],t,0);
        fo(i,1,top) f[i]=inf,bz[i]=0;
        f[s]=0;
        heap.clear();
        fo(i,1,top){
            zlt.x=f[i];zlt.id=i;
            heap.insert(zlt);
        }
        while (!heap.empty()){
            zlt=*heap.begin();
            heap.erase(heap.begin());
            k=zlt.id;
            bz[k]=1;
            now=h[k];
            while (now){
                if (!bz[go[now]]&&f[k]+dis[now]<f[go[now]]){
                    zlt.x=f[go[now]];zlt.id=go[now];
                    heap.erase(heap.find(zlt));
                    zlt.x=f[go[now]]=f[k]+dis[now];
                    heap.insert(zlt);
                }
                now=next[now];
            }
        }
        if (f[t]==inf) printf("impossible\n");else printf("%d\n",f[t]);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值