2020牛客NOIP赛前集训提高组#6-B-艰难睡眠(RMQ,单调队列)

题目描述在这里插入图片描述

样例

input:
3 6 3
1 1
3 2
4 3
0 1 2 7 1 2
1 2 0 3 4 5
7 9 5 0 4 6
output:
4
PS: 本题需快读,若采用出题人给的快读,需要从文件读入,测试的时候要freopen,但是提交的时候不用。

分析

由于对于每个吵闹的人,可以算出从 i i i开始睡觉的情况下,这个吵闹的人需要被调到那个区间开始吵。设其他量与题目描述一致, l e n len len表示吵闹的人可以开始吵闹的区间 S S S的大小,则易得:
l e n = m − k − b i + 1 len=m-k-b_i+1 len=mkbi+1
可以看到, l e n len len的大小是固定的,因此可以看做是一个滑动窗口。

接下来可以枚举开始睡觉的时间 j j j,那么对于这个时间,可以通过查询 S S S中的 c i , j c_{i,j} ci,j的最小值来算得从 j j j开始睡觉所需要的最小代价,其基础是由于每个吵闹的人之间是相互独立的。

PS: 可以从前一天睡到第二天,也可以从前一天吵到后一天,因此是一个环,需要倍长展开。

那么最后的问题是怎么求区间最小值呐?我考场上想到线段树,但是显然常数巨大的 O ( n m l o g m ) O(nmlogm) O(nmlogm) T T T掉最后二十分。如果用 S T ST ST,比如某大佬就是,那么很容易 M L E MLE MLE而爆零。
所以,最后留下一个办法,区间滑动求 R M Q RMQ RMQ可以用单调队列线性解。
单调队列参见这里

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
namespace io {
const int L = (1 << 21) + 1;
char ibuf[L], *iS, *iT, obuf[L], *oS = obuf, *oT = obuf + L - 1, c, st[55];
int f, tp;
#define gc() (iS==iT?(iT=(iS=ibuf)+fread(ibuf,1,L,stdin),(iS==iT?EOF:*iS++)):*iS++)
inline void gi(int& x) {
    for (f = 1, c = gc(); c < '0' || c > '9'; c = gc())
        if (c == '-') f = -1;
    for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15);
    x *= f;
}
};
using io::gi;
const int MAXN=5010;
int n,m,k;
int a[MAXN],b[MAXN];
int c[MAXN][MAXN],mn[MAXN][MAXN];
deque<int> q;
int main()
{
	gi(n);gi(m);gi(k);
	for(int i=1;i<=n;i++){
		gi(a[i]);gi(b[i]);
		if(k+b[i]>m) return !puts("-1");//若S区间大于m,则不可能
	}memset(c,0x3f,sizeof(c));
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++) gi(c[i][j]);
		for(int j=m+1;j<=m*2;j++) c[i][j]=c[i][j-m];//扩充一倍
	}memset(mn,0x3f,sizeof(mn));
	for(int i=1;i<=n;i++){
		int len=m-k-b[i]+1;
		while(!q.empty()) q.pop_back();
		q.push_back(0);
		for(int j=1;j<=2*m;j++){
			while(!q.empty()&&j-q.front()>=len) q.pop_front();
			while(!q.empty()&&c[i][q.back()]>=c[i][j])
				q.pop_back();q.push_back(j);
			mn[i][j]=min(mn[i][j],c[i][q.front()]);
		}
	}//单调队列
	//mn[i][j]指第i个人从j往前len区间内的最小的代价
	int ans=5e7+10;
	for(int i=1;i<=m;i++){
		int now=0;
        for(int j=1;j<=n;j++){
            int ed=i-b[j];
            if(ed<=m-k-b[j]+1) ed+=m;//由于环的原因,在len以前的mn数据不能反映最小代价
            //因此需要向前移m位来纠正
            now+=mn[j][ed];
        }ans=min(now,ans);
	}printf("%d\n",ans);
}

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值