这场比赛真的是...打的好颓废啊...

所有题面的传送门

T1

分析:

我们发现 二分答案 + \(n^3\) \(dp\) 判断可行性 可以拿 60 分(于是就写好了啊!)

然后我们发现上面的 \(dp\) 可以优化成 \(n^2\) 于是我们就可以拿到 80 分了(够了吧?)

就是我们设计 \(f[i][j]\) 表示第一行取 i 个,第二行取 j 个的最小步数

然后转移就不说了(我只会正解,真的)

但是我们发现 100 分也不是那么难拿(然鹅想出了正解复杂度也没有写退化却被卡常,还是 80 滚粗)

我们考虑放置图画的性质:

我们可以看到,放置无非就是两行都放,放上面,放下面三种,而放上面 + 放下面组合成的还是一个矩形那么我们可不可以考虑 \(dp\) 状态只有一维然后维护两个指针每次往前面跳,每跳一次加一贡献,然后更新 f 数组呢?

当然可以啦,我们每次取靠右的指针指向的 f 值来更新当前的 f 就好了

但是复杂度呢?我们考虑每次往前面跳是要二分位置的,加个 \(log ~n\) ,原本的二分答案要加个 \(log~ sum\) ,\(dp\) 一维要 \(O(n)\) ,然后跳的次数最坏的情况下是 \(O(n)\) 的...(好像比前面更慢了啊)

但是注意,这里说跳的次数最坏是 \(O(n)\) 的,但是事实上只要跳的次数大于 m 了就肯定不合法了,那么这个时候如果 f 还没有达到小于 m 的值我们直接返回 \(false\) 就好了, \(O(n)\) 变成 \(O(m)\)

但是这里还有一个二分位置 \(log ~n\) 啊,这样是卡不过去的哈!

我们考虑每次判断可行性的时候用到的跳的路径其实很大一部分是相同的,所以我们每次二分了一个答案之后可以先 \(n~log~ n\) 把指针跳跃的 \(nxt\) 数组处理出来,那么这样的复杂度就可以接受了

(然后我的代码成功的因为一些无脑常数 T 上了天,要跑 1 s 多,将近 2 s 吧)

然后我把原来的代码改了两个地方就勉强卡过去了...

//by Judge
#pragma GCC optimize("Ofast") //这玩意儿在这里好像没什么用?
#include<cstdio>
#include<cstring>
#include<iostream>
#define fp(i,a,b) for(int i=(a),I=(b)+1;i<I;++i)
#define ll unsigned int //这里是第一个修改, ll 改成 uint 就会快一些了,数据超不了 uint(我好想也是后来才知道,题面里面没写...大坑,而且我也不知道要卡常我觉得自己常数差不多了啊)
using namespace std;
const int M=1e5+3; //第二个修改,原本这里是 100e5 (佩服自己,好像是因为一些小问题一怒之下改成 100e5 的QWQ)
#ifndef Judge
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
char buf[1<<21],*p1=buf,*p2=buf;
inline int Max(int a,int b){return a>b?a:b;}
inline bool cmax(ll& a,ll b){return a<b?a=b,1:0;}
inline bool cmin(ll& a,ll b){return a>b?a=b,1:0;}
inline ll read(){ ll x=0,f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f;
} ll n,m,tim,pre[3][M],f[M],a[3][M],sum[3][M];
inline int find(int x,int tp){ int l=0,r=x,mid; //二分位置
for(;l<=r;){ mid=l+r>>1;
if(tp==1) (sum[2][x]-sum[2][mid])-sum[1][x]+sum[1][mid]<=tim?r=mid-1:l=mid+1;
else if(tp==2) sum[2][x]-sum[2][mid]<=tim?r=mid-1:l=mid+1;
else if(tp==0)sum[1][x]-sum[1][mid]<=tim?r=mid-1:l=mid+1;
} return l;
}
inline bool check(){ int j,k,num;
memset(f,0x3f,sizeof f),f[0]=0;
fp(i,1,n) pre[0][i]=find(i,0),pre[1][i]=find(i,1); //预处理 nxt 数组(pre也差不多的意思啦)
fp(i,1,n){ j=k=i,num=0;
for(;true;j<k?k=pre[1][k]:j=pre[0][j],++num){ //这里可以判一下 num 然后加速?
cmin(f[i],f[Max(j,k)]+num);
if(!j&&!k) break;
} j=find(i,2),cmin(f[i],f[j]+1);
if(f[i]>m) return 0; //可以直接返回,节省时间
} return 1;
} ll l=0,r=0,mid;
int main(){ n=read(),m=read();
fp(j,1,2) fp(i,1,n) sum[j][i]=a[j][i]=read(),r+=a[j][i];
fp(j,1,2) fp(i,1,n) sum[j][i]+=sum[j][i-1]+sum[j-1][i]-sum[j-1][i-1];
// l=r/m 可以省去一些判断
for(l=r/m;l<=r;) tim=mid=l+r>>1,check()?r=mid-1:l=mid+1;
return !printf("%lld\n",l);
}

