[雅礼集训]bsh(分治)

题目描述:
给定一个正n边形及其三角剖分, 共2*n-3条边( n条多边形的边和 n-3 条对角线), 每条边
的长度为1.
共 q 次询问, 每次询问给定两个点, 求它们的最短距离.
n<=52000,1<=q<=2n

这题思路挺简单的(但我太弱考试的时候还是没做出来。。。
考虑分治,每次选处于当前多边形最中间的那条边把当前区域分成两半;
当分治到只剩3个点的时候停止,因为n变形的三角剖分最后一定能剩一个三角形;
每次从选出的那条边两端各跑一遍bfs求最短路,如果询问的两点分别在选的这条边两边,可以直接利用这次bfs寻找答案,否则去相应的区间寻找答案;

不过这题代码是真的难写,其中还有一个坑点:分成的两个多边形的点数加起来比原来的大多边形的点数大2,因为中间分开的那条边的两个端点在两边的多边形内都存在;
这就要求每次需要先把分治分开的组存下来后先递归处理右区间,否则会覆盖一部分信息;而且存点数的数组也要开大一点。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using std::min;
const int maxn=60005;
const int inf=1000000007;
struct LINE{
    int x,y;
}line[maxn];
struct QUERY{
    int a,b,id;
}query[maxn*2];
int ans[maxn*2];
struct asd{
    int next,to;
}edge[maxn*4];
int etot=0;
bool can[maxn];
int node[maxn],d1[maxn],d2[maxn];
int point[maxn*2];
int n,t;
QUERY bowlq[maxn*2][2];
LINE bowll[maxn][2];
int bowlp[maxn][2];
int lc[maxn]; 
int que[maxn],head,tail;
void bfs(int s,int *d){
    d[s]=0;
    head=tail=0;
    que[tail++]=s;
    for(int now=que[head];head<tail;now=que[++head])
    {
        for(int i=node[now];i;i=edge[i].next)
            if(can[edge[i].to]&&d[edge[i].to]==inf){
                d[edge[i].to]=d[now]+1;
                que[tail++]=edge[i].to;
            }   
    }
} 
void solve(int *p,int ptot,QUERY *q,int qtot,LINE *l,int ltot){
//依次是当前的点,询问,边;
    if(!qtot)return;
    if(ptot<=3){
        for(int i=1;i<=qtot;i++)
            if(q[i].a!=q[i].b)ans[q[i].id]=1;
            else ans[q[i].id]=0;
        return ;
    }
    for(int i=1;i<=ptot;i++)lc[p[i]]=i;
    int cut=0;
    int cv=maxn;
    for(int i=1;i<=ltot;i++){
        int lc1=lc[l[i].x];
        int lc2=lc[l[i].y];
        int tv=abs(abs(lc1-lc2)*2-ptot);
        if(tv<cv)cut=i,cv=tv;
    }
    int lc1=lc[l[cut].x];
    int lc2=lc[l[cut].y];
    if(lc1>lc2){
        std::swap(lc1,lc2);
        std::swap(l[cut].x,l[cut].y);
    }
    for(int i=1;i<=ptot;i++){
        can[p[i]]=true;
        d1[p[i]]=d2[p[i]]=inf;
    }
    bfs(p[lc1],d1);
    bfs(p[lc2],d2);
    for(int i=1;i<=ptot;i++)can[p[i]]=false;
    int lq=0,lp=0,ll=0,rq=0,rp=0,rl=0;
    for(int i=1;i<=ptot;i++){
        if(i>=lc1&&i<=lc2)bowlp[++lp][0]=p[i];
        if((i>=1&&i<=lc1)||(i<=ptot&&i>=lc2))bowlp[++rp][1]=p[i];
    }
    for(int i=1;i<=qtot;i++){
        int tc1=lc[q[i].a];
        int tc2=lc[q[i].b];
        if(tc1>tc2)std::swap(tc1,tc2);
        if((tc1<=lc1&&tc2>=lc1&&tc2<=lc2)||(tc1>=lc1&&tc1<=lc2&&tc2>=lc2)){
            if(tc1==tc2)ans[q[i].id]=0;
            else ans[q[i].id]=min(d1[p[tc1]]+d1[p[tc2]],d2[p[tc1]]+d2[p[tc2]]);
        }else{
            if(tc1>=lc1&&tc1<=lc2)bowlq[++lq][0]=q[i];
            else bowlq[++rq][1]=q[i];
        }
    }
    for(int i=1;i<=ltot;i++){
        if(i==cut)continue;
        int tc1=lc[l[i].x];
        int tc2=lc[l[i].y];
        if(tc1>tc2)std::swap(tc1,tc2); 
        if(tc1>=lc1&&tc2<=lc2)bowll[++ll][0]=l[i];
        else bowll[++rl][1]=l[i];
    }
    for(int i=1;i<=lq;i++)q[i]=bowlq[i][0];
    for(int i=1;i<=lp;i++)p[i]=bowlp[i][0];
    for(int i=1;i<=ll;i++)l[i]=bowll[i][0];
    for(int i=1;i<=rq;i++)q[lq+i]=bowlq[i][1];
    for(int i=1;i<=rp;i++)p[lp+i]=bowlp[i][1];
    for(int i=1;i<=rl;i++)l[ll+i]=bowll[i][1];
    solve(p+lp,rp,q+lq,rq,l+ll,rl);
    solve(p,lp,q,lq,l,ll);
}
void add(int x,int y){
    edge[++etot].next=node[x];
    edge[etot].to=y;
    node[x]=etot;
}
int main()
{
    freopen("bsh.in","r",stdin);
    freopen("bsh.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)point[i]=i,lc[i]=i;
    for(int i=1;i<=n-3;i++){
        scanf("%d%d",&line[i].x,&line[i].y);
        add(line[i].x,line[i].y);
        add(line[i].y,line[i].x);
    }
    for(int i=1;i<n;i++)add(i,i+1),add(i+1,i);
    add(1,n),add(n,1);
    scanf("%d",&t);
    for(int i=1;i<=t;i++){
        scanf("%d%d",&query[i].a,&query[i].b);
        query[i].id=i;
    }
    solve(point,n,query,t,line,n-3);
    for(int i=1;i<=t;i++)printf("%d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值