2020牛客暑期多校训练营Cinema(状压DP,离散化)

本文介绍了如何解决一个关于电影院座位安排的问题,其中需要在疫情限制下确保最大入座率。通过分析数据,作者采用状压动态规划方法,并结合离散化技术优化状态表示,避免非法状态的枚举。文章详细讲解了状态定义、非法状态的识别与处理,以及如何使用DFS进行预处理,降低复杂度。
摘要由CSDN通过智能技术生成

Cinema

题目描述

在这里插入图片描述

输入描述:

在这里插入图片描述

输出描述:

在这里插入图片描述

示例1

输入

3
3 3
4 5
1 5

输出(MD原因,截图)

在这里插入图片描述

题目大意

给定一个 n , m n,m n,m,现有 n ∗ m n*m nm大小的电影院。由于疫情,一个位置如果有人,那么上下左右都不能坐人了。现在要求,在整个电影院坐到无法再坐时,最少有多少人能入座。

分析

看数据猜算法。 m ≤ 15 m\le 15 m15,果断状压。再看题目,嗯每行的状态只和上一行有关,太棒了就是状压。

接下来看看怎么状压吧。首先肯定是定义状态怎么表示。不妨
0表示没有人,1表示有人。
但是可以想想,这样两种够了吗???如果是求最多,那么也许这就可以了。但是我们是求最少,问题就复杂了,因为一个座位有人可以贡献4个座位不能坐人。如果两个座位的贡献重叠了,明显会导致答案更差对吧。所以,我们还要存一个点是否是贡献点,即,这个点周围是否有人。
那么我们就可以重新定义,就分为:
没有人,四周也没有人–0
没有人但是周围有人–1
有人–2

于是,这就是一个3进制的状压了。但是,飘一眼数据范围, 3 15 ∗ 1000 3^{15}*1000 3151000这明显是炸了的,再说还有 T = 1000 T=1000 T=1000组。所以,要想办法处理掉。
首先看官可以先看看这题,这也是一道状压的题目,分析后同样会发现不够。我们考虑 d p dp dp中存的状态,显然,很多状态在本行的限定内就是不合法的(比如两个2相邻,显然可以不枚举,但是裸的 d p dp dp会枚举从而浪费时间和空间)。那么这个操作就是离散化
接下来就是一个怎么离散化的问题了。首先肯定是要找出在同一行内就不合法的情况,有一下4种:
1、相邻两个2
2、相邻两个为0和2
3、相邻两个0(这样肯定还有人可以坐)
4、相邻两个1周围没有2(这样上一行就有两个相邻的2了)
所以预处理一下每个 m m m的不合法情况,剔除,复杂度降为 6000 6000 6000左右。这样就不会爆了。

那么怎么预处理呢?我一开始是暴力,发现会炸,看了kuangbin的,太强了,用 d f s dfs dfs跑了类似与筛法的东西,可以迅速地筛出所有合法状态。还有一个技巧就是把询问全部存下来,然后对于出现的 m m m再跑就可以了。具体见代码吧。

代码

有点shi,将就一下吧……

