1 条题解
-
0
自动搬运
来自洛谷,原作者为

VinstaG173
Si Dieu n'existait pas, il faudrait l'inventer.搬运于
2025-08-24 22:34:51,当前版本为作者最后更新于2021-04-30 13:52:35,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
数据范围:$1 \le k \le n \le 3 \times 10^6,1 \le S \le 3 \times 10^{17}$。
首先原来的做法显然用不了了。我们考虑找一下 dSG 函数(即从 SG 值对应回最少石子数的函数)的性质。
我们发现数列长这样(称为数列 ,下标从 开始,其余数列下标均从 开始):
差分一下(得到的数列称为数列 ):
再差分一下(得到的数列称为数列 ):
发现数列 里全是 或 。多算一些会发现当我们以每个 为结尾带上前面连续一些 组成一些段后,这些段的长度分别是 ,即二进制表示下为 与 相间的数。我们设第 个这样的数为 。
首先我们尝试解数列 的通式。由于数列 里只有 和 ,我们考虑把数列 里的数表示成 的形式,令 。则对于每个 有 ,对于数列 中第 个数为 的 有 ,否则有 。
令 为 的前缀和,则当且仅当 有数列 中第 个数为 。于是 ,。
那么我们可以分别求 和 的前缀和得到数列 中的数。设数列 中下标为 的是 ,则 为 的前缀和, 为 的前缀和。因此有
令 ,则上式
$$=\sum_{i=1}^{t}(v-S_t)+\sum_{j=1}^{t}\sum_{i=1}^{j-1}x_j $$$$=\sum_{i=1}^{t}\left(v-S_t+\sum_{j=i+1}^{t}x_j\right) $$令 为 的前缀和,则 。
因此数列 的第 项为 。由于 不减,并且 ,因此我们只要二分出最大的 的 ,再求出满足 的最大 ,那么 就是 的 SG 值。前面的二分是 的,后面求 由于左边是二次函数且开口朝上,可以直接解出方程正根然后下取整,显然就是所求。这一步是 的。
然后由于暴力求每个二进制位上数值和是 的,会被卡,所以要优化(如果您能卡过去可以私信我)。以下优化方案由永远滴神
https://www.luogu.com.cn/user/60864考虑分治计算贡献。设所有 SG 值最大的二进制位数为 ,我们把每个数的二进制拆成 段,每段不超过 位。(尽可能平均分,为简便接下来省略上取整符号)
对于每段,先将每个数这一段中二进制位对应的所有可能取值出现次数用一个下标为 的桶存起来。如 ,某个数的二进制第 位为 ,则将 位的计数器加上 ,类似处理。
然后对于这 个数暴力枚举每个数二进制表示里面 的位置计算贡献。
这样的时间复杂度是 ,由于 SG 值不超过 ,取 时复杂度是 的,可以通过。
总时间复杂度是 。
Code:
#include<cmath> #include<cstdio> #define rg register #define ll long long #define lb(x) (x&(-x)) inline char gc() { static char buf[1048576],*pn=buf,*pe=buf; return (pn==pe)&&(pe=(pn=buf)+fread(buf,1,1048576,stdin),pn==pe)?EOF:*pn++; } inline int read() { int x=0; char cc=gc(); while(cc<'0'||cc>'9')cc=gc(); while(cc>='0'&&cc<='9')x=x*10+cc-'0',cc=gc(); return x; } inline ll _read() { ll x=0; char cc=gc(); while(cc<'0'||cc>'9')cc=gc(); while(cc>='0'&&cc<='9')x=x*10+cc-'0',cc=gc(); return x; } ll S,s; int n,k; int t,w,m; int v,flag; ll ssmv[37]; int cnt[37],pw2[17]; int x[37],sm[37],ssm[37]; int cnt1[32773],cnt2[32773]; int ans1[32773],ans2[32773]; int main() { n=read(),k=read()+1,S=_read(); while(1ll<<(m<<1)<=S)++m; --m,t=(m>>1)+1,w=(1<<t)-1; pw2[0]=x[1]=sm[1]=ssm[1]=1; for(rg int i=1;i<t;++i)pw2[i]=(pw2[i-1]<<1); for(rg int i=2;i<=m;++i) { x[i]=(x[i-1]<<1)|(i&1),sm[i]=sm[i-1]+x[i]; ssm[i]=ssm[i-1]+sm[i],ssmv[i]=(sm[i]+3ll+i)*sm[i]-ssm[i]; } for(rg int i=0,l,r,mid;i<n;++i) { s=_read(),l=0,r=m; while(l<r)mid=r-((r-l)>>1),(ssmv[mid]<=s)?(l=mid):(r=mid-1); v=int((sqrt((l+3)*(l+3)+((s+ssm[l])<<2))-l-3)/2),++cnt1[v&w],++cnt2[v>>t]; } for(rg int i=0;i<=w;++i) { for(rg int j=i;j;j^=lb(j)) { ans1[lb(j)]+=cnt1[i]; } } for(rg int i=0;i<t;++i)cnt[i]=ans1[pw2[i]]; for(rg int i=0;i<=w;++i) { for(rg int j=i;j;j^=lb(j)) { ans2[lb(j)]+=cnt2[i]; } } for(rg int i=0;i<t;++i)cnt[i+t]=ans2[pw2[i]]; for(rg int i=0;i<=m;++i)(cnt[i]%k)&&(flag=1); puts((flag)?"YES":"NO"); return 0; }关于解法中关于 个数的结论我还没有找到一个证明(但是跑完整个数据范围验证确实暂时没有出错),如果有神仙找到了证明方法或举出了反例欢迎私信我。
如果证明很简单请顺带把我这个垃圾爆踩一顿
- 1
信息
- ID
- 6802
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者