P1314 聪明的质检员 二分 与 前缀和 方法

P1314 聪明的质检员

题目描述:

小T是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有
𝑛 个矿石,从 1 到 𝑛逐一编号,每个矿石都有自己的重量 𝑤𝑖 以及价值
𝑣𝑖
检验矿产的流程是:
1.给定 𝑚 个区间 [𝑙𝑖,𝑟𝑖] ;
2.选出一个参数W;
3.对于一个区间 [𝑙𝑖,𝑟𝑖]计算矿石在这个区间上的检验值 𝑦𝑖。

yi 为 区间 [li, ri] 上的质量小于W矿石数量的总和与该区间上质量大于W的矿石的价值的总和的乘积。检验结果y即为每个区间的yi的和;若这批矿产的检验结果与所给标准值 𝑠相差太多,就需要再去检验另一批矿产。小T 不想费时间去检验另一批矿产,所以他想通过调整参数 𝑊 的值,让检验结果尽可能的靠近标准值 𝑠,即使得 ∣𝑠−𝑦∣ 最小。请你帮忙求出这个最小值。

输入格式:

第一行包含三个整数 𝑛,𝑚,𝑠,分别表示矿石的个数、区间的个数和标准值。接下来的 𝑛 行,每行两个整数,中间用空格隔开,第 𝑖+1 行表示 𝑖 号矿石的重量 𝑤𝑖 和价值 𝑣𝑖 。接下来的 𝑚 行,表示区间,每行两个整数,中间用空格隔开,第 𝑖+𝑛+1 行表示区间 [𝑙𝑖,𝑟𝑖] 的两个端点 𝑙𝑖 和 𝑟𝑖 。注意:不同区间可能重合或相互重叠。

输出格式:

一个整数,表示所求的最小值。

这个P1314数字还挺吉利的,就是硬是让我写了一晚上

题目解读 + 暴力算法:

这道题属实困扰了我好久,对于这道题我们先考虑暴力算法,暴力算法一般理解了题意就基本写得出来,我们知道这个W只能取在这些矿石的最大的质量以及最小的质量之间,我们需要找到这两个最大最小值,然后在这个区间内遍历一遍,每个W都有他固定的y值,而y值的计算只需按照题目所描述的计算方法按部就班就OK啦!

#include<bits/stdc++.h>
using namespace std;
#define N 200010

typedef long long ll;

ll n, m, s, l[N], r[N], w[N], v[N];// 这边定义一些基本的量,这里不过多赘述
ll maxw = 0, minw = 0x3f3f3f3f3f3f3f3f;//这边为了找出W的最大最小值确定区间 

ll sum_wv(int W)//这个函数用来计算y的值
{
	ll y = 0;//初始化y值为0;
	for(int i = 1; i <= m; i ++)//遍历每个区间
	{
		ll sumw = 0, sumv = 0;//这个分别是题目所描述的区间内大于W的数量与其质量
		for(int j = l[i]; j <= r[i]; j ++)//遍历该区间
		{
			if(w[j] >= W)//筛选掉那些不满足的矿石
			{
				sumw ++;
				sumv += v[j];
			}
		}
		y += sumw * sumv;//计算该区间的yi并加到y上
	}
	return y;//返回y值
}
int main()
{
	scanf("%lld %lld %lld", &n, &m, &s);//这一部分是常规的输入不过多赘述了
	
	for(int i = 1; i <= n; i ++)
	{
		scanf("%lld %lld", &w[i], &v[i]);
		
		maxw = max(maxw, w[i]);//计算出W的左右边界
		minw = min(minw, w[i]);
	}
	for(int j = 1; j <= m; j ++)
	{
		scanf("%lld %lld", &l[j], &r[j]);
	}
	ll ans = 0, min_ans = 0x3f3f3f3f3f3f3f3f;//这个是为了找出最优的W,以及|y - s|
	for(int i = minw - 1; i <= maxw + 2; i ++)
	{
		int er = sum_wv(i);//这是把该W的y值赋值给er
		if(abs(s - er) < min_ans)//打擂台不断地比较选出最优秀的那个
		{
			min_ans = abs(s - er);
			ans = i;
		}
	}
	cout << min_ans << endl;//输出
	return 0;
}

