NOI2019D2T1弹跳

· · 题解

前天发了篇D1T1的题解,今天我又来了

看完全部题目,T2T3是啥啊,还是回来看T1吧

哈哈哈,又是最短路!

dij的性质就是每个点只会被标记一次,并且权值非负。

这是个很好的性质!就是说,我们dij的时候只要把新增的矩形内没有被标记过的点标记就好了。

转化:每次把一个矩形内的点全部去掉,并赋值。

看到这里,如果你做过线段树的拓展,就很容易想到线段树!

类似线段树题目:bzoj4127 Abs luogu4145 花神游历各国

其实我只是听过二维线段树而已,根本没写过,当场瞎敲一通就过了

dij部分就不多说了,转化后怎么实现?

这里,我们不用考虑树套树,因为直接用二维线段树就行了。

好像很多人都不知道二维线段树?

二维线段树,就是用每个点表示一个正方形,这个正方形还可以分为4个子正方形。(类似二维st表)

我们用sum来表示一个线段树的正方形内剩下没被标记过的点的总数

我们用递归来标记点,这样当一个正方形内没有未标记的点时就可以剪掉很多枝。

也就是,当递归到sum=0时,直接返回。

如果当前线段树矩形和dij里更新的矩形没有重合部分,也返回。

否则,继续递归四个子矩形。

当矩形缩成一个点时,在dij那边处理,并将sum清零。

回溯时更新sum值,就这么简单。

不过w和h最大都是n,没关系!用动态开点(就是用到一个区间再开一个区间)!

如果用动态开点,初始化就得一个一个城市加入。反正时限2秒,复杂度也对。

好像跑得挺快?虽然我不会算二维的复杂度

代码:

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int MAXN=7e4+5,MAXM=1.5e5+5;
int n,m,w,h;
struct node{
    int x,y;
    int h;
    int bgnid;  //用来输出
}city[MAXN];
inline bool cmp_city(node a,node b){
    if(a.x==b.x) return a.y<b.y;
    return a.x<b.x;
}
int GetID(int x,int y){ //根据坐标找城市编号(先排好序)
    node f;
    f.x=x,f.y=y;
    int l=1,r=n;
    while(l<r){
        int mid=l+r>>1;
        if(cmp_city(city[mid],f)) l=mid+1;
        else r=mid;
    }
    return r;
}
struct edge{
    int t,l1,r1,l2,r2;  //存一个矩形
    int d;
    inline void Init_bgn(){
        d=0;
        for(int i=1;i<=n;i++)
            if(city[i].bgnid==1){
                l1=l2=city[i].x;
                r1=r2=city[i].y;
                return ;
            }
    }
}ed[MAXM];
int cnte,nx[MAXM];
inline void adde(int &h,int t,int a,int b,int c,int d){
    cnte++;
    ed[cnte].t=t;
    ed[cnte].l1=a;
    ed[cnte].l2=b;
    ed[cnte].r1=c;
    ed[cnte].r2=d;
    nx[cnte]=h;
    h=cnte;
}
bool operator <(edge a,edge b){
    return a.d>b.d; //dij里给优先队列重载
}
int dis[MAXN];
priority_queue<edge> que;
edge hd;
const int SIZ=1e7+5;
int cntseg,c1[SIZ],c2[SIZ],c3[SIZ],c4[SIZ];
#define getc1 c1[k],l1,r1,midx,midy     //4个子正方形的宏定义(降低代码量)
#define getc2 c2[k],l1,midy+1,midx+1,r2
#define getc3 c3[k],midx+1,r1,l2,midy
#define getc4 c4[k],midx+1,midy+1,l2,r2
int sum[SIZ];
inline void pushup(int k){
    sum[k]=sum[c1[k]]+sum[c2[k]]+sum[c3[k]]+sum[c4[k]]; //标记上传
}
void modify1(int &k,int l1,int r1,int l2,int r2,const node &ct){    //初始化用的函数
    if(!k) k=++cntseg;
    if(l1==l2&&r1==r2){
        sum[k]=1;
        return ;
    }
    int midx=l1+l2>>1,midy=r1+r2>>1;
    if(ct.x<=midx){
        if(ct.y<=midy) modify1(getc1,ct);
        else modify1(getc2,ct);
    }
    else{
        if(ct.y<=midy) modify1(getc3,ct);
        else modify1(getc4,ct);
    }
    pushup(k);
    return ;
}