T2

我们看到凸包就不想做了...

其实这道题没那么复杂,我们要做的就是 \(dp\) (又是\(dp\)!这套题专考 \(dp\) ,好吧下一题不是)

\(dp\) 个啥子呢?我们考虑把问题分成两份处理,我们要找到一个点数最多的左凸壳和一个点数最多的右凸壳,然后看看是否能够合并起来更新答案

//by Judge
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define fp(i,a,b) for(int i=(a),I=(b)+1;i<I;++i)
#define ll long long
#define db double
using namespace std;
const int M=253;
#ifndef Judge
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
char buf[1<<21],*p1=buf,*p2=buf;
inline void cmax(int& a,int b){if(a<b)a=b;}
inline int read(){ int x=0,f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f;
} int n,cnt,ans=1,f1[M],f2[M];
struct node{int x,y; //向量结构体
bool operator <(node b)const{return x^b.x?x<b.x:y<b.y;}
inline node operator -(node b){return (node){x-b.x,y-b.y};}
inline ll operator *(node b){return x*b.y-b.x*y;}
}a[M];
struct edge{int a,b;}s1[M*M],s2[M*M];
inline bool cmp1(edge x,edge y){ //极角坐标顺时针排序
ll tp=(a[x.b]-a[x.a])*(a[y.b]-a[y.a]);
return tp!=0?tp<0:x.a<y.a;
}
inline bool cmp2(edge x,edge y){ //极角坐标逆时针排序
ll tp=(a[x.b]-a[x.a])*(a[y.b]-a[y.a]);
return tp!=0?tp>0:x.a<y.a;
}
struct Edgex{ int pre[M*M],nxt[M*M]; //一个链表,可以加点速(虽说好像可以不用、、、额有了 t1 的经验我们能卡的常还是要卡一下)
inline void init(int cnt){
pre[0]=0,pre[cnt+1]=cnt;
nxt[0]=1,nxt[cnt+1]=cnt+1;
fp(i,1,cnt) pre[i]=i-1,nxt[i]=i+1;
}
inline void del(int x){
pre[nxt[x]]=pre[x];
nxt[pre[x]]=nxt[x];
}
}e1,e2;
int main(){ n=read();
fp(i,1,n) a[i].x=read(),a[i].y=read(); sort(a+1,a+1+n); //所有的点按坐标排序
fp(i,1,n) fp(j,i+1,n) ++cnt,s1[cnt]=s2[cnt]=(edge){i,j}; //构造出所有不朝下的边(向量)
sort(s1+1,s1+1+cnt,cmp1),sort(s2+1,s2+1+cnt,cmp2); //所有的边极角排序(顺时针和逆时针)
//以便下面维护两个凸壳
e1.init(cnt),e2.init(cnt); //链表初始化
fp(i,1,n){ //注意每次清空答案
memset(f1,0,sizeof f1);
memset(f2,0,sizeof f2);
f1[i]=f2[i]=1; //上面的 ans 初始为 0 是考虑了如果是只有一个点的凸壳(虽说不可能)
for(int j=e1.nxt[0];j<=cnt;j=e1.nxt[j])
if(s1[j].a<i) e1.del(j); //删除以 i 之前的点为出发点的边(向量)
else if(f1[s1[j].a]) cmax(f1[s1[j].b],f1[s1[j].a]+1);
for(int j=e2.nxt[0];j<=cnt;j=e2.nxt[j])
if(s2[j].a<i) e2.del(j);
else if(f2[s2[j].a]) cmax(f2[s2[j].b],f2[s2[j].a]+1);
fp(j,1,n) cmax(ans,f1[j]+f2[j]-2); //合并两个凸壳,要减去上下两个合并点
} return !printf("%d\n",ans);
}

