题目链接:
https://ac.nowcoder.com/acm/contest/882/D
题意:
N
N
N个点,每个点有对应的权
w
i
w_i
wi,还有边
e
i
j
∈
{
0
,
1
}
e_{ij}∈\left\{ 0,1 \right \}
eij∈{0,1},
e
i
j
=
1
e_{ij} = 1
eij=1表示
i
i
i和
j
j
j连通,现求第
K
K
K大的团的值。
题解:
因为权值都为正值,所以在当前团的基础上,你每多加一个点,就会使团的值变大,所以可以考虑优先队列,一个一个的加入点让团的值由小到大。这里需要保证新加入的点必须与当前团中的点是连通的,如果是用vector数组记录的话太费空间(每个点团都要记录),这里用到了bitset,因为我们只需要知道哪些点在团中,所以对应二进制位数如果是1那就是在,只要新加入的点的状态与当前团的状态按位与结果不变,即(now.bt & state[i]) != now.bt,那就能说明新加入的点与原来的点是连通的。
代码:
const int MAX = 110;
struct clique {
int n;//记录上个新加入的点
ll w;//记录团的值
bitset<MAX> bt;//bitset记录连通情况
clique() {
n = 0;
w = 0;
bt.reset();
}
bool operator < (const clique& a) const {//优先队列比较
return w > a.w;
}
};
int N, K;
ll w[MAX];
bitset<MAX> state[MAX];
char str[MAX];
priority_queue<clique> q;
void solve() {
clique now;
q.push(now);
while (!q.empty()) {
clique now = q.top(); q.pop(); K--;
if (K == 0) {//说明当前就是第K个
printf("%lld\n", now.w);
return;
}
for (int i = now.n + 1; i <= N; i++) {//从当前团上一个新加入的点之后开始,避免重复状态入队
clique nxt = now;
if ((now.bt & state[i]) != now.bt)//当前团和新加入点按位与结果不变 说明都是连通的
continue;
nxt.bt.set(i);// -> 团中有i这个点
nxt.n = i;
nxt.w += w[i];
q.push(nxt);
}
}
printf("-1\n");
}
int main() {
scanf("%d%d", &N, &K);
for (int i = 1; i <= N; i++)
scanf("%lld", &w[i]);
for (int i = 1; i <= N; i++) {
scanf("%s", str + 1);
for (int j = 1; j <= N; j++)
if (str[j] == '1')
state[i].set(j);//位运算表示map[i][j]=1
}
solve();
return 0;
}