题目
题目描述
境况危急!
z
x
y
\tt zxy
zxy 等奆佬正在对
j
z
m
\tt jzm
jzm 等弱者进行无情的屠杀!
为了躲避, j z m \tt jzm jzm 准备挖避难所。 j z m \tt jzm jzm 可能挖避难所的地方一共 n n n 个,它们之间一共有 m m m 条连接二者的小路。为了让躲避效果最好, j z m \tt jzm jzm 认为,如果一个地方没挖避难所,那么与它有小路相连的地方就一定 都 要挖避难所。
挖避难所是浩大的工程。对于第 i i i 个地方,挖避难所需要 c i c_i ci 天。 j z m \tt jzm jzm 希望你告诉他,最少需要多少天才能完成挖掘工作?
数据范围与提示
对于
40
%
40\%
40% 的数据,
n
≤
20
n\le 20
n≤20 。
对于 100 % 100\% 100% 的数据, n ≤ 50 n\le 50 n≤50 且 m ≤ 500 m\le 500 m≤500 且 c i ≤ 1000 c_i\le 1000 ci≤1000 。
思路
不会真的有人认为是折半搜索吧 😐
显然就是最小覆盖(或者最大独立集),这是 N P \tt NP NP 问题,别想着解决它,搜就完了!
有一些优化。当图稀疏时,使用前向星(会快非常多),又考虑到此时独立集的可行性很高,按照 c c c 排序后进行考虑;当图稠密时,选择很少,所以直接搜索也不会有太大问题。
复杂度 Θ ( 2 n ) , Ω ( n 2 ) \mathcal \Theta(2^n),\;\mathcal \Omega(n^2) Θ(2n),Ω(n2) 。至于折半搜索?你自己试试看 :l
代码
随机数据,运行极快;大环的情况,仍然极快——不开 − O 2 -O2 −O2 的结果。
稠密图应该也不会很糟糕。我想应该是很难卡掉了。
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxN = 50;
map< int_,int > has; // from 2^k to k
int_ g[MaxN];
int c[MaxN], n, ans;
int fuck; // 阙值,超过则不要
void dfs(int_ S,int now){
if(now >= ans || now > fuck)
return ;
if(S == 0){ // 得到答案
ans = now; return ;
}
int id = has[S&-S];
/* 情况一:不选 id */ ;
int nxt = now;
int_ S_ = S&g[id]; // 需要处理的
while(S_ != 0){
nxt += c[has[S_&-S_]];
S_ -= (S_&-S_);
}
S ^= (1ll<<id); // 总是没它
dfs(S&(~g[id]),nxt);
/* 情况二:选 id */ ;
dfs(S,now+c[id]);
}
int id[MaxN], pos[MaxN];
bool cmp(const int &a,const int &b){
return c[a] > c[b];
}
int main(){
n = readint();
int m = readint(), all = 0;
for(int i=0; i<n; ++i){
c[i] = readint();
all += c[i]; // 算平均值
id[i] = i; // 排完序更快?
}
sort(id,id+n,cmp); // 相当于 struct
sort(c,c+n,greater<int>());
for(int i=0; i<n; ++i)
pos[id[i]] = i;
for(int a,b; m; --m){
a = readint()-1;
b = readint()-1;
a = pos[a], b = pos[b];
g[a] |= (1ll<<b);
g[b] |= (1ll<<a);
}
for(int i=0; i<n; ++i)
has[1ll<<i] = i; // 看成 O(1)
const int infty = 1000000;
ans = infty; int zxy = 0;
int_ S = (1ll<<n)-1; // 去掉自环的点
for(int i=0; i<n; ++i)
if(g[i]&(1ll<<i)){ // 自环
S ^= (1ll<<i);
zxy += c[i];
}
fuck = (all+4)/5; // 瞎猜的值
dfs(S,0); // 先做一次
if(ans == infty){
fuck = infty; // 可以猜到边很多
dfs(S,0); // 那么放开了做,也很快
}
printf("%d\n",ans+zxy);
return 0;
}