T3

首先题目的表述不大清楚,我们应该将三种操作(解锁、+、- ) 视为有效操作,也就是说题目中输入的 n 次操作均有效,不存在音量为 max 的时候按下 + 号,或者音量为 0 的时候按下 - 号,除非此时按下是为了解锁

这道题没什么好讲的?就是线段树维护的 最 大/小 左子段和

怎么说呢?维护这玩意儿干啥?

首先我们考虑把操作倒过来,也就是我们要用最终音量 V2 变到初始音量 V1

然后我们考虑 \(O(n)\) 从大到小枚举时间 T (就是说我们按照每两次操作间隔的时间差当 T )

那么我们考虑 V2 的变换是否合法:

当我们打完暴力之后就会发现,我们在暴力算法中可以维护两个指针(表示某一操作前的音量最小值与最大值),然后向前推,最后返回最大值,那么在这种情况下我们发现在 r 回升的时候 l 必须回升(除非 l 为 0),注意 r 为 vmax 时保持不变,而 l 达到了vmax 后如果还要回升那么就出现问题了:l > r ,无解,于是我们返回 -1 表示无解,回降同理

(上面的解释好玄学我看不懂...)

简而言之,现在是倒过来看的话,那么 r 回升就代表 r 可以从回升到的那个点转移过来,此时如果一直回升上去,那么当我们正过来看的话就会发现无论音量初始值多大都无法转移到 V2 ,因为中间过程曾到达过峰顶,然后降到了 V2 的下方

至于 l 也是同理

(上面的解释还是好雾啊...)

那么我们再举个例子:

如果当前音量最大值为 7 ,最后音量为 5 ,然后我们倒过来操作的时候发现 5 连续回升了 3 格,那么我们正回来看就是在到达 5 之前音量连降了 3 格,这样无论初始值是多少我们都无法到达最终的 5

容易发现,V2 如果在翻转后的操作过程中没有变为负数且没有超过 Max,那么这个变换方案就是合法的,于是我们看看逆操作的前缀和加上 V2 有没有超过 max 或者小于 0 就好啦

(代码有点玄学【雾,还是打份暴力理解理解好)

