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;
}