题解 高维游走

传送门

感觉是个很巧妙的优化方式

首先发现模数是 2,由 Lucas 定理知此时 \(\binom{n}{k}=1\) 仅当 \(k\)\(n\) 的子集
于是可以过 subtask 1

令在每个位置选的数的数量为 \(x_i\)
则在 \(t_0\) 处的选法数是 \(\binom{t_0}{x_1\ x_2\ x_3\ \cdots\ x_m}\),注意这里不是组合数(我赛时挂在这里了)
尝试找些性质:
发现 \(\{x_1, x_2, x_3,\cdots,x_m, t_0-\sum x_i\}\)\(t_0\) 的一个二进制划分
这个证明可以归纳证,因为 \(x_1\) 一定是 \(t_0\) 的子集,所以 \(x_2\) 一定是 \(t_0-x_1\) 的子集,以此类推
于是有个做法是对每一位做背包
构造一个 \(31\times (m+1)\) 的 01 矩阵 \(g\),其中 \(g_{i, j}=1\) 仅当 \(j=1\)\(t_0\)\(t_{j-1}\) 的第 \(i-1\) 位都是 1
我们需要在每行选一个 1,若在位置 \(j\) 选则对体积的贡献为 \(2^{i-1}(j-1)\)
于是就可以做背包了
这样可以过 subtask 2

发现对 2 取模等价于异或,所以可以 bitset 优化
可以过 subtask 3

再优化需要找些性质了:
发现这个背包的转移很有特点:\(f_{i, j}\gets f_{i, j}+f_{i-1, j-k2^i}\)
这里 \(k\in [0, m]\)
就可以发现 \(j\) 按照对 \(2^i\) 的余数分成了若干组
虽然组数有很多,但本质不同的组数只有 \(2^m\)
于是可以想到对这个东西做 DP 套 DP
\(r\equiv j\pmod{2^i}\)
那么每组形如 \(\{g_{i,r}\ , g_{i, 2^i+r}\ , g_{i, 2\times2^i+r}\ ,\cdots\}\)
考虑状压枚举一组的状态
对于第 \(i\) 层,状态为 \(s\) 的一组,可以先用 \(s\) 做背包
这样我们得到了 \(k\in [0, 2m]\) 内的一个状态,这时是 \(k2^i\)
我们需要将模数改为 \(2^{i+1}\)
简单写一下:

\[0\times 2^i+r=0\times 2^{i+1}+r \]

\[1\times 2^i+r=0\times 2^{i+1}+2^i+r \]

\[2\times 2^i+r=1\times 2^{i+1}+r \]

\[3\times 2^i+r=1\times 2^{i+1}+2^i+r \]

