前言
这是一套甚至可能比上次还更有水平的题目。感谢教练能找到这套题。
题面
T1
一个 n ∗ 2 n*2 n∗2 的矩阵,每个位置有一个权值。你可以将它分成 m m m 个小矩阵,问这些矩阵的权值的最大值最小为多少?
n ≤ 1 0 5 n \leq 10^5 n≤105, m ≤ 100 m \leq 100 m≤100,权值 ( 0 , 2 31 ) (0,2^{31}) (0,231)。
T2
n n n 个点的点集,你可以从中选出任意的子集,问子集的凸包上顶点的数量最多为多少?
n ≤ 250 n \leq 250 n≤250,坐标 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] n≤105,max≤5000,ti∈[0,231−1],保证 T T T 有解。
题解
T1
我唯一可做的一道题。。。结果考场写挂,改题被卡。
首先容易想到二分答案然后 check。
用 dp 可以 check。考虑原矩阵必然被数个宽为 2 的矩形分割,中间的部分用宽为 1 的矩形填充。我们可以令 d p i dp_i dpi 为前 i i i 列被完全分割成的最小矩形数。
两种情况:
- 当前位置被宽为 1 的矩形填满;
- 当前位置被宽为 2 的矩形填满。
容易发现,如果是第二种情况,可以直接往前找到最远的覆盖位置转移即可。
如果是第一种情况就不太好办。不过考虑到 m ≤ 100 m \leq 100 m≤100,也就是再大的情况会不合法,我们可以直接暴力跳。每次选择上下两端点中较靠右的一个,向左跳到最远的能覆盖的位置。对于中途经历的每个断点,都做一次转移。
单次 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;
}