P1083 [NOIP2012 提高组] 借教室(二分答案+差分)

题意:找到第一个不满足分配的订单编号

一般可以先通过暴力的方法来寻找思路,我们要找左侧第一个点,左侧边界

那么先对编号数组进行二分

while(l<r)
{
    int mid=l+r>>1;
    if(check())
        r=mid;
    else
        l=mid+1;
}

check表示当前不满足条件 ,就可以向左侧二分,找到第一个点

我们发现,每一个订单都是对于区间操作

开始~结束   都-=要租借的订单数                

所以自然可以想到差分的操作,对区间修改,最后运用前缀和求某一个特定的点的值

1.附上代码O(nlogn)

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e6+5;
int a[N];
int b[N];
struct node
{
	int d;
	int st, ed;
}t[N];
int n, m;
//前缀和:减小对区间查询的时间复杂度
bool check(int x)
{
	for (int i = 1;i <= n;i++)
		b[i] = a[i] - a[i - 1];
	for (int i = 1;i <= x;i++)
	{
		b[t[i].st] -= t[i].d;
		b[t[i].ed + 1] += t[i].d;
	}
	for (int i = 1;i <= n;i++)
	{
		b[i] += b[i - 1];
		if (b[i] < 0) return true;
	}
	return false;
}
int main()
{
	cin >> n >> m;
	for (int i = 1;i <= n;i++)
		cin >> a[i];
	for (int i = 1;i <= m;i++)
		cin >> t[i].d >> t[i].st >> t[i].ed;
	int l = 0, r = m;
	while (l < r)
	{
		int mid = l + r >> 1;
		if (check(mid))
			r = mid;	//如果分配失败,往左走
		else			//分配成功,往右走
			l = mid + 1;
	}
	if (check(l))
		cout << -1 << endl << l;
	else
		cout << 0;
	return 0;
}

这里提一下为什么要进行二分 

如果,我们直接枚举,时间复杂度是O(n^2),如果运用差分优化之后,尽管每次修改的时间复杂度仅为O(1),但是我们本题是要求哪一个是第一个不满足条件的订单,这样子每一个订单修改区间后都需要查询有没有小于0,时间复杂度又变成O(n^2),所以需要二分,时间复杂度是O(nlogn)

2.优化后的二分答案,时间复杂度无限接近于O(n+logn)

#include <cstdio>
#include <iostream>
#include <cstdlib>
using namespace std;
const int maxn = 1000005;
int n, m, ans = 20021228;
int r[maxn], c[maxn], now;
int d[maxn], s[maxn], t[maxn];
bool check(int mid)  //判断当前的二分值是否可行  
{
    int i, sum = 0;
    if(now > mid) 
      for(i = mid + 1; i <= now; i++) {
          c[s[i]] -= d[i];
          c[t[i] + 1] += d[i];
      }
    else
      for(i = now + 1; i <= mid; i++) {  //注意这里是将以前操作的恢复原样,操作相反
          c[s[i]] += d[i];
          c[t[i] + 1] -= d[i];
      }
    now = mid;
    for(i = 1; i <= n; i++) {
      sum += c[i];
      if(sum > r[i]) return true;
    }
    return false;
}
int main()
{
    int i;
    scanf("%d%d", &n, &m);
    for(i = 1; i <= n; i++)
      scanf("%d", &r[i]);
    for(i = 1; i <= m; i++) {
      scanf("%d%d%d", &d[i], &s[i], &t[i]);
    }
    int l = 1, r = m, mid;
    while(l <= r) {  //二分答案
      mid = (l + r) / 2;
      if(check(mid)) ans = min(ans, mid), r = mid -1;
      else l = mid + 1;
    }
    if(ans != 20021228) 
      printf("-1\n%d", ans);
     else printf("0"); 
    return 0;
}

3.暴力枚举    O(n^2)

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100005;
int a[N];
int q[N];
struct node
{
	int d;
	int st, ed;
}t[N];

void print(int i)
{
	cout << -1 << '\n';
	cout << i;
}
/*暴力的做法:我们发现,找到第一个会让等式不满足的点就可以了*/
int main()
{
	int n,m;
	cin >> n>>m;
	for (int i = 1;i <= n;i++)
		cin >> a[i];
	for (int i = 1;i <= m;i++)
		cin >> t[i].d >> t[i].st >> t[i].ed;
	bool flag = false;
	for (int i = 1;i <= m;i++)
	{
		node k = t[i];//某个订单
		for (int j = k.st;j <= k.ed;j++)
		{
			a[j] -= k.d;
			if (a[j] < 0)
			{
				flag = true;
				print(i);break;
			}
		}
		if (flag) break;
	}
	if (flag)
		return 0;
	else
		cout << 0;
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值