UVa #1627 Team them up! (例题9-19)

86 篇文章 0 订阅

要求同一个队伍里的人互相认识,则一对互相不认识或者单方面认识的人必须被分到不同的队伍。根据后面这条规则进行分组会比较简单


所以我们可以从一个人出发,递归寻找所有“存在着不互相认识”关系的人,形成一个图,再给这个图里的所有人加正反标记,保证相邻的人都有相反的标记。若存在相邻的人有着相同的标记,则输出无解。之后再根据正反标记,把他们放到相应的组里面。


这样就把人分成了很多个连通块,每一个连通块维护着一个临时的分组。现在我们只需将每个连通块中的两个组里的人放进最终答案的两个组,这里便有两个选择:1->1 2->2或者1->2 2->1。


动态规划要做的就是找出正确的放法,使得最终两组人数差最小。


后话:

这里动态规划的“最优子结构”的感觉并不明显。动态规划的过程中,没有一个明确的目标,而只是将所有的可能性列举出来,直到最后才从所有的可能解中选出最优解。

Rujia将这道题和0-1背包进行类比。但是在0-1背包中,目标很明确:装的物品总价值最大。因此每次决策都要从两种决策中选出使得物品总价值最大的。

相比之下,这道题的动态规划,不应该从两种决策中选出“两队人数差最小的”,如果用这种贪心方法,会丢失全局最优解。事实上,只有最后一个连通块的决策才有着明确的目标,动态规划的其他中间过程在做决策时,是无法判断优劣的,只能枚举所有可能性。

因此我觉得这道题和0-1背包是貌合神离。


dfs复杂度O(n)(每个人恰好访问一次),动态规划复杂度O(n^2)(枚举决策复杂度将是O(2^n),这里用动态规划的思想消除了重叠自问题,很好的降低了复杂度),总复杂度O(n^2)



Run Time: 0.062s

#define UVa  "LT9-19.1627.cpp"		//
char fileIn[30] = UVa, fileOut[30] = UVa;

#include<cstring>
#include<cstdio>
#include<vector>

using namespace std;

struct CC {
    vector<int> group[2];
    void clear() {
        group[0].clear();
        group[1].clear();
    }
};

//Global Variables. Reset upon Each Case!
const int maxn = 100 + 5;
int T, n, cccnt;
int G[maxn][maxn];
int vis[maxn];
CC cc;
vector<CC> ccs;
int d[maxn][2*maxn];
/


int dfs(int u, int flag) {
    vis[u] = flag;
    for(int v = 0; v < n; v ++) {
        if(u != v && !(G[u][v] && G[v][u])) {
            if(!vis[v] && !dfs(v, -flag)) return 0;
            if(vis[u] == vis[v]) return 0;
        }
    }
    if(flag == 1) cc.group[0].push_back(u);
    else cc.group[1].push_back(u);
    return 1;
}
void insert(vector<int>& src, vector<int>&targ) {
    for(int i = 0; i < src.size(); i ++) targ.push_back(src[i]);
}

void print_ans(int j) {
    vector<int> result[2];
    for(int i = cccnt - 1; i >= 0; i --) {
        int diff = ccs[i].group[0].size() - ccs[i].group[1].size();
        if(d[i][j] == 1) {
            insert(ccs[i].group[0], result[0]);
            insert(ccs[i].group[1], result[1]);
            j -= diff;
        }
        else if(d[i][j] == -1) {
            insert(ccs[i].group[1], result[0]);
            insert(ccs[i].group[0], result[1]);
            j += diff;
        }
    }
    printf("%d", result[0].size());
    for(int i = 0; i < result[0].size(); i ++) {
        printf(" %d", result[0][i]+1);
    }
    printf("\n");

    printf("%d", result[1].size());
    for(int i = 0; i < result[1].size(); i ++) {
        printf(" %d", result[1][i]+1);
    }
    printf("\n");
}

void solve() {
    memset(d, 0, sizeof(d));
    cccnt = ccs.size();
    for(int i = 0; i < cccnt; i ++) {
        int diff = ccs[i].group[0].size() - ccs[i].group[1].size();
        for(int j = 0; j <= 2*n; j ++) {
            if(i == 0) {
                d[i][n + diff] = 1;
                d[i][n - diff] = -1;
                break;
            }
            else {
                if(d[i-1][j]) {
                    d[i][j + diff] = 1;
                    d[i][j - diff] = -1;
                }
            }
        }
    }
    for(int i = 0; i <= n; i ++) {
        if(d[cccnt - 1][n+i]) {
            print_ans(n+i);
            return;
        }
        if(d[cccnt - 1][n-i]) {
            print_ans(n-i);
            return;
        }
    }
}


int main() {
    scanf("%d", &T);
    while(T--) {

        memset(G, 0, sizeof(G));
        ccs.clear();
        scanf("%d", &n);
        for(int i = 0; i < n; i ++) {
            int b;
            while(scanf("%d", &b) && b)
                G[i][b-1] = 1;
        }

        memset(vis, 0, sizeof(vis));
        int ok = 1;
        for(int i = 0; i < n; i ++) {
            if(!vis[i]) {
                cc.clear();
                if(!dfs(i, 1)) {
                    ok = 0;
                    break;
                }
                ccs.push_back(cc);
            }
        }
        if(!ok)
            printf("No solution\n");
        else
            solve();
        if(T) printf("\n");
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值