#LOJ2264. 「CTSC2017」吉夫特 DP计数

这篇博客探讨了LOJ2264竞赛题的解题思路,主要涉及一个长度为n的序列中不上升子序列的计数问题。通过卢卡斯定理转化题目的条件,发现满足条件的子序列对应于前一个数的二进制子集。作者提出使用动态规划(DP)的方法,以序列结尾的数字i来定义状态f[i],并枚举i的子集进行转移,复杂度为O(3^log2maxa)。尽管看起来复杂度较高,但实际运行时由于常数项小,能在规定时间内完成。此外,还讨论了使用meet in middle优化技术,将复杂度降低到O(6^9),使得解法更加高效,适用于解决类似题目。
摘要由CSDN通过智能技术生成

题意

  • 输入一个长度为 n n n的序列 a i a_i ai,求有多少个长度大于等于 2 2 2的不上升子序列满足 ∏ i = 2 k ( a b i − 1 a b i )   m o d   2 > 0 \displaystyle\prod_{i = 2}^{k} \binom{a_{b_{i - 1}}}{a_{b_i}} \bmod 2 > 0 i=2k(abiabi1)mod2>0.

用卢卡斯定理转化一下性质

发现这个东西模 2 2 2 0 0 0,当且仅当前一个数存在一个二进制位为 0 0 0而后一个数那一位是 1 1 1

再转化一下那后一个数就是前一个数的子集,那么不妨设 f [ i ] f[i] f[i]为以 i i i结尾的序列数

直接枚举 i i i的子集转移即可 复杂度 O ( 3 l o g 2 m a x a ) O(3^{log_2^{maxa}}) O(3log2maxa)

大概是 O ( 3 18 ) O(3^{18}) O(318) 看起来这个复杂度比较虚但实际上

这个 d p dp dp常数很小而且又有两秒而且复杂度又没有卡满 就过去了

Codes
#include<bits/stdc++.h>

using namespace std;

const int N = 3e5 + 10;
const int mod = 1e9 + 7;

int a[N], n, f[N];

int add(int x, int y) {
	return x + y >= mod ? x + y - mod : x + y;
}

int main() {
#ifdef ylsakioi
	freopen("3773.in", "r", stdin);
	freopen("3773.out", "w", stdout);
#endif
	int ans = 0;
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i) {
		scanf("%d", &a[i]); ++ f[a[i]];
		for(int sub = a[i]; sub; (sub -= 1) &= a[i])
			if(sub != a[i])
				f[sub] = add(f[sub], f[a[i]]);
		ans = add(ans, f[a[i]] - 1);
	}
	cout << ans << endl;
	return 0;
}

还是学一学复杂度比较正的正解做法吧

毕竟 C C F CCF CCF的老爷机是不会有 L O J LOJ LOJ这样的速度

一般二进制集合相关的题目都可以采用 m e e t   i n   m i d d l e meet \ in \ middle meet in middle来优化

这样子复杂度直接变成了根号

像这道题 我们记 f [ i ] [ j ] f[i][j] f[i][j]为前 9 9 9位是 i i i,后 9 9 9位为 j j j子集的方案数,那么每次枚举 i i i的超集记下来之后再转移到 j j j的子集上,那么这个复杂度就是 O ( 6 9 ) O(6^9) O(69),就可以过了

Codes
#include<bits/stdc++.h>

using namespace std;

const int N = sqrt(3e5);
const int mod = 1e9 + 7;
const int all = (1 << 9) - 1;

int f[N][N], n;

int main() {
#ifdef ylsakioi
	freopen("4903.in", "r", stdin);
	freopen("4903.out", "w", stdout);
#endif
	int x, y, ans = 0, z;
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i) {
		int now = 0;
		scanf("%d", &z);
		x = (z >> 9), y = z & all;
		for(int j = x; j <= all; j = (j + 1) | x)
			(now += f[j][y]) %= mod;
		(ans += now) %= mod; ++ now;
		for(int j = y; ; j = (j - 1) & y) {
			(f[x][j] += now) %= mod;
			if(!j) break;
		}
	}
	cout << ans << endl;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值