题解 团不过

传送门

又一个神仙题

  • 貌似有个思路是处理形如「异或和为零」的限制时,让前 \(n-1\) 位随便选,通过调整最后一位将异或和消成0

\(p(i)\)\(i\) 堆的所有选法数,\(p(i)=(2^n-1)^{\underline{i}}\)
\(f(i)\)\(i\) 堆所有异或和为零的方案数
计算 \(f(i)\) 的时候考虑用所有方案数减去不合法方案数
\(i-1\) 位确定了,最后一位就确定了,所以加上 \(p(i-1)\),这里一开始想假了
若前 \(i-1\) 位异或和为0,则第 \(i\) 位没有合法方案,所以减去 \(f(i-1)\)
然后考虑去掉重复使用同一个元素的
若有一个元素与第 \(i\) 个相同,即在前 \(i-1\) 堆中有 \(i-2\) 堆异或和为0
于是枚举另一堆在哪及值为多少,减去 \((i-1)(2^n-1-(i-1))f(i-2)\)
于是可以 \(O(n)\) 递推

Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 10000010
#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 n;
const ll mod=1e9+7;
inline ll qpow(ll a, ll b) {ll ans=1; for (; b; a=a*a%mod,b>>=1) if (b&1) ans=ans*a%mod; return ans;}

namespace force{
	int lim;
	ll fac[N], ans;
	void dfs(int u, int lst, int sum) {
		if (u>n) {
			if (sum) ++ans;
			return ;
		}
		for (int i=lst+1; i<lim; ++i)
			dfs(u+1, i, sum^i);
	}
	void solve() {
		lim=1<<n;
		fac[0]=fac[1]=1;
		for (int i=2; i<=n; ++i) fac[i]=fac[i-1]*i%mod;
		dfs(1, 0, 0);
		printf("%lld\n", ans*fac[n]%mod);
		exit(0);
	}
}

namespace task{
	ll p[N], f[N];
	void solve() {
		p[0]=1;
		ll tem=qpow(2, n);
		for (int i=1; i<=n; ++i) p[i]=p[i-1]*(tem-i)%mod;
		for (int i=3; i<=n; ++i) f[i]=(p[i-1]-f[i-1]-(i-1)*(tem-i+1)%mod*f[i-2]%mod)%mod;
		printf("%lld\n", ((p[n]-f[n])%mod+mod)%mod);
		exit(0);
	}
}

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

	n=read();
	task::solve();

	return 0;
}
posted @ 2021-10-30 06:35  Administrator-09  阅读(0)  评论(0编辑  收藏  举报