每日一题 2.16 + 2.15 (维护凸包的单调栈)

这两个每日一题感觉能力有限,都只能做一题,惭愧。
现说2.15号的题目,http://codeforces.com/contest/406/problem/D
题意
n座山,按x坐标升序给出他们的山峰坐标。第i个山峰的坐标(xi, yi)。
如果a山的顶点可以被b山看到,并且ab山顶点间的连线不会和其他山相交或相接,那么这两座山峰相互可达。
现在我们给出m个团队,每个团队两个人。初始时他们分别在ai,bi山峰,人每次移动时都会移动到他能够到达的最右的山峰处,
如果另一个两个登山者已经在此处相遇或者另一个登山者最终会在当前山峰相遇,那么当前登山者就会在此处停下。
每个团队两个人按照上述移动方式,直到到达同一个山峰时,问这座山的编号是多少。

数据
(1 <= n <= 1e5), (1 <= xi <= 1e7), (1 <= yi <= 1e11)。
(1 <= m <= 1e5), (1 <= ai <= bi <= n)。ai可能等于bi。

这个题属于比较能想到的题,因为我们把每个山看成一个点,那么每个点只会往右连接一个点, 所以整个图会形成 以 第n个点为根的一棵树。。。
至于题意要求的东西,很明显可看出来是lca。

上面都是很容易想到了,但是我T了几发= = 因为我建图的姿势水平还不够高。n*n的建图方法真的是 拿衣服! 实际上建图过程可以看成从n->1的一个维护凸包的过程。 维护凸包我们使用一个单调栈即可,,, 虽然以前看过这个知识点,但是太不熟练了。


比如上图假设三座山分别是a,b,c,那么明显k1(a,b) < k2(a,c), 所以我们可以把b忽略,因为不管后来出现什么样的山a, 都会有k2>k1,都不会连接 a-b这条边。
如果k1>k2,同理我们也可以把 c山忽略。
于是可以利用单调栈建图:

    for(int i=n;i>=1;i--){ //用类似维护凸包的单调栈来建图。
        while(top>=2){
             double k1 = (y[stk[top]] - y[i]) *1.0/ (x[stk[top]] - x[i]);
             double k2 = (y[stk[top-1]] - y[stk[top]])*1.0 / (x[stk[top-1]] - x[stk[top]]);
             if(k1<k2) top--; // n-2 n-1 n (i,top,top-1)于是我们可以把n-1去掉了(可证)
             else break;  //否则不动
        }
        if(top>=1){
            addedge(stk[top],i);
            addedge(i,stk[top]);
        }
        stk[++top]=i;  //相当于把原top-1去掉
        //printf("add:%d %d\n",to,i);
    }

接下来的lca 套套以前自己写的板子,就过了

#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>

#include<string.h>
using namespace std;
#define ll __int64
/*
 人每次移动时都会移动到他能够到达的最右的山峰处,
如果已经另一个人已经在这座山,或者会经过这座山,那么会停下来

我感觉像个图,  而且是棵树,不过要反着建图,然后求lca似乎就可以解决此问题。

于是要解决在简单的时间复杂度下如何建图。
假设本山坐标为(x,y)
其实就是求最右边的山峰(X,Y) 使得 Y-y/X-x 最大。
*/
const int MAXN=100010;
int rmq[2*MAXN]; //rmq数组, 记录每个节点在树中的深度
struct ST{
    int mm[2*MAXN];
    int dp[2*MAXN][20]; //dp 直接存下标
    void init(int n){
        mm[0]=-1;
        for(int i=1;i<=n;i++){
            mm[i]=((i&(i-1))==0)? mm[i-1]+1 :mm[i-1]; //???莫名其妙定义长度
            dp[i][0]=i; //初始化dp数组
        }
        for(int j=1;j<=mm[n];j++)   //可以看出来mm[n] 就是一个长度为n的序列j的最大值
            for(int i=1;i+(1<<j)-1 <=n ; i++){
                if(rmq[dp[i][j-1]] < rmq[dp[i+(1<<(j-1))][j-1] ]){
                    dp[i][j]=dp[i][j-1];
                }
                else
                    dp[i][j]=dp[i+(1<<(j-1))][j-1];
            }
    }
    int query(int a,int b){  //这个询问返回的是位置pos, F[pos]才是值
            if(a>b)
                swap(a,b);
            int k=mm[b-a+1];
            if(rmq[dp[a][k]] <= rmq[dp[b-(1<<k)+1][k]] ){
                return dp[a][k];
            }
            else
                return dp[b-(1<<k)+1][k];
    }
};
struct Edge{
    int to,next;
    int w; // 用w来记录权值:即到根部的距离。
}edge[MAXN*2];

