COCI 2012 Inspektor

coci 2012 inspektor

街道由左到右分布着\(N\)个办公室,编号为\(1\)\(N\),最开始,每个办公室都是空的,一些公司将入住,并赶走办公室里面现有的公司。一人每天会路过一些连续的办公室。他会查帐,找到最富的公司。

​公司入住的描述如下:
\(1\ T\ K\ Z\ S\)\(T\)表示搬来的时间,\(K\)表示入住的办公室编号,\(Z\)表示公司每天的收益,\(S\)表示原来的余额。如果办公室原来有单位,原来的将离开,公司入住当天没有收益。

​对于查帐的描述:
\(2\ T\ A\ B\) , \(T\)表示查帐的时间。\(A\)\(B\)表示要查的办公室在\(A\)\(B\)之间(包涵端点)。查帐时要算当天的收益。

输入

第一行两个正整数 \(N (1 ≤ N ≤ 100 000) and M (1 ≤ M ≤ 300 000)\),表示办公室的间数,\(M\)表示有\(M\)个事件。

接下来\(M\)行,每行描述一个事件\(1\ T\ K\ Z\ S\)\(2\ T\ A\ B\)所有事件按时间顺序给出。 每天最多发生一个事件,所以\(T\)严格递增 \(T\)小于\(10^6\),\(Z\)\(S\)的绝对值小于\(10^6\).

输出

对于每一次查帐输出一行,表示最富的公司的钱数,如果没有公司输出\(nema\).

input

2 4
1 1 1 2 4
1 2 2 3 2
2 5 1 2
2 7 1 2

output

12
17

input

3 6
1 1 1 4 -2
1 2 2 2 6
2 3 3 1
2 4 3 1
1 5 3 -6 20
2 6 2 3

output

 8
10
14

input

5 9
1 1 5 4 -5
2 2 3 5
1 3 4 6 9
2 4 1 2
1 6 2 2 3
2 8 2 1
1 9 4 0 17
2 10 5 5
2 11 1 4

output

-1
nema
7
31
17

当前是第\(T\)天时,对于每一个公司,盈利是

\[(T-T_0)*z_i+s_i \]

其中,\(T_0,z_i,s_i\)分别表示,搬来的时间,每天盈利,初始盈利;

等价于

\[T*z_i+s_i-T_0*z_i \]

发现这是一个一次函数,意味着每个公司都可以用一条直线来表示;

我们可以维护每个时间点的坐标系,找到当\(x\)\(T\)时的最大值,这就是答案;

我们先不考虑修改操作;看怎样找到\(T\)取值下的最大值;

在一个坐标系里,有可能被取成最大值只可能是最上面的直线;

比如:

geogebra-export(6)

只有红色部分可能是答案直线,我们也只用维护这一部分;

这其实就是一个下凸壳,我们可以用单调栈来构造,用一个指针扫描来寻找答案;


关于下凸壳的构造

我们需要按顺序维护出在下凸壳上的直线的编号,

可以发现下凸壳上的直线的斜率是单调递增的,两两直线的交点与直线对应,横坐标也是单调递增的;

