[BZOJ1077][SCOI2008]天平(差分约束)

211 篇文章 0 订阅
15 篇文章 0 订阅

题目描述

传送门

题目大意:有n个砝码,重量只可能为1g,2g或3g,给出一些砝码之间的重量关系。现在将a和b两个砝码放在天平的左边,现在要选出另外两个砝码放在方程的右边,问有多少种方法使得天平左边重、一样重、右边重。(只有结果保证惟一的选法才统计在内)

题解

对于两个砝码x,y,若只考虑+和=的情况,那么可以列出不等式
d(x)-d(y)>=1;d(x)>=d(y),d(y)>=d(x)
对于最小值设一个源点d(0),满足d(0)=3,且d(i)<=d(0);对于最大值设d(0),满足d(0)=1,且d(i)>=d(0)
这样搞成差分约束最短路的模型,跑单源最短路和单源最长路求出每一个点可能的最大值和最小值
然后对于任意的两个点xy,在图中暴力找出xy的关系,即x>y,y>x,x=y或xy不相关
暴力枚举放在右边的两个砝码,再暴力枚举这4个砝码的重量[Min,Max],然后暴力判断这4个砝码的重量关系是否满足在图中的关系,同时满足一种情况才合法
刚开始的时候让我困惑的一点是在暴力枚举重量之后,为什么只需要判断这4个砝码的关系就行了,不需要考虑其它的砝码。实际上这个问题非常蠢,题目保证了至少有一种可行解,在[Min,Max]区间内枚举就保证了其它的点都有可行解

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
#define N 55
#define E 10005

int n,a,b,inf,c1,c2,c3;
char s[N];bool flag[N],can[N][N];
int tot,point[N],nxt[E],v[E],c[E],_tot,_point[N],_nxt[E],_v[E],_c[E];
int dis[N],_dis[N],d[N][N];bool vis[N];
queue <int> q;

void add(int x,int y,int z)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=z;
}
void _add(int x,int y,int z)
{
    ++_tot; _nxt[_tot]=_point[x]; _point[x]=_tot; _v[_tot]=y; _c[_tot]=z;
}
void spfa()
{
    memset(dis,127,sizeof(dis));
    dis[0]=3;vis[0]=1;q.push(0);
    while (!q.empty())
    {
        int now=q.front();q.pop();
        vis[now]=0;
        for (int i=point[now];i;i=nxt[i])
            if (dis[v[i]]>dis[now]+c[i])
            {
                dis[v[i]]=dis[now]+c[i];
                if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
            }
    }
}
void _spfa()
{
    memset(_dis,128,sizeof(_dis));
    _dis[0]=1;vis[0]=1;q.push(0);
    while (!q.empty())
    {
        int now=q.front();q.pop();
        vis[now]=0;
        for (int i=_point[now];i;i=_nxt[i])
            if (_dis[_v[i]]<_dis[now]+_c[i])
            {
                _dis[_v[i]]=_dis[now]+_c[i];
                if (!vis[_v[i]]) vis[_v[i]]=1,q.push(_v[i]);
            }
    }
}
void dfs(int x,int rt)
{
    vis[x]=1;can[rt][x]=1;
    for (int i=point[x];i;i=nxt[i])
        if (!vis[v[i]])
            dfs(v[i],rt);
}
bool check(int A,int B,int I,int J,int a,int b,int i,int j)
{
    if (d[A][B]!=inf)
    {
        if (a<b&&d[A][B]>=0) return 0;
        if (a==b&&d[A][B]) return 0;
        if (a>b&&d[A][B]<=0) return 0;
    }
    if (d[A][I]!=inf)
    {
        if (a<i&&d[A][I]>=0) return 0;
        if (a==i&&d[A][I]) return 0;
        if (a>i&&d[A][I]<=0) return 0;
    }
    if (d[A][J]!=inf)
    {
        if (a<j&&d[A][J]>=0) return 0;
        if (a==j&&d[A][J]) return 0;
        if (a>j&&d[A][J]<=0) return 0;
    }
    if (d[B][I]!=inf)
    {
        if (b<i&&d[B][I]>=0) return 0;
        if (b==i&&d[B][I]) return 0;
        if (b>i&&d[B][I]<=0) return 0;
    }
    if (d[B][J]!=inf)
    {
        if (b<j&&d[B][J]>=0) return 0;
        if (b==j&&d[B][J]) return 0;
        if (b>j&&d[B][J]<=0) return 0;
    }
    if (d[I][J]!=inf)
    {
        if (i<j&&d[I][J]>=0) return 0;
        if (i==j&&d[I][J]) return 0;
        if (i>j&&d[I][J]<=0) return 0;
    }
    return 1;
}
int compare(int A,int B,int I,int J)
{
    int c1=0,c2=0,c3=0;
    for (int a=_dis[A];a<=dis[A];++a)
        for (int b=_dis[B];b<=dis[B];++b)
            for (int i=_dis[I];i<=dis[I];++i)
                for (int j=_dis[J];j<=dis[J];++j)
                    if (check(A,B,I,J,a,b,i,j))
                    {
                        if (a+b>i+j) ++c1;
                        if (a+b==i+j) ++c2;
                        if (a+b<i+j) ++c3;
                    }
    if (c1&&!c2&&!c3) return 1;
    if (!c1&&c2&&!c3) return 2;
    if (!c1&&!c2&&c3) return 3;
    return 0;
}
int main()
{
    scanf("%d%d%d",&n,&a,&b);
    for (int i=1;i<=n;++i)
    {
        scanf("%s",s+1);
        for (int j=1;j<=n;++j)
            if (s[j]=='+')
                add(i,j,-1),_add(j,i,1);
            else if (s[j]=='='&&i<=j)   
                add(i,j,0),add(j,i,0),_add(i,j,0),_add(j,i,0);
    }
    for (int i=1;i<=n;++i) add(0,i,0),_add(0,i,0);
    spfa();_spfa();
    for (int i=1;i<=n;++i)
    {
        memset(vis,0,sizeof(vis));
        dfs(i,i);
    }
    memset(d,127,sizeof(d));inf=d[0][0];
    for (int i=1;i<=n;++i)
        for (int j=i;j<=n;++j)
        {
            if (can[i][j]&&can[j][i]) d[i][j]=d[j][i]=0;
            else if (can[i][j]) d[i][j]=1,d[j][i]=-1;
            else if (can[j][i]) d[i][j]=-1,d[j][i]=1;
        }
    for (int i=1;i<=n;++i)
        if (i!=a&&i!=b)
            for (int j=i+1;j<=n;++j)
                if (j!=a&&j!=b)
                {
                    int opt=compare(a,b,i,j);
                    if (opt==1) ++c1;
                    if (opt==2) ++c2;
                    if (opt==3) ++c3;
                }
    printf("%d %d %d\n",c1,c2,c3);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值