NOIP 2017 宝藏 状压DP

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/89389058

title

LUOGU 3959
JYOJ 1465
题目描述

参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋, 也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远, 也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路 则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某 个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以 任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路 所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏 屋之间的道路无需再开发。
新开发一条道路的代价是:
L × K \mathrm{L} \times \mathrm{K}
L代表这条道路的长度,K代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的 宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋) 。
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代 价最小,并输出这个最小值。

输入输出格式
输入格式:

第一行两个用空格分离的正整数 n,m,代表宝藏屋的个数和道路数。
接下来 m 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏 屋的编号(编号为 1-n),和这条道路的长度 v。

输出格式:

一个正整数,表示最小的总代价。

输入输出样例
输入样例#1:

4 5
1 2 1
1 3 3
1 4 1
2 3 4
3 4 1

输出样例#1:

4

输入样例#2:

扫描二维码关注公众号,回复: 6000246 查看本文章

4 5
1 2 1
1 3 3
1 4 1
2 3 4
3 4 2

输出样例#2:

5

说明

【样例解释1】
小明选定让赞助商打通了 1 1 号宝藏屋。小明开发了道路 1 2 1 \to 2 ,挖掘了 2 2 号宝 藏。开发了道路 1 4 1 \to 4 ,挖掘了 4 4 号宝藏。还开发了道路 4 3 4 \to 3 ,挖掘了 3 3 号宝 藏。工程总代价为: 1 × 1 + 1 × 1 + 1 × 2 = 4 1 \times 1 + 1 \times 1 + 1 \times 2 = 4
【样例解释2】
小明选定让赞助商打通了 1 1 号宝藏屋。小明开发了道路 1 2 1 \to 2 ,挖掘了 2 2 号宝 藏。开发了道路 1 3 1 \to 3 ,挖掘了 3 3 号宝藏。还开发了道路 1 4 1 \to 4 ,挖掘了 4 4 号宝 藏。工程总代价为: 1 × 1 + 3 × 1 + 1 × 1 = 5 1 \times 1 + 3 \times 1 + 1 \times 1 = 5
【数据规模与约定】
对于 20 % 20\% 的数据: 保证输入是一棵树, 1 n 8 v 5000 1 \le n \le 8,v \le 5000 且所有的 v v 都相等。
对于 40 % 40\% 的数据: 1 n 8 0 m 1000 v 5000 1 \le n \le 8,0 \le m \le 1000,v \le 5000 且所有的 v v 都相等。
对于 70 % 70\% 的数据: 1 n 8 0 m 1000 v 5000 1 \le n \le 8,0 \le m \le 1000,v \le 5000
对于 100 % 100\% 的数据: 1 n 12 0 m 1000 v 500000 1 \le n \le 12,0 \le m \le 1000,v \le 500000

analysis

我们考虑一个最优解

  • 显然这是一棵生成树,如果我们能构造出来 g [ i ] [ j ] g[i][j] 表示从 i i 状态挖到 j j 状态的最小花费
    我们就可以按照层次加入点集更新答案了。

  • f [ i ] [ j ] f[i][j] 表示当前深度为 i i ,状态为 j j 的最小花费
    f [ i ] [ s ] = f [ i 1 ] [ t ] + g [ s ] [ t ] ( i 1 ) f[i][s]=f[i−1][t]+g[s][t]∗(i−1)
    时间复杂度 O 3 n n O(3^n*n)
    如果预处理不够优秀的话 O 4 n O(4^n)

  • 预处理我们再开一个数组 h [ s ] [ i ] h[s][i] 表示状态 s s 挖到点 i i 的最小花费(不考虑深度)
    然后我们用边权更新 h h 数组,再用 h h 数组更新 g g 数组即可

如果想要透彻理解此题,建议到1035719430

code

状压DP

