【动态规划 状压dp】JZOJ_6316 djq的朋友圈

题意

给出一张图,有若干点之间连边,边权 ∈ { 0 , 1 } \in\{0,1\} {0,1},对于点1,与它直接连边的点且边权为0,就是盟友,否则是情敌;间接连边,就要按某种顺序同时列出它们的熟人,如果关系相同,那么这两个人就是盟友,否则是情敌。求点1最多的盟友。

思路

把直接与点1连边的点成为A类点,间接的称为B类点。

70分做法,状态压缩A点选哪些,更新答案。

100分做法,取A,B类点状态小的进行状压dp。

思路

#include <cstdio>
#include <cstring>
#include <algorithm>
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)

int n, m, aCnt, bCnt, ans;
int f[1 << 23], id[43];
long long con[43], dev[43], v[43][43];

int main() {
	scanf("%d %d", &n, &m);
	memset(v, -1, sizeof(v));
	for (int i = 1, x, y, op; i <= m; i++) {
		scanf("%d %d %d", &x, &y, &op);
		v[x][y] = v[y][x] = op;
	}
	for (int i = 2; i <= n; i++) {
		if (v[1][i] == -1) continue;
		id[i] = ++aCnt;
		ans += v[1][i] ^ 1;
		for (int j = 2; j <= n; j++)
			if (v[1][j] == -1 && v[i][j] != -1) {
				if (!id[j]) id[j] = ++bCnt;
				con[id[i] - 1] |= 1 << id[j] - 1;
				dev[id[i] - 1] |= (v[1][i] ^ v[i][j] ^ 1) << id[j] - 1;
			}
	}
	if (aCnt < bCnt) {
		int S = 1 << aCnt;
		memset(f, 0xcf, sizeof(f));
		f[0] = 0;
		for (int i = 0; i < S; i++) {
			if (f[i] < 0) continue;
			int k = 0;
			for (int j = 0; j < aCnt; j++)
				if (i & (1 << j)) k |= con[j];
			for (int j = 0; j < aCnt; j++)
				if (!(i & (1 << j)))
					f[i | (1 << j)] = std::max(f[i | (1 << j)], f[i] + __builtin_popcountll(dev[j] & (~k)));
		}
		printf("%d", ans + f[S - 1]);
	} else {
		int S = 1 << bCnt;
		memset(f, 0xcf, sizeof(f));
		f[0] = 0;
		for (int i = 0; i < S; i++) {
			if (f[i] < 0) continue;
			int k = 0;
			for (int j = 0; j < aCnt; j++)
				if ((i | con[j]) != i)
					f[i | con[j]] = std::max(f[i | con[j]], f[i] + __builtin_popcountll(dev[j] & (~i)));
		}
		printf("%d", ans + f[S - 1]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值