@description@

给定一个n个点,m条边的无向图,其中你在第i个点建立旅游站点的费用为C[i]。在这张图中,任意两点间不存在节点数超过10的简单路径。

请找到一种费用最小的建立旅游站点的方案,使得每个点要么建立了旅游站点,要么与它有边直接相连的点里至少有一个点建立了旅游站点。

Input

第一行包含两个正整数n,m(1<=n<=20000,0<=m<=25000),分别表示点数和边数。

第二行包含n个整数,其中第i个数为Ci,表示在第i个点建立旅游站点的费用。

接下来m行,每行两个正整数u,v(1<=u,v<=n),表示u与v之间连了一条边,保证没有重边

Output

输出一行一个整数,即最小的总费用。

Sample Input

6 6

3 8 5 6 2 2

1 2

2 3

1 3

3 4

4 5

4 6

Sample Output

7

Explanation

分别在1,5,6号站点建立旅游站点。

@solution@

如果在树上做,这道题就是道树 dp 入门题。

如果在一般的图上做,这道题就是道 np 问题。

但是我们有一个 “任意两点间不存在节点数超过10的简单路径” 的条件,这使我们联想到状压/搜索。

考虑将图转为树,建出每一个连通子图(注意不一定整张图连通)的 dfs 树,由上面那个条件我们有树的深度 <= 10。

考虑 dfs 树的性质:边要么是树边要么是返祖边。这意味着一个点连向它祖先的边 <= 10。我们或许可以状压它所有祖先的状态。

定义状态 dp[i][s] 表示考虑 dfs 序的前 i 个元素,第 i 个元素到根的路径上所有点的状态为 s。其中根据我们树 dp 入门题的思想,将 s 的状态设为三进制,分别表示 “0 -不选且相邻点也都不选”,“1 - 不选但已经存在相邻点选”,“2 - 选”。

考虑从 dp[i][s] 转移到 dp[i+1][s']。这个过程可以看作退栈,将栈顶的不合法元素全部弹出后再在栈中加入新的元素。

首先要保证 s 中不是 i+1 祖先的点为 1 or 2,过后将它们从状态 s 中删除。转移考虑 i+1 选不选,再考虑这个决策对返祖边的影响:如果选,返祖边的 0 状态全部改为 1 状态;如果不选,如果返祖边存在一个 2 状态则 i + 1 为 1 状态,否则为 0 状态。

时间复杂度 O(3^10*(n + m))。虽然很不科学不过应该跑得挺快的。

(但我常数大加了随机选dfs树根+二进制代替三进制+转移的一点优化才过)(但我总感觉是因为加了这些东西才跑得慢)

@accepted code@

#include<cstdio>
#include<vector>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 40000;
const int MAXM = 50000;
const int SIZE = (1<<10);
const int INF = (1<<30);
struct Graph{
struct edge{
int to; edge *nxt;
}edges[2*MAXM + 5], *adj[MAXN + 5], *ecnt;
Graph() {ecnt = &edges[0];}
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
}G1, G2;
vector<int>vec[MAXN + 5];
int dep[MAXN + 5], dfn[MAXN + 5], tid[MAXN + 5], dcnt = 0;
void dfs(int x, int f) {
dep[x] = dep[f] + 1, tid[x] = (++dcnt), dfn[dcnt] = x;
for(Graph::edge *p=G1.adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
if( !tid[p->to] )
G2.addedge(x, p->to), dfs(p->to, x);
else {
if( tid[p->to] < tid[x] )
vec[x].push_back(p->to);
}
}
}
int C[MAXN + 5];
int dp[2][SIZE + 5][SIZE + 5];
void init(int t1, int t2) {
for(int s1=0;s1<t1;s1++) {
int s2 = s1;
do {
dp[1][s1][s2] = dp[0][s1][s2];
if( !s2 ) break;
s2 = s1 & (s2 - 1);
}while( true );
}
for(int s1=0;s1<t2;s1++) {
int s2 = s1;
do {
dp[0][s1][s2] = INF;
if( !s2 ) break;
s2 = s1 & (s2 - 1);
}while( true );
}
}
int get_ans(int x, int m) {
int lst = 0; dp[0][0][0] = 0;
for(int p=tid[x];p<=tid[x]+m-1;p++) {
int i = dfn[p];
init(1<<lst, 1<<dep[i]);
int t1 = (1<<(dep[i]-1)), t2 = ((1<<(lst-(dep[i]-1)))-1)<<(dep[i]-1), t3 = t1>>1;
for(int j=0;j<vec[i].size();j++)
t3 |= (1<<(dep[vec[i][j]]-1));
for(int s3=0;s3<=t2;s3+=t1) {
for(int s1=0;s1<t1;s1++) {
int s2 = s1;
do {
int s4 = s1|t2, s5 = s2|s3;
if( dp[1][s4][s5] != INF ) {
if( s5 & t3 ) dp[0][s1|t1][s2] = min(dp[0][s1|t1][s2], dp[1][s4][s5]);
else dp[0][s1][s2] = min(dp[0][s1][s2], dp[1][s4][s5]);
dp[0][s1|t3|t1][s2|t1] = min(dp[0][s1|t3|t1][s2|t1], dp[1][s4][s5] + C[i]);
}
if( !s2 ) break;
s2 = s1 & (s2 - 1);
}while( true );
}
}
lst = dep[i];
}
int t = (1<<lst), ret = INF;
for(int i=0;i<t;i++)
ret = min(ret, dp[0][t-1][i]);
return ret;
}
int fa[MAXN + 5], siz[MAXN + 5];
int find(int x) {return fa[x] = (fa[x] == x ? x : find(fa[x]));}
void unite(int x, int y) {
int fx = find(x), fy = find(y);
if( fx != fy )
fa[fx] = fy, siz[fy] += siz[fx];
}
vector<int>v[MAXN + 5];
int main() {
srand(20040911);
int n, m; scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)
scanf("%d", &C[i]), fa[i] = i, siz[i] = 1;
for(int i=1;i<=m;i++) {
int u, v; scanf("%d%d", &u, &v);
G1.addedge(u, v), unite(u, v);
}
int ans = 0;
for(int i=1;i<=n;i++)
v[find(i)].push_back(i);
for(int i=1;i<=n;i++)
if( fa[i] == i ) {
int rt = v[i][((unsigned int)(rand() << 16 | rand())) % v[i].size()];
dfs(rt, 0), ans += get_ans(rt, siz[i]);
}
printf("%d\n", ans);
}