我们先把直线按斜率大小排序(小的在前

我们可以按横坐标维护一个存交点的单调栈;

因为交点也是两条直线得来的,我们可以直接把直线放到栈内,相邻两条直线的交点的横坐标保证单调递增;

当我们新加入一条直线时,如果这条直线与栈顶直线的交点,在栈顶交点(就是栈顶直线与栈内的第二条直线的交点,它的横坐标是当前最大的) 右边,那么这条直线可以直接加入栈中;

如果在栈顶交点的左边,应当把栈顶交点弹出(其实就是把栈顶直线弹出,这个交点就没有了),直到当前直线与栈顶直线的交点成为最大值,再把当前直线加入栈中。(单调栈的基本操作)

最后栈内的直线,就是下凸壳中的直线;

举个例子

我先加入第一条直线,这是斜率最小的一条直线

geogebra-export(1)

再加入第二条直线,因为当前栈内直线只有一条,还没有交点,就直接加入

geogebra-export(3)

红色部分就是当前栈维护出的下凸壳;

\(A\)就是现在横坐标最大的交点

再加入第三条直线,它与第二条直线的交点在\(A\)右边,我们可以直接加入;

geogebra-export(2)

\(B\)就是现在横坐标最大的交点

geogebra-export(8)

第三条直线是栈顶直线,红色部分是下凸壳

再加入第四条直线,它与第三条直线的交点在\(B\)左边,我们发现第三条直线已不能在下凸壳里了,应先把第三条直线弹出,

再发现第四条直线与第二条直线的交点在\(A\)右边,可以加入第四条直线;

geogebra-export(7)

geogebra-export(6)

现在的\(B\)是横坐标的最大的交点

栈内元素应该是\(1,2,4\),也就是下凸壳中的直线(红色部分);

构造的时间复杂度是\(O(nlogn)\)


关于在已有下凸壳上求解

我们想知道横坐标是\(T\)时,下凸壳上的纵坐标;

主要目的就是找\(x=T\)这条直线与哪条直线相交

根据之前的操作,我们知道下凸壳中直线的分界是依据交点的;

我们先要保证交点与直线对应,

假设这个交点所对应的直线是相交的两条直线的前一条;

我们找到第一个横坐标大于等于\(T\)的交点,那么它对应的直线应该与\(x=T\)相交,也就是答案直线;

geogebra-export(6) - 副本

当前第一个大于等于\(T\)的交点是\(B\),可知\(x=T\)与直线\(2\)相交

又由于我们询问的\(T\)是严格单增的,那答案交点也是单增的,我们只需要记录这个凸壳已经被扫到哪里了,下次从这里开始就可以了;

查询的总时间复杂度是\(O(T)\)


考虑有修改操作的情况

修改具有不可继承性,修改后的凸壳和之前的凸壳是可能没有相同点的;

修改一条直线可能改变凸壳的全貌;

意味着每次修改一条直线,都需要重构凸壳,这样成本太高了;

我们可以考虑分块;

\(n\)分块,假设每个块大小是\(B\),我们在每个块中维护一个这个区间的凸壳;

对于查询,不是整块的暴力扫描,是整块的用指针在对应块找答案,前者复杂度是\(O(B)\),后者总时间复杂度是\(O(\frac{n}{B}*T)\),\(T\)是最大时间;

对于修改,把待修改的块打上标记,当调用它是进行一次重构,对于一次重构时间复杂度是\(O(BlogB)\)

\(Code\)

#include<bits/stdc++.h> 
#define ll long long 
#define re register
using namespace std;
const int N=100005;

int n,m,len,opt;
int pos[N],L[N],R[N];
struct line
{
	ll k,b;//斜率和截距 
	bool operator<(line l)const
	{
		return k==l.k?b>l.b:k<l.k; //按斜率排序,斜率相同按截距 
	}
}a[N],b[N];
bool vis[N],use[N],need[N];
int head[N],tail[N]; //第i块在栈中的首尾 
int sta[N]; //栈,存直线的下标 
double point[N]; //记录交点 
inline ll max(ll x,ll y){return x>y?x:y;}

inline int read()
{
	int x=0,f=1;char st=getchar();
	while(st<'0'||st>'9'){if(st=='-') f=-1;st=getchar();}
	while(st>='0'&&st<='9') x=x*10+st-'0',st=getchar();
	return x*f;
}

inline void write(ll x)
{
	if(x<0) x=~x+1,putchar('-');
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

inline double getx(line x,line y)
{
	return (double)(x.b-y.b)/(y.k-x.k);  //算两条直线交点的横坐标  
}

inline void rebuild(int x)
{
	int st=L[x]-1,top=L[x];
	for(re int i=L[x];i<=R[x];++i)
		if(vis[i]) b[++st]=a[i]; //把需要重构的直线加入 
	sort(b+L[x],b+st+1);
	sta[top]=L[x];
	for(re int i=L[x]+1;i<=st;++i)
	if(b[i].k!=b[i-1].k)  //如果两条直线斜率相同,只可能取到截距大的一条  
	{
		while(top>L[x]&&getx(b[i],b[sta[top]])<getx(b[sta[top]],b[sta[top-1]])) top--;
		point[top]=getx(b[i],b[sta[top]]); //算交点 
		sta[++top]=i;
	}
	head[x]=L[x];
	tail[x]=top;
}


inline void doit(int t,int l,int r)
{
	if(l>r) swap(l,r);
	ll tmp=-1e18;
	int p=pos[l],q=pos[r];
	if(p==q)
	{
		for(re int i=l;i<=r;++i)
			if(vis[i]) tmp=max(tmp,t*a[i].k+a[i].b); //暴力 
	}
	else 
	{
		for(re int i=l;i<=R[p];++i)
			if(vis[i]) tmp=max(tmp,t*a[i].k+a[i].b); 
		for(re int i=L[q];i<=r;++i)
			if(vis[i]) tmp=max(tmp,t*a[i].k+a[i].b);	
		for(re int i=p+1;i<q;++i)
		{
			if(need[i])  //如果需要重构 
			{
				rebuild(i);
				need[i]=0;
			}
			if(use[i])
			{
				while(head[i]<tail[i]&&point[head[i]]<t) head[i]++;  //找第一个大于等于t的交点 
				tmp=max(tmp,t*b[sta[head[i]]].k+b[sta[head[i]]].b);
			}
		}
	}
	if(tmp==-1e18) puts("nema");
	else write(tmp),puts("");
}

int main()
{
	freopen("inspektor.in","r",stdin);
	freopen("inspektor.out","w",stdout);
	n=read();m=read();
	len=sqrt(n/log2(n))+1;
//	len=144;
	for(re int i=1;i<=n;++i)
		pos[i]=(i-1)/len+1;
	for(re int i=1;i<=pos[n];++i)
		L[i]=R[i-1]+1,R[i]=R[i-1]+len;
	R[pos[n]]=n;
	while(m--)
	{
		opt=read();
		if(opt==1)
		{
			int T=read(),x=read(),z=read(),s=read();
			vis[x]=1;use[pos[x]]=1;//这个点有,这个块有 
			need[pos[x]]=1;//需要重构 
			a[x].k=z;a[x].b=s-(ll)T*z;
		}
		else 
		{
			int T=read(),l=read(),r=read();
			doit(T,l,r);
		}
	}
	fclose(stdin);
	fclose(stdout);
	return 0;	
}
posted @ 2019-12-08 21:32  YudeS楪  阅读(253)  评论(1编辑  收藏  举报