牛客周赛137补题

D 小苯的序列涂色

image

观察数据范围,发现才5000,那么可以考虑 n2的方法,n2的算法不多,dp算一种,观察问题,问的是最小总代价(凡是说最小代价,最大方案,之类的一般都可以考虑dp),故而考虑dp,用dp[i] 表示从1到i位置所消耗的最小代价。

选定i了,那么i之后的我们先不考虑,所以一定有个染色操作是以i为结尾的,我们遍历1-i去找最小的异或值才可以,找到左边界之后,我们还要处理1-左边界这些数,如果直接+dp[j-1]肯定不对,因为这个题是可以重复染色的,所以应该去j-1到i-1中找最小值才行,可是这样又要加一层循环,不如从i开始倒序循环,j的范围是从i到1,同时维护最小的dp值,最终可以得到结果。

因为对于每一个i来说,dp[i]都是1-i染色的最小代价,所以最后得到的dp[n]也是最小的。

code

void solve()
{
    cin>>n;
    vi a(n+1),b(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        b[i]=b[i-1]^a[i];
    }
    vi dp(n+1,1e18);
    dp[0]=0;
    for(int i=1;i<=n;i++)
    {
        dp[i]=dp[i-1]+a[i];
        int mi=1e18;
        for(int j=i;j>=1;j--)
        {
            mi=min(mi,dp[j-1]);
            dp[i]=min(dp[i],mi+(b[i]^b[j-1]));
        }
    }
    cout<<dp[n]<<endl;
}

E 小苯的凝聚区间

image

要求max[(M-m+1)-(r-l+1)],也就是求[(M-m)-(r-l)]的最大值。

我们通过手写一个数组可以得到,M和m一定是子区间的左端点和右端点
接下来进行证明
(假设该数组为4 5 3 4 2 4 8 3 5

选取从数字5到数字8这个区间,你会很容易的发现左端点如果是3会更优,进一步发现如果左端点是2会更优,也就是这个区间内不可能会比其中的较小端点更小,同理,选取数字2到数字5这个区间,很容易发现右端点选取8会更优,故而得证。

那么答案有两种形式,
一种是左端点为最小值,右端点为最大值
那么原式就相当于(ar -r)-(al - l),所以定义一个数组bi=ai-i,正向遍历数组,在途中维护bi的最小值,用ans=max(ans,bi-mi) 更新最大值ans。

第二种就是左端点是最大值,右端点为最小值,同理,原式就等于(al +l)-(ar + r),所以定义一个数组ci=ai-i,正向遍历数组,在途中维护ci的最小值.用ans=max(ans,mx-bi) 更新最大值ans.

体会:遇到式子的时候要手模几个数组出来推一推,看看能否化简式子。

code

void solve()
{
    cin>>n;
    vi a(n+1),b(n+1),c(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        b[i]=a[i]-i;
        c[i]=a[i]+i;
    }
    int mi=1e18;
    int mi2=-1e18;
    int ans=-1e18;
    for(int i=1;i<=n;i++)
    {
        mi=min(mi,b[i]);
        mi2=max(mi2,c[i]);
        ans=max(b[i]-mi,ans);
        ans=max(mi2-c[i],ans);
    }
    cout<<ans<<endl;
}

F 小苯的糖果盒

image

做法:贪心+DP

贪心:比较进阶的一个贪心模型:搬运模型
意思是两个长度相同,元素总和相同的数组,分别为a,b,你可以对a数组实现ai++,aj--的操作,如果要求把a数组变成b数组的最小代价,那就是分别求出a,b的前缀和数组,从1-n遍历,每一次遍历对答案产生abs(preAi-preBi)的贡献,也就是前缀和差值的绝对值求和。

证明略~~。

注意数据范围特别小,我们可以dp。

现在我们有a数组,轻而易举得到a的前缀和数组,看似我们要构造b数组,但不对,我们要求的是preB数组,定义dp状态,dp[i][j]表示从1到i两数组相同,且总和为j的最小成本。

因为b的元素一定为完全平方数,所以枚举b该位元素。这个dp就类似于背包,转移方程就是
dp[i][j]=min(dp[i][j],dp[i-1][j-temp]+abs(j-a[i]))

最后所求出的dp[n][a[n]]就是答案。

code

void solve()
{
    cin>>n;
    vi a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        a[i]+=a[i-1];
    }
    vvi dp(n+1,vi(a[n]+1,1e9));
    dp[0][0]=0;
    for(int p=0;p*p<=a[n];p++)
    {
        int temp=p*p;
        for(int i=1;i<=n;i++)
        {
            for(int j=temp;j<=a[n];j++)
            {
                dp[i][j]=min(dp[i][j],dp[i-1][j-temp]+abs(j-a[i]));
            }
        }
    }
    if(dp[n][a[n]]>1e8)
    {
        cout<<-1<<endl;
        return;
    }
    cout<<dp[n][a[n]]<<endl;
}
posted @ 2026-04-01 20:01  Lambda_L  阅读(15)  评论(0)    收藏  举报