NOI online R3 T3 优秀子序列

题意:

洛谷链接

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

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

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

  • 对于 10% 的数据,保证 ai1
  • 对于 30% 的数据,保证 ai1000
  • 对于 60% 的数据,保证 ai30000
  • 另有 10% 的数据,保证 n20
  • 对于 100% 的数据,保证 1n1060ai2×105

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

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

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

转移背包时,枚举每个值,然后枚举所有二进制位包含它的集合,n2 的复杂度由于常数小可以获得 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];

复杂度证明,一个数二进制上如果有 i1,那么它子集数量为 2in 位的数一共有 Cni 个这样的数,那么所有 i 位有值的数价值和为 Cni×2i

Cn0×20+Cn1×21+Cn2×22+...+Cnn×2n

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

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

坑的话,注意特判0,因为0可以选多个,最后要将答案乘上 2a0a0为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; }

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


__EOF__

本文作者枫叶晴
本文链接https://www.cnblogs.com/maple276/p/12961089.html
关于博主:菜菜菜
版权声明:呃呃呃
声援博主:呐呐呐
posted @   maple276  阅读(280)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示