[xsy2300]好题

题意:有一棵树,每个节点有颜色,要找出最小的连通块使得其中的点至少有$k$种不同的颜色,只需输出这个最小连通块的大小

因为$k$很小,所以如果颜色只有$k$种,我们可以直接状压DP,设$f_{i,j}$表示在$i$的子树中包含颜色集合为$j$的最小连通块大小(这个连通块必须包含$i$),那么可以枚举$s$的子集$t$,转移即为$f_{x,s}\gets f_{x,t}+f_{u,s-t}\left(u\in son_x\right)$

但颜色最多有$n$种,这时候我们就可以trick一下:随机地把$n$种颜色映射到$k$种颜色再DP,随机多几次错误的概率就很低了

......但我第一次交还是WA了啊==同样的代码再交一次就A了(论一个非洲人的自我修养)

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<time.h>
int min(int a,int b){return a<b?a:b;}
int h[10010],nex[20010],to[20010],c[10010],p[10010],f[10010][32],M,k;
void add(int a,int b){
	M++;
	to[M]=b;
	nex[M]=h[a];
	h[a]=M;
}
void dfs(int fa,int x){
	int i,s,t;
	f[x][p[c[x]]]=1;
	for(i=h[x];i;i=nex[i]){
		if(to[i]!=fa){
			dfs(x,to[i]);
			for(s=0;s<1<<k;s++){
				for(t=s;t;t=(t-1)&s)f[x][s]=min(f[x][s],f[x][t]+f[to[i]][s^t]);
			}
		}
	}
	for(s=0;s<1<<k;s++){
		for(t=s;t;t=(t-1)&s)f[x][t]=min(f[x][t],f[x][s]);
	}
}
int main(){
	srand(time(0));
	int T,n,i,x,y,ans;
	scanf("%d%d",&n,&k);
	for(i=1;i<=n;i++)scanf("%d",c+i);
	for(i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	T=50;
	ans=n;
	while(T--){
		for(i=1;i<=n;i++)p[i]=1<<(rand()%k);
		memset(f,63,sizeof(f));
		dfs(0,1);
		for(i=1;i<=n;i++)ans=min(ans,f[i][(1<<k)-1]);
	}
	printf("%d",ans);
}

转载于:https://www.cnblogs.com/jefflyy/p/9092843.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值