int tot,head[MAXN];
int F[MAXN*2];   // 按顺序存储节点 ,下标从1开始,长度为2*n-1
int first[MAXN]; // i在F中第一次出现的位置
int cnt;
int fa[MAXN];  //  记录每个点的父亲节点,以此来找路径会特别方便
int w[MAXN];
int dir[MAXN];
ST st;
void addedge(int u,int v){ //无向边自然加两次
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
void dfs(int u,int pre,int dep){ //u起点,dep表示深度
     F[++cnt] = u;    // cnt就是这个节点的下标
     fa[u]=pre;
     rmq[cnt]=dep;    //rmq记录节点在树中的深度
//     printf("dfs:%d %d %d %d\n",u,dep,rmq[c],pre);
     first[u]=cnt;       //这个first 不会被更新掉,因为我们把continue了回去的路
     for(int i=head[u];i!=-1; i=edge[i].next){
         int v=edge[i].to;
         if(v==pre) continue;
         dfs(v,u,dep+1);
         F[++cnt] = u;     //虽然continue回去的路,但是询问叶子节点还要返回:1-2-1 --....
         rmq[cnt] = dep;
     }
}
void LCA_init(int root ,int node_num){ //查询LCA前的初始化
    cnt=0;
    dfs(root,root,0);
    st.init(2*node_num-1); // 注意 ,这里dp的不是n,而是对F进行dp
}
int query_lca(int u,int v){   //查询lca(u,v)
    return F[st.query(first[u],first[v])];
}



ll x[100005],y[100005];
void init(){
    tot=0;
    memset(head,-1,sizeof(head));
//  memset(rmq,0,sizeof(rmq));
}
int stk[MAXN];
int main() {
    init();
    //freopen("1.txt", "r", stdin);
    int n,m;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%I64d %I64d",&x[i],&y[i]);
    }
    int top=1;
    stk[top]=n;
    for(int i=n;i>=1;i--){ //用类似维护凸包的单调栈来建图。
        while(top>=2){
             double k1 = (y[stk[top]] - y[i]) *1.0/ (x[stk[top]] - x[i]);
             double k2 = (y[stk[top-1]] - y[stk[top]])*1.0 / (x[stk[top-1]] - x[stk[top]]);
             if(k1<k2) top--; // n-2 n-1 n 于是我们可以把n-1去掉了(可证)
             else break;  //否则不动
        }
        if(top>=1){
            addedge(stk[top],i);
            addedge(i,stk[top]);
        }
        stk[++top]=i;
        //printf("add:%d %d\n",to,i);
    }
    LCA_init(n,n);
    scanf("%d",&m);
    while(m--){
        int a,b;
        scanf("%d %d",&a,&b);
        int ans=query_lca(a,b);
        printf("%d\n",ans);
    }

return 0;
}

再说2.16
http://codeforces.com/problemset/problem/670/E
题意
给出一个合法的括号序列,其长度为n,接下来要做m个操作,位置p处有一个光标。
我们可以做3种操作:
1.<>,将当前光标左移一个位置。
2.<>, 将当前光标右移一个位置。
3.<>, 将当前光标位置,与其对应的光标位置,以及这之间所有的括号全部删除。然后光标移动到被删除的字符串右一位。
如果删除的是一个后缀,那么就移动到新串的末位置。
然后将最终得到的序列输出。

这个题记得一年前似乎写过一次,当时属于乱搞,没写出来,T了。

这一次回顾,然后用双向链表写了写,注意下如果删除后缀应该往前移一位


#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>

#include<string.h>
using namespace std;
#define ll __int64
/*


*/

char str[500005];
char op[500005];
struct node{
    int i;
    int next,pre;
    int p;
}f[500005];
int main() {
    //freopen("1.txt", "r", stdin);
    int n,m,p;
    scanf("%d %d %d",&n,&m,&p);
    scanf("%s %s",str+1,op+1);
    stack<node>q;
    for(int i=1;i<=n;i++){
        f[i].next=i+1;
        f[i].i=i;
        f[i].pre=i-1;
        if(str[i]=='('){
            q.push(f[i]);
        }
        else{
            node cnt=q.top();
            q.pop();
            f[i].p=cnt.i;
            f[cnt.i].p=i;
        }
    }
    f[0].next=1;
    f[n+1].pre=n;

    for(int i=1;i<=m;i++){
        if(op[i]=='R'){
            p=f[p].next;
        }
        else if(op[i]=='L'){
            p=f[p].pre;
        }
        else if(op[i]=='D'){
            //   . P....to.
            //   . to....p.
            int to=f[p].p;
            //printf("j: %d %d\n",p,to);
            if(to>p){
                f[ f[p].pre ].next=f[to].next;
                f[ f[to].next].pre=f[ f[p].pre].i;
                p=f[to].next;
                if(p==n+1)  //如果是后缀,往前移一位
                    p=f[p].pre;
            }
            else{
                f[ f[to].pre].next=f[p].next;
                f[ f[p].next].pre=f[ f[to].pre].i;
                p=f[p].next;
                if(p==n+1)
                    p=f[p].pre;
            }
        }
    }
    int sta=0;
    while(sta<=n){
        if(sta>0 && sta<=n)
            printf("%c",str[sta]);
        sta=f[sta].next;
    }
    printf("\n");

return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值