[loj#2277] [HAOI2017] 方案数

题意简述

在无限大的三维空间中走,有3种位移操作:

  1. ((x,y,z) o (x',y,z)) 当且仅当 ((x&x')=x)
  2. ((x,y,z) o (x,y',z)) 当且仅当 ((y&y')=y)
  3. ((x,y,z) o (x,y,z')) 当且仅当 ((z&z')=z)

给定 (o) 个点不能走。求从 ((0,0,0))((n,m,r)) 的方案数,对998244353取模。

(oleq 10^4)
(m,n,r leq 10^{18})


想法

首先,这种若干个点不让走、求路径数的问题是经典的容斥问题。
(g[i]) 表示路径中第一个经过的障碍点为 (i) 的路径数。
为方便表示,不妨设 (trans(x,y)) 表示从点 (x)(y) 的总路径数(经不经过障碍都算)。
(g[i]=trans(起点,i)-sumlimits_{j=1}^{i-1} g[j] imes trans(j,i))
如果 (trans(x,y)) 可以 (O(1)) 得到的话,总转移复杂度就是 (O(o^2)) 没有问题。

观察此题中的位移操作,发现 (x) 中为1的二进制位上 (x') 也为1,且 (x') 中的1的个数比 (x) 中的多。((y与y',z与z') 也是这样)
所以转移只与每维的坐标的二进制中1的个数之差有关。
于是可以 (dp) 进行预处理。
(dp[a][b][c]) 表示从起点到终点,每维的1个数之差分别为 (a,b,c)(a,b,c leq 60)
(dp[a][b][c]=sumlimits_{i=0}^{a-1} inom{a}{i} dp[i][b][c] + sumlimits_{i=0}^{b-1} inom{b}{i} dp[a][i][c] + sumlimits_{i=0}^{c-1} inom{c}{i} dp[a][b][i])

然后就可以了。


总结

模型

经典容斥!!!
(g[i]=trans(起点,i)-sumlimits_{j=1}^{i-1} g[j] imes trans(j,i))

技巧

求一个较大数二进制中1的个数:打表+分块。

int dabiao(){
    for(int i=0;i<65536;i++) {
		bt[i]=0;
		x=i;
		while(x) bt[i]+=(x&1),x>>=1;
	}
}
int cal(ll x) { 
	int t=0;
	while(x>=65536){
		t+=bt[x&65535];
		x>>=16;
	} 
	return t+bt[x];
}

代码

#include<cstdio>
#include<iostream>
#include<algorithm>

#define P 998244353

using namespace std;

const int N = 10005;
typedef long long ll;

ll read(){
	ll x=0;
	char ch=getchar();
	while(ch<'0' || ch>'9') ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	return x;
}

ll n,m,r;
int o;
struct data{
	ll x,y,z;
	int cx,cy,cz;
	bool operator < (const data &b) const{ 
		return x<b.x || (x==b.x && y<b.y) || (x==b.x && y==b.y && z<b.z);
	}
}d[N];

int f[62][62][62],g[N],c[62][62],bt[65536];
int Plus(int x,int y) { return x+y>=P ? x+y-P : x+y; }
int Minus(int x,int y) { return x>=y ? x-y : x-y+P; }
void init(){
	c[0][0]=1;
	for(int i=1;i<=60;i++){
		c[i][0]=c[i][i]=1;
		for(int j=1;j<i;j++) c[i][j]=Plus(c[i-1][j-1],c[i-1][j]);
	}
	for(int i=0;i<=60;i++)
		for(int j=0;j<=60;j++)
			for(int k=0;k<=60;k++){
				if(!i && !j && !k) { f[i][j][k]=1; continue; }
				f[i][j][k]=0;
				for(int t=0;t<i;t++)
					f[i][j][k]=Plus(f[i][j][k],1ll*f[t][j][k]*c[i][t]%P);
				for(int t=0;t<j;t++)
					f[i][j][k]=Plus(f[i][j][k],1ll*f[i][t][k]*c[j][t]%P);
				for(int t=0;t<k;t++)
					f[i][j][k]=Plus(f[i][j][k],1ll*f[i][j][t]*c[k][t]%P);
			}
	int x;
	for(int i=0;i<65536;i++) {
		bt[i]=0;
		x=i;
		while(x) bt[i]+=(x&1),x>>=1;
	}
}
int cal(ll x) { 
	int t=0;
	while(x>=65536){
		t+=bt[x&65535];
		x>>=16;
	} 
	return t+bt[x];
}

int main()
{
	n=read(); m=read(); r=read();
	o=read();
	for(int i=1;i<=o;i++) { d[i].x=read(); d[i].y=read(); d[i].z=read(); }
	++o;
	d[o].x=n; d[o].y=m; d[o].z=r;
	sort(d+1,d+1+o);
	
	init();
	for(int i=1;i<=o;i++){
		d[i].cx=cal(d[i].x); d[i].cy=cal(d[i].y); d[i].cz=cal(d[i].z);
		g[i]=f[d[i].cx][d[i].cy][d[i].cz];
		for(int j=1;j<i;j++)
			if((d[j].x&d[i].x)==d[j].x && (d[j].y&d[i].y)==d[j].y && (d[j].z&d[i].z)==d[j].z)
				g[i]=Minus(g[i],1ll*g[j]*f[d[i].cx-d[j].cx][d[i].cy-d[j].cy][d[i].cz-d[j].cz]%P);
	}
	
	printf("%d
",g[o]);
	
	return 0;
}
原文地址:https://www.cnblogs.com/lindalee/p/12404765.html