P7689 [CEOI2002] Bugs Integrated,Inc.

Link\text{Link}

题意

一个 mmnn 列的网格,里面可以放 2×32\times33×23\times2 两种不同的芯片,其中 kk 个格子被损坏不能占用,求最多可以放下多少个芯片。

分析

注意到 mm 特别小,不超过 1010,因此可以考虑使用状态压缩 dp\text{dp},参考炮兵阵地这题在《算法竞赛进阶指南》上的解法二,我们可以设计一种类似的状态表示方式:

  • 2×32\times 3 的芯片的表示方法:
2    1    02    1    0\begin{aligned}2\ \ \ \ 1\ \ \ \ 0\\ 2\ \ \ \ 1\ \ \ \ 0\end{aligned}
  • 3×23\times 2 的芯片的表示方法:
1    01    01    0\begin{aligned}1\ \ \ \ 0\\1\ \ \ \ 0\\1\ \ \ \ 0\end{aligned}

2×32\times3 的芯片的两个 223×23\times2 的三个 11 都表示放了一个芯片,规定 22 后面必须填 1111 后面必须填 00,只有 00 后面才可以放新的芯片,我们称之为“自由格”,记为 1-1,显然,1-1 的位置可能填 001122

这样,每一列都可以被状态压缩为一个 mm 位三进制数,可以用一个 03m10\sim 3^m-1 之间的一个十进制整数表示。

dpi,sdp_{i,s} 表示第 ii 列状态为 ss 时,前 ii 列可以放的芯片个数,若状态不合法则值应为 1-1。我们枚举一个合法的状态 ss,把它按三进制位拆分到 a1ma_{1\sim m} 中,考虑它可以转移到第 i+1i+1 列的哪些状态 tt,先求出 tt 的待定数组 b1mb_{1\sim m},根据前面的表示方法,有 bi=ai1b_i=a_i-1,若 bib_i 已经被损坏,则 bi=0b_i=0,若 bib_i 被损坏的同时 ai1a_i\ge1,说明在状态 ssbib_i 应被占用却是损坏的,要转移到 tt 也是不合法的,应该排除这种情况下的状态 ss

然后我们通过待定数组 bb 求出合法转移状态 tt,但像这种状态表示比较复杂,冗余比较多的题目,我们不一定非要写出确切的状态转移方程,可以通过深度优先搜索确定可以转移到的 tt

  • bi0b_i\le0,则 bib_i 被强制用为 00 或为自由格,这个格子可以填 00
  • bi=1b_i=1,则 bib_i 被强制用为 11,这个格子必须填 11
  • bi=1b_i=-1,则 bib_i 为自由格,则查询接下去的 1122 个格子是否也是自由格,若是则可以放芯片,芯片数量增加 11

目标状态为 dpn,0dp_{n,0}

看起来这道题目已经做完了,不过交上去你会发现 MLE\text{MLE} 了,原来这道题目过于毒瘤,只留了 8MB\text{8MB} 的空间,于是我们可以使用滚动数组优化空间,不过要注意重置数组(我就是这样调到凌晨 11 点的)。

这种做法的时间复杂度是 O(N3M×?)O(N3^M\times?),十分玄学,会 TLE\text{TLE} 得死死的,我们可以加一个小优化:注意到枚举状态 ss 时,有一些损坏的格子的状态一定是 00,完全不需要花太多时间去枚举他们,我们记录第 ii 列的损坏的格子的个数 iniin_i,只要枚举 3mini3^{m-in_i} 个 未损坏的格子的状态即可。

然而这种做法还是过于垃圾,只能得到 7070 分,得加上大量的优化,选择合适的语言,开启 O2\text{O2} 后卡卡常数才可以压线过。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
	long long x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
void write(long long x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
const int N=160,M=12,S=6e4+10;
int d,n,m,k,m3[M]={1},dp[2][S],in[N],a[M],s,b[M];
bool v[N][M];
void make(int x,int j){
	s=0;
	for(int i=m;i>=1;i--){
		if(v[x][i])
			a[i]=0;
		else{
			a[i]=j%3;
			j/=3;
		}
		s=s*3+a[i];
		b[i]=a[i]-1;
		if(v[x+1][i]&&a[i]){//跳过特殊不合法状态 
			s=m3[m];
			return;
		}
		if(v[x+1][i])
			b[i]=0;
	}
}
void dfs(int x,int y,int t,int sum){
	if(!y){
		dp[x^1][t]=max(dp[x^1][t],dp[x][s]+sum);
		return;
	}
	//填 0
	if(b[y]<=0)
		dfs(x,y-1,t*3,sum);
	//强制填 1 
	if(b[y]==1)
		dfs(x,y-1,t*3+1,sum);
	//可以放芯片 
	if(b[y]==-1){
		//3*2
		if(y-2>0&&b[y-1]==-1&&b[y-2]==-1)
			dfs(x,y-3,t*27+1*9+1*3+1,sum+1);
		//2*3
		if(y-1>0&&b[y-1]==-1)
			dfs(x,y-2,t*9+2*3+2,sum+1);
	}
}
int main(){
	for(int i=1;i<12;i++)
		m3[i]=m3[i-1]*3;
	d=read();
	while(d--){
		n=read();m=read();k=read();
		memset(v,0,sizeof(v));
		memset(in,0,sizeof(in));
		memset(dp,-1,sizeof(dp));
		for(int i=1,x,y;i<=k;i++){
			x=read();y=read();
			v[x][y]=1;
			in[x]++;
		}
		dp[0][0]=0;
		for(int i=0;i<n;i++){
			for(int j=0;j<m3[m-in[i]];j++){
				make(i,j);
				if(dp[i&1][s]==-1)//不合法状态 
					continue;
				dfs(i&1,m,0,0);
			}
			for(int j=0;j<m3[m-in[i+2]];j++){//重置数组 
				make(i+2,j);
				dp[i&1][s]=-1;
			}
		}
		write(dp[n&1][0]);
		puts("");
	}
	return 0;
}
posted @ 2022-04-15 11:36  luckydrawbox  阅读(15)  评论(0)    收藏  举报  来源