Cry_For_theMoon  

今天模拟赛 T1,赛后才知道是这个早就听过的题... 还好赛场上切了。

这个题强在模拟赛上卡了一位JS的E类爷/se

题意:

给定一颗 \(n\) 点树,边带正权,给定 \(m\) 种颜色,用它们对树的节点全部染色,使得每种颜色在树上至少出现 \(1\) 次,同时,第 \(m\) 种颜色必须恰好出现 \(k\) 次。设节点 \(i\) 的颜色为 \(c_i\)

试求一种染色方案,满足 \(\sum_{<u,v> \in E}[c_u=c_v]w\) 尽可能小(即如果一条边的两端点颜色相同,它的边权对最后的值产生影响,否则不产生影响)。输出这个最小值。如果不存在合法染色方案,则输出 \(-1\)

Data range:\(2\le m,k \le 300,w\le 10^5\)

分析:

我就直接莽,因为这真的是个 sb 题,还能加强到 \(1000\)

首先判掉节点数不够的情况,注意特判 \(m=1\),\(k\neq n\)(模拟赛里的毒瘤点,这里大于 \(1\) 不用考虑)。然后发现一定有一个最优解让每种颜色都出现一次,这是显然的,所以是个假条件。

直接设 \(f(i,j,k)\)\(i\) 为根的子树,\(j\) 个染了颜色 \(m\),根节点颜色为 \(k\) 的方案数。转移的时候暴力枚举 \(v\) 的颜色,还有 \(m\) 颜色节点个数,可以做到 \(O(n^4)\)(默认 \(n,m,k\) 同阶)。

然后观察到我们只关注子树颜色是否等于 \(k\),可以每次 Merge 答案的时候预处理 \(f(v,x)\) 的前缀最小值和后缀最小值,即可 \(O(n^3)\) 做完。

\(O(n^2)\) 的话,考虑最后只关注根节点颜色是否为 \(m\),所以是 \(f(i,j,0/1)\) 就行了。

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define op(x) ((x&1)?x+1:x-1)
#define odd(x) (x&1)
#define even(x) (!odd(x))
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define lowbit(x) (x&-x)
#define Max(a,b) (a>b?a:b)
#define Min(a,b) (a<b?a:b)
#define next Cry_For_theMoon
#define il inline
#define pb(x) push_back(x)
#define is(x) insert(x)
#define sit set<int>::iterator
#define mapit map<int,int>::iterator
#define pi pair<int,int>
#define ppi pair<int,pi>
#define pp pair<pi,pi>
#define fr first
#define se second
#define vit vector<int>::iterator
#define mp(x,y) make_pair(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef double db;
using namespace std;
const int MAXN=310,INF=1e9;
struct Edge{
	int u,v,w;
}edge[MAXN*2];
int first[MAXN],next[MAXN*2],tot;
int f[MAXN][MAXN][MAXN],pre[MAXN][MAXN],suf[MAXN][MAXN],tmp[MAXN][MAXN],sz[MAXN];
int n,m,K;
void addedge(int u,int v,int w){
	edge[++tot]=(Edge){u,v,w};
	next[tot]=first[u];first[u]=tot;
}
void Merge(int u,int v,int w){
	//合并v的答案到u上,边权为w 
	//预处理v的前后缀最小值
	rep(i,0,K){
		pre[i][0]=suf[i][m+1]=INF;
		rep(j,1,m){
			pre[i][j]=Min(pre[i][j-1],f[v][i][j]);
		} 
		per(j,m,1){
			suf[i][j]=Min(suf[i][j+1],f[v][i][j]);
		}
	} 
	//初始化tmp 
	rep(i,0,K)rep(j,1,m){tmp[i][j]=f[u][i][j];f[u][i][j]=INF;}
	//转移
	rep(i,0,Min(sz[u],K)){
		rep(j,1,m){
			//枚举子树v里的被第m个人选择的个数 
			rep(x,0,Min(sz[v],K)){
				if(i+x>K)break;
				//1.选择颜色j
				f[u][i+x][j]=Min(f[u][i+x][j],tmp[i][j]+f[v][x][j]+w);
				//2.不选择颜色j
				f[u][i+x][j]=Min(f[u][i+x][j],tmp[i][j]+pre[x][j-1]); 
				f[u][i+x][j]=Min(f[u][i+x][j],tmp[i][j]+suf[x][j+1]);
			}
		}
	} 
}
void dfs(int u,int fa){
	//初始化 
	rep(i,0,K)rep(j,1,m)f[u][i][j]=INF;
	sz[u]=1;
	rep(i,1,m-1){f[u][0][i]=0;}
	f[u][1][m]=0;
	//dp
	for(int j=first[u];j;j=next[j]){
		int v=edge[j].v;
		if(v==fa)continue;
		dfs(v,u);
		Merge(u,v,edge[j].w);
		sz[u]+=sz[v];
	}
}
int main(){
//	freopen("Isolation.in","r",stdin);
//	freopen("Isolation.out","w",stdout);
	scanf("%d%d%d",&n,&m,&K);
	rep(i,1,n-1){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		addedge(u,v,w);
		addedge(v,u,w);
	}
	if(m+K-1>n){
		printf("-1\n");
		return 0;
	}
	if(m==1 && K!=n){
		printf("-1\n");
		return 0;
	}
	//最优解一定合法
	dfs(1,0);
	printf("%d",f[1][K][m]);
	return 0;
}
posted on 2021-08-11 23:48  Cry_For_theMoon  阅读(47)  评论(0编辑  收藏  举报