「HCOI-R1」哀之变化

· · 题解

官方题解。

自评难度介于黄和绿之间。

暂时没考虑到其他做法。

\text{Subtask 0}

对于每次询问暴搜。

\text{Subtask 1}

考虑离线处理,将所有询问按 k 从小到大排序,然后一次 dp 出所有答案。

或者直接记忆化搜索。

复杂度为 \operatorname{O}(n^2)

注:以上复杂度中的 n 仅表示题面中 n,k 的数量级。

\text{Subtask 2}

考虑求出达到 n 的最小次数,然后尝试在过程中浪费一些操作以达到 k 次。

首先特判 n=0,1 的情况。

n\ge 1 时,我们可以通过 1\times2-1=1 这样的操作浪费掉任意偶数次。

n\ge 2 时,我们还可以通过 2\times 2-1-1=2 这样的操作浪费掉 3 次。

显然可以凑出大于 1 的任意整数。

考虑能否浪费 1 次,显然只有 a\times2-1-1=(a-1)\times2

如果 n 的最短操作中不包括 (a-1)\times2,以上方案就无法实现。

此时 n 一定为 2 的幂次或 2 的幂次 -1(最多只允许最后有一次 -1 操作)。

根据以上条件即可判断是否可行,复杂度 \operatorname{O}(T\times\log{n})

或许您会不知道如何求达到 n 的最小次数,有一种较简单的思考方法。

转换此题为一个本质完全相同的问题(也是出题人最开始想到的问题)。

给定一个整数 n,进行 k 次操作,问最终能否为 1

每次操作从以下两种中选一个:

他们的最小次数也是一样的。

考虑尽可能多的 a\gets\frac{a}{2} 即可。

放一下代码吧。

```cpp #include<bits/stdc++.h> using namespace std; typedef long long ll; const ll maxn=1e5+84,maxm=4e3+23; struct Query{ ll k,n,id; bool ans; inline void read(ll x){ scanf("%lld%lld",&k,&n); id=x; return ; } friend bool operator<(Query xy,Query zb){ return xy.k<zb.k; } }q[maxn]; ll T,j; bitset<maxn> ans,Next; inline bool cmp(Query xy,Query zb){ return xy.id<zb.id; } int main(){ scanf("%lld",&T); for(ll i=1;i<=T;i++) q[i].read(i); sort(q+1,q+T+1); ans[1]=1; j=1; for(int i=1;i<=q[T].k+2;i++){ while(j<=T&&i==q[j].k+1){ q[j].ans=ans[q[j].n]==1; j++; } Next.reset(); for(int i=ans._Find_first();i!=ans.size()&&i<=maxm;i=ans._Find_next(i)){ if(i*2<=maxm) Next[i*2]=1; if(i>=1) Next[i-1]=1; } ans=Next; } sort(q+1,q+T+1,cmp); for(int i=1;i<=T;i++) puts(q[i].ans?"Yes":"No"); return 0; } ``` $100pts$: ```cpp // 哀太可爱辣 #include<bits/stdc++.h> using namespace std; typedef long long ll; ll T,k,n,res,ans; inline ll Q(ll x){ res=0; while(x!=1){ if(x&1) x++; else x>>=1; res++; } return res; } inline bool check(ll x){ if(x&1) x++; return (x&(-x))==x; } int main(){ scanf("%lld",&T); while(T--){ scanf("%lld%lld",&k,&n); if(!k) puts(n==1?"Yes":"No"); else if(!n) puts("Yes"); else if(n==1) puts(k%2==0||k>=5&&k%2?"Yes":"No"); else{ ans=Q(n); puts(ans>k||ans+1==k&&check(n)?"No":"Yes"); } } return 0; } ```