@details@

我现在还是感觉是因为我加了些奇奇怪怪的优化才跑得非常慢。

人家跑 6000ms,可我要跑 13000ms 左右。。。一开始还 T 了几发。。。

果然有些东西还是不要乱用。

@bzoj - 3836@ [Poi2014]Tourism的更多相关文章

  1. 【刷题】BZOJ 4543 [POI2014]Hotel加强版

    Description 同OJ3522 数据范围:n<=100000 Solution dp的设计见[刷题]BZOJ 3522 [Poi2014]Hotel 然后发现dp的第二维与深度有关,于是 ...

  2. 主席树||可持久化线段树||BZOJ 3524: [Poi2014]Couriers||BZOJ 2223: [Coci 2009]PATULJCI||Luogu P3567 [POI2014]KUR-Couriers

    题目:[POI2014]KUR-Couriers 题解: 要求出现次数大于(R-L+1)/2的数,这样的数最多只有一个.我们对序列做主席树,每个节点记录出现的次数和(sum).(这里忽略版本差值问题) ...

  3. BZOJ 3836 Codeforces 280D k-Maximum Subsequence Sum (模拟费用流、线段树)

    题目链接 (BZOJ) https://www.lydsy.com/JudgeOnline/problem.php?id=3836 (Codeforces) http://codeforces.com ...

  4. BZOJ 3524: [Poi2014]Couriers [主席树]

    3524: [Poi2014]Couriers Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 1892  Solved: 683[Submit][St ...

  5. BZOJ 3524: [Poi2014]Couriers

    3524: [Poi2014]Couriers Time Limit: 20 Sec  Memory Limit: 256 MBSubmit: 1905  Solved: 691[Submit][St ...

  6. Bzoj 3831 [Poi2014]Little Bird

    3831: [Poi2014]Little Bird Time Limit: 20 Sec Memory Limit: 128 MB Submit: 310 Solved: 186 [Submit][ ...

  7. [BZOJ 3829][POI2014] FarmCraft

    先贴一波题面... 3829: [Poi2014]FarmCraft Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 421  Solved: 197[ ...

  8. BZOJ 3526: [Poi2014]Card

    3526: [Poi2014]Card Time Limit: 25 Sec  Memory Limit: 64 MBSubmit: 267  Solved: 191[Submit][Status][ ...

  9. BZOJ.3522.[POI2014]Hotel(DP)

    题目链接 BZOJ 洛谷 以为裸点分治,但数据范围怎么这么小?快打完了发现不对.. n^2做的话其实是个水题.. 枚举每一个点为根,为了不重复计算,我们要求所求的三个点必须分别位于三棵子树上. 考虑当 ...

随机推荐

  1. 关于python 环境变量

    1.默认命令行的启动的python 版本,这依赖于系统的环境变量. 见上一篇关于linux 环境变量的PATH 变量的设置 2.python 中 import 包的搜索路径, 即除了当前程序目录,能i ...

  2. Spring MVC 搭建web项目示例

    环境为Eclipse 1:新建Dynamic web project  : springMvcDemo 2:下载spring的jar包,把jar包复制到WEB-INF/lib目录下 3.添加配置文件w ...

  3. appium+python 启动一个app步骤

    询问度娘搭好appium和python环境,开启移动app自动化的探索(基于Android),首先来记录下如何启动待测的app吧! 如何启动APP?1.获取包名:2.获取launcherActivit ...

  4. JAVA Sftp 上传下载

    SftpUtils package xxx;import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelSftp; import com ...

  5. day36 10-Hibernate中的事务:解决丢失更新

    演示hibernate如何产生丢失更新的 丢失更新是怎么玩的?首先两个事务先都把它查出来. A事务里面去修改的数据没了,被B事务覆盖掉了.这是被B事务提交覆盖,B事务回滚也能覆盖.这就是丢失更新的效果 ...

  6. linux挂载点 和 文件系统$ mount$ cat /etc/fstab$ vgs$ pvs$ lvs$ df -h$ lsof +D / /* beware not to kill your box */

    $ mount$ cat /etc/fstab$ vgs$ pvs$ lvs$ df -h$ lsof +D / /* beware not to kill your box */ 一共挂载了多少文件 ...

  7. Leetcode622.Design Circular Queue设计循环队列

    设计你的循环队列实现. 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环.它也被称为"环形缓冲器". 循环队列的一个好处是 ...

  8. javascript最大公约数与最小公倍数

    var a = 5 ; var b = 15 ; var min = Math.min(a,b); var max = Math.max(a,b); // for循环求最大公约数 for(var i ...

  9. leetcode 21-30 easy

    21. Merge Two Sorted Lists Merge two sorted linked lists and return it as a new list. The new list s ...

  10. C++ Socket 获取本机可用端口号(QT)

    :加载套接字库,创建套接字(WSAStartup()/socket()): :绑定套接字到一个IP地址和一个端口上(bind()): :将套接字设置为监听模式等待连接请求(listen()): :请求 ...