这一刻的救赎感! || solution - CF1452F

· · 题解

你懂不懂看到 \tt \color{#00B027} Accepted 那一刻的救赎感啊喂!!!

思路是比较容易的,但是细节比较多。

a:b 为用 a 次操作可以使 \le x 的元素增加 b 个,它的的比值为 \frac a b

贪心。

分析一下可以发现,对一个 \le 2^x 的元素操作,结果一定是 1:1;对一个 >2^x 的元素操作,令这个元素为 2^{x+i},那么在一直分解直到 2^x 的过程中,结果为 2^i-1 : 2^i

所以先操作 2^{x+i} 一定是更优的,而且不难发现一定是从小到大按顺序操作,这样可以使比值尽量大。

直到剩余的 k 不够 2^i 的时候,就不能完全操作 2^{x+i} 了,这时候就可能有两种操作:

可以发现后者的结果是不确定的。

但是可以发现一个元素操作后变成编号小 1 的两个元素,这可以形成一个递归的结构,所以考虑递归地解决。

因为 2^{x+i}2^i-1 : 2^i 会被分解成 2^{i-1} : 2^{i-1} , 2^{i-1}-1 : 2^{i-1},所以这个标记是可以往下传递的。且贪心地,一定会先走左子树再走右子树(因为如果右子树没走,那么左子树是走不了的),于是可以判一下左子树的贡献与 k 的大小关系,然后递归到子问题。

大体思路就是这样,然后就是递归部分的细节了。

下面给出调代码的过程,应该是能踩的坑我都踩到了。

代码:

// === / 走吧 就算我们无法让大雨停下 还有我 陪你在雨里放肆奔跑啊 / ===

int n,Q,a[N],b[N];

int d,c;
il int dfs(int k,int x,int y)
{
    if(x>=y && c>=k) return k;
    if(y<=2)
    {
        int ans=0;
        x>y && (ans+=c,k-=c,c=0);
        k && (ans+=x);
        return ans;
    }

    int _k=k;
    if((_k-=y>>1)<=0) return dfs(k,x-(y>>1)+1,y>>1);
    else return c+=(y>>1)*((1<<d)-1),(x-(y>>1)+1)+dfs(_k,(y>>1)-1,y>>1);
}

il int sol(int x,int k)
{
#define chk if(k<=0) return ans

    // no sol
    int ccc=0; rep(i,0,n-1) if((ccc+=a[i]*(1<<i))>=k) break;
    if(ccc<k) return -1;

    int ans=0,tmp;

    // qwq
    k-=gsum(a,a+x+1);
    chk;

    // first x+i
    bool enough;
    rep(i,x+1,n-1)
    {
        tmp=min(a[i],k/(1<<i-x)),enough=tmp!=a[i];
        ans+=((1<<i-x)-1)*tmp,k-=(1<<i-x)*tmp;
        a[i]-=tmp,a[x]+=(1<<i-x)*tmp;
        if(enough) break;
    }
    chk;

    // then <=i
    int c0=0;
    rep(i,0,x)
    {
        c0+=((1<<i)-1)*a[i],a[i]=0;
        if(c0>=k) return ans+k;
    }
    chk;

    // 
    int y;
    rep(i,x+1,n-1) if(a[i]) {y=i;break;}
    return c=c0,d=x,ans+dfs(k,(1<<y-x)-1,1<<y-x);
}

il void solve()
{
    read(n,Q),_::r(b-1,n);

    int op,x,k,cnt2=0;
    while(Q--)
    {
        read(op,x,k);
        if(op==1) {b[x]=k; continue;}
        memcpy(a,b,sizeof(b));
        write(sol(x,k),'\n');
    }
}

华风夏韵,洛水天依!