#include<bits/stdc++.h>
#define ll long long
#define inf 1<<30
using namespace std;
const int MAXN=6600;
vector<pair<int,int> > Qs[20];//存储每个询问
vector<int> Sol[MAXN],nxtsta[MAXN];
int qm[MAXN],num0[MAXN],num2[MAXN],sta[MAXN],ans[MAXN],a[MAXN],dp[1010][MAXN],pre[1010][MAXN];
int tot,id_sta[13000000];//tot 状态数量 id_sta 每种状态的编号
void dfs(int pos,int now,int m)
{
	if(pos==m){
		if(pos>=2&&now%3==1&&now/3%3==1&&(pos-2<0||now/9%3!=2)) return;
		id_sta[now]=tot;sta[tot++]=now;return;
	}
	if(pos==0){dfs(pos+1,now*3+0,m);dfs(pos+1,now*3+1,m);dfs(pos+1,now*3+2,m);return;}
	if(now%3==2||now%3==0){dfs(pos+1,now*3+1,m);return;}
	dfs(pos+1,now*3+2,m);
	if(!(pos>=2&&now%3==1&&now/3%3==1&&(pos-2<0||now/9%3!=2))){
		dfs(pos+1,now*3,m);dfs(pos+1,now*3+1,m);
	}
}//找对于一个m的所有合法的情况
void find(vector<int> &res,int a[],int m,int now,int pos)
{
	if(pos>=m){res.push_back(now);return;}
	if(a[pos]==2) find(res,a,m,now*3+1,pos+1);
	else if(a[pos]==0){
		if(pos+1<m) find(res,a,m,(now*3+2)*3+1,pos+2);
		else find(res,a,m,now*3+2,pos+1);
	}
	else{
		if(pos+1<m){
			if(a[pos+1]==0) find(res,a,m,now*3+1,pos+1);
			else if(a[pos+1]==2) find(res,a,m,now*3,pos+1),find(res,a,m,now*3+2,pos+1);
			else{
				if(pos+2>=m||a[pos+2]==2)
					find(res,a,m,(now*3+2)*3+1,pos+2),find(res,a,m,(now*3+1)*3+2,pos+2);
				else if(a[pos+2]==0)
					find(res,a,m,(now*3+0)*3+1,pos+2),find(res,a,m,(now*3+2)*3+1,pos+2);
				else{
					if(pos+3>=m||a[pos+3]==2)
						find(res,a,m,(now*3+2)*3+1,pos+2),
						find(res,a,m,((now*3+0)*3+1)*3+2,pos+3),
						find(res,a,m,((now*3+1)*3+2)*3+1,pos+3);
				}
			}
		}else{
			find(res,a,m,now*3,pos+1);
			find(res,a,m,now*3+2,pos+1);
		}
	}
}
int get2(int x){int ret=0;while(x) ret+=(x%3==2),x/=3;return ret;}//找2的个数
int get0(int x,int m){int ret=0;for(int i=0;i<m;i++) ret+=(x%3==0),x/=3;return ret;}
//由于可能有前导0所以要循环所有位,即m
bool check(int s,int m)
{
	int a[MAXN];
	for(int i=0;i<m;i++) a[i]=s%3,s/=3;
	for(int i=0;i<m;i++)
		if(a[i]==1&&(i==0||a[i-1]!=2)&&(i==m-1||a[i+1]!=2))
			return 0;
	return 1;
}//检查第一行是否合法
void getans(vector<pair<int,int> > Q,int m)
{
	if(Q.size()==0) return;
	sort(Q.begin(),Q.end());
	tot=0;dfs(0,0,m);
	for(int i=0;i<tot;i++){
		num2[i]=get2(sta[i]);num0[i]=get0(sta[i],m);
		nxtsta[i].clear();
		int tmp=sta[i];
		for(int j=m-1;j>=0;j--)	a[j]=tmp%3,tmp/=3;
		find(nxtsta[i],a,m,0,0);
	}memset(dp,-1,sizeof(dp));
	for(int i=0;i<tot;i++) if(check(sta[i],m)) dp[1][i]=num2[i];
	int index=0;
	for(int i=1;i<=1000;i++){
		while(index<Q.size()&&Q[index].first==i){
			int Qid=Q[index].second;
			ans[Qid]=i*m;Sol[Qid].clear();
			int tmp=-1;
			for(int j=0;j<tot;j++)
				if(dp[i][j]!=-1&&ans[Qid]>dp[i][j]+num0[j])
					tmp=j,ans[Qid]=dp[i][j]+num0[j];
			Sol[Qid].push_back(sta[tmp]);
			for(int j=i;j>1;j--)
				tmp=pre[j][tmp],Sol[Qid].push_back(sta[tmp]);
			index++;
		}
		if(index==Q.size()) break;
		for(int j=0;j<tot;j++){
			if(dp[i][j]==-1) continue;
			for(int k=0;k<nxtsta[j].size();k++){
				int id=id_sta[nxtsta[j][k]];
				if(dp[i+1][id]==-1||dp[i+1][id]>dp[i][j]+num2[id])
					dp[i+1][id]=dp[i][j]+num2[id],
					pre[i+1][id]=j;
			}
		}
	}
}//跑状压
void print(vector<int> vec,int m)
{
	for(int i=vec.size()-1;i>=0;i--){
		int s=vec[i];
		for(int j=0;j<m;j++){
			if(s%3==2||(i==0&&s%3==0)) printf("*");
			else printf(".");s/=3;
		}printf("\n");
	}
}//输出
int main()
{
	int t,n,m;
	scanf("%d",&t);
	for(int i=0;i<t;i++){
		scanf("%d%d",&n,&m);qm[i]=m;
		Qs[m].push_back(make_pair(n,i));
	}//对于每个m都存入其询问
	//这样在离散化的时候会比较快
	for(int i=1;i<=15;i++)
		getans(Qs[i],i);
	for(int i=0;i<t;i++){
		printf("Case #%d: %d\n",i+1,ans[i]);
		print(Sol[i],qm[i]);
	} 
}

END

最难一道的状压题。我好蒻蒻。~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值