把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷7450】[THUSCH2017] 巧克力(随机化+斯坦纳树)

点此看题面

  • 给定一张\(n\times m\)的网格图,每个格子有一个颜色和一个权值,一些障碍格无法选择。
  • 要求选出的格子构成一个连通块且至少包含\(k\)种颜色,求选择格子数量的最小值及在此前提下选出格子权值中位数的最小值。
  • 数据组数\(\le5,n\times m\le233,k\le5\)

随机映射

包含\(k\)种颜色最好的做法应该就是状压,然而颜色数量很大根本不可能直接状压。

因此我们考虑随机映射,即给每种颜色随机一个\([0,k)\)之间的值,把它转化为\(k\)种颜色,这样一来我们只要保证选出的格子至少包含映射后的\(k\)种颜色,这就可以状压了。

考虑这种做法正确,当且仅当最优解对应的\(k\)种颜色映射后恰好被分在\(k\)种不同的颜色中,概率是\(\frac{k!}{k^k}\),因此随机个\(100\)次就差不多了。

斯坦纳树

比较好的斯坦纳树题目。

首先说明,不要局限地认为斯坦纳树状压的只能是点集,实际上也可以状压颜色集合,只不过初始化每个点的状态应该是它对应的颜色。

选择格子数量最小是很简单的,就是个板子。

而要让权值的中位数最小,比较套路地去考虑二分答案,则一个数大于等于中位数,就需要满足比它小的数的个数大于比它大的数的个数。因此把比答案大的权值设为\(1\),比答案小的权值设为\(-1\)

那么只要把距离和权值绑在一起,以距离为第一关键字,权值为第二关键字去跑斯坦纳树,就能求出最小距离,以及距离最小的前提下的最小权值了。然后判下权值是否小于等于\(0\)即可更新二分答案区间。

代码:\(O(100nm2^k)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define NM 233
#define K 5
#define INF (int)1e9
#define P(x,y) (((x)-1)*m+(y))
#define H(x) (((x)-1)/m+1)
#define L(x) (((x)-1)%m+1)
using namespace std;
const int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
int n,m,k,lim,p[NM+5],c[NM+5],a[NM+5];
struct node
{
	int v,c;I node(CI a=0,CI b=0):v(a),c(b){}
	I node operator + (Con node& o) Con {return node(v+o.v,c+o.c);}
	I node operator - (Con node& o) Con {return node(v-o.v,c-o.c);}
	I bool operator < (Con node& o) Con {return v^o.v?v<o.v:c<o.c;}//距离v为第一关键字,权值c为第二关键字
}f[NM+5][1<<K],v[NM+5];
int IQ[NM+5];queue<int> q;I void SPFA(CI s)//SPFA处理边的转移
{
	#define Ex(x,y) (1<=(x)&&(x)<=n&&1<=(y)&&(y)<=m&&~c[P(x,y)])
	RI i,k,t,x,y,nx,ny;W(!q.empty()) for(k=q.front(),q.pop(),
		IQ[k]=0,x=H(k),y=L(k),i=0;i^4;++i) nx=x+dx[i],ny=y+dy[i],Ex(nx,ny)&&
			(t=P(nx,ny),f[k][s]+v[t]<f[t][s]&&(f[t][s]=f[k][s]+v[t],!IQ[t]&&(q.push(t),IQ[t]=1)));
}
I node Steiner(CI mid)//斯坦纳树
{
	RI i,j;for(i=1;i<=n*m;++i) for(j=0;j^lim;++j) f[i][j]=node(INF,INF);//初始化极大值
	for(i=1;i<=n*m;++i) ~c[i]&&(f[i][1<<p[c[i]]]=v[i]=node(1,a[i]>mid?1:-1),0);//每个格子对应颜色
	for(RI s=1;s^lim;++s)//枚举状态
	{
		for(i=1;i<=n*m;++i) for(j=s;j;j=(j-1)&s) f[i][s]=min(f[i][s],f[i][j]+f[i][j^s]-v[i]);//按点转移
		for(i=1;i<=n*m;++i) f[i][s].v^INF&&(q.push(i),IQ[i]=1);SPFA(s);//按边转移
	}
	node t(INF,INF);for(i=1;i<=n*m;++i) t=min(t,f[i][lim-1]);return t;//统计最优答案
}
int ans,res;I void Solve()//处理一次映射后的答案
{
	RI x=Steiner(1e6).v;if(x>ans) return;x<ans&&(ans=x,res=1e6);//如果个数更大直接return,如果个数更小更新ans
	RI l=1,r=1e6,mid;W(l^r) Steiner(mid=l+r-1>>1).c<=0?r=mid:l=mid+1;res=min(res,r);//二分中位数
}
int main()
{
	RI Tt,i,j;srand(1174320748),scanf("%d",&Tt);W(Tt--)
	{
		for(scanf("%d%d%d",&n,&m,&k),lim=1<<k,i=1;i<=n;++i) for(j=1;j<=m;++j) scanf("%d",c+P(i,j));
		for(i=1;i<=n;++i) for(j=1;j<=m;++j) scanf("%d",a+P(i,j));
		for(ans=INF,i=1;i<=100;++i) {for(j=1;j<=n*m;++j) p[j]=rand()%k;Solve();}//随机映射100次
		ans==INF?puts("-1 -1"):printf("%d %d\n",ans,res);
	}return 0;
}
posted @ 2021-03-27 07:51  TheLostWeak  阅读(96)  评论(0编辑  收藏  举报