Lyk Love painting/convex hull/mumsic
这场比赛真的是...打的好颓废啊...
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的更多相关文章
- 凸包(Convex Hull)构造算法——Graham扫描法
凸包(Convex Hull) 在图形学中,凸包是一个非常重要的概念.简明的说,在平面中给出N个点,找出一个由其中某些点作为顶点组成的凸多边形,恰好能围住所有的N个点. 这十分像是在一块木板上钉了N个 ...
- Convex Hull 实现理论+自制Python代码
Convex Hull 概述 计算n维欧式空间散点集的凸包,有很多的方法.但是如果要实现快速运算则其难点在于:如何快速判断散点集的成员是否是在凸集的内部.如果可以简化判断的运算过程,则可以极大简化迭代 ...
- OpenCV入门之寻找图像的凸包(convex hull)
介绍 凸包(Convex Hull)是一个计算几何(图形学)中的概念,它的严格的数学定义为:在一个向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包. 在图像处理过程中,我们 ...
- 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 ...
- Monotone Chain Convex Hull(单调链凸包)
Monotone Chain Convex Hull(单调链凸包)算法伪代码: //输入:一个在平面上的点集P //点集 P 按 先x后y 的递增排序 //m 表示共a[i=0...m]个点,ans为 ...
- convex hull
1 什么是convex hull 就是凸包,是计算几何中的一个概念,计算几何是计算机图形学的基础之一. 对于二维平面来说是这样的:对于二维平面上的点集,凸包是位于最外层的点构成的包围其它所有的点的凸多 ...
- opencv::凸包-Convex Hull
概念介绍 什么是凸包(Convex Hull),在一个多变形边缘或者内部任意两个点的连线都包含在多边形边界或者内部. 正式定义:包含点集合S中所有点的最小凸多边形称为凸包 Graham扫描算法 首先选 ...
- 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 ...
- 20210826 Lighthouse,Miner,Lyk Love painting,Revive
考场 T1 这不裸的容斥 T2 这不裸的欧拉路,先从奇数度点开始走,走不了就传送 T3 什么玩意,暴力都不会 T4 点分树??? 仔细想了一波,发现 T1 T2 都好做,T3 二分答案后可以暴力贪心, ...
随机推荐
- Mac下显示网页全屏快捷键
control+command+F mac下谷歌浏览器全屏时隐藏头部:(隐藏标签页和地址栏) command+shift+B
- Ruby on Rails,一对多关联(One-to-Many)
在上一篇文章中,我们知道通过has_one和belongs_to方法定义一对一关联关系.接下来是更常见的情况,一对多关联.比如老师与所教课程的关系,一个老师负责多个课程.换成对象之间的关系就是:一个老 ...
- 【整理】Linux 下 自己使用的 debug宏 printf
#ifdef __DEBUG_PRINTF__ /* * * Some Debug printf kit for devlopment * * Date : 2019.03.04 * * Editor ...
- Docker-01 无人值守升级 CentOS 6.x 系统内核到 3.10.x 长期支持版
#!/bin/bash # # 无人值守升级 CentOS .x 系统内核到 3.10.x 长期支持版 # # # .检查操作系统是否为 CentOS .x # cat /etc/centos-rel ...
- 几本不错的数据仓库和Hadoop书籍
<<Pentaho Kettle解决方案:使用PDI构建开源ETL解决方案>>, Matt Casters等著,初建军翻译<<Hadoop应用架构>> ...
- windows 下使用 sc 添加创建exe服务;
SC 是用于与服务控制管理器和服务进行通信的命令行程序. 1.SC 命令 格式 sc <server> [command] [service name] <option1> & ...
- 嵌入式-迅为iTOP-4418开发板Flash空间问题
我的4418开发板 是4G版本 16G存储空间的.u-boot和文件系统.内核都是光盘自带的,进入linux系统之后 我使用df -h命令看到的存储空间不对,我用U盘做了测试:u盘里面放1G的内容往a ...
- 牛客 被3整除的子序列dp
题意很清楚,直接dp即可,dp[i][j]表示到第i个字符的状态为j的方案数,这里状态指的是子序列最大下标到第i直接dp即可,dp[i][j]表示到第i个字符的状态为j的方案数,这里状态指的是子序列最 ...
- 导弹拦截 dp
n∗lognn*lognn∗logn写法,lis[i]的意义为:所有最长上升子序列长度为i的位置上的最小a数组元素值lis[i]的意义为:所有最长上升子序列长度为i的位置上的最小a数组元素值lis[i ...
- easyui 进阶之表单校验、自定义校验
前言 easyui是一种基于jQuery的用户界面插件集合,它为创建现代化,互动,JavaScript应用程序,提供必要的功能,完美支持HTML5网页的完整框架,节省网页开发的时间和规模.非常的简单易 ...