void modify2(int k,int l1,int r1,int l2,int r2,const edge &e){  //dij更新一个矩形内的点
    if(l1>e.l2||r1>e.r2||l2<e.l1||r2<e.r1) return ; //没有重合部分
    if(!sum[k]) return ;
//  printf("mdf2 %d %d %d %d\n",l1,r1,l2,r2);
    if(l1==l2&&r1==r2){
        //缩成一个点,直接修改dij里面用的优先队列
        sum[k]=0;
        int p=GetID(l1,r1);
        dis[p]=e.d;
        for(int i=city[p].h;i;i=nx[i]){
            edge t=ed[i];
            t.d=e.d+t.t;
            que.push(t);
        }
        return ;
    }
    int midx=l1+l2>>1,midy=r1+r2>>1;
    modify2(getc1,e);   //递归4个子正方形
    modify2(getc2,e);
    modify2(getc3,e);
    modify2(getc4,e);
    pushup(k);
    return ;
}

inline void Print(){//调试用
    for(int i=1;i<=n;i++)
        printf("u %d dis %d\n",city[i].bgnid,dis[i]);
    puts("");
    return ;
}

void dij(){
    cntseg=1;
    int one=1;
    for(int i=1;i<=n;i++)
        modify1(one,1,1,n,n,city[i]);   //线段树初始化

    memset(dis,0x3f,sizeof(dis));
//  dis[1]=0;
    edge bgn;
    bgn.Init_bgn();
    que.push(bgn);
    while(!que.empty()){
        edge hd=que.top();
        que.pop();  //dij没什么好讲的吧
//      printf("Start mdf2 %d %d %d %d dis %d\n",hd.l1,hd.r1,hd.l2,hd.r2,hd.d);
        modify2(1,1,1,n,n,hd);
//      Print();
    }
    return ;
}
/*

5 3 5 5
1 1
3 1
4 1
2 2
3 3
1 123 1 5 1 5
1 50 1 5 1 1
3 10 2 2 2 2

*/
int ans[MAXN];
int main(){
//  freopen("jump.in","r",stdin);
//  freopen("jump.out","w",stdout);
    scanf("%d%d%d%d",&n,&m,&w,&h);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&city[i].x,&city[i].y),city[i].bgnid=i;
    for(int i=1;i<=m;i++){
        int p,t,a,b,c,d;
        scanf("%d%d%d%d%d%d",&p,&t,&a,&b,&c,&d);
        adde(city[p].h,t,a,b,c,d);
    }
    sort(city+1,city+n+1,cmp_city);
    dij();
    for(int i=1;i<=n;i++)
        ans[city[i].bgnid]=dis[i];
    for(int i=2;i<=n;i++)
        printf("%d\n",ans[i]);
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}

总结

轻松水过T1!相比Day1,只花了2h做T1,还能AC,进步真大!

看来,有时候得分与目标是成正比的。Day1老是想打部分分,结果与正解擦边而过。今天想A题,不仅不用管部分分,还节省了时间。

今年NOI,spfa基本死地彻彻底底了,因为dij有一个被OIer们遗忘很久的性质。

一些算法看似浅显,却像海绵一样(大家都知道我想说什么了)

OI里面到底有没有像文化课里的套路,这真是个谜。谁也说不清算法里面有多少性质可以用,谁知道明年会不会考个spfa把dij卡掉呢?其实人家spfa还是有很多性质的(比如说很不稳定)