状态压缩DP-Islandsand Bridges(POJ 2288)

 

Islandsand Bridges

Time Limit: 4000MS

Memory Limit: 65536K

Total Submissions: 8493

Accepted: 2190

Description

Givena map of islands and bridges that connect these islands, a Hamilton path, as weall know, is a path along the bridges such that it visits each island exactlyonce. On our map, there is also a positive integer value associated with eachisland. We call a Hamilton path the best triangular Hamilton path if itmaximizes the value described below. 

Suppose there are n islands. The value of a Hamilton path C1C2...Cn iscalculated as the sum of three parts. Let Vi be the value-- for the island Ci.As the first part, we sum over all the Vi values for each island in the path.For the second part, for each edge CiCi+1 in the path, we add the product Vi*Vi+1.And for the third part, whenever three consecutive islands CiCi+1Ci+2 in the path forms a triangle in themap, i.e. there is a bridge between Ci and Ci+2, we add the productVi*Vi+1*Vi+2

Most likely but not necessarily, the best triangular Hamilton path you aregoing to find contains many triangles. It is quite possible that there might bemore than one best triangular Hamilton paths; your second task is to find thenumber of such paths. 

Input

The input file starts with a number q (q<=20) on the firstline, which is the number of test cases. Each test case starts with a line withtwo integers n and m, which are the number of islands and the number of bridgesin the map, respectively. The next line contains n positive integers, the i-thnumber being the Vi value of island i. Each value is no more than 100. Thefollowing m lines are in the form x y, which indicates there is a (two way)bridge between island x and island y. Islands are numbered from 1 to n. You mayassume there will be no more than 13 islands. 

Output

For each test case, output a line with two numbers, separated by aspace. The first number is the maximum value of a best triangular Hamiltonpath; the second number should be the number of different best triangularHamilton paths. If the test case does not contain a Hamilton path, the outputmust be `0 0'. 

Note: A path may be written down in the reversed order. We still think it isthe same path.

Sample Input

2
3 3
2 2 2
1 2
2 3
3 1
4 6
1 2 3 4
1 2
1 3
1 4
2 3
2 4
3 4

Sample Output

22 3
69 1

Source

Shanghai 2004

 

【题目大意】给出了n个地点和m座桥(连接顶点的),画出哈密尔顿圈并求出最大值,他们的最大值由三部分决定,1:n个地点的value值之和,2:顶点之间连接之积,3:三个(连续的)顶点之积;要求输出最大值,并且存在最大值的情况总数,同时,对于两个完全逆向的路径可以认为是同一条路径;

【分析】定义三维数据dp[state][i][j]记录当前的状态和最近两个顶点的编号,最困难的还是要怎么算出总数这个问题,这就要求我们必须记录对于每种完成哈密尔顿圈的路径做一个记录;那么需要怎么记录呢?显然要记录路径的完整性显然与动态规划的性质背道而驰了,动态规划是主要是相同子问题的一次求解(也就是相同子问题合并),那么其实我们不妨记录相同子问题的合并次数,如果需要合并那么我们++即可,与我们定义了另一个三维的数组way[state][i][j]他的值为当前被合并的次数;那么所有的难点基本已经解决了,开始写代码吧。。。因为对于两个完全逆向的路径可以认为是同一条路径,所以最终的结果应该是sum/2;

【状态表示】dp[state][i][j],状态为state,以j节点结尾,i节点为上一个节点表示,值为当前的最大值;

【状态转移方程】dp[state][i][j]= max(dp[state][i][j], dp[state’][k][i]+value)其中value为从i到j根据它的三部分取值算出来的;

【边界方程】:分别以每个起点开始;

 

Java代码如下:

import java.util.Arrays;
import java.util.Scanner;

public class Main {

	private long dp[][][];
	private long num[][][];

	private int link[][];
	private long value[];

	public Main(int N) {

		dp = new long[1 << N][N][N];
		num = new long[1 << N][N][N];
		link = new int[N][N];
		value = new long[N];
		for (int i = 0; i < dp.length; i++) {
			for (int j = 0; j < N; j++) {
				Arrays.fill(dp[i][j], 0);
				Arrays.fill(num[i][j], 0);
			}
		}

		for (int i = 0; i < N; i++) {
			Arrays.fill(link[i], 0);
		}

	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Scanner cin = new Scanner(System.in);
		int P, N, M;
		Main ma;
		P = cin.nextInt();

		while (P > 0) {
			P--;
			N = cin.nextInt();
			M = cin.nextInt();

			ma = new Main(N);
			for (int i = 0; i < N; i++) {
				ma.value[i] = cin.nextInt();
			}

			int start, end;

			for (int i = 0; i < M; i++) {
				start = cin.nextInt() - 1;
				end = cin.nextInt() - 1;
				ma.link[start][end] = 1;
				ma.link[end][start] = 1;
			}

			ma.getAndPrintIt();
		}
	}

	private void getAndPrintIt() {
		//注意一个点的情况
		if(link.length == 1){
			System.out.println(value[0]+" 1");
			return;
		}
		// TODO Auto-generated method stub
		// 先计算出边界情况
		for (int i = 0; i < link.length; i++) {
			for (int j = 0; j < link.length; j++) {
				if (i == j) {
					continue;
				}
				if (link[i][j] == 1) {
					dp[(1 << i) | (1 << j)][i][j] = value[i] * value[j];
					num[(1 << i) | (1 << j)][i][j] = 1;
				}
			}
		}
		long temp;
		// 遍历所有的点;
		//最外层是状态,因为状态时不断增加的,每次增加都能保证在下一次能够被取到;
		for (int s = 3; s < dp.length; s++) {
			//j表示数组dp的第二维
			for (int j = 0; j < link.length; j++) {
				if((s &(1<<j)) == 0){
					continue;
				}
				for (int k = 0; k < link.length; k++) {
					//k表示数组dp的第三维
					if (j == k) {
						continue;
					}
					
					if (dp[s][j][k] != 0) {
						//m表示要添加的下一个节点
						for(int m =0;m<link.length;m++){
							if((s & (1<<m)) != 0){
								//说明已经包含了节点m了
								continue;
							}
							
							if(link[k][m] == 0){
								continue;
							}
							
							temp = dp[s][j][k]+value[k]*value[m]+value[j]*value[m]*value[k]*link[j][m];
							if(dp[s|(1<<m)][k][m] < temp){
								//说明是新的值;
								dp[s|(1<<m)][k][m] = temp;
								num[s|(1<<m)][k][m] = num[s][j][k];
							}else if(dp[s|(1<<m)][k][m] == temp){
								num[s|(1<<m)][k][m]+=num[s][j][k];
							}
							
						}
					}
					
				}
			}
		}
		long valueans = 0;
		long numans = 0;
		//可以计算最终的值了;
		for(int i=0;i<link.length;i++){
			for(int j=0;j<link.length;j++){
				if(i==j){
					continue;
				}
				if(valueans < dp[dp.length-1][i][j]){
					valueans = dp[dp.length-1][i][j];
					numans = num[dp.length-1][i][j];
				}else if(valueans == dp[dp.length-1][i][j]){
					numans = numans + num[dp.length-1][i][j];
				}
			}
		}
		
		if(valueans == 0){
			System.out.println("0 0");
		}else{
			
			for(int i=0;i<link.length;i++){
				valueans=valueans+value[i];
			}
			
			numans = numans/2;
			System.out.print(valueans+" ");
			System.out.println(numans);	
		}
	}
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值