#include<bits/stdc++.h>
using namespace std;
const int maxn=15,maxs=(1<<12)+100;
const int inf=0x3f3f3f3f;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int e[maxn][maxn],h[maxn][maxs];
int f[maxn][maxs],g[maxs][maxs];
int main()
{
	int n,m;read(n);read(m);
	memset(e,0x3f,sizeof(e));
	for (int i=1; i<=m; ++i)
	{
		int x,y,z;
		read(x);read(y);read(z);
		e[x][y]=e[y][x]=min(e[x][y],z);
	}
	int tot=(1<<n)-1;
	for (int i=1; i<=n; ++i)
		for (int s=1; s<=tot; ++s)
			{
				h[i][s]=inf;
				if (!(s&(1<<i-1)))
					for (int j=1; j<=n; ++j)
						if (s&(1<<j-1))
							h[i][s]=min(h[i][s],e[i][j]);
			}
	for (int s=1; s<=tot; ++s)
	{
		int t=s&(s-1);
		while (t)
		{
			int x=s^t;
			for (int i=1; i<=n; ++i)
				if (x&(1<<i-1))
				{
					g[s][t]+=h[i][t];
					if (g[s][t]>inf) g[s][t]=inf;
				}
			t=s&(t-1);
		}
	}
	for (int i=1; i<=n; ++i)
		for (int s=0; s<=tot; ++s)
			f[i][s]=inf;
	for (int i=1; i<=n; ++i)
		f[1][1<<i-1]=0;
	for (int i=2; i<=n; ++i)
		for (int s=1; s<=tot; ++s)
		{
			int t=s&(s-1);
			while (t)
			{
				int tmp=0;
				if (g[s][t]<inf) tmp=g[s][t]*(i-1);
				else tmp=inf;
				if (f[i-1][t]<inf) f[i][s]=min(f[i][s],f[i-1][t]+tmp);
				t=s&(t-1);
			}
		}
	int ans=inf;
	for (int i=1; i<=n; ++i)
		ans=min(ans,f[i][tot]);
	printf("%d\n",ans);
	return 0;
}

模拟退火大法好

#include<bits/stdc++.h>
using namespace std;
const int maxn=30;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int n,m;
int g[maxn][maxn];
struct rec
{
	int d[maxn],dep[maxn];
	rec()
	{
		for (int i=1; i<=n; ++i)
			d[i]=i,dep[i]=0;
	}
	rec(const rec &path)
	{
		memcpy(d,path.d,sizeof(d));
		memset(dep,0,sizeof(dep));
		swap(d[rand()%n+1],d[rand()%n+1]);
	}
	inline double dist()
	{
		double ans=0;
		dep[d[1]]=1;
		for (int i=2; i<=n; ++i)
		{
			double tmp=9999999;
			for (int j=1; j<i; ++j)
				if (g[d[j]][d[i]]!=0x3f3f3f3f && dep[d[j]]*g[d[j]][d[i]]<tmp)
					tmp=dep[d[j]]*g[d[j]][d[i]],dep[d[i]]=dep[d[j]]+1;
			if (tmp!=9999999) ans+=tmp;
			else return tmp;
		}
		return ans;
	}
};
inline int SA()
{
    const double max_temper=10000;
    const double dec=0.998;
    double tmp=max_temper;
    rec path;
    while (tmp>0.01)
	{
        rec path2(path);
        double del=path2.dist()-path.dist();
        if (del<=0) path=path2;
        else if (exp(-del/tmp)*RAND_MAX>=rand()) path=path2;
        tmp*=dec;
    }
    return (int)path.dist();
}
int main()
{
    srand(19260817);
    read(n);read(m);
    memset(g,0x3f,sizeof g);
    for (int i=1; i<=m; ++i)
	{
        int x,y,z;
        read(x);read(y);read(z);
        g[x][y]=g[y][x]=min(g[x][y],z);
    }
    int ans=99999999,T=60;
    while (T--) ans=min(ans,SA());
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/89389058
今日推荐