poj 1112 经典最小差值dp(超级好题)

题意:
给你n个人,告诉你谁认识谁。要求将他们分成两组,每组的人相互都认识,且每组人数尽量接近(人数的差值最小)。

输入:第一行输入有几个人,第i行表示第i-1个人认识那些人,0表示结束。
输出:共两行,第i行第一个数表示i组有多少个人,然后依次输出这几个人,第二行同理。

Sample Input

5
2 3 5 0
1 4 5 3 0
1 2 5 0
1 2 3 0
4 3 2 1 0
Sample Output

3 1 3 5
2 2 4

思路:
先抽象成二分图,然后看能不能二分,如果不能直接输出,如果能,就抽象成可以dp的问题,用dp找差值最小的解。
抽象过程:
对于每一个节点,都有跟他相互连接的一堆节点,组成一个强连通分量,然后给这个强连通分量染色。问题就抽象成了这样,

有t个物品,每个物品有u价值和v价值,同时你可以选择将物品的u价值和v价值可以互换(swap(u,v)),对于每种物品,你都可以选择换或者不换。su = u价值的和,sv等于v价值的和,求abs(su-sv)最小时,所有物品换了或者没换。

初步定义dp[i]表示前i个物品的最小abs(su-sv)

一般的求最优解的dp都有一下思路:这一次的最优解由前几次的最优解构成。但是这个dp不一样,当前最优解可能是上一次的次优解得到或者是上一次的第三优解,所以这种dp无法写出状态转移方程。

再次定义dp[i][j]表示前i种物品能否构成差值为j的解,

这样定义的话,可行解可以推出可行解,状态转移方程就很明显了。

下面直接上代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <iomanip>
#include <string>
#include <algorithm>
#include <stack>
#include <queue>
#include <set>
#include <vector>
#include <map>

#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define ll long long
#define ull unsigned long long
#define uint unsigned int
#define l(x) ((x)<<1)
#define r(x) ((x)<<1|1)
#define lowbit(x) ((x)&(-(x)))
#define ms(a,b) memset(a,b,sizeof(a))
#define NSYNC std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);

using namespace std;

const int maxn = 110;
int n;//有多少个节点
int total;//有多少个强连通分量
int g[maxn][maxn];//i节点和j节点是否相连
int color[maxn];//第i个节点的值(颜色)是多少
int col[maxn][3];//第i个分量里值为j的节点有几个
int mark[maxn][3][maxn];//第i个分量里值为j的第k个节点是谁
int f[maxn][maxn * 2];//前i个连通分量能不能构成差值为j
int path[maxn][maxn * 2];//用于记录最优解是怎么构成的

int dfs(int u) {
	for (int i = 1; i <= n; i++) {
		if (i != u && (!g[u][i] || !g[i][u])) {
			if (color[i] == color[u]) return 0;

			if (!color[i]) {
				color[i] = 3 - color[u];
				mark[total][color[i]][col[total][color[i]]++] = i;
				if (!dfs(i))return 0;
			}
		}
	}
	return 1;
}

int main() {
	scanf("%d", &n);
	int tem, a; ms(color, 0);
	for (int i = 1; i <= n; i++)
		while (scanf("%d", &tem) && tem)
			g[i][tem] = 1;

	total = 0;
	for (int i = 1; i <= n; i++) 
		if (!color[i]) {
			total++;
			color[i] = 1;
			mark[total][1][col[total][1]++] = i;
			if (!dfs(i)) {
				printf("No solution\n");
				return 0;
			}
		}
	
	int a1, a2; ms(f, 0);
	f[1][col[1][1] - col[1][2] + n] = 1;
	f[1][col[1][2] - col[1][1] + n] = 1;
	path[1][col[1][1] - col[1][2] + n] = 1;
	path[1][col[1][2] - col[1][1] + n] = 2;
	for (int i = 2; i <= total; i++) {
		a = col[i][1] - col[i][2];
		for (int j = 0; j <= n * 2; j++) {
			if ((j - a) >= 0 && (j - a) <= 2 * n&&
				f[i - 1][j - a] == 1) {
				f[i][j] = 1;
				path[i][j] = 1;
			}
			else if ((j + a) >= 0 && (j + a) <= 2 * n&&
				f[i - 1][j + a] == 1) {
				f[i][j] = 1;
				path[i][j] = 2;
			}
		}
	}

	int i, j;
	for (i = n; i <= 2 * n; i++)if (f[total][i])break;
	for (j = n - 1; j >= 0; j--)if (f[total][j])break;
	int ans = (i - n) < (n - j) ? i : j;
	int tro = ((n - (ans - n)) / 2) + (ans - n);
	printf("%d", tro);
	int loc;
	tem = ans;
	for (int i = total; i >= 1; i--) {
		loc = path[i][tem];
		for (int j = 0; j < col[i][loc]; j++)
			printf(" %d", mark[i][loc][j]);
		tem = tem - (col[i][loc] - col[i][3 - loc]);
	}

	tem = ans;
	printf("\n");
	printf("%d", n - tro);
	for (int i = total; i >= 1; i--) {
		loc = 3 - path[i][tem];
		for (int j = 0; j < col[i][loc]; j++)
			printf(" %d", mark[i][loc][j]);
		tem = tem - (col[i][3 - loc] - col[i][loc]);
	}
	printf("\n");

	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值