欢迎神犇吊打|

Hanx16Msgr

园龄:2年8个月粉丝:12关注:3

2022-07-14 11:33阅读: 9评论: 0推荐: 0

#P2058. 全国动员

#P2058. 全国动员

题目描述

X国有n个城市,n个城市之间相互连通,且互相只有一条路可以到达,首都在1号城市,现在有m项任务需要完成,每个城市都可以选择来完成一项任务,也可以不选择做。完成的任务需要传输到首都。传输是需要时间的,完成任务也需要时间,请计算完成所有任务最少的时间。

输入格式

第一行是2个整数nm

接下来n-1行,每行3个整数x,y,w表示城市x到y有一条路,传输时间为w

接下来是nm列个整数,第ij列表示城市i完成任务j需要的时间

输出格式

一个整数,表示答案,范围在int范围

样例

输入数据 1
6 3
1 2 1
1 3 1
2 4 1
2 5 1
3 6 1
3 4 3
3 2 5
6 1 2
1 8 9
8 8 1
4 7 6
输出数据 1
7

数据规模与约定

50%的数据有 n<=10, m<=5。

100%数据n<=55, m<=11,保证 n>=m。

Solution

刚看到这道题以为是一道正常的树形 DP ,结果推了半天的方程也没推出来,然后看了看数据范围……好吧肯定有状压,压缩的内容一定与 m 有关。

m 的规模入手,那么使用状态压缩的话与 m 相关,因此状态压缩应该存储的是任务的完成情况,显然这个大小是 211 的,完全可以存的下。接下来就可以按照正常的树形 DP 来进行思考了。

f[x][i] 是以 x 为根的子树中任务完成状态为 i 的最小时间。考虑 f[x][i] 的来源,首先如果 x 是叶子节点,那么 f[x][i] 一定需要保证__builting_popcount(i)==1i 二进制上只有一个为 1),因为叶子结点总共只能完成一个任务,因此对于叶子结点的更新就很简单,直接 f[x][(1<<(k1))]=cost[x][k]k 表示第 k 个任务)。

然后需要思考的就是对于非叶节点的转移。很明显,非叶节点应该有两个状态来源:

自己不做任务

对于这一种情况, f[x][i] 就是完全从子节点转移来的。那可以从子节点的哪些状态转移来呢?显然,只能从 i 的子集转移来,但是又出现了一个非常棘手的问题,就是我并不知道每个子节点到底会对父节点的那些状态贡献多少,如果一个一个枚举的话时间复杂度直接飙升至 O(n22m) ,对于本题来说完全卡不过去。那么该如何更新这种状态呢?

因为我们是一个一个向下 DFS 子节点的,所以前面子节点对父节点产生的贡献已经算在 f 内了,所以我们只需要调用 f 内存储的信息即可,具体方程就是这样的: f[x][i]=min{f[x][i],f[sonx][t]+f[x][i^t]},ti 。值得注意的是,因为更新 f[x][i] 时访问的 f 数组的第二维下标一定都是小于 i 的,而且我们要避免访问的 f 已经被当前结点更新(否则会出现一个节点做了好几个任务的情况),所以对于 i 的枚举应该从大往小(可以用 01 背包理解)。

自己做一个任务

这种情况在上一种情况的基础上其实很好转移。假设当前结点 x 做了一个任务 t ,变成的状态是 i ,那么即可将其分解为为是当前节点 x 在状态 i^(1<<(t1)) 没做任务的最小时间,完成任务 t 所需时间。知道这一点即可推出转移方程: f[x][i]=min{f[x][i],f[x][i^(1<<(t1))]+cost[x][t]} 。同样需要注意的是,这里更新 f[x][i] 时访问的 f 也都位于小于 i 的位置,所以也需要枚举 i 的时候倒序枚举来避免多做任务。

开始前初始化 f 为一个极大值,但是需要注意这个极大值不能太大,因为在转移的过程中涉及到加法的操作,如果直接无脑 0x7fffffff ,会导致加法计算时溢出,导致答案变成负数,然后喜提 WA (当然如果你要用 unsigned 我也没办法)。另外需要注意, f 的所有第二维为 0 的时候初值都应该赋为 0 (既然啥任务都不做那时间肯定就是 0 了啊)。

另外,在代码实现的时候,需要注意上面 DP 转移的先后顺序,自己做任务的转移应该放在自己不做任务转移的后面,因为自己做任务的转移是需要用到不做任务转移的信息的,具体阐释上面已经写了。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<limits.h>
#include<cmath>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
template<typename T> void read(T &k)
{
 	k=0;
	T flag=1;char b=getchar();
	while (b<'0' || b>'9') {flag=(b=='-')?-1:1;b=getchar();}
	while (b>='0' && b<='9') {k=(k<<3)+(k<<1)+(b^48);b=getchar();}
	k*=flag;
}
const int _SIZE=55,_STATSIZE=(1<<11);
int n,m;
struct EDGE{
	int next,to,len;
}edge[(_SIZE<<1)+5];
int tot,head[_SIZE+5],InDe[_SIZE+5];
void AddEdge(int x,int y,int v)
{
	++tot;
	edge[tot].next=head[x];
	edge[tot].to=y;
	edge[tot].len=v;
	InDe[y]++;
	head[x]=tot;
}
int f[_SIZE+5][_STATSIZE+5];
int cost[_SIZE+5][_SIZE+5];
void dfs(int x,int fa)
{
	for (int i=head[x];i;i=edge[i].next)
	{
		int twd=edge[i].to,c=edge[i].len;
		if (twd==fa) continue;
		dfs(twd,x);
		for (int i=(1<<m)-1;i;i--)
			for (int t=i;t;t=(t-1)&i)
			{
				f[x][i]=min(f[twd][t]+f[x][i^t]+c,f[x][i]);
			}
	}
	for (int i=(1<<m)-1;i;i--)
		for (int k=0;k<m;k++)
			if (i>>k&1) f[x][i]=min(f[x][i],f[x][i^(1<<k)]+cost[x][k+1]);
}
int main()
{
	read(n),read(m);
	for (int i=1;i<n;i++)
	{
		int a,b,c;
		read(a),read(b),read(c);
		AddEdge(a,b,c);
		AddEdge(b,a,c);
	}
	for (int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			read(cost[i][j]);
	for (int i=1;i<=n;i++)
		for (int j=1;j<1<<m;j++)
			f[i][j]=INT_MAX>>2;//防止溢出
	dfs(1,0);
	printf("%d\n",f[1][(1<<m)-1]);
	return 0;
}

又是一道树形状压 DP 呢。

posted @   Hanx16Msgr  阅读(9)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起