2020-3-8模拟测验记录与题解

前言

这是一套甚至可能比上次还更有水平的题目。感谢教练能找到这套题。

题面

T1

一个 n ∗ 2 n*2 n2 的矩阵,每个位置有一个权值。你可以将它分成 m m m 个小矩阵,问这些矩阵的权值的最大值最小为多少?

n ≤ 1 0 5 n \leq 10^5 n105 m ≤ 100 m \leq 100 m100,权值 ( 0 , 2 31 ) (0,2^{31}) (0,231)

T2

n n n 个点的点集,你可以从中选出任意的子集,问子集的凸包上顶点的数量最多为多少?

n ≤ 250 n \leq 250 n250,坐标 int 范围。

T3

一个值,初值为 x x x,对它进行 n n n 次操作,每个操作有一个时间点 t i t_i ti,最终该值变为 v v v

特性:将操作按时间排序,如果相邻两个操作时间间隔 > T > T >T,则后一个操作不起作用。

操作类型有两种:

+ + +:如果该值达到上限 m a x max max,则不变;否则 + 1 +1 +1

− - :如果该值为 0 0 0,则不变;否则 − 1 -1 1

补充:如果第一个操作时间点 > T > T >T,则第一个操作会无效。

已知 n , m a x , v n,max,v n,max,v 以及每个操作的类型和 t i t_i ti,问 T T T 的最大值,以及在 T T T 取最大的前提下 x x x 的最大值。如果 T T T 可以取到正无穷,则只输出 infinity

n ≤ 1 0 5 , m a x ≤ 5000 , t i ∈ [ 0 , 2 31 − 1 ] n \leq 10^5,max \leq 5000,t_i \in [0,2^{31}-1] n105max5000ti[0,2311],保证 T T T 有解。

题解

T1

我唯一可做的一道题。。。结果考场写挂,改题被卡。

首先容易想到二分答案然后 check。

用 dp 可以 check。考虑原矩阵必然被数个宽为 2 的矩形分割,中间的部分用宽为 1 的矩形填充。我们可以令 d p i dp_i dpi 为前 i i i 列被完全分割成的最小矩形数。

两种情况:

  1. 当前位置被宽为 1 的矩形填满;
  2. 当前位置被宽为 2 的矩形填满。

容易发现,如果是第二种情况,可以直接往前找到最远的覆盖位置转移即可。

如果是第一种情况就不太好办。不过考虑到 m ≤ 100 m \leq 100 m100,也就是再大的情况会不合法,我们可以直接暴力跳。每次选择上下两端点中较靠右的一个,向左跳到最远的能覆盖的位置。对于中途经历的每个断点,都做一次转移。

单次 check 时间复杂度 O ( n m ) O(nm) O(nm),总复杂度 O ( n m log ⁡ V ) O(nm \log V) O(nmlogV)

T2(咕咕咕)

凸包都不会做马?咕咕咕。。。

T3(by lzy)

lzy 学长看题后胡出来的一种做法。

考虑用线段树维护所有操作,按 t t t 间隔从大到小的顺序添加操作。

对于每段区间,我们需要知道每个值进来以后出去会变成什么。

然后,我们可以发现如果以 in 为横轴,out 为纵轴,会是这样的:

在这里插入图片描述
上下两条横平的线,中间的斜率为 1。

所以我们只需要维护一下两个拐点的坐标就好了。

合并很 trivial,就是分类讨论有点多。

每次插入以后看一下可达范围包不包含 v v v 就好了。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

T3(std)

出题人给出的标算。

还是按 t t t 间隔从大到小添加操作。

我们从 v v v 开始倒着考虑,遇到一个 + + + − 1 -1 1,否则 + 1 +1 +1

发现:如果在到达 − 1 -1 1 前没有到达过 m a x max max,则不合法。

同样的,如果在到达 m a x + 1 max+1 max+1 前没有到达过 0 0 0,也不合法。

那么为什么其他情况都合法呢?

考虑如下情况:

在这里插入图片描述
在到达 m a x max max 后,为什么可以允许接下来的一个拐点 < 0 <0 <0

因为从正着的角度看,这样你就可以改变初始值,比如让 m a x max max 左侧那条线提前顶到 m a x max max,令最后一个 + + + 操作无效。得到如下情况:

在这里插入图片描述

不断平移直到左侧的拐点卡至 0 0 0 处,这样就避免了不合法。

用类似的方法可以证明到达 m a x + 1 max+1 max+1 前到达 0 0 0 可以避免不合法。

所以只需要使用线段树维护后缀和,查询时线段树上分治找到右至左 − 1 , 0 , m a x , m a x + 1 -1,0,max,max+1 1,0,max,max+1 第一次出现的位置即可判断是否合法。

容易证明随着起始音量增加,最终音量也是不减的。所以一旦合法,你就可以二分判断最大的起始音量了。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),正常实现常数比上一种做法要小。

代码

