今天模拟赛 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;
}