题目描述
输入描述:
输出描述:
示例1
输入
3
3 3
4 5
1 5
输出(MD原因,截图)
题目大意
给定一个 n , m n,m n,m,现有 n ∗ m n*m n∗m大小的电影院。由于疫情,一个位置如果有人,那么上下左右都不能坐人了。现在要求,在整个电影院坐到无法再坐时,最少有多少人能入座。
分析
看数据猜算法。 m ≤ 15 m\le 15 m≤15,果断状压。再看题目,嗯每行的状态只和上一行有关,太棒了就是状压。
接下来看看怎么状压吧。首先肯定是定义状态怎么表示。不妨
0表示没有人,1表示有人。
但是可以想想,这样两种够了吗???如果是求最多,那么也许这就可以了。但是我们是求最少,问题就复杂了,因为一个座位有人可以贡献4个座位不能坐人。如果两个座位的贡献重叠了,明显会导致答案更差对吧。所以,我们还要存一个点是否是贡献点,即,这个点周围是否有人。
那么我们就可以重新定义,就分为:
没有人,四周也没有人–0
没有人但是周围有人–1
有人–2
于是,这就是一个3进制的状压了。但是,飘一眼数据范围,
3
15
∗
1000
3^{15}*1000
315∗1000这明显是炸了的,再说还有
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
最难一道的状压题。我好蒻蒻。~