题解 P5527 [Ynoi2012] NOIP2016 人生巅峰
人生第一道 Ynoi,同时也是 1k 通过。不卡常不难写,小清新 Ynoi 真的不多见了。
前置知识:抽屉原理,树状数组,bitset,动态规划基础。
首先考虑一个事实,当这个区间够长是必然有解的。
对于一个区间 ,它会产生 种不同的贡献。而它只有 个不同的子集。所以当 时必然有解。
我们来做一个类比,假设现在有两种颜色的小球,你要在这些小球中选三个,那么必然会有两个小球的颜色是一样的。同理,我们现在有 种和,我们要在这些和中选 个,那么当 时一定会有至少一种和重复了一次。
我们把 的最大值带进去,解一个不等式就可以得到当 时必然有解。
现在区间长度小了,我们就可以直接背包做了。
具体地,令 表示前 个数能不能凑出 。
转移方程显然为 ,这里要减 是因为贡献是 。
那么重点来了,如何判断有解?
先下结论,如果有一个 使得 有解。
考虑每一个状态其实都表示一个集合,那么我们设 为集合 , 为集合 ,那么 和 都是贡献为 的集合。
而我们同时删除一些相同的元素,两个集合的贡献显然还是相等的。这个时候假设 那么 。
所以当 时必然有解。
最后如何处理区间修改操作?当然可以欧拉定理降幂,但没有这个必要。
我们考虑先用树状数组记录每个位置被立方了多少次,修改的时候区间修改,假设我们用到了这个位置的数,单点查询一下,然后倍增处理即可。
具体地,令 表示 被立方了 后的值。用到的时候二进制分解算一下就好了。
代码:
int lowbit(int x) { return x&(-x); } void add(int x,int k) { while(x<=n) { t[x]+=k; x+=lowbit(x); } return ; } int query(int x) { int ret=0; while(x) { ret+=t[x]; x-=lowbit(x); } return ret; } void clear() { vis.reset(); vis.set(0,1); } int main() { read(n);read(m);read(v); for(int i=1;i<=n;i++) read(a[i]); for(int j=0;j<v;j++) { tot[j][0]=j*j*j%v; } for(int i=1;i<=21;i++) { for(int j=0;j<v;j++) { tot[j][i]=tot[tot[j][i-1]][i-1]; } } while(m--) { int opt,l,r; read(opt); read(l); read(r); flag=0; if(opt==2) { add(l,1); add(r+1,-1); } if(opt==1) { if(r-l+1>13) { puts("Yuno"); } else { clear(); for(int i=l;i<=r;i++) { int k=query(i); int x=a[i]; for(int j=21;j>=0;j--) { if(k&(1<<j)) { x=tot[x][j]; } }//倍增查询x的值 if((vis&(vis<<(x+1))).any())//这个就相当于 dp[i-1][j]=dp[i-1][j-a[i]-1] { puts("Yuno"); flag=1; break; } vis=vis|(vis<<(x+1));//更新 dp 值 } if(flag==0) puts("Yuki"); } } } return 0; }
本文作者:zplqwq
本文链接:https://www.cnblogs.com/zplqwq/p/16812913.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步