bzoj 4197: [Noi2015]寿司晚宴 状压dp

       这题其实N出到800都没问题吧。。

       显然,如果两个数有一个质因数相同则不能在两边;更进一步,按照最大质因数,可以把1~N的所有的数分组,那么统一组的不能在两边。显然次小质因数必然<=N^0.5,只有2,3,5,7,11,13,17,19这8个数,那么可以把这8个质因数是否存在压成一个状态,那么f[i][j][k]就可以表示第一个人的集合为i,第二个人的集合为j,当前组是分给第一个人(k=1),第二个人(k=2),或者当前组没有选(k=0),就可以O(1)转移了。

       实际上这样是O(N*4^8),但是注意到最终只有i&j=0的才有用。因此有用的状态只有3^8种,因此就是O(N*3^8)。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 65536
using namespace std;

const int prm[8]={2,3,5,7,11,13,17,19};
int n,mod,cnt,bin[25],f[N][3],g[3],p[N];
struct node{ int x,y; }a[505];
bool cmp(const node &u,const node &v){ return u.x<v.x; }
bool check(int x){
	for (; x; x>>=2) if ((x&3)==3) return 0;
	return 1;
}
int main(){
	scanf("%d%d",&n,&mod); int i,j,k,l;
	bin[0]=1; for (i=1; i<15; i++) bin[i]=bin[i-1]<<1;
	for (i=2; i<=n; i++){
		for (j=0,k=i; j<8 && k>1; j++) if (!(k%prm[j])){
			a[i].y|=bin[j<<1];
			while (!(k%prm[j])) k/=prm[j];
		}
		a[i].x=k;
	}
	sort(a+2,a+n+1,cmp);
	for (i=0; i<N; i++) if (check(i)) p[++cnt]=i;
	f[0][0]=1;
	for (i=2; i<=n; i++){
		if (a[i].x!=a[i-1].x || a[i].x==1)
			for (j=1; j<=cnt; j++){
				k=p[j];
				f[k][0]=(f[k][0]+f[k][1])%mod; f[k][0]=(f[k][0]+f[k][2])%mod;
				f[k][1]=f[k][2]=0;
			}
		for (j=cnt; j; j--){
			k=p[j]; memcpy(g,f[k],sizeof(f[k]));
			for (l=0; l<3; l++) if (g[l]){
				if (l!=2 && !(k&(a[i].y<<1))) f[k|a[i].y][1]=(f[k|a[i].y][1]+g[l])%mod;
				if (l!=1 && !(k&a[i].y)) f[k|(a[i].y<<1)][2]=(f[k|(a[i].y<<1)][2]+g[l])%mod;
			}
		}
	}
	long long ans=0;
	for (i=1; i<=cnt; i++)
		for (j=0; j<3; j++) ans+=f[p[i]][j];
	printf("%lld\n",ans%mod);
	return 0;
}

by lych

2016.5.3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值