[GYM102832J]Abstract Painting

CXXVI.[GYM102832J]Abstract Painting

考虑将一个圆心为 \((x,0)\),半径为 \(R\) 的圆,转换为 \(x\) 轴上线段 \([x-R,x+R]\),问题转换为求无交的线段覆盖方案数。

因为所有的圆半径很小(\(5\)),所以我们考虑状压位置 \(i\) 前面 \(10\) 位的信息(在实际应用中会发现只要状压 \(9\) 位即可,因为第 \(i-1\) 位必然为 \(0\)),表示第 \(i\) 位能否作为一个圆的左端点。当我们在位置 \(i\) 加入一个圆 \([l,i]\) 时,需要保证当前状压的状态中第 \(l\) 位为 \(0\)\(l\geq0\)\(i-l\)\(\leq10\) 的偶数。在加入这么一个圆后,第 \(l+1\sim i-1\) 位都不能作为圆的左端点,更新状态即可。同时,因为一个位置可以作为不止一个圆的右端点,所以还得枚举作为哪些圆的右端点。因为所有可能的直径只有 \(2,4,6,8,10\),所以直接 \(2^5\) 枚举即可。记得判断加入的圆的集合是否包含了必须加入的圆的集合!

总复杂度 \(n\times2^9\times2^5\),可以通过。

(附,通过子集枚举等trick可以将复杂度优化到 \(3^5\times 2^5\),但二者实际效率只不过差了个大约 \(2\) 的常数,而且不加也能过,就不加了)

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,m,mus[1010],f[1010][1<<9],all=(1<<0)|(1<<2)|(1<<4)|(1<<6)|(1<<8),res;
int fob(int ip){
	int lim=1;
	while(lim<=ip)lim<<=1;
	return lim-1;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),mus[x+y]|=(1<<((y-1)<<1));
	f[0][0]=1;
	for(int i=1;i<=n;i++)for(int j=all;;j=(j-1)&all){
		if((j&mus[i])==mus[i]&&j<(1<<min(i-1,9))){
			int J=fob(j);
//			printf("%d:%d:%d\n",i,j,J);
			for(int k=0;k<(1<<min(i-1,9));k++){
				if(k&j)continue;
				(f[i][((k<<1)&((1<<9)-1))|J]+=f[i-1][k])%=mod;
			}			
		}
		if(!j)break;
	}
	for(int i=0;i<(1<<9);i++)(res+=f[n][i])%=mod;
	printf("%d\n",res);
	return 0;
} 

原文地址:https://www.cnblogs.com/Troverld/p/14601465.html