于是就可以将这个 \([0, 2m]\) 内的状态分成两个模 \(2^{i+1}\) 意义下的状态,就可以转移了
复杂度 \(O(Tm2^m\log t)\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define ll long long
#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int m;
int t[N];

namespace check{
	const int mod=1e9+7;
	int fac[N], inv[N];
	inline int C(int n, int k) {return n<k?0:fac[n]*inv[k]%mod*inv[n-k]%mod;}
	inline int lucas(int n, int k) {return !k?1:lucas(n/mod, k/mod)*C(n%mod, k%mod)%mod;}
	void solve() {
		fac[0]=fac[1]=1; inv[0]=inv[1]=1;
		for (int i=2; i<=1000; ++i) fac[i]=fac[i-1]*i%mod;
		for (int i=2; i<=1000; ++i) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
		for (int i=2; i<=1000; ++i) inv[i]=inv[i-1]*inv[i]%mod;
		for (int i=1; i<=20; ++i) cout<<(C(i, 3)&1)<<' '; cout<<endl;
	}
}

namespace table{
	int fac[N], inv[N];
	inline int C(int n, int k) {return n<k?0:fac[n]*inv[k]%2*inv[n-k]%2;}
	inline int lucas(int n, int k) {return !k?1:lucas(n/2, k/2)*C(n%2, k%2)%2;}
	void solve() {
		fac[0]=fac[1]=1; inv[0]=inv[1]=1;
		for (int i=2; i<=1000; ++i) fac[i]=fac[i-1]*i%2;
		for (int i=2; i<=1000; ++i) inv[i]=(2-2/i)*inv[2%i]%2;
		for (int i=2; i<=1000; ++i) inv[i]=inv[i-1]*inv[i]%2;
		for (int i=1; i<=20; ++i) cout<<lucas(i, 3)<<' '; cout<<endl;
		for (int i=1; i<=20; ++i) cout<<(((3&i)==3)?1:0)<<' '; cout<<endl;
	}
}

namespace task1{
	inline ll qpow(ll a, ll b) {ll ans=1; for (; b; a=a*a,b>>=1) if (b&1) ans=ans*a; return ans;}
	void solve() {cout<<qpow(2, __builtin_popcount(t[0]&t[1]))<<endl;}
}

namespace task2{
	int f[11][41][220];
	void solve() {
		// int s0=13;
		// cout<<"s0: "<<bitset<10>(s0)<<endl;
		// for (int s=s0; s; s=(s-1)&s0) {
		// 	cout<<bitset<10>(s)<<endl;
		// }
		int lim=t[0], sum=0;
		for (int i=1; i<=m; ++i) sum+=t[i];
		lim=min(t[0], sum);
		memset(f, 0, sizeof(f));
		f[0][0][0]=1;
		for (int i=1; i<=m; ++i) {
			for (int s=0; s<=t[i]; ++s) if ((s&(t[i]))==s) {
				for (int j=lim; j>=s; --j)
					for (int k=i*s; k<220; ++k)
						f[i][j][k]+=f[i-1][j-s][k-i*s];
			}
		}

		int ans=0;
		for (int i=0; i<220; ++i) {
			int sum=0;
			for (int j=0; j<=lim; ++j) if ((j&t[0])==j) sum+=f[m][j][i];
			ans+=(sum&1);
		}
		cout<<ans<<endl;
	}
}

namespace task3{
	bool mp[35][15];
	int f[35][10000];
	void solve() {
		memset(mp, 0, sizeof(mp));
		memset(f, 0, sizeof(f));
		for (int i=1; i<=31; ++i)
			for (int j=1; j<=m+1; ++j)
				mp[i][j]=(j==1)||((t[0]&(1<<(i-1)))&&(t[j-1]&(1<<(i-1))));
		f[0][0]=1;
		for (int i=1; i<=31; ++i) {
			for (int j=1; j<=m+1; ++j) if (mp[i][j]) {
				int dlt=((j-1)<<(i-1));
				for (int k=dlt; k<10000; ++k)
					f[i][k]=(f[i][k]+f[i-1][k-dlt])&1;
			}
		}
		int ans=0;
		for (int i=0; i<10000; ++i) if (f[31][i]&1) ++ans;
		cout<<ans<<endl;
	}
}

namespace task{
	int f[2][1<<11], now;
	void solve() {
		memset(f[now], 0, sizeof(f[now]));
		f[now][1]=1;
		int lim=1<<(m+1), ans=0;
		for (int i=0; i<=31; ++i,now^=1) {
			memset(f[now^1], 0, sizeof(f[now^1]));
			int mp=1;
			for (int j=1; j<=m; ++j) if ((t[0]&t[j]&(1<<i))) mp|=1<<j;
			for (int s=0; s<lim; ++s) if (f[now][s]) {
				int mask=0, half[2]={0, 0};
				for (int j=0; j<=m; ++j) if (mp&(1<<j)) mask^=(s<<j);
				for (int j=2*m; ~j; --j) half[j&1]=(half[j&1]<<1)|((mask>>j)&1);
				f[now^1][half[0]]+=f[now][s];
				f[now^1][half[1]]+=f[now][s];
			}
		}
		for (int s=0; s<lim; ++s) ans+=f[now][s]*__builtin_popcount(s);
		cout<<ans<<endl;
	}
}

signed main()
{
	freopen("travel.in", "r", stdin);
	freopen("travel.out", "w", stdout);

	int T=read();
	while (T--) {
		m=read();
		for (int i=0; i<=m; ++i) t[i]=read();
		// if (m==1) task1::solve();
		// else task2::solve();
		// task2::solve();
		task::solve();
	}
	// check::solve();
	// table::solve();
	// task1::solve();
	// task2::solve();
	
	return 0;
}
posted @ 2022-03-06 21:20  Administrator-09  阅读(1)  评论(0编辑  收藏  举报