POJ 3123 Ticket to Ride 状压dp+树形dp 斯坦纳树

题目链接:点击打开链接

题意:

给定n个城市和m条可选择修建的道路

下面n行给出每个城市的名字

下面m行给出每条道路及修建该道路的花费。

下面4行,每行给出一对城市。

目标:使得最后4行的每对城市连通(不同对之间可以不连通)所需要修建的最小花费。

数据保证存在可行解

思路:

首先如果这个问题问的是所有城市都连通,就是一个最小生成树的问题。

这里就相当于多个最小生成树的 问题。

当然图里我们只关心最后的4行8个点,所以我们状压这8个点。

设dp[i][j] 表示以i为根 ,j为8个点中是否在 i 的子树里 时的最小花费。

(注意i这个点是不一定出现在j里的,因为i点可以是除这8个点之外的点)

首先跑个floyd。

dp[i][j] 的初始化就是以8个点其中一个点为叶子,任意一个点为根来初始化一遍,

转移:

1、从2个点集合并得到

dp[i][j] = min( dp[i][ X ]+dp[i][ Y ])

其中X | Y == i;

即我们把 根为i ,点集为X 的子树和根同样为i 点集为Y的子树的花费和 就是根为i ,点集为 j = X+Y 的一个花费,取个最小即可。

2、把i点加到一个子树上得到。

我们枚举这个子树,显然这个子树的点集就是j,当然根是不确定的,因为根不一定是j中的点,所以枚举根K。

这样我们就能得到子树的状态是 dp[K] [j] , 而加入i点时 可以和树上任意一个点相连

(难道要枚举树上所有点和i连一次?其实我们就认为是 add(i, K); 因为我们一定会枚举到树上那个最优 的点)

3、求答案。

首先一个可能的解就是dp[ anypoint ][ 255 ].

当然这样的结果等同于最小生成树了。

实际上是每对点都可能互相独立也可能互相连通之类之类的。

我们可以造4个盒子(编号为0,1,2,3),然后任意地把4对点扔进去,在一个盒子里的点就表示他们是连通的,不在一个盒子里就说明是不连通的。

每个点对有4个状态(0,1,2,3),表示所在哪个盒子,即四进制状压。

