【BZOJ1202/HNOI2005】狡猾的商人

本文详细解析了BZOJ1202题目,通过并查集和差分约束两种方法来解决账本真假判断的问题。并查集方法利用关系传递性维护前缀和,差分约束方法则通过SPFA算法判别负环。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:BZOJ1202

解析:

  并查集/差分约束。
  差分约束是很明显的,对于每次记录之间分别建立 ( s − 1 , t , v ) , ( t , s − 1 , − v ) (s-1,t,v),(t,s-1,-v) (s1,t,v)(t,s1,v)的有向边,然后SPFA判负环就行了。
  为什么可以用并查集呢,是因为存在关系的传递.
  一旦已经知道了 s [ a ] − s [ b ] , s [ b ] − s [ c ] s[a]-s[b],s[b]-s[c] s[a]s[b],s[b]s[c] s s s为前缀和),再给出一条 [ a , c ] [a,c] [a,c]就可以判断"账本的真假"了。那么就可以用并查集维护 s u m sum sum s u m [ i ] sum[i] sum[i]表示以 i i i为右端点与其最左端点之间的差。如果 s , t s,t s,t在同一个连通块中,那么它们之间的和即为 s [ s ] − s [ t − 1 ] = ( s [ s − 1 ] − s [ r t ] ) − ( s [ t ] − s [ r t ] ) = s u m [ s − 1 ] − s u m [ t ] s[s]-s[t-1]=(s[s-1]-s[rt])-(s[t]-s[rt])=sum[s-1]-sum[t] s[s]s[t1]=(s[s1]s[rt])(s[t]s[rt])=sum[s1]sum[t]

代码(差分约束):

#include <bits/stdc++.h>
using namespace std;

const int Max=105;
int t,n,m,size,tag;
int first[Max],dis[Max],v[Max],sum[Max];
struct shu{int to,next,len;}e[4005];

inline int get_int()
{
    int x=0,f=1;char c;
    for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
    if(c=='-') f=-1,c=getchar();
    for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
    return x*f;
}

inline void clean(){size=tag=0;for(int i=0;i<=n+1;i++) dis[i]=1e9,v[i]=first[i]=sum[i]=0;}
inline void build(int x,int y,int z){e[++size].next=first[x],first[x]=size,e[size].to=y,e[size].len=z;}
inline bool SPFA(int s)
{
    queue<int>q;
    q.push(s),sum[s]++,dis[s]=0;
    while(q.size())
    {
      int p=q.front();q.pop();v[p]=0;
      for(int u=first[p];u;u=e[u].next)
      {
      	int to=e[u].to;
      	if(dis[to]>dis[p]+e[u].len)
      	{
      	  dis[to]=dis[p]+e[u].len;
      	  if(!v[to])
      	  {
          	sum[to]++;
            if(sum[to] >= n) return 0; 
            v[to]=1,q.push(to);
      	  }
      	}
      }
    }
    return 1;
}
inline void init()
{
    t=get_int();
    while(t--)
    {
      n=get_int(),m=get_int();clean();
      for(int i=1;i<=m;i++)
      {
      	int x=get_int(),y=get_int(),z=get_int();
      	build(y,x-1,-z),build(x-1,y,z);
      }
      for(int i=0;i<=n;i++) build(n+1,i,0);
      if(!SPFA(n+1)) puts("false");
      else puts("true");
    }
}

int main()
{
    init();
    return 0;
}

代码(并查集):

#include <bits/stdc++.h>
using namespace std;

const int Max=10005;
int n,m,t,tag;
int fa[Max],d[Max];

inline int get_int()
{
	int x=0,f=1;char c;
	for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
	if(c=='-') f=-1,c=getchar();
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
	return x*f;
}

inline int get(int v)
{
	if(fa[v]==v) return v;
	int root=get(fa[v]);
	d[v]+=d[fa[v]];
	return fa[v]=root;
}
inline void init()
{
	t=get_int();
	while(t--)
	{
	  n=get_int(),m=get_int(),tag=0;
	  for(int i=0;i<=n;i++) fa[i]=i,d[i]=0;
	  while(m--)
	  {
	  	int x=get_int()-1,y=get_int(),z=get_int();
	  	if(get(x)==get(y))
		{
		  if(d[x]-d[y]!=z) tag=1;
		}
	  	else
	  	{
	  	  int fx=get(x),fy=get(y);
	  	  fa[fy]=fx,d[fy]=d[x]-d[y]-z;
	  	}
	  }
	  tag?puts("false"):puts("true");
	}
}

int main()
{
	init();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值