【总结】单调队列

[CEOI2020]花式围栏

题意
在所给的围栏中算出所含矩形的个数。

solution:
对于计算一个长为 l l l,宽为 r r r的矩形,可以算出方案为:
( l + 1 2 ) ∗ ( r + 1 2 ) {l+1 \choose 2}*{r+1 \choose 2} (2l+1)(2r+1)

考虑将围栏分割成若干个规则的矩形,分别计算贡献并相加。

如图,可以将围栏分成若干个由红色矩形组成的区域,相加即可。(注意,这里是求左端点在给定矩形内时的矩形数,而且必须是极大矩阵,即左右都不能扩展)

如右图,第一个的方案为:
( l + 1 2 ) ∗ ( r + 1 2 ) − ( l + 1 2 ) ∗ ( k + 1 2 ) {l+1 \choose 2}*{r+1 \choose 2}-{l+1 \choose 2}*{k+1 \choose 2} (2l+1)(2r+1)(2l+1)(2k+1)

这启示我们用单调队列来维护一个高度递增的序列。

若大于当前高度,则如队列;否则将高度大于的弹出,高度差为 m i n ( h [ t o p ] − h [ t o p − 1 ] , h [ t o p ] − h [ i ] ) min(h[top]-h[top-1],h[top]-h[i]) min(h[top]h[top1],h[top]h[i])

注意,对于高度相等的部分应该合并,而不是单独计算答案。
在这里插入图片描述

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1e5+5;
const LL mod=1e9+7;

LL n,a[N],b[N],ans;
LL tp,h[N],w[N];

void read(LL &x) {
	LL f=1;x=0;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	x*=f;
}


LL calc(LL x, LL y)
{
	x %= mod; y %= mod;
	x = ((1 + x) * x % mod) * 500000004 % mod;//500000004是2关于膜1e9+7的乘法逆元
	y = ((1 + y) * y % mod) * 500000004 % mod;
	return x * y % mod;
}


int main() {
	read(n);
	for(int i=1;i<=n;i++) read(a[i]);
	for(int i=1;i<=n;i++) read(b[i]);
	for(int i=1;i<=n;i++) { 
	    LL tot=0;
		while(tp>0&&h[tp]>a[i]) {
			tot+=w[tp];
			ans=(ans+calc(h[tp],tot)-calc(max(h[tp-1],a[i]),tot)+mod)%mod;
			b[i]+=w[tp];
			tp--;
		}
		h[++tp]=a[i],w[tp]=b[i];
	}
	LL tot=0;
	for(int i=tp;i>=1;i--) {
		tot+=w[i];
		ans=(ans+calc(h[i],tot)-calc(h[i-1],tot)+mod)%mod;
	}
	printf("%lld\n",ans);
}

「NOIP2017普及组」跳房子

solution:
d p dp dp+二分的做法不难想到。

本题显然限制了上界和下界。也就是说,距离太小的不能进,距离太大的要舍弃。而距离是单调递增的,所以用 c n t cnt cnt记录一下位置,把符合条件的加入队列即可。不过本题不是每遍历了一个点就加入一个点,而是在移动 c n t cnt cnt指针时加入。

#include<cstdio>
#include<stack>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int MAXN=500005;
struct node{
	ll x,w;
}a[MAXN];
ll n,d,k,sum,dp[MAXN];
bool check(ll mid) {
	deque<ll> q;
	ll k1=max(1ll,d-mid),k2=d+mid;
	ll cnt=0;
	for(int i=1;i<=n;i++) {
		for(;cnt<i&&a[i].x-a[cnt].x>=k1;cnt++) {
			if(!q.size()) q.push_back(cnt);
			else {
				while(q.size()&&dp[q.back()]<=dp[cnt]) q.pop_back();
				q.push_back(cnt);
			}
		}
		while(q.size()&&a[i].x-a[q.front()].x>k2) q.pop_front();
		if(q.size()) dp[i]=dp[q.front()]+a[i].w;
		else dp[i]=-1e18;
		if(dp[i]>=k) return 1;
	}
	return 0;
}
int main() {
	scanf("%lld%lld%lld",&n,&d,&k);
	for(int i=1;i<=n;i++) {
		scanf("%lld%lld",&a[i].x,&a[i].w);
		if(a[i].w>0) sum+=a[i].w;
	}
	if(sum<k) {
		printf("-1");
		return 0;
	}
	ll l=1,r=max(d-1,a[n].x-d);
	while(l<r) {
		ll mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%lld",l);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值