BZOJ1202: [HNOI2005]狡猾的商人(差分约束/带权并查集)

题意:传送门

题解:这个题还是比较简单的省选题了,第一我想到用差分约束,将点考虑成前缀和,然后将等号拆为两个,之后在spfa中跑是否有负环,也没有什么暗含条件之类的,直接写就可以。

附上代码:

 
#include<bits/stdc++.h>
#define IO ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
using namespace std;
inline int read()
{
    char ch=getchar();
    int f=1,x=0;
    while(!(ch>='0'&&ch<='9')){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
const int maxn=1e2+5;
const int maxm=2*(1e3+5);
const int inf=0x7fffffff;
struct edge{int v,w,next;}edges[maxm];
int head[maxn],tot;
void init()
{
    memset(head,-1,sizeof(head));tot=0;
}
void add_edges(int u,int v,int w)
{
    edges[tot].v=v;edges[tot].w=w;edges[tot].next=head[u];head[u]=tot++;
    edges[tot].v=u;edges[tot].w=-w;edges[tot].next=head[v];head[v]=tot++;
}
int w,n,m,s,t,v,dist[maxn],cnt[maxn];
bool vis[maxn];
bool spfa()
{
    queue<int>q;
    for(int i=0;i<=n;i++)
    {
        cnt[i]=0;dist[i]=inf;vis[i]=false;
    }
    q.push(0);
    dist[0]=0;vis[0]=true;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        if(++cnt[u]>n)return false;
        vis[u]=false;
        for(int i=head[u];~i;i=edges[i].next)
        {
            int v=edges[i].v,w=edges[i].w;
            if(dist[v]>dist[u]+w)
            {
                dist[v]=dist[u]+w;
                if(!vis[v])
                {
                    vis[v]=true;
                    q.push(v);
                }
            }
        }
    }
    return true;
}
int main()
{
    w=read();
    while(w--)
    {
        init();
        n=read();m=read();
        for(int i=0;i<m;i++)
        {
            s=read();t=read();v=read();
            add_edges(s-1,t,v);
        }
        if(spfa())printf("true\n");
        else printf("false\n");
    }
    return 0;
}

第二个参考了个博客,使用的是带权并查集,很有参考价值,用s[x]表示从第0天到第x天的总收益,之后用并查集上的点权val[x]表示s[f[x]]-s[x]的值,因为题目中不是说了一定保证s<t,那么如果s,t不在一个并查集里,那么合并两个森林,我们考虑的是合并两个森林的父节点,p=f[x],q=f[y],那么令f[p]=q,之后还得为p赋值,怎么赋值呢?可以发现s[q]-s[p]=s[q]-s[y]-(s[p]-s[x])+w,val[p]=val[y]-val[x]+w就是森林合并的公式,当然,并查集上不了路径压缩,比如是这样一个并查集,f[a]=b,f[b]=c,现在要将a合并到c下面,那么应该怎么路径压缩呢?可以发现从上往下压缩,然后s[c]-s[a]=s[c]-s[b]+s[b]-s[a],val[c]+=val[b]直接就能导出公式,如果要查询的s,t在一个并查集里,那么直接用val[x]-val[y]看是否等于w,val[x]-val[y]=s[p]-s[x]-(s[p]-s[y]),然后这三个操作证明完毕,直接码就是了。

附上代码:

#include<bits/stdc++.h>
#define IO ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
using namespace std;
inline int read()
{
    char ch=getchar();
    int f=1,x=0;
    while(!(ch>='0'&&ch<='9')){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
const int maxn=1e2+5;
int w,n,m,s,t,v,f[maxn],val[maxn];
bool flag;
int find(int x)
{
    if(x==f[x])return x;
    int t=find(f[x]);
    val[x]+=val[f[x]];
    f[x]=t;
    return f[x];
}
void work(int x,int y,int w)
{
    int p=find(x),q=find(y);
    if(p!=q)
    {
        f[p]=q;
        val[p]=val[y]-val[x]+w;
    }
    else if(val[x]-val[y]!=w)flag=false;
}
int main()
{
    w=read();
    while(w--)
    {
        n=read();m=read();
        flag=true;
        for(int i=0;i<=n;i++)
        {
            f[i]=i;val[i]=0;
        }
        for(int i=0;i<m;i++)
        {
            s=read();t=read();v=read();
            work(s-1,t,v);
        }
        printf("%s\n",flag?"true":"false");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhouzi2018/article/details/87884631