poj 2570/3275/1975 传递闭包+位运算(从a到b选哪个公司能到呢)

2570题意:有若干家公司(每个公司用一个小写字母表示,所以公司的数量不超过26),他们分别在若干个城市的路由器间铺设了自己的光纤。给定这个城市的路由器铺设方案,现在多组查询(A,B):如果要从路由器A到路由器B传送资料,可以使用哪家公司铺设的光纤?如能则分别输出这些公司的代号。


思路:大方向上的思路是首先求出每个公司的传递闭包,然后对于查询直接查找即可。传递闭包的求法可以使用floyd,这样得做26次floyd,有点难以承受。但是这道题可以巧妙地使用位运算来简化计算。dis[i][j]的最右边一位表示从i到j用a公司能否通过,以此类推。更新的时候用这个表达式即可:dis[i][j] |= (dis[i][k] &dis[k][j]);(表示如果i到k和k到j都有路径(26家公司就一起计算了),那么i到j就一定有路径)

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s));
#define N 205
int dis[N][N],n;
char s[30];
int main(){
    while(scanf("%d",&n) && n){
        int i,j,k,a,b;
        clr(dis, 0);
        while(scanf("%d %d ",&a,&b) && (a+b)){
            scanf("%s",s);
            for(i = 0;s[i];i++)
                dis[a][b] |= 1<<(s[i]-'a');
        }
        for(k = 1;k<=n;k++)
            for(i = 1;i<=n;i++)
                for(j = 1;j<=n;j++)
                    dis[i][j] |= (dis[i][k] & dis[k][j]);
        while(scanf("%d %d",&a,&b) && (a+b)){
            j = k = 0;
            for(i = 0,j = 1;i<26;i++){
                if(dis[a][b] & j){
                    k = 1;
                    putchar('a'+i);
                }
                j <<= 1;
            }
            if(!k)
                putchar('-');
            printf("\n");
        }
        printf("\n");
    }
    return 0;
}


3275题意:FJ想按照奶牛产奶的能力给她们排序。现在已知有N头奶牛(1 ≤ N ≤ 1,000)。FJ通过比较,已经知道了M(1 ≤ M ≤ 10,000)对相对关系。每一对关系表示为“X Y”,意指X的产奶能力强于Y。现在FJ想要知道,他至少还要调查多少对关系才能完成整个排序。

思路:N头奶牛一共有C(N, 2) = N * (N - 1) / 2对关系。由现在已知的关系如能确认K对关系,则要调查的次数就是C(N, 2) - K。这很容易理解,最坏情况下调查完一对关系之后只能确定一对关系。剩下的就变成求现有图的传递闭包,算出传递闭包中已经有的边,用总数减去。

针对这道题用floyd求传递闭包有个优化,就是内层循环枚举i、j的时候只需要从k的入边中枚举i,从出边中枚举j。

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s));
#define N 1005
int s[N][N],t[N][N],dis[N][N];
int n,m;
int main(){
    int i,j,k,a,b,res=0;
    scanf("%d %d",&n,&m);
    for(i = 1;i<=n;i++)
        s[i][0] = t[i][0] = 0;//s[x][0]存放出边的数量,t[x][0]存放入边的数量
    clr(dis, 0);
    for(i = 1;i<=m;i++){
        scanf("%d %d",&a,&b);
        dis[a][b] = 1;
        s[a][++s[a][0]] = b;
        t[b][++t[b][0]] = a;
    }
    for(k = 1;k<=n;k++){
        for(i = 1;i<=t[k][0];i++){
            a = t[k][i];
            for(j = 1;j<=s[k][0];j++){
                b = s[k][j];
                if(dis[a][k]+dis[k][b]==2 && !dis[a][b]){
                    dis[a][b] = 1;
                    s[a][++s[a][0]] = b;
                    t[b][++t[b][0]] = a;
                }
            }
        }
    }
    res = n*(n-1)/2;
    for(i = 1;i<=n;i++)
        for(j = 1;j<=n;j++)
            res -= dis[i][j];
    printf("%d\n",res);
    return 0;
}

1975题意:n个珠子(保证n是奇数),给出m对比较。a,b表示a比b重。问最终能确定多少个珠子必然不是中位数重量。

思路:和3275如出一辙,求传递闭包。

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s));
#define N 105
int s[N][N],t[N][N],dis[N][N];
int T,n,m;
int main(){
    scanf("%d",&T);
    while(T--){
        int i,j,k,a,b,flag = 0;
        scanf("%d %d",&n,&m);
        clr(dis, 0);
        for(i = 1;i<=n;i++)
            s[i][0] = t[i][0] = 0;
        for(i = 1;i<=m;i++){
            scanf("%d %d",&a,&b);
            dis[a][b] = 1;
            s[a][++s[a][0]] = b;
            t[b][++t[b][0]] = a;
        }
        for(k = 1;k<=n;k++){
            for(i = 1;i<=t[k][0];i++){
                a = t[k][i];
                for(j = 1;j<=s[k][0];j++){
                    b = s[k][j];
                    if(dis[a][k]+dis[k][b]==2 && !dis[a][b]){
                        dis[a][b] = 1;
                        t[b][++t[b][0]] = a;
                        s[a][++s[a][0]] = b;
                    }
                }
            }
        }
        k = 0;
        for(i = 1;i<=n;i++){
            a = b = 0;
            for(j = 1;j<=n;j++){
                a += dis[i][j];
                b += dis[j][i];
            }
            if(a==n/2 && b==n/2){
                flag = 1;
                break;
            }
            if(a>n/2 || b>n/2)
                k++;
        }
        if(flag)
            printf("%d\n",n-1);
        else
            printf("%d\n",k);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值