over

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Scanner;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Queue;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class Main {
	int n, m;
	int[][] dis = new int[N][N], dp = new int[30][1<<8];
	int[] po = new int[8], v = new int[30];
	Map<String, Integer> map = new HashMap();
	void floyd(){
		for(int k = 0; k < n; k++)
			for(int i = 0; i < n; i++)
				for(int j = 0; j < n; j++)
					dis[i][j] = min(dis[i][j], dis[i][k]+dis[k][j]);
	}
	void input() throws Exception{
		map.clear();
		for(int i = 0; i < n; i++){
			map.put(Next(), i);
			for(int j = 0; j < n; j++)dis[i][j] = (i==j)?0:inf;
		}
		while(m-->0){
			int u = map.get(Next());
			int v = map.get(Next());
			int d = Int();
			dis[u][v] = dis[v][u] = min(dis[u][v], d);
		}
		for(int i = 0; i < 8; i++)	po[i] = map.get(Next());
	}
	void work() throws Exception{
		while(true){
			n = Int(); m = Int(); if(n==0 && m==0)return ;
			input();
			floyd();
			for (int i = 0; i < n; i++)for (int j = 0; j < 256; j++)dp[i][j] = inf;
			for (int j = 0; j < n; j++)
				for (int i = 0; i < 8; i++)
					dp[j][1 << i] = dis[po[i]][j];
			for (int i = 1; i < 256; i++){
				if (0 == (i&(i - 1)))continue;
				for (int j = 0; j < n; j++)
				{
					for (int sub = i; sub > 0; sub = (sub - 1)&i)
						dp[j][i] = min(dp[j][i], dp[j][sub] + dp[j][i-sub]);
				}
				for(int k = 0; k < n; k++)v[k] = 0;
				for (int j = 0; j < n; j++)
				{
					int b = inf, pos = 0;
					for(int k = 0; k < n; k++)
						if(dp[k][i] <= b && v[k] == 0)
							b = dp[pos = k][i];
					v[pos] = 1;
					for(int k = 0; k < n; k++)
						dp[pos][i] = min(dp[pos][i], dp[k][i]+dis[k][pos]);
				}
			}
			int ans = inf;

			for (int i = 0; i < 256; i++){
				int tmp = 0;
				for (int j = 0; j < 4; j++)	v[j] = 0;
				for (int j = 0, k = i; j < 4; j++){
					int now = k & 3;
					k >>= 2;
					v[now] |= 1 << (j * 2);
					v[now] |= 1 << (j * 2 + 1);
				}
				for (int j = 0; j < 4; j++){
					if(v[j] == 0)continue;
					int hehe = inf;
					for (int top = 0; top < n; top++)
					for (int k = 0; k < 256; k++)
						hehe = min(hehe, dp[top][k | v[j]]);
					tmp += hehe;
				}
				ans = min(ans, tmp);
			}
			out.println(ans);
		}
	}

    public static void main(String[] args) throws Exception{
        Main wo = new Main();
    	in = new BufferedReader(new InputStreamReader(System.in));
    	out = new PrintWriter(System.out);
  //  	in = new BufferedReader(new InputStreamReader(new FileInputStream(new File("input.txt"))));
  //  	out = new PrintWriter(new File("output.txt"));
        wo.work();
        out.close();
    }

	static int N = 32;
	static int M = 2005;
	DecimalFormat df=new DecimalFormat("0.0000");
	static int inf = (int)1e8;
	static long inf64 = (long) 1e18*2;
	static double eps = 1e-8;
	static double Pi = Math.PI;
	static int mod = 1000000009 ;

	private String Next() throws Exception{
    	while (str == null || !str.hasMoreElements())
    	    str = new StringTokenizer(in.readLine());
    	return str.nextToken();
    }
    private int Int() throws Exception{
    	return Integer.parseInt(Next());
    }
    private long Long() throws Exception{
    	return Long.parseLong(Next());
    }
    StringTokenizer str;
    static BufferedReader in;
    static PrintWriter out;
  /*
	class Edge{
		int from, to, dis, nex;
		Edge(){}
		Edge(int from, int to, int dis, int nex){
			this.from = from;
			this.to = to;
			this.dis = dis;
			this.nex = nex;
		}
	}
	Edge[] edge = new Edge[M<<1];
	int[] head = new int[N];
	int edgenum;
	void init_edge(){for(int i = 0; i < N; i++)head[i] = -1; edgenum = 0;}
	void add(int u, int v, int dis){
		edge[edgenum] = new Edge(u, v, dis, head[u]);
		head[u] = edgenum++;
	}/**/
	int upper_bound(int[] A, int l, int r, int val) {// upper_bound(A+l,A+r,val)-A;
		int pos = r;
		r--;
		while (l <= r) {
			int mid = (l + r) >> 1;
			if (A[mid] <= val) {
				l = mid + 1;
			} else {
				pos = mid;
				r = mid - 1;
			}
		}
		return pos;
	}

	int Pow(int x, int y) {
		int ans = 1;
		while (y > 0) {
			if ((y & 1) > 0)
				ans *= x;
			y >>= 1;
			x = x * x;
		}
		return ans;
	}
	double Pow(double x, int y) {
		double ans = 1;
		while (y > 0) {
			if ((y & 1) > 0)
				ans *= x;
			y >>= 1;
			x = x * x;
		}
		return ans;
	}
	int Pow_Mod(int x, int y, int mod) {
		int ans = 1;
		while (y > 0) {
			if ((y & 1) > 0)
				ans *= x;
			ans %= mod;
			y >>= 1;
			x = x * x;
			x %= mod;
		}
		return ans;
	}
	long Pow(long x, long y) {
		long ans = 1;
		while (y > 0) {
			if ((y & 1) > 0)
				ans *= x;
			y >>= 1;
			x = x * x;
		}
		return ans;
	}
	long Pow_Mod(long x, long y, long mod) {
		long ans = 1;
		while (y > 0) {
			if ((y & 1) > 0)
				ans *= x;
			ans %= mod;
			y >>= 1;
			x = x * x;
			x %= mod;
		}
		return ans;
	}

	int gcd(int x, int y){
		if(x>y){int tmp = x; x = y; y = tmp;}
		while(x>0){
			y %= x;
			int tmp = x; x = y; y = tmp;
		}
		return y;
	}
	int max(int x, int y) {
		return x > y ? x : y;
	}

	int min(int x, int y) {
		return x < y ? x : y;
	}

	double max(double x, double y) {
		return x > y ? x : y;
	}

	double min(double x, double y) {
		return x < y ? x : y;
	}

	long max(long x, long y) {
		return x > y ? x : y;
	}

	long min(long x, long y) {
		return x < y ? x : y;
	}

	int abs(int x) {
		return x > 0 ? x : -x;
	}

	double abs(double x) {
		return x > 0 ? x : -x;
	}

	long abs(long x) {
		return x > 0 ? x : -x;
	}

	boolean zero(double x) {
		return abs(x) < eps;
	}
	double sin(double x){return Math.sin(x);}
	double cos(double x){return Math.cos(x);}
	double tan(double x){return Math.tan(x);}
	double sqrt(double x){return Math.sqrt(x);}
}
时间: 2024-08-27 15:16:29

