闵可夫斯基和

定义:将两个凸包合并
相当于要将两个凸包叠加起来,最后再求凸包
image
【学习笔记】浅谈闵可夫斯基和嫖了一个图过来
仔细思考发现就是要将凸包的差分数组,然后按斜率排序,形成新的凸包的差分数组
用这个办法还可以解决凸包的max卷积问题

例题

Gym 103202L Forged in the Barrens
可以发现一个段的最大贡献可以变成,随意选一个数减去随意选另一个数的差的最大值,那么就可以设出一个状态f[i][j][0/1/2]表示现在前i个数,用了j段,最后一段有没有选数/有没有加数/有没有减数,然后考虑分治来优化,设f[0/1/2][0/1/2],表示左边和右边一段的选数状态,发现转移就是在做max卷积,然后这题就可以在O(nlogn)的时间解决了,至于为什么是凸的可以通过建费用流模型来证明

点击查看代码
#include<cstdio>
#include<vector>
#include<algorithm>
#define ll long long
using namespace std;
void read(int &res)
{
	res=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while('0'<=ch&&ch<='9') res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
}
const int N=2e5+100;
const ll inf=1e18;
#define vcl vector<ll>
void merge(vcl &c,vcl a,vcl b)
{
	int lena=a.size(),lenb=b.size();
	c.push_back(a[0]+b[0]);
	int i=1,j=1;
	while(i<lena&&j<lenb)
	{
		if(a[i]>b[j])
			c.push_back(a[i]),i++;
		else
			c.push_back(b[j]),j++;
	}
	while(i<lena) c.push_back(a[i]),i++;
	while(j<lenb) c.push_back(b[j]),j++;
}
vcl Merge(vcl a,vcl b)
{
	if(a.size()==0) a.push_back(-inf);
	if(b.size()==0) b.push_back(-inf);
	int lena=a.size(),lenb=b.size();
	for(int i=lena-1;i>=1;i--) a[i]-=a[i-1];
	for(int i=lenb-1;i>=1;i--) b[i]-=b[i-1];
	vcl c;
	merge(c,a,b);
	for(int i=1;i<lena+lenb-1;i++) c[i]+=c[i-1];
	return c; 
}
void print(vcl res)
{
	for(ll v:res) printf("%lld ",v);puts("");
}
void Max(vcl &a,vcl b)
{
//	print(a),print(b);
	int lenb=b.size();
	while(a.size()<lenb) a.push_back(-inf);
	for(int i=0;i<lenb;i++)
		a[i]=max(a[i],b[i]);
}
struct node
{
	vcl f[3][3];
};
int n,a[N+10];
node solve(int l,int r)
{
	if(l==r)
	{
		node res;
		res.f[0][0].push_back(0);
		res.f[0][0].push_back(0);
		res.f[1][0].push_back(a[l]);
		res.f[0][1].push_back(a[l]);
		res.f[2][0].push_back(-a[l]);
		res.f[0][2].push_back(-a[l]);
		return res;
	}
	int mid=l+r>>1;
	node L=solve(l,mid),R=solve(mid+1,r);
	node res;
	for(int i=0;i<=2;i++)
		for(int j=0;j<=2;j++)
		{
			res.f[i][j]=Merge(L.f[i][0],R.f[0][j]);
//			print(res.f[i][j]);
			vcl tmp;
			Max(tmp,Merge(L.f[i][1],R.f[2][j]));
			Max(tmp,Merge(L.f[i][2],R.f[1][j]));
			tmp.insert(tmp.begin(),-inf);
			Max(res.f[i][j],tmp);
			Max(res.f[i][j],L.f[i][j]);
			Max(res.f[i][j],R.f[i][j]);
		}
	return res;
}
int main()
{
//	freopen("a.in","r",stdin);
	read(n);
	for(int i=1;i<=n;i++)
		read(a[i]);
	node ans=solve(1,n);
	for(int i=1;i<=n;i++) printf("%lld\n",ans.f[0][0][i]);
	return 0;
}

Gym 105677A Titanomachy
考虑线段树,对于每个区间,维护每个前缀的凸包和每个后缀的凸包,还有区间内所有子区间的凸包,通过闵可夫斯基和来合并凸包,查询时在凸包上二分,时间复杂度是O(nlog2n)
还有另一种简单些,且复杂度更优秀的做法,对于当前的x,对于线段树上的每个节点求出,前缀最大和的一次函数和后缀最大和的一次函数和最大区间和的一次函数,并且求出改变这个节点一次函数大小关系的最小的x,由于每个数都只会成为一次最大一次函数,所以时间复杂度是O(nlogn)
我实现的是第二种方法

