CF比赛1610D. Not Quite Lee(Codeforces Global Round 17)(数学)(计数)

给出一个序列的数字, a i a_i ai表示要选择一串 a i a_i ai个连续数字。问有多少个子序列,满足这些连续数字和为0。

题目传送门

正解:

从等差数列求和来考虑。长度 a i a_i ai是给定的,但开始位置是待定的,记为 s i s_i si

这一串数字的和为 s i ∗ a i + 1 2 ( a i − 1 ) ∗ a i s_i*a_i + \frac12 (a_i-1)*a_i siai+21(ai1)ai,可以看作 a i ∗ ( s i + 1 2 a i − 1 2 ) a_i*(s_i + \frac12 a_i - \frac12) ai(si+21ai21)

由于 s i s_i si的任意性,不妨设 k ∈ Z k\in \mathbb{Z} kZ

对于 a i a_i ai是奇数,可以化为 a i ∗ k a_i * k aik

对于 a i a_i ai是偶数,只能化成 a i ∗ ( k + 1 2 ) a_i * (k + \frac12) ai(k+21)

目标是子序列的数和为 0 0 0


可以发现,只要出现了一个奇数,这个子序列一定是满足条件的。

所有问题可以拆分成:存在奇数的子序列,纯偶数的子序列。

前者是好解决的, ( 2 奇数个数 − 1 ) ∗ 2 偶数个数 (2^{奇数个数} - 1 ) * 2^{偶数个数} (2奇数个数1)2偶数个数。减一是除去了一个奇数不选的情况。

后者也可以解决。先确定一个最小偶数为 a i a_i ai后,那剩的一个 1 2 \frac12 21的消除方式只能让偶数个这样的数加起来。

这样的方案数是 ( n 2 ) + ( n 4 ) + ⋯ + ( n ⌊ n / 2 ⌋ ∗ 2 ) \binom{n}{2} + \binom{n}{4} + \cdots + \binom{n}{\lfloor n/2\rfloor *2} (2n)+(4n)++(n/22n),也就是偶数项相加,无论 n n n是奇还是偶,和是固定的 2 n − 1 − 1 2^{n - 1} - 1 2n11。减一是去掉了 ( n 0 ) \binom{n}{0} (0n)

所有相加即可。


我的错误解(闲人免进):

我也想到了纯偶数情况下和为 ( 1 2 + k ) a i (\frac12 + k)a_i (21+k)ai,想着找到 ∑ 1 2 a i = 0 \sum \frac12 a_i = 0 21ai=0的情况。这东西很难求,错就错在没有划分讨论。先确定最小偶数为xx,那方案数就可以逐步求解了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const int N = 2e5 + 10;

ll qkpow(ll a, ll n)
{
	ll ret = 1;
	while(n)
	{
		if(n & 1) ret = ret * a % mod;
		a = a * a % mod;
		n >>= 1;
	}
	return ret;
}

ll bin[N], fac[N], ifac[N];

void init()
{
	bin[0] = 1;
	for(int i = 1; i < N; i++) bin[i] = (bin[i - 1] << 1) % mod;
	fac[0] = 1;
	for(int i = 1; i < N; i++) fac[i] = fac[i - 1] * i % mod;
	ifac[N - 1] = qkpow(fac[N - 1], mod - 2);
	for(int i = N - 2; i >= 0; i--) ifac[i] = ifac[i + 1] * (i + 1);
}

int count(int x)
{
	int ret = 0;
	while((x & 1) == 0 && x) ret++, x >>= 1; // debug x & 1 == 0  (x)
	return ret;
}

int n, a[N], cnt[100];

void solve()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
	{
		scanf("%d", &a[i]);
		cnt[count(a[i])]++;
	}
	int rest= n - cnt[0];
	ll ans = (bin[cnt[0]] - 1) * bin[rest] % mod;
	for(int i = 1; i <= 35; i++)
	{
		if(!cnt[i]) continue; // debug
		rest -= cnt[i];
		ll tmp = (bin[cnt[i] - 1] - 1) * bin[rest] % mod;
		ans = (ans + tmp) % mod;
	}
	printf("%lld\n", ans);
}

signed main()
{
	int T = 1;
//	scanf("%d", &T);
	init();
	while(T--) solve();
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值