(2022昆明站icpc)Blocks(概率DP)

题目链接:登录—专业IT笔试面试备考平台_牛客网

样例输入:

1
8
5 5
0 0 2 2
2 2 5 5
0 2 2 5
2 0 5 2
0 0 1 1
1 1 5 5
0 1 1 5
1 0 5 1

样例输出:

10

题意:给定一个矩形,长度为W,高度为H,然后给定n个小矩形,每个小矩形给出对角线的两个坐标,让我们选取若干个小矩形可以覆盖一开始给定的矩形,问我们选取小矩形数目的期望值,选取每个矩形的概率是相同的。

这显然是一道概率DP题目,设f[i]表示当前状态是i时还需要选取的小矩形数目的期望值,那么怎样进行状态方程的转移呢?

分析如下:

对于状态i来说,如果我们下次选取的小矩形是已经选取过的,那么就会有1+f[i],概率就是count(i)/n,count(i)是i二进制表示中1的个数,如果我们下次选取的小矩形还没有被选过,那么平均期望就有\sum (1+f[i|1<<j]),那么就有等式:

f[i]=count(i)/n*f[i]+\sum (1+f[i|1<<j]),整理得:

f[i]=(n+\sum (f[i|1<<j]))/(n-t)

有了这个状态转移方程我们就可以直接进行动态转移了,剩下的就是对f数组进行初始化了

看了下数据范围就知道我们需要进行初始化,由于题目中给定的是点的坐标,并不是格子,所以我们要把点的坐标平移进格子中间,但是需要注意的一点比如给定两个小矩形,第一个小矩形的对角线上两个点的坐标是(0,0)和(1,1),第二个小矩形的对角线上两个点的坐标是(2,0)和(4,1),那么经过离散化后他们所在的格子就相连了,如下图所示:

--------->

所以我们必须要把在实际值中相邻的两个点之间再插入一个点,那么为了不加入一个小数(避免因为精度引发的一系列问题),我们可以将每个数乘以2放入离散化数组中,再把每个数乘以2+1放入离散化数组中,这样就可以把两个相邻的数分开了,这是在一些需要进行离散化的题目中经常会遇到的一个问题。

最后需要注意的一个问题就是不要忘记特判即使把所有小矩形全选也无法完全覆盖原矩形的情况。

 下面是代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
using namespace std;
const int N=20,mod=998244353;
int f[1<<N];//f[i]表示状态为i时的期望次数
int W,H,x1[N],Y1[N],x2[N],y2[N];
vector<int>alls;
int n;
int find(int x)
{
	return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
bool check(int st)//判断状态st是否能把所给矩形完全覆盖 
{
	int a[101][101]={0};
	for(int i=0;i<n;i++)
	{
		if(st>>i&1)
		{
			a[x1[i]][Y1[i]]++;
			a[x1[i]][y2[i]+1]--;
			a[x2[i]+1][Y1[i]]--;
			a[x2[i]+1][y2[i]+1]++;
		}
	}
	for(int i=1;i<=W;i++)
	for(int j=1;j<=H;j++)
	{
		a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1];
		if(!a[i][j]) return false;
	}
	return true;
}
int count(int x)//求取x二进制位中1的个数 
{
	int cnt=0;
	while(x)
	{
		cnt++;
		x-=x&-x;
	}
	return cnt;
}
long long qpow(long long a,long long b)
{
	long long ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		b>>=1;
		a=a*a%mod;
	}
	return ans;
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		scanf("%d%d%d",&n,&W,&H);
		alls.clear();
		for(int i=0;i<n;i++)
		{
			scanf("%d%d%d%d",&x1[i],&Y1[i],&x2[i],&y2[i]);
			alls.push_back(2*x1[i]);alls.push_back(2*x1[i]+1);
			alls.push_back(2*Y1[i]);alls.push_back(2*Y1[i]+1);
			alls.push_back(2*x2[i]);alls.push_back(2*x2[i]+1);
			alls.push_back(2*y2[i]);alls.push_back(2*y2[i]+1);
		}
		alls.push_back(2*W);alls.push_back(2*W+1);
		alls.push_back(2*H);alls.push_back(2*H+1);
		alls.push_back(0);alls.push_back(1);
		sort(alls.begin(),alls.end());
		alls.erase(unique(alls.begin(),alls.end()),alls.end());
		for(int i=0;i<n;i++)
		{
			x1[i]=find(2*x1[i]);
			x2[i]=find(2*x2[i]);
			Y1[i]=find(2*Y1[i]);
			y2[i]=find(2*y2[i]);
		}
		W=find(2*W);H=find(2*H);
		memset(f,-1,sizeof f);
		for(int i=0;i<1<<n;i++)//求取所有可以完全覆盖矩形的状态 
			if(check(i)) f[i]=0;
		for(int i=(1<<n)-1;i>=0;i--)
		{
			if(f[i]==0)	  continue;//说明状态i是可以直接覆盖给定矩形的,不需要更新 
			long long ans=0;
			for(int j=0;j<n;j++)
				if(!(i>>j&1))
					ans=(ans+f[i|(1<<j)])%mod;
			f[i]=(n+ans)*qpow(n-count(i),mod-2)%mod;
		}
		if(!check((1<<n)-1)) cout<<"-1"<<endl;//所有矩形全选也不能完全覆盖给定图形 
		else	printf("%lld\n",f[0]);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值