[HNOI2012]集合选数(构造,状态压缩,DP)

神仙题。

莫名其妙的就试一试把所有数放进一个类似矩阵的东西里面。

首先把 (1) 放到左上角,然后在每个数的右边放它的 (3) 倍(大于 (n) 就不用放了),下面放它的 (2) 倍(大于 (n) 就不用放了)。

注意这样子有些数会不在里面。那么从小到大,每次选最小的且没有出现过的数作为一个新的“矩阵”的左上角。容易发现这些“矩阵”互不干扰,对每个矩阵分别求方案数,乘起来就好了。

对于一个矩阵,发现就是对于一个数,如果它选了,那么它右边和下面的都不能选。

问题就变成选一些数,互不相邻的方案数。

由于这个矩阵不会太大(长和宽都是 (log) 级别的),所以直接状压。

时间复杂度,大力分析一波(我的分析特别松,首先假设所有矩阵都是满的,也就是右下角不会空出一块,然后状压时也不考虑能不能删掉无用状态),会得到一个极其松的上界 (3 imes 10^7)。状压 DP 时剪掉无用状态,就能跑得飞快了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=100010,mod=1000000001;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
	char ch=getchar();ll x=0,f=0;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
int n,ans=1,len[18],f[18][2048],ok[2048],ol;
bool vis[maxn];
bool check(int x){
	FOR(i,0,11) if((x>>i)&(x>>(i+1))&1) return false;
	return true;
}
int solve(int x){
	int y=x,w,s=0;
	FOR(i,1,n){
		if(y>n) break;
		int z=y;
		FOR(j,1,n){
			if(z>n) break;
			len[i]=j;
			vis[z]=true;
			z*=3;
		}
		w=i;
		y*=2;
	}
	FOR(i,1,ol){
		if(ok[i]>=(1<<len[1])) break;
		f[1][i]=1;
		if(w==1) s=(s+1)%mod;
	}
	FOR(i,2,w) FOR(j,1,ol){
		if(ok[j]>=(1<<len[i])) break;
		f[i][j]=0;
		FOR(k,1,ol){
			if(ok[k]>=(1<<len[i-1])) break;
			if(!(ok[j]&ok[k])) f[i][j]=(f[i][j]+f[i-1][k])%mod;
		}
		if(i==w) s=(s+f[i][j])%mod;
	}
	return s;
}
int main(){
	n=read();
	FOR(i,0,2047) if(check(i)) ok[++ol]=i;
	FOR(i,1,n) if(!vis[i]) ans=1ll*ans*solve(i)%mod;
	printf("%d
",ans);
}
原文地址:https://www.cnblogs.com/1000Suns/p/11372204.html