合唱问题(网络流——二元关系)

题目描述:

甲乙两人要合唱一首有m个音符的歌,歌中每个音符音高都是1~n之间的正整数。
甲能够唱音高在[1,b]范围内的音符,乙能够唱音高在[a,n]范围内的音符。
现在两个人要合唱这首歌,要满足两个条件:
1、一个人不能唱他不能唱的音符。
2、为了保持韵律的和谐,所有音高相同的音符都要由同一个人演唱。
你所要求的东西在“编程任务”标题中。
对于给定的歌曲和甲乙能演唱的音高范围,计算出一种符合以上两个条件的歌唱方案使得甲乙之间切换的次数尽量少,所谓切换,就是指唱当前音符的人和唱下一个音符的人不一样,你只需输出最少的切换次数即可。

题解:

这显然是个流,考虑用经典的二元关系去流。

如果两个颜色x,y相邻,连(1,1)。
如果x只属于甲或只属于乙,S到x连∞的边或x到T连∞的边。

如果它可以属于甲或者乙,则S->x连1,x->T连1,最后ans要减去这个多余的流量。

这是一种建图的方法。

还有一种是按序号建图,相邻的序号要连(1,1).

对于同一音符的序号,它们要不都属于S,要不都属于T。

建新的点a’,对于每个x,到a’连(∞,∞)。

Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define mem(a) memset(a, 0, sizeof a)
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;

const int N = 1e4 + 5, INF = 1 << 30;

int Q, n, m, a, b, c[N], p[N], td;
int tot, final[N], next[N], to[N], r[N];
int d[N], co[N], cur[N], S, T, ans;

void link(int x, int y, int z, int z2) {
    next[++ tot] = final[x], to[tot] = y, r[tot] = z, final[x] = tot;
    next[++ tot] = final[y], to[tot] = x, r[tot] = z2, final[y] = tot;
}

int dg(int x, int flow) {
    if(x == T) return flow;
    int use = 0;
    for(int i = cur[x]; i; i = next[i]) {
        int y = to[i];
        if(d[y] + 1 == d[x] && r[i]) {
            int tmp = dg(y, min(r[i], flow - use));
            r[i] -= tmp; r[i ^ 1] += tmp; use += tmp;
            if(use == flow) return use;
        }
    }
    cur[x] = final[x];
    if(!(-- co[d[x]])) d[S] = T;
    ++ co[++ d[x]];
    return use;
}

int cmp(int x, int y) {
    return c[x] < c[y];
}

int main() {
    freopen("sing.in", "r", stdin);
    freopen("sing.out", "w", stdout);
    for(scanf("%d", &Q); Q; Q --) {
        mem(final); mem(next);
        mem(d); mem(co); mem(cur);
        tot = 1;
        scanf("%d %d %d %d", &n, &m, &a, &b);
        fo(i, 1, m) scanf("%d", &c[i]);
        S = n + m + 1; T = S + 1;
        fo(i, 1, m) p[i] = i; sort(p + 1, p + m + 1, cmp);
        int la = 1; td = m;
        fo(i, 2, m + 1) {
            if(c[p[i]] != c[p[i - 1]]) {
                if(c[p[i - 1]] >= a && c[p[i - 1]] <= b) {
                    td ++;
                    fo(j, la, i - 1) link(p[j], td, INF, INF);
                }
                la = i;
            }
        }
        fo(i, 1, m) {
            if(c[i] <= b && c[i] < a) link(S, i, INF, 0);
            if(c[i] >= a && c[i] > b) link(i, T, INF, 0);
        }
        fo(i, 2, m) link(i - 1, i, 1, 1);
        ans = 0; co[0] = td + 2;
        for(; d[S] < T;) ans += dg(S, INF);
        printf("%d\n", ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值