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

所有题面的传送门

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. Git(工作区和暂存区概念)

    Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念. 先来看名词解释. 工作区(Working Directory) 就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工 ...

  2. forEach、for in、for of 三者对比

    forEach forEach专门用来循环数组,可以直接取到元素,同时也可以取到index值 存在局限性,不能continue跳过或者break终止循环,没有返回值,不能return let arr ...

  3. protobuf使用简介

    官网:https://github.com/google/protobuf 环境:windows,java 1. protobuf概述protobuf是Google开发一种数据描述格式,能够将结构化数 ...

  4. plsql developer 11 + Oracle 11g 开发环境setup

    这是一个很水的博客, 介绍搭建plsql developer 11+ Oracle 11g 开发环境. 1. 本机上安装Oracle 11g express 对于开发足够了, 300MB的下载文件, ...

  5. Linq中Sum和Group的使用

    ].AsEnumerable() group c by c.Field<int>("Name") into s select new { ID = s.Select(m ...

  6. jqweui Picker使用一个小问题

    地址:http://jqweui.com/extends#picker加了Display Value后,会产生改变值后,Picker显示Value而不显示Text情况.需要在OnClose里做如下处理 ...

  7. ES6 快速开始

    Refs 现代框架 Vue React 都使用到了 ES6 (包括D3js) [常量] // ES5 中常量的写法 Object.defineProperty(window, "PI2&qu ...

  8. tensorflow faster rcnn 代码分析一 demo.py

    os.environ["CUDA_VISIBLE_DEVICES"]=2 # 设置使用的GPU tfconfig=tf.ConfigProto(allow_soft_placeme ...

  9. PHP设计——单例模式与工厂模式

    一.单例模式又称为职责模式,它用来在程序中创建一个单一功能的访问点,通俗地说就是实例化出来的对象是唯一的.所有的单例模式至少拥有以下三种公共元素:1. 它们必须拥有一个构造函数,并且必须被标记为pri ...

  10. 剑指Offer-把数组排成最小的数

    题目描述 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个.例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323. 思路 可以看 ...