CF2075D 题解

CF2075D 题解

除法下取整,背包。

题意

给定 \(x\)\(y\) ,可以进行若干次操作,每一次操作选择一个任意的正整数 \(k\),将 \(x\)\(y\) 中的任意一者变成 \(\lfloor \frac{t}{2^k}\rfloor\),单次代价为 \(2^k\),并且每一个 \(k\) 只能使用一次。求最小的总代价,使得经过若干次(可以为 \(0\))操作之后 \(x\)\(y\) 相等。

分析

引理

对于任意的 \(t,a,b\),都有 \(\lfloor\frac{\lfloor\frac{t}{2^a}\rfloor}{2^b}\rfloor=\lfloor\frac{t}{2^{a+b}}\rfloor\)

题解

根据以上引理(非常 crucial),其实可以发现,我们对于对于任意一个数进行的操作,其代价只和幂次和有关。

由于每个数只能用一次,所以很容易想到 01背包 来记录一下 \(x,y\) 分别除以若干幂次的最小代价。

定义 \(dp[t][i][j]\) 为 考虑了前 \(t\) 个幂次,\(x\)\(y\) 的幂次分别是 \(i\)\(j\) 的最小代价,这样转移就是显然的,并且可以滚掉 \(t\) 这一维。

并且这个 dp 的值和 \(x,y\) 的值是独立的,只需要预处理即可。

然后 check 答案的时候直接遍历 dp 数组检查是否能让 \(x\)\(y\) 相等就好了。

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=75;
const int lim=61;
const int INF=1e18;
int dp[N][N];
inline void solve()
{
    int x,y;
    cin>>x>>y;
    int ans=INF;
    for(int i=0;i<=lim;++i)
    {
        for(int j=0;j<=lim;++j)
        {
            if((x>>i)==(y>>j))ans=min(ans,dp[i][j]);
        }
    }
    cout<<ans<<'\n';
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int T;cin>>T;
    for(int i=0;i<=lim;++i)
        for(int j=0;j<=lim;++j)
            dp[i][j]=INF;
    dp[0][0]=0;
    for(int t=1;t<=lim;++t)
    {
        for(int i=lim;i>=0;--i)
        {
            for(int j=lim;j>=0;--j)
            {
                if(i-t>=0)dp[i][j]=min(dp[i][j],dp[i-t][j]+(1ll<<t));
                if(j-t>=0)dp[i][j]=min(dp[i][j],dp[i][j-t]+(1ll<<t));
            }
        }
    }
    while(T--)solve();
    return 0;
}

Bonus

实际上开始看题看成了 \(x\)\(y\) 同时除以 \(2^k\) ,这个其实也是背包做的,和上面没有什么差别。

如果说每个数可以用多次,那就变成了多重背包。

如果说每个数可以无限用,虽然说是完全背包,但是这个题里面是只会选择 \(2\) 的一次幂的,直接暴力枚举即可。

虽然说我没有想出来,但是背包确实是一个很有用的算法!!!

引理中的结论不止对 \(2\) 成立,还对所有的 \(n\in N\) 成立。

posted @ 2025-03-19 11:09  Hanggoash  阅读(44)  评论(0)    收藏  举报
动态线条
动态线条end