题解 P3385 【【模板】负环】

顾z

2018-08-28 16:41:12

Solution

# [顾](https://www.luogu.org/blog/RPdreamer/#)[z](https://www.cnblogs.com/-guz/) ~~你没有发现两个字里的blog都不一样嘛~~ qwq ## 题目描述 ~~毒瘤数据~~要求**判负环** ## 分析: 还是融合了不少题解的思想的。 **负环定义**: ~~权值和为负的环~~ //在网络上并没有找到一个官方定义,暂且这么理解。 **SPFA:** 支持负边权的情况. spfa是最短路算法.如果一个环上的边权有负的,我们可以重复走这条路来获得更小的边权,所以这可以作为我们使用**spfa判断负环**的根据 //如果一个位置**入队次数不小于n次**,那它一定位于环上,所以这可以作为我们的判断标准。 **听说STL的队列比较慢,换掉!** 但是如果手打队列的话 队尾指针队首指针一直++根本停不下来怎么办? 我们可以重复使用! 像这样↓ ```cpp //这三行代码只是演示,并不是程序中这样写。 if(l>n)l=0; if(r>n)r=0; if(l<0)l=n; //因为r一直增加或者重置为0,所以没必要判断r<0 ``` **考虑把dis值大的放在最下边,dis值小的放在最上边。** 因为我们取出的位置一直是向前的,所以把dis值小的放在最下边 (也不能说是最下边,即放在刚刚取过的位置处,再比较一下与原位置dis值大小.) **至于为什么尽量去取dis值小的?** 因为这些被更新过的点dis值小,我们可能是通过一条负边到达的此节点,我们再去对它更新一下,可以尽可能早的判断出负环. //偷懒的话应该可以用优先队列来做,不过没有尝试,留给您了! ## 坑: #### YE5是5!!! N0是0!!! ----------------AC代码----------------- ```cpp // 3106ms 改数据之前 (话说我也不知道啥时候改的. //改数据之后 吸氧 571ms qwq. // Creator: 顾z // Date:2018.08.29 //------------------------------------------------ #include<bits/stdc++.h> #define IL inline #define RI register int #define N 100086 #define clear(a) memset(a,0,sizeof a) #define rk for(RI i=1;i<=n;i++) IL void read(int &x){ int f=1;x=0;char s=getchar(); while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();} x*=f; } int n,m,T,s; struct code{int u,v,w;}edge[N<<1]; bool vis[N]; int head[N],tot,dis[N],cnt[N],q[N]; IL void add(int x,int y,int z){edge[++tot].u=head[x];edge[tot].v=y;edge[tot].w=z;head[x]=tot;} IL bool spfa(int s) { int l,r; l=r=0; memset(dis,0x3f,sizeof dis); clear(vis);clear(cnt);clear(q); vis[s]=true;cnt[s]=1;dis[s]=0; q[r++]=s; while(l!=r) { int u=q[l++]; if(l>n)l=0;//重复使用 vis[u]=false; for(RI i=head[u];i;i=edge[i].u) { if(dis[edge[i].v]>dis[u]+edge[i].w) { dis[edge[i].v]=dis[u]+edge[i].w; cnt[edge[i].v]=cnt[u]+1;//题解思想. if(cnt[edge[i].v]>=n and edge[i].w<0) return true; //这里需要判断一下边权是否为负。 //因为看到讨论区的一组hack数据,所以尝试改一下, //然后就过啦~~~ if(!vis[edge[i].v]) { vis[edge[i].v]=true; if(dis[edge[i].v]>dis[q[l]]) { l--; if(l<0) l=n;//重复使用 q[l]=edge[i].v; } else { q[r++]=edge[i].v; if(r>n) r=0;//重复使用 } } } } } return false; } int main() { read(T); while(T--) { s=1,tot=0;clear(head); read(n),read(m); for(RI i=1,u,v,w;i<=m;i++) { read(u),read(v),read(w); add(u,v,w); if(w>=0)add(v,u,w); } puts(spfa(s)?"YE5":"N0"); } } ``` **写在后面** //这份代码并没有考虑多个连通图中的负环情况 //因此依旧可以被hack掉. //可能 正确性 or 内容是错误的 //提供参考啦~~ ## UPD **--2018.09.24.** 为啥这个题的数据点改了 emmm ~~撤了10个毒瘤数据点?~~ 本人尝试了~~神~~深搜spfa,依旧被卡. 码了一通宽搜spfa. **4355ms** ~~卡过?~~ 没了毒瘤数据点,感觉这题还是比较好做 emmm. ~~所以说为啥要改数据 QAQ~~ 这里放一下代码. **注意边权有0的情况,也要建双向边.** //否则只有90pts qwq. ----------------(bfs)spfa---------------- ```cpp //没有吸氧 4355ms. qwq //吸氧 1138ms. qwq #include<bits/stdc++.h> #define IL inline #define RI register int #define N 100086 #define clear(a) memset(a,0,sizeof a) #define rk for(RI i=1;i<=n;i++) using namespace std; IL void read(int &x) { int f=1;x=0;char s=getchar(); while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();} while(s<='9'&&s>='0'){x=x*10+s-'0';s=getchar();} x*=f; } int n,m,T; struct code{int u,v,w;}edge[N]; bool vis[N]; int head[N],tot,dis[N],cnt[N]; IL void add(int x,int y,int z){edge[++tot].u=head[x];edge[tot].v=y;edge[tot].w=z;head[x]=tot;} IL bool spfa(int now) { rk vis[i]=false,dis[i]=2147483647,cnt[i]=false; queue<int>q; q.push(now); vis[now]=true; dis[now]=0; while(!q.empty()) { int u=q.front();q.pop();vis[u]=false; if(cnt[u]>=n)return true; for(RI i=head[u];i;i=edge[i].u) { if(dis[edge[i].v]>dis[u]+edge[i].w) { dis[edge[i].v]=dis[u]+edge[i].w; if(!vis[edge[i].v]) { q.push(edge[i].v); vis[edge[i].v]=true; cnt[edge[i].v]++; if(cnt[edge[i].v]>=n)return true; } } } } return false; } int main() { read(T); while(T--) { read(n),read(m); tot=0;clear(head); for(RI i=1,u,v,w;i<=m;i++) { read(u),read(v),read(w); if(w<0)add(u,v,w); else add(u,v,w),add(v,u,w); } puts(spfa(1)?"YE5":"N0"); } } ```