Lyk Love painting/convex hull/mumsic

时间:2019-03-18
本文章向大家介绍Lyk Love painting/convex hull/mumsic,主要包括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 打击好大明明想出了标算然后被卡成暴力分嘤嘤嘤)