Nameless Nervu 题解
Nameless Nervu 题解
#5185 YAOI Round #25 (Div.1) C. Nameless Nervu
题意分析
(预先说明:本文所述的不等式组均满足题目给定的条件,即对于 \(a_u - a_v \le w\) 保证不出现 \(u=v\) 或 \(a_u - a_v \le w_1,a_v - a_u \le w_2\) 的情况,且 \(u_i,v_i\) 不重复。对于整个不等式组,保证是一个完整的、任意两点不独立的不等式组。)
给定一个不等式组,删除一个不等式将其分成两个互不相关的,且两者都有解。
思路
判断一个不等式组是否有解,可以想到差分约束系统。因此这个问题需要转换为图论问题进行求解。
将一个不等式组分为两个不等式组,相当于把一个图分成两个,因此删掉的不等式就是图的桥。
因此,很容易想到先跑一遍 tarjan 求出所有割边,再跑 SPFA 计算是否有解。
优化
为了进一步优化,我们需要先简单证明关于不等式组的两点。
-
对于两个独立且有解的不等式组,将它们用任意一个不等式连起来,组合成的不等式组也一定有解。
证明:我们知道将一个有解的不等式组中所有数同时加或减任意一个数,不等式仍然有解。因此,只需要对这两个独立且有解的不等式组各自进行同加减的操作,就可以满足组合成的不等式组也有解。
因此,对于一个有解的不等式组,删除一个条件使其变为两个独立的不等式组,这两者一定也有解。
-
对于一个无解的不等式组,删除一个不等式使其变为两个独立的不等式组,这两者至少有一个无解。
证明:不等式无解只在其中出现负环时发生。如果删除的桥破坏了负环,则说明桥必定在一个环上,此时环上的节点在桥的两边。因此环需要经过这个桥两次,而题目规定不会出现这种情况(保证不出现 \(a_u - a_v \le w_1,a_v - a_u \le w_2\))。因此破坏桥不可能影响到负环,不等式仍然无解。
因此,对于一个无解的不等式组,删除一个不等式使其变为两个独立的不等式组,这两者至少有一个无解。
有了这两点,我们可以发现如果不等式原本就无解,就不必再计算每个桥了,可以直接结束程序、判断无法做到。而如果原不等式有解,答案便是这个图所有的桥。
细节
边的问题
数组首先需要开到两倍的 \(n\),因为在跑 tarjan 时需要连双向的边(变为无向图)。
但是差分约束系统需要的不等式是有向边,因此我们在读入时先建一条边,再在跑 tarjan 之前连另一条。而差分约束时连的 \(n\) 条超级原点的边,我们可以在连反向边之前将边数 \(-n\),进行删除。
在割边时候我们需要记录边的编号,原有的边为 \(1 - m\) 由于反向边是后来加的,编号是 \(m+1 - 2m\)。割边不一定割的是哪条边,我们便需要判断编号是否大于 \(m\)。实现如 i>m?i-m:i。
(不过也可以建两个图)
顺序输出
sort 一下很方便,但是在第五个测试点会被卡,因此还需要写个桶排。
正解
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int w=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
w=w*10+ch-48;
ch=getchar();
}
return w*f;
}//读入优化,但是不加也勉强可以过
#define MAXN (int)1e6
int n,m;
struct edge{
int u,v,w,nxt;//为了连反边,需要储存下起始点
}e[MAXN*2];
int head[MAXN],ecnt=0;
void build(int u,int v,int w){
e[++ecnt].v=v,e[ecnt].w=w,e[ecnt].u=u;
e[ecnt].nxt=head[u],head[u]=ecnt;
}//建图用
int dfn[MAXN],low[MAXN],fa[MAXN],dcnt;
int br[MAXN],bcnt=0;//割边用
int dis[MAXN],vis[MAXN],c[MAXN];//差分约束用
int t[MAXN];//桶排用
void tarjan(int u){
dfn[u]=low[u]=++dcnt;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
fa[v]=u;
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])br[++bcnt]=(i>m?i-m:i);//注意算出正确的编号
}else if(fa[u]!=v)low[u]=min(low[u],dfn[v]);
}
}
bool spfa(){
queue<int> q;
for(int i=1;i<=n;i++)build(0,i,0);//n 条超级原点的边在这里
memset(dis,-2e9,sizeof(dis));
dis[0]=0,vis[0]=1;
q.push(0);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(dis[v]>=dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;
if(!vis[v]){
vis[v]=1,++c[v];
if(c[v]==n)return 0;
q.push(v);
}
}
}
}
return 1;
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),w=read();
build(v,u,w);//先连差分约束需要的边
}
if(!spfa()){
puts("-1");
return 0;
}//如果无解结束程序
ecnt-=n;//删除超级原点的边
for(int i=1;i<=m;i++)build(e[i].v,e[i].u,0);//连反边
tarjan(1);//由于保证图连通,只需要跑一遍即可
if(!bcnt){
puts("-1");
return 0;
}//如果无割边结束程序
for(int i=1;i<=bcnt;i++)t[br[i]]=1;
for(int i=1;i<=m;i++)if(t[i])printf("%d ",i);//桶排,输出
return 0;
}

浙公网安备 33010602011771号