T1
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N=1e5+1;
int n,m,val[N][2],dp[N],bk[N][3];
ll sum[N][2];
inline bool check(const ll &v)
{
	int i,tp0=0,tp1=0,tp2=0;
	for(i=1;i<=n;i++)
	{
		while(sum[i][0]-sum[tp0][0]>v) tp0++;
		while(sum[i][1]-sum[tp1][1]>v) tp1++;
		while(sum[i][0]+sum[i][1]-sum[tp2][0]-sum[tp2][1]>v) tp2++;
		bk[i][0]=tp0,bk[i][1]=tp1,bk[i][2]=tp2;
	}
	for(i=1;i<=n;i++)
	{
		if(i==bk[i][2]) dp[i]=dp[i-1]+2;
		else dp[i]=dp[bk[i][2]]+1;
		int t1=i,t2=i,stp=0;
		while((t1!=0||t2!=0)&&stp<=m)
		{
			stp++;
			if(t1==t2) stp++;
			int x=bk[t1][0],y=bk[t2][1];
			if(x>y) t1=x,dp[i]=min(dp[i],dp[t1]+stp);
			else if(x<y) t2=y,dp[i]=min(dp[i],dp[t2]+stp);
			else t1=x,t2=y,dp[i]=min(dp[i],dp[t1]+stp);
		}
		if(dp[i]>m) return false;
	}
	return true;
}
int main()
{
	int i,lst=0;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++) scanf("%d",&val[i][0]),sum[i][0]=sum[i-1][0]+val[i][0],lst=max(lst,val[i][0]);
	for(i=1;i<=n;i++) scanf("%d",&val[i][1]),sum[i][1]=sum[i-1][1]+val[i][1],lst=max(lst,val[i][1]);
	ll l=lst,r=sum[n][0]+sum[n][1];
	while(l<r)
	{
		ll mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%lld",l);
	return 0;
}
T2(咕咕咕)
T3(std解法)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=1e5+1,INF=0x7fffffff;
struct zt
{
	int pos,tim;
} z[N];
int cmp(zt a,zt b){return a.tim>b.tim;}
char typ[N][5];
int tm[N];
int n,Max,v2,tag[N<<2],maxx[N<<2],minn[N<<2];
inline void up(int now)
{
	int ls=now<<1,rs=now<<1|1;
	maxx[now]=max(maxx[ls],maxx[rs]);
	minn[now]=min(minn[ls],minn[rs]);
}
inline void down(int now)
{
	if(!tag[now]) return;
	int ls=now<<1,rs=now<<1|1;
	tag[ls]+=tag[now],tag[rs]+=tag[now];
	maxx[ls]+=tag[now],minn[ls]+=tag[now];
	maxx[rs]+=tag[now],minn[rs]+=tag[now];
	tag[now]=0;
}
void modify(int now,int l,int r,int ql,int qr,int v)
{
	if(ql<=l&&r<=qr)
	{
		tag[now]+=v;
		maxx[now]+=v,minn[now]+=v;
		return;
	}
	int mid=(l+r)>>1;
	down(now);
	if(ql<=mid) modify(now<<1,l,mid,ql,qr,v);
	if(qr>mid) modify(now<<1|1,mid+1,r,ql,qr,v);
	up(now);
}
int query(int now,int l,int r,int qv)
{
	if(qv<minn[now]||qv>maxx[now]) return 0;
	if(l==r) return l;
	int mid=(l+r)>>1,ls=now<<1,rs=now<<1|1;
	down(now);
	if(minn[rs]<=qv&&maxx[rs]>=qv) return query(rs,mid+1,r,qv);
	else return query(ls,l,mid,qv);
}
void build(int pos,int l,int r)
{
	if(l==r)
	{
		minn[pos]=maxx[pos]=v2;
		return;
	}
	int mid=(l+r)>>1;
	build(pos<<1,l,mid);
	build(pos<<1|1,mid+1,r);
	up(pos);
}
int check(int now,int lim)
{
	int i,ret=now;
	for(i=1;i<=n;i++)
	{
		if(tm[i]-tm[i-1]>lim) continue;
		if(typ[i][1]=='+') ret=min(Max,ret+1);
		else ret=max(0,ret-1);
	}
	return ret;
}
int main()
{
	int i,lst=0;
	scanf("%d%d%d",&n,&Max,&v2);
	build(1,1,n+1);
	for(i=1;i<=n;i++)
	{
		scanf("%s%d",typ[i]+1,&tm[i]);
		z[i].pos=i,z[i].tim=tm[i]-lst;
		if(typ[i][1]=='-') modify(1,1,n+1,1,i,1);
		else modify(1,1,n+1,1,i,-1);
		lst=tm[i];
	}
	sort(z+1,z+n+1,cmp);
	int T=INF;
	for(i=1;i<=n;)
	{
		int p0=query(1,1,n+1,0),pu0=query(1,1,n+1,-1);
		int pmx=query(1,1,n+1,Max),pumx=query(1,1,n+1,Max+1);
		if((pu0&&pmx<pu0)||(pumx&&p0<pumx))
		{
			int tmp=i;
			while(i<=n&&z[i].tim==z[tmp].tim)
			{
				int tp=z[i++].pos;
				if(typ[tp][1]=='-') modify(1,1,n+1,1,tp,-1);
				else modify(1,1,n+1,1,tp,1);
			}
			T=z[tmp].tim-1;
		}
		else break;
	}
	if(T==INF) printf("infinity");
	else
	{
		printf("%d ",T);
		int l=0,r=Max,ans=0;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(check(mid,T)<=v2) ans=mid,l=mid+1;
			else r=mid-1;
		}
		printf("%d",ans);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值