//by Judge
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define fp(i,a,b) for(int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(int i=(a),I=(b)-1;i>I;--i)
#define ll long long
using namespace std;
const int inf=2e9+7;
const int M=1e5+3;
#ifndef Judge
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
char buf[1<<21],*p1=buf,*p2=buf;
inline int MAX(int a,int b){return a>b?a:b;}
inline int MIN(int a,int b){return a<b?a:b;}
inline int read(){ int x=0,f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f;
} inline int cread(){ char c=getchar();
while(c!='+'&&c!='-') c=getchar(); return c=='+';
} int n,vmax;
struct T{ int sum,Max,Min; }t[M<<2],A;
struct rec{ int a,t,id; }a[M<<2];
inline bool cmp(rec a,rec b){return a.t>b.t;}
inline T merge(T a,T b){ T c;
c.sum=a.sum+b.sum;
c.Max=MAX(a.Max,a.sum+b.Max);
c.Min=MIN(a.Min,a.sum+b.Min); return c;
}
#define ls k<<1
#define rs k<<1|1
#define mid (l+r>>1)
#define lson ls,l,mid
#define rson rs,mid+1,r
inline void pushup(int k){t[k]=merge(t[ls],t[rs]);}
void build(int k,int l,int r){
if(l==r) return t[k].sum=t[k].Max=t[k].Min=a[l].a,void();
build(lson),build(rson),pushup(k);
}
void update(int k,int l,int r,int x){
if(l==r) return t[k].sum=t[k].Max=t[k].Min=0,void();
if(x<=mid) update(lson,x); else update(rson,x); pushup(k);
}
T query(int k,int l,int r,int x){
if(l==r) return t[k];
if(x<=mid) return query(lson,x);
return merge(t[ls],query(rson,x));
}
int query1(int k,int l,int r){ //前缀和大于 vmax 的位置
if(l==r) return merge(A,t[k]).Max<vmax;
T B=merge(A,t[ls]);
if(B.Max>=vmax) return query1(lson);
return A=B,query1(rson)+mid-l+1;
}
int query2(int k,int l,int r){ //前缀和小于 0 的位置
if(l==r) return merge(A,t[k]).Min>0;
T B=merge(A,t[ls]);
if(B.Min<=0) return query2(lson);
return A=B,query2(rson)+mid-l+1;
}
inline int check(){
int t1,t2;
A.sum=A.Max=A.Min=0;
t1=query1(1,0,n);
A.sum=A.Max=0;
A.Min=vmax+1;
t2=query2(1,0,n);
if(t1==n+1&&t2==n+1)
return t[1].sum;
if(t1==t2) return vmax;
if(t1>t2){
A=query(1,0,n,t1);
if(A.Min<0) return -1; //相当于达到峰顶前掉到过谷底之下
if(t1==n+1) return t[1].sum; //
return vmax;
} else{
A=query(1,0,n,t2);
if(A.Max>vmax) return -1;
return vmax;
}
}
int main(){ int t;
n=read(),vmax=read(),a[0].a=read();
fd(i,n,1) t=cread(),a[i].a=t?-1:1,
a[i].id=i,a[i].t=read();
fp(i,1,n-1) a[i].t-=a[i+1].t; //这里默认所有操作时间递增?题目里好像并没有说吧
build(1,0,n); int tt=check();
if(tt>=0) return !puts("infinity");//先判断是否 T 可以为 inf
sort(a+1,a+1+n,cmp); //操作倒着排序
fp(i,1,n){
update(1,0,n,a[i].id);
while(i<n&&a[i+1].t==a[i].t)
update(1,0,n,a[++i].id);
if((tt=check())!=-1)
return !printf("%d %d\n",a[i].t-1,tt); //注意 -1 输出
} return 0;
}

总结

这套题并没有看上去那么难?假的,考场上想不出来的,我太菜了! 至少算法大家都会...

总之太懒了,除了 T1 好好想过写其他题的时候好像都在睡觉...(不过 T1 打击好大明明想出了标算然后被卡成暴力分嘤嘤嘤)

Lyk Love painting/convex hull/mumsic的更多相关文章

  1. 凸包(Convex Hull)构造算法——Graham扫描法

    凸包(Convex Hull) 在图形学中,凸包是一个非常重要的概念.简明的说,在平面中给出N个点,找出一个由其中某些点作为顶点组成的凸多边形,恰好能围住所有的N个点. 这十分像是在一块木板上钉了N个 ...

  2. Convex Hull 实现理论+自制Python代码

    Convex Hull 概述 计算n维欧式空间散点集的凸包,有很多的方法.但是如果要实现快速运算则其难点在于:如何快速判断散点集的成员是否是在凸集的内部.如果可以简化判断的运算过程,则可以极大简化迭代 ...

  3. OpenCV入门之寻找图像的凸包(convex hull)

    介绍   凸包(Convex Hull)是一个计算几何(图形学)中的概念,它的严格的数学定义为:在一个向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包.   在图像处理过程中,我们 ...

  4. 2D Convex Hulls and Extreme Points( Convex Hull Algorithms) CGAL 4.13 -User Manual

    1 Introduction A subset S⊆R2 is convex if for any two points p and q in the set the line segment wit ...

  5. Monotone Chain Convex Hull(单调链凸包)

    Monotone Chain Convex Hull(单调链凸包)算法伪代码: //输入:一个在平面上的点集P //点集 P 按 先x后y 的递增排序 //m 表示共a[i=0...m]个点,ans为 ...

  6. convex hull

    1 什么是convex hull 就是凸包,是计算几何中的一个概念,计算几何是计算机图形学的基础之一. 对于二维平面来说是这样的:对于二维平面上的点集,凸包是位于最外层的点构成的包围其它所有的点的凸多 ...

  7. opencv::凸包-Convex Hull

    概念介绍 什么是凸包(Convex Hull),在一个多变形边缘或者内部任意两个点的连线都包含在多边形边界或者内部. 正式定义:包含点集合S中所有点的最小凸多边形称为凸包 Graham扫描算法 首先选 ...

  8. Convex Hull | Set 1

    Given a set of points in the plane. the convex hull of the set is the smallest convex polygon that c ...

  9. 20210826 Lighthouse,Miner,Lyk Love painting,Revive

    考场 T1 这不裸的容斥 T2 这不裸的欧拉路,先从奇数度点开始走,走不了就传送 T3 什么玩意,暴力都不会 T4 点分树??? 仔细想了一波,发现 T1 T2 都好做,T3 二分答案后可以暴力贪心, ...

随机推荐

  1. springboot下整合各种配置文件

    本博是在springboot下整合其他中间件,比如,mq,redis,durid,日志...等等  以后遇到再更.springboot真是太便捷了,让我们赶紧涌入到springboot的怀抱吧. ap ...

  2. DirectX11 With Windows SDK--02 顶点/像素着色器的创建、顶点缓冲区

    前言 由于在Direct3D 11中取消了固定管线,要想绘制图形必须要了解可编程渲染管线的流程,一个能绘制出图形的渲染管线最少需要有这两个可编程着色器:顶点着色器和像素着色器. 本章会直接跳过渲染管线 ...

  3. tmux用法【常用】

    类似各种平铺式窗口管理器,tmux使用键盘操作,常用快捷键包括: Ctrl+b 激活控制台:此时以下按键生效 系统操作 ? 列出所有快捷键:按q返回 d 脱离当前会话:这样可以暂时返回Shell界面, ...

  4. webpack3.x版本实战案例【基础配置篇】(一)

    本文旨在通过一个一个实战例子来学习webpack如何配置,更加深入的学习webpack在实战项目中如何配置. 我们学习哪些配置呢? [基础配置] 打包JS 编译ES6 编译typeScript 打包公 ...

  5. Tornado基本应用

    Tornado简介 Tornado有自己的socket(异步非阻塞,原生支持WebSocket),Django没有. Tornado的模板语言更接近Python风格,比Django要好理解. Demo ...

  6. Vue.js入门系列教程(三)

    序言

  7. js强制将页面放到最大

    <!DOCTYPE html> <html> <head> <title></title> <script language=&quo ...

  8. 使用js请求Servlet时的路径

    项目结构如下: 全是web的html页面 js部分重要代码: function ajaxValidate() { var flag=false; $.ajax({ "url":&q ...

  9. 【python小练】0010

    第 0010 题:使用 Python 生成类似于下图中的字母验证码图片 思路: 1. 随机生成字符串 2. 创建画布往上头写字符串 3. 干扰画面 code: # codeing: utf-8 fro ...

  10. 20155324 2016-2017-2 《Java程序设计》第4周学习总结

    20155324 2016-2017-2 <Java程序设计>第4周学习总结 教材学习内容总结 继承 面对对象中,子类继承父类,避免重复定义行为就使用继承.在Java中,继承时使用exte ...