POJ 3123 Ticket to Ride 状压dp+树形dp 斯坦纳树的相关文章

【BZOJ 2595】2595: [Wc2008]游览计划 (状压DP+spfa,斯坦纳树?)

2595: [Wc2008]游览计划 Time Limit: 10 Sec  Memory Limit: 256 MBSec  Special JudgeSubmit: 1572  Solved: 739 Description Input 第一行有两个整数,N和 M,描述方块的数目. 接下来 N行, 每行有 M 个非负整数, 如果该整数为 0, 则该方块为一个景点:否则表示控制该方块至少需要的志愿者数目. 相邻的整数用 (若干个) 空格隔开,行首行末也可能有多余的空格. Output 由 N

HDU 4085 Peach Blossom Spring 斯坦纳树 状态压缩DP+SPFA

状态压缩dp+spfa解斯坦纳树 枚举子树的形态 dp[i][j] = min(dp[i][j], dp[i][k]+dp[i][l]) 其中k和l是对j的一个划分 按照边进行松弛 dp[i][j] = min(dp[i][j], dp[i'][j]+w[i][j])其中i和i'之间有边相连 #include <cstdio> #include <cstring> #include <queue> using namespace std; const int maxn

POJ 2411 Mondriaan&#39;s Dream(状压DP)

http://poj.org/problem?id=2411 求一个n*m矩阵用1*2方块去填满的情况有几种 思路:状压dp,先预处理那些状态之间能互相到达,情况就几种,上一个两个1,下一个状态也两个1,上一个为0,下一个必须为1,还有一种是上一个为1,下一个为0的情况 然后就一层层往后递推即可 代码: #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int

POJ 2411 Mondriaan&#39;s Dream ——状压DP 插头DP

[题目分析] 用1*2的牌铺满n*m的格子. 刚开始用到动规想写一个n*m*2^m,写了半天才知道会有重复的情况. So Sad. 然后想到数据范围这么小,爆搜好了.于是把每一种状态对应的转移都搜了出来. 加了点优(gou)化(pi),然后poj上1244ms垫底. 大概的方法就是考虑每一层横着放的情况,剩下的必须竖起来的情况到下一层取反即可. 然后看了 <插头DP-从入门到跳楼> 这篇博客,怒抄插头DP 然后16ms了,自己慢慢YY了一下,写出了风(gou)流(pi)倜(bu)傥(tong)

poj 2411 Mondriaan&#39;s Dream 状压dp入门

题意: 求h*w的矩形被1*2的小矩形覆盖的方案数. 分析: 状压dp入门,<挑战程序设计竞赛>上讲的很好,好几天才看懂. 代码: #include <iostream> using namespace std; __int64 ans[16][16]; int n,m; __int64 dp[2][1<<16]; __int64 solve() { int i,j,used; memset(dp,0,sizeof(dp)); __int64 *crt=dp[0],*n

POJ 2411 Mondriaan&#39;s Dream (状压DP)

题意:给出一个n*m的棋盘,及一个小的矩形1*2,问用这个小的矩形将这个大的棋盘覆盖有多少种方法. 析:对第(i,j)位置,要么不放,要么竖着放,要么横着放,如果竖着放,我们记第 (i,j)位置为0,(i+1,j)为1,如果横着放,那么我们记 (i,j),(i,j+1)都为1,然后dp[i][s]表示 第 i 行状态为 s 时,有多少方法,那么我们就可以考虑与和匹配的状态,这个也很容易判断. 代码如下: #pragma comment(linker, "/STACK:1024000000,102

POJ 1185 炮兵阵地(状压DP入门)

http://poj.org/problem?id=1185 状压DP: 1 #include<iostream> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 int dp[105][100][100]; 7 int ma[105],st[105]; 8 9 int ok(int x) 10 { 11 return (x&(x<<1))+(x&(x&

POJ 3254 Corn Fields 【状压DP】

[题目大意]一个矩阵里有很多格子,每个格子有两种状态,可以放牧和不可以放牧,可以放牧用1表示,否则用0表示,在这块牧场放牛,要求两个相邻的方格不能同时放牛,即牛与牛不能相邻.问有多少种放牛方案(一头牛都不放也是一种方案) [解析]根据题意,把每一行的状态用二进制的数表示,0代表不在这块放牛,1表示在这一块放牛.首先很容易看到,每一行的状态要符合牧场的硬件条件,即牛必须放在能放牧的方格上.这样就能排除一些状态.另外,牛与牛之间不能相邻,这样就要求每一行中不能存在两个相邻的1,这样也能排除很多状态.

POJ 3254 Corn Fields //入门状压dp

Corn Fields Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 7578   Accepted: 4045 Description Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1 ≤ N ≤ 12) square parcels. He wants to grow some yumm