在这里插入图片描述

TLE是很正常的,你看看这复杂度都多少了!!!!

优化方案:二分, 前缀和!

我们知道了一个W对应着一个y值,并且随着W的增加你筛选掉的矿石也只会越来越多,那么你的y值就会越来越小,那么现在你发现了终极规律,其实这个看似毫无规律的玩意儿,居然是有序的,那么接下来就该轮到今天的主角:二分,二分这东西很玄乎,就是像我这样的初学者很难看出来这是啥玩意,我们通过在minw与maxw之间二分选出最适合的y值,其最终原因是因为他是有序的,它能够根据W的值而改变,而我们只需找到最适合的W就行了,但是,如果只用二分很明显依旧还是很慢,无法通过所有的测试集。但当我拿出前缀和时阁下该如何应对前缀和:是一种基础算法(不了解的可以去别处转转看,很好理解的不会花太多时间我这里就直接讲了)我们需要用前缀和求出区间内的数量和与价值和:我们直接先写个check函数:

long long check(int W) {
   // 这里需要把sumw,与sumv两个前缀和数组全部初始化为零。
   //memset当然也行
   for (int i = 0; i <= n; ++i) {
       sumw[i] = 0;
       sumv[i] = 0;
   }

   for(int i = 1; i <= n; i ++) //这个就是计算前缀和的循环了
   {
       if(w[i] >= W) //只有当w[i]大于W才能允许加上
       {
           sumw[i] = sumw[i - 1] + 1;
           sumv[i] = sumv[i - 1] + v[i];
       }
       else
       {
       	sumw[i] = sumw[i - 1];
       	sumv[i] = sumv[i - 1];
   	   }
   }

   // 计算检验结果 y
   ans = 0;
   for(int i = 1; i <= m; i++) //这个就是前缀和的运用了直接计算区间和
   {
       ans += (sumw[r[i]] - sumw[l[i] - 1]) * (sumv[r[i]] - sumv[l[i] - 1]);
   }

   return ans;//这个是y值
}

得到y值以后就直接进入二分内判断就OK啦,上代码!!!不废话了:

#include<bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;

int n, m;
int w[N], v[N];
int l[N], r[N];
long long sumw[N], sumv[N];
long long ans, su, s;


long long check(int W) {
    // 这里需要把sumw,与sumv两个前缀和数组全部初始化为零。
    //memset当然也行
    for (int i = 0; i <= n; ++i) {
        sumw[i] = 0;
        sumv[i] = 0;
    }

    for(int i = 1; i <= n; i ++) //这个就是计算前缀和的循环了
    {
        if(w[i] >= W) //只有当w[i]大于W才能允许加上
        {
            sumw[i] = sumw[i - 1] + 1;
            sumv[i] = sumv[i - 1] + v[i];
        }
        else
        {
        	sumw[i] = sumw[i - 1];
        	sumv[i] = sumv[i - 1];
		   }
    }

    // 计算检验结果 y
    ans = 0;
    for(int i = 1; i <= m; i++) //这个就是前缀和的运用了直接计算区间和
    {
        ans += (sumw[r[i]] - sumw[l[i] - 1]) * (sumv[r[i]] - sumv[l[i] - 1]);
    }

    return ans;//这个是y值
}

int main() {
    cin >> n >> m >> s;
    int maw = 0, miw = INT_MAX;
    for(int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i];
        maw = max(maw, w[i]);
        miw = min(miw, w[i]);
    }
    for(int i = 1; i <= m; i++) {
        cin >> l[i] >> r[i];
    }

    // 初始化 res 使得 res 是最大的方便后续打擂台
    long long res = 0x3f3f3f3f3f3f3f3f;

    // 二分搜索
    int l = miw - 1, r = maw + 2;//这是矿物质量的范围
    while(l <= r) 
	{
        int mid = (l + r) >> 1;//基本模版
        long long y = check(mid);
        su = llabs(s - y);
        res = min(res, su);

        if(y > s) {//这段为了找出最接近s的y值不断地调整l与r的值
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }

    cout << res << endl;
    return 0;
}

快乐ac吧!!!!!!!!!!!!!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值