【DJ+手打堆板子】WA#1#3#4求hack数据

P4779 【模板】单源最短路径(标准版)

C83_AD @ 2024-11-29 13:26:04

如题。昨天发过,今天又调了好久,仍然没过。。

测试点过大,找不到错误。

评测记录

代码如下:

#include<cstdio>
#define INF 0x3f3f3f3f
using namespace std;
int min(int a,int b){return a<b?a:b;}
int h[100003];//h[i]:the last out-edge of vertice i
struct tu_edge{
    int a,b,w,n;//st&ed and last out-edge of a
    void input(int i){
        scanf("%d%d%d",&a,&b,&w);
        n=h[a],h[a]=i;
    }
}edge[200003];
int dis[100003];//distance of each vertice to s
int pos[100003];//position in f_queue of each vertice
struct f_queue{
    private:
        int tail,tree[100003];
        void _upper(int x){
            int temp=tree[x];
            pos[tree[x/2]]=x,tree[x]=tree[x/2];
            pos[tree[x]]=x/2,tree[x/2]=temp;
        }
        void _update(int x){
            if(dis[tree[x]]<dis[tree[x/2]]) _upper(x),_update(x/2);
            else{
                if(x*2 >tail) return;
                int temp=x*2;
                if(temp<tail) if(dis[tree[temp]]>dis[tree[temp+1]]) ++temp;
                if(dis[tree[temp]]<dis[tree[x]]) _upper(temp),_update(temp);
            } 
        }
    public:
        void push(int a){
            tree[++tail]=a,pos[a]=tail;
            _update(tail);
        }
        void redate(int a){
            _update(pos[a]);
        }
        int pop(){
            int temp=tree[1];
            pos[tree[1]]=0 ,pos[tree[tail]]=1;
                tree[1] =       tree[tail--];
            _update(1);
            return temp;
        }
        bool empty(){
            return !tail;
        }
}que;
int n,m,s;
int main(){
    //input&pre
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=n;++i) dis[i]=INF;
    dis[s]=0,que.push(s);
    for(int i=1;i<=m;++i) edge[i].input(i);
    //dj
    while(!que.empty()){
        int temp=que.pop(),tmp;//new blue vertice
//      printf("%d:\n",temp);
        for(int i=h[temp];i;i=edge[i].n){
            tmp=edge[i].b;
            if (dis[tmp]>dis[temp]+edge[i].w) 
                dis[tmp]=dis[temp]+edge[i].w,//printf(" +%d=%d->%d:%d->%d\n",i,edge[i].w,tmp,dis[temp],dis[tmp]),
                pos[tmp]?que.redate(tmp):que.push(tmp);
        }
    }
    for(int i=1;i<=n;++i) printf("%d ",dis[i]);
    return 0;
}

现在初步判断是堆或者堆的使用的问题(?)因为我试了试改成使用STL实现,A了。评测记录。代码如下:

#include<cstdio>
#include<queue>
#define N 1000000
using namespace std;
int n,m,st,dis[N],u[N],h[N];
struct tu_edge{
    int a,b,w,n;//st&ed and last out-edge of a
    void input(int i){
        scanf("%d%d%d",&a,&b,&w);
        n=h[a],h[a]=i;
    }
}edge[200003];
struct dnt{
    int ans,id;
    bool operator<(const dnt &x)const{return x.ans<ans;}
};
priority_queue<dnt> q;
int main(){
    scanf("%d%d%d",&n,&m,&st);
    for(int i=1;i<=m;i++) edge[i].input(i);
    for(int i=1;i<=m;i++) dis[i]=0x3f3f3f3f;
    dis[st]=0,q.push((dnt){0,st});
    while(!q.empty()){
        int temp=q.top().id;q.pop();
        if(u[temp]) continue;
        u[temp]=1;
        for(int i=h[temp];i;i=edge[i].n){
            int tmp=edge[i].b;
            if (dis[tmp]>dis[temp]+edge[i].w){
                dis[tmp]=dis[temp]+edge[i].w;
                if(!u[tmp]) q.push((dnt){dis[tmp],tmp});
            }
        }
    }
    for(int i=1;i<=n;i++) printf("%d ",dis[i]);
    return 0;
}

然而如果把u的判定删除(也就是每次更新不判定对象是否已经是蓝点)上面那段代码会在#2#3#6出现TLE(疑似死循环,本蒻已经下不了数据了)。评测记录。但是根据DJ的原理,不应该存在能够更新已经是蓝点的节点的情况,这个判定在我看来是无用的,为什么没了不行呢?也希望各位大佬解答一下。


by Harlem @ 2024-11-29 13:48:46

考前攒RP

不懂你在说什么,但如果你在说 if(u[temp])continue;的话……

你有没有发现每更新一次,对应节点就会被加入队列,这样你队列里会有很多个相同的节点,如果你每一个节点都去多次更新所有相邻节点,时间复杂度就炸了。

虽然我不会,但这个应该容易被卡,特别是这道题专门卡数据。

另外,没必要手写堆。


by lzm0107 @ 2024-11-29 13:51:22

@C83_AD 不更新其他点 \ne 不影响复杂度


by C83_AD @ 2024-11-29 14:07:58

@Harlem是这样的,这一点我理解了,感谢。


by C83_AD @ 2024-11-29 14:08:10

关于手打堆,有两个原因:

1.记忆力过于烂,就算记了也没办法保证考场能写对,不得不找个兜底的法子()

2.我的能力很多时候不足以使我能考虑到一些STL中功能的固有性质,可能会导致很多错误。这时需要自己写一个符合自己思路的功能。

比如说,我之所以首先选择手打堆,是因为不知道该怎么处理

队列里会有很多个相同的节点,如果你每一个节点都去多次更新所有相邻节点,时间复杂度就炸了

的情况。于是写了一个功能,是用来直接更改一个本就在堆内的节点的键值的,见第一份代码。第二份代码是改自我若干年前写的东西。


by Harlem @ 2024-11-29 14:18:09

@C83_AD

建议还是记忆STL的内容,考场上手写堆很浪费时间,并且STL需要记忆的内容远小于手写,并且正确性有保障。

至于内容修改,说实话,单纯的堆中一般用不到(比如这道题就不用),当然学的时候肯定可以学一下。

如果有修改节点需求的话,一般不这么写,一般直接写平衡树(可以用Treap,Tree+Heap),删除原节点并插入新节点,不过你目前应该不需要学这个……(如果你在学普及内容的话)


by Harlem @ 2024-11-29 14:19:43

@C83_AD

这道题你直接看对应节点是不是已经被操作过就行了,打个标记记录一下,堆中左边的数其实不用管它,只是用来排序的。实际距离看dis数组就行。


by C83_AD @ 2024-11-29 14:42:38

@Harlem别说最近刚好在学平衡树,刚刚也想着或许可以用来优化这个()

可能今晚会写一下bra

应该说目前我的训练环境过于差了,也没个教练什么的,基本上什么算法都需要自己摸索,所以很多时候要从底层考虑一遍。最近也是因为明天考试在刷板子题

谢谢说明与建议。


by Harlem @ 2024-11-29 14:44:26

@C83_AD

加油,祝好


by junjie_zhao @ 2024-11-29 18:11:20

虽然STL非常好用,但手写堆也是有必要的,好像能做到 O(N\log N+M),而STL只能 O(M\log N+N)

但考场用STL更好


by Madokaaa @ 2024-12-11 19:46:11

@junjie_zhao 大佬请问为什么会有这样的差异?堆不是对N个点按dist排序吗?为什么会出现M


|