图论只会折半搜索

122 篇文章 0 订阅
7 篇文章 0 订阅

题目

题目描述
境况危急! 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 n20

对于 100 % 100\% 100% 的数据, n ≤ 50 n\le 50 n50 m ≤ 500 m\le 500 m500 c i ≤ 1000 c_i\le 1000 ci1000

思路

不会真的有人认为是折半搜索吧 😐

显然就是最小覆盖(或者最大独立集),这是 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值