首页 > 其他 > 详细

NOI online R3 T3 优秀子序列

时间:2020-05-25 23:15:29      阅读:86      评论:0      收藏:0      [点我收藏+]

题意:

洛谷链接

给你一个长度为n的序列,让你找出序列所有满足条件的子集(包括空集)的价值和,一个子集满足条件当其中的任意两个元素按位与等于0,即a&b=0,只有一个元素的集合或空集都满足条件。

一个满足条件的集合的价值为:\(\varphi (1+\)集合中所有元素的和 \()\) 。答案对1e9+7取模。

由于是考试题,列出数据范围:

  • 对于 \(10\%\) 的数据,保证 \(a_i\le 1\)
  • 对于 \(30\%\) 的数据,保证 \(a_i\le 1000\)
  • 对于 \(60\%\) 的数据,保证 \(a_i\le 30000\)
  • 另有 \(10\%\) 的数据,保证 \(n\le 20\)
  • 对于 \(100\%\) 的数据,保证 \(1\le n\le 10^6\)\(0\le a_i\le 2\times 10^5\)

我们注意到,一个满足条件的子集没有任意两个数异或值大于零,这说明二进制的每一位,集合中最多有一个数在该位置上有值。如果集合中包含了这个数,就相当于占用了这几位,就是将状压后的每一位看成花费做背包。

具体的,可以设 \(f[i][j]\) 表示状态为,选择前i个数和为j的方案数,因为元素和的二进制每一位最多是1,和也不会超过最大位。最后价值和就可以枚举集合元素的和,然后预处理出来varphi值乘上方案数。背包第一维滚掉。

然后发现一个十分令人头大的事,对于前面60%的部分分,n的值一直是10^6,乘上1000就已经爆的很惨了。然后仔细想想,如果是相同值的元素,在集合中肯定只会出现一次(除非是0),因为出现两次的话按位与就不是零了,所以我们把相同值的元素压缩到一起来考虑,每次计算价值时加上它的数量。

转移背包时,枚举每个值,然后枚举所有二进制位包含它的集合,n^2的复杂度由于常数小可以获得60分的成绩。

当你在考虑n=20怎么敲暴力时,你发现用不着,前面的情况判一下如果某个值出现次数为0就可以直接continue,枚举二进制集合最多只会枚举20次,直接顺带解决了。

\(70 -> 100\),只需要在枚举集合的时候将枚举完再判断改为枚举子集就可以,枚举u子集的代码:

for(int i=u; i; i=(i-1)&u) f[u] += f[i] * a[u^i];

复杂度证明,一个数二进制上如果有i个1,那么它子集数量为2^i,n位的数一共有 \(C_n^i\) 个这样的数,那么所有i位有值的数价值和为 \(C_n^i\times 2^i\)

\(C_n^0\times 2^0+C_n^1\times 2^1+C_n^2\times 2^2+...+C_n^n\times 2^n\)

然后发现这东西等于 \((1+2)^n\)。所以复杂度是 \(3^n\)。常数小点可以过去,像我的大常数代码得加点if才能过。

然后好像用多项式科技可以达到 \(2^n\times n^2\) ,这就涉及到我的知识盲区了。。。

坑的话,注意特判0,因为0可以选多个,最后要将答案乘上 \(2^{a_0}\)\(a_0\)为0的出现次数。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define QWQ cout<<"QwQ"<<endl;
#define ll long long
#include <vector>
#include <queue>
#include <stack>
#include <map>
#define R register
using namespace std;
const ll N=301010;
const ll qwq=303030;
const ll inf=0x3f3f3f3f;
const ll mp=1000000007;

int n,m,mx;
ll a[N];
ll f[N];
ll ans;

inline ll read() {
	ll sum1 = 0, f1 = 1; char c = getchar();
	while(c<‘0‘ || c>‘9‘) { if(c==‘-‘) f1 = -1; c = getchar(); }
	while(c>=‘0‘&&c<=‘9‘) { sum1 = sum1 * 10 + c - ‘0‘; c = getchar(); }
	return sum1 * f1;
}

ll p[N],vis[N],cnt;
ll phi[N];

void qiu(ll h) {
	phi[1] = 1;
	for(ll i=2; i<=h; i++) {
		if(!vis[i]) {
			p[++cnt] = i;
			phi[i] = i-1;
		}
		for(ll j=1; j<=cnt && i*p[j]<=h; j++) {
			vis[ i*p[j] ] = 1;
			if(i%p[j]==0) {
				phi[ i*p[j] ] = phi[i] * p[j];
				break;
			}
			phi[ i*p[j] ] = phi[i] * (p[j]-1);
		}
	}
}

int main() {
	qiu(N-100);
	int x;
	n = read();
	for(R int i=1;i<=n;i++) {
		x = read(); m = max(m,x);
		a[x]++;
	}
	mx = 1;
	while(mx<=m) mx <<= 1; mx--;
	f[0] = 1;
	for(R int i=1;i<=mx;i++) {
		if(!a[i]) continue;
		R int bu = mx ^ i;
		for(R int k=bu; ; k=(k-1)&bu) {
			if(f[k]) (f[i^k] += f[k] * a[i] %mp) %= mp;
			if(!k) break;
		}
	}
	for(R int i=0;i<=mx;i++) (ans += phi[i+1] * f[i] %mp) %= mp;
	while(a[0]--) ans = ans * 2 %mp;
	cout<<ans;
	return 0;
}

考场上没做出来,好菜啊。

NOI online R3 T3 优秀子序列

原文:https://www.cnblogs.com/clever-sheep/p/12961089.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!