点击查看代码
#include<cstdio>
#include<vector>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e6+100;
const ll inf=1e18;
int n;
ll a[N+10];
ll X;
struct node
{
	ll k,b;
};
node operator +(node a,node b)
{
	return (node){a.k+b.k,a.b+b.b};
}
ll cmpp(node a,node b)//a要大于b的最小的x 
{
	if(a.k>b.k) return (b.b-a.b)/(a.k-b.k)+1;
	else return inf;
}
ll Max(node &a,node b)
{
	if(a.k*X+a.b>b.k*X+b.b)
		return cmpp(b,a);
	else
	{
		ll tmp=cmpp(a,b);
		a=b;
		return tmp;
	}
}
struct SEG
{
	node suf,pre,maxn,sum;
	ll minc;
}t[N<<2|1];
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r>>1)
SEG operator +(SEG a,SEG b)
{
	SEG c;c.minc=inf;
	c.pre=a.pre;
	c.minc=min(c.minc,Max(c.pre,b.pre+a.sum));
	c.suf=a.suf+b.sum;
	c.minc=min(c.minc,Max(c.suf,b.suf));
	c.maxn=a.maxn;
	c.minc=min(c.minc,Max(c.maxn,b.maxn));
	c.minc=min(c.minc,Max(c.maxn,a.suf+b.pre));
	c.sum=a.sum+b.sum;
	c.minc=min(c.minc,a.minc);
	c.minc=min(c.minc,b.minc);
	return c;
}
void build(int p=1,int l=1,int r=n)
{
	if(l==r)
	{
		t[p].maxn=(node){0,0};
		t[p].minc=Max(t[p].maxn,(node){1,a[l]});
		t[p].pre=t[p].suf=t[p].maxn;
		t[p].sum=(node){1,a[l]};
		return;
	}
	build(ls,l,mid),build(rs,mid+1,r);
	t[p]=t[ls]+t[rs];
}
SEG query(int L,int R,int p=1,int l=1,int r=n)
{
	if(L<=l&&r<=R)
		return t[p];
	if(L>mid) return query(L,R,rs,mid+1,r);
	if(R<=mid) return query(L,R,ls,l,mid);
	return query(L,R,ls,l,mid)+query(L,R,rs,mid+1,r);
}
void upd(int p=1,int l=1,int r=n)
{
	if(t[p].minc>X) return;
	if(l==r)
	{
		t[p].maxn=(node){0,0};
		t[p].minc=Max(t[p].maxn,(node){1,a[l]});
		t[p].pre=t[p].suf=t[p].maxn;
		t[p].sum=(node){1,a[l]};
		return;
	}
	upd(ls,l,mid),upd(rs,mid+1,r);
	t[p]=t[ls]+t[rs];
}
#undef ls
#undef rs
#undef mid
int cnt;
struct qst
{
	int l,r,id;ll x;
}p[N+10];
bool cmp(qst a,qst b)
{
	return a.x<b.x; 
}
int q;
char s[10];
ll ans[N+10];
int main()
{
//	freopen("a.in","r",stdin);
	scanf("%d %d",&n,&q);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	ll res=0;
	for(int i=1;i<=q;i++)
	{
		scanf("%s",s);
		if(s[0]=='A')
		{
			int l,r;
			scanf("%d %d",&l,&r);
			cnt++;
			p[cnt]=(qst){l,r,cnt,res};
		}
		else
		{
			ll x;
			scanf("%lld",&x);
			res+=x;
		}
	}
	sort(p+1,p+1+cnt,cmp);
	X=p[1].x;
	build();
//	printf("minc %lld\n",t[1].minc);
	for(int i=1;i<=cnt;i++)
	{
//		printf("uu %lld %d\n",p[i].x,p[i].id);
		X=p[i].x;
		upd();
		SEG res=query(p[i].l,p[i].r);
		ans[p[i].id]=res.maxn.k*X+res.maxn.b;
	}
	for(int i=1;i<=cnt;i++)
		printf("%lld\n",ans[i]);
	return 0;
}
posted @   _doctorZ  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示