每日一题~abc364 D+E(二分答案,交换权值法(将一维的信息和dp值域交换)dp)

D
题意:
N长度的数组A .Q 次询问。
每一次询问:
bi k
在这里插入图片描述
分析:
先对A 排序。我们可以二分距离d,在区间[b-d,b+d]内的A 数组中数字的个数具有单调性。我们要找到满足 个数>=k 的最小值。至于A 数组中元素的个数,我们可以使用 upper_bound 和 lower_bound 来计算下标。两个下标之间的差+1 就是 和 b 的距离小于等于 d 的点。
当时看完这道题之后,感觉应该使用二分。但是不知道要二分什么。怎么check 。当时 ,还不知道要怎么处理这个距离。看了题解才明白。loser~~
再次感叹 二分答案的美妙之处。包括三分答案。只用外面套一层log 的代价。就可以 由 构造答案,转化成 检验答案。大部分情况下后者比前者容易much。
一定要想到二分啊~~
时间复杂度为 nlog^2

#include <bits/stdc++.h>
using namespace std;
int b,k;
vector<int>a;
int check(int d)
{
    int m=lower_bound(a.begin(),a.end(),b-d)-a.begin();
    int n=upper_bound(a.begin(),a.end(),b+d)-a.begin()-1 ;
    return n-m+1;
}
void solve()
{
    int n,q;cin>>n>>q;
    a.resize(n);
    for (int i=0;i<n;i++)
    {
        cin>>a[i];
    }
    sort(a.begin(),a.end());
    
    while(q--)
    {
        cin>>b>>k;
        //枚举 靠近b 的第K个点,的距离
        int l=0;int r=3e8;
        while(l<=r)
        {
            int mid=l+r>>1;
            if (check(mid)>=k)r=mid-1;
           
            else l=mid+1;
        }
        cout<<r+1<<"\n";
    }

}
int  main()
{
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int t; t=1;
    //cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}

E:
我发现我看 数据范围的意识不是很强 ,赛时根本没有注意到 小的离谱的 N(只有80),反而一门心思扑在了 我想出来的错误的贪心上。以后一定要有看 数据范围的习惯啊~~
在这里插入图片描述
**对于某些状态较多,但是dp 值域比较小的动态规划。我们可以考虑将其中的一维信息和值域交换。采用交换权值的办法。达到降低dp的时间复杂度的目的。**背包问题。注意到非常小的N,尝试使用dp解决问题。定义dp[i][j][k] 表示 考虑前i 个物品,甜度为j ,咸度为k ,最多吃的菜数为 dp[i][j][k].但因为j k 的数值到1e4,所以这样设计必然超时。因为n 很小,也就是dp 的值域很小。可以将 值域和咸度的信息交换。重新定义dp[i][j][k] 表示考虑前i 个物品,吃的菜的数量是j 甜度是 k, 此时最小的咸度。
一个小细节:我们可以吃 超过的,dp 是合法的。所以最后会有一个 min(n,i+1)
转移:转移的时候,注意一下。一开始写的是 被注释掉的一行,直接程序崩溃了。

#include <bits/stdc++.h>
using namespace std;
const int N=85;
const int M=1e4+5;
int dp[N][N][M];

void solve()
{
   int n,x,y;cin>>n>>x>>y;
   vector<int>a(n+1);
   vector<int>b(n+1);
   for (int i=1;i<=n;i++)
    {
        cin>>a[i]>>b[i];
    }
    memset(dp,0x3f,sizeof(dp));
    dp[0][0][0]=0;
    for (int i=1;i<=n;i++)
    {
        for (int j=0;j<=i;j++)
        {
            for (int k=0;k<=x;k++)
            {
                dp[i][j][k]=min(dp[i-1][j][k],dp[i][j][k]);
               // dp[i][j][k]=min(dp[i-1][j][k],dp[i-1][j-1][k-a[i]]+b[i]);
                if (j&&k>=a[i])dp[i][j][k]=min(dp[i-1][j-1][k-a[i]]+b[i],dp[i][j][k]);
            }
        }
    }
    int ans=0;
    for(int i=0;i<=n;i++)
    {
        for (int j=0;j<=x;j++){
            if (dp[n][i][j]<=y)ans=max(ans,min(n,i+1));
        }
    }
    cout<<ans<<"\n";
   
}
int  main()
{
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int t; t=1;
    //cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值