zoukankan      html  css  js  c++  java
  • 8月19日考试 题解(堆+二分答案+树状数组+反悔贪心)

    今天没能做出来一道题,但是部分分给的比较良心。继续加油吧。

    T1 climb

    题目大意:给定一棵含有$n$个结点的树,每条边有边权,根节点为$1$。对于某些结点,可以直接到达深度小于等于它的结点,花费为$(dep[x]-dep[to])*k$。问每个结点到根节点的最小代价。

    考试的时候就差最后一步,没能把$O(n)$优化成$O(log n)$,挺可惜的。

    我们先遍历整棵树,把每个结点的深度和到根节点的距离求出来,分别记为$dep$和$dis$。

    然后我们按照深度遍历结点,对于一个结点,它的答案有两种情况:

    1.$dis[fa]+edge[i]$

    2.找到深度小于等于它的且最小的$dis$,此时有$dis[to]+(dep[i]-dep[to])*k$。

    对于第二种情况,我们自然可以用堆来维护这个最小的$dis$。时间复杂度$O(nlog n)$。

    实际上我们可以先将边权减去$k$,这样在过程种可以省很多事,最后输出的时候在加上$dep*k$即可。(不知道为什么我的代码不这样操作会出锅……

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=500005;
    const int inf=1e9;
    int n,k,dep[maxn],dis[maxn],edge[maxn],fa[maxn],maxx;
    bool ban[maxn];
    struct node
    {
        int pos,dis;
        bool operator < (const node &x) const{return x.dis<dis;}
    };priority_queue<node> q;
    vector<int> v[maxn];
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    int main()
    {
        n=read();k=read();
        dep[1]=0;dis[1]=0;
        for (int i=2;i<=n;i++)
        {
            int f=read(),w=read();ban[i]=read();w-=k;
            fa[i]=f;dep[i]=dep[f]+1;dis[i]=dis[f]+w;edge[i]=w;
            v[dep[i]].push_back(i);maxx=max(maxx,dep[i]);
        }
        q.push((node){1,dis[1]});
        for (int i=1;i<=maxx;i++)
        {
            int minn=inf,x=q.top().dis;
            for (int j=0;j<v[i].size();j++)
            {
                int now=v[i][j];
                dis[now]=min(dis[now],edge[now]+dis[fa[now]]);
                if (!ban[now]) dis[now]=min(dis[now],x);
                minn=min(minn,dis[now]);
            }
            for (int j=0;j<v[i].size();j++)
            {
                if (!ban[v[i][j]]) dis[v[i][j]]=min(dis[v[i][j]],minn);
                q.push((node){v[i][j],dis[v[i][j]]}); 
            }
        }
        for (int i=1;i<=n;i++) printf("%d
    ",dis[i]+dep[i]*k);
        return 0;
    }

    T2 h

    题目大意:数轴上有$n$个点,它们的初始位置为$p$,移动速度为$v$。一些点可能在移动过程中相遇,如果可能相遇,那么这些时刻显然有一个是最小的。现在你可以删去$k$个点,然后求出最小的相遇时刻。如果不可能相遇输出"Forever"。

    考试的时候没想到二分答案,纯粹是靠着良心的部分分骗了60分?(雾

    我们先把点按$p$为关键字进行排序。将$t$时刻时它们的位置存入一个新数组中,记为$b$。开始时对于两个位置$i<j$,均有$a_i<a_j$;如果$b_i>b_j$,那么它们将会碰撞。

    为了避免这种情况,我们要在$b$数组中把$i<j$且$b_i>b_j$的情况删去。也就是说我们要尽可能少地让$b$成为最长上升子序列。

    于是我们可以用树状数组求最长上升子序列。时间复杂度$O(nlog^2 n)$。

    代码:

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const double eps=1E-8;
    const double inf=1E12;
    const int maxn=50005;
    int tree[maxn],n,k;
    struct node
    {
        double p,v;
        int id;
        bool operator < (const node &x) const{return p<x.p;}
    }a[maxn],b[maxn];
    inline int lowbit(int x){return x&(-x);}
    bool cmp(node x,node y){return x.p<y.p;}
    inline void add(int x,int y)
    {
        while(x<=n)
        {
            tree[x]=max(tree[x],y);
            x+=lowbit(x);
        }
    }
    inline int query(int x)
    {
        int res=0;
        while(x>0)
        {
            res=max(res,tree[x]);
            x-=lowbit(x);
        }
        return res;
    }
    inline bool check(double t)
    {
        memset(tree,0,sizeof(tree));
        for (int i=1;i<=n;i++) b[i].id=i,b[i].p=a[i].p+a[i].v*t;
        sort(b+1,b+n+1);
        for (int i=1;i<=n;i++) a[b[i].id].id=i;
        for (int i=1;i<=n;i++)
        {
            int x=a[i].id;
            int y=query(x-1);
            add(x,y+1);
        }
        return n-query(n)<=k;
    }
    signed main()
    {
        ios::sync_with_stdio(false);
        cin>>n>>k; 
        for (int i=1;i<=n;i++) cin>>a[i].p>>a[i].v;
        sort(a+1,a+n+1);
        double l=0,r=inf,mid;
        while(abs(l-r)>eps)
        {
            mid=(l+r)/2;
            if (check(mid)) l=mid;
            else r=mid;
        }
        if (abs(mid-inf)<=eps) cout<<"Forever";
        else cout<<fixed<<setprecision(4)<<mid;
        return 0;
    }

     T3 pancake

    题目大意:给定一个长度为$n$的序列,有$q$次询问。你可以将每个序列划分成$b_{1,1},b_{1,2},cdots ,b_{1,k_i}$,但要求$sumlimits_{i=1}^n k_i=k$且$sumlimits_{i=1}^n sumlimits_{j=1}^{k_i}  b_{i,j}$最小。每次询问是以下三种操作之一:1.$k+=1$ 2.$k-=1$ 3.$a[++n]=x$。对于一开始的序列和每一次询问,都需要输出这个最小值。

    暴力乱搞混了25分。正解实在很妙,确实没想到。

    可以发现,一个数划分的越平均越好。可以证明:

    假设$ageq b$

    $(a^2+b^2)-(a+1)^2-(b-1)^2=2(b-a)-2<0$

    所以划分的数的差值不超过$1$。

    注意到对于同一个长度$len$,如果切的刀数越多,那么代价就越小,并且代价减小的速度是越来越慢的。令$cost(len,k)$表示长度为$len$的数切$k$刀的代价,那么$cost(len,k+1)-cost(len,k)$是单调上升且小于$0$的。

    这样我们可以维护两个multiset,一个用来插入,一个用来删除。每次找到$cost(len,k+1)-cost(len,k)$的最小的那个值,加上它,然后删除;再在用来插入的set插入$cost(len,k+2)-cost(len,k+1)$,在用来删除的set插入$cost(len,k)-cost(len,k+1)$。删除操作也是一样的。这样我们就可以在$log n$时间完成操作1和操作2。

    对于操作3,我们其实可以暴力改。把上述的差分想象成一个表格,就相当于我们让别的地方少占一个格子,然后看新加入的数的这一列能多添加几个格子,直到答案最优为止。可以证明总操作数是$nlog n$级别的。

    具体实现可以看代码。时间复杂度$O(nlog^2 n)$。

    代码:

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int maxn=1000005;
    const int inf=8e18;
    int k[maxn],a[maxn],ans,n,q,cut,opt;//k:number
    struct node
    {
        int num,w;
        node(int a=0,int b=0):num(a),w(b){}
        bool operator < (const node &x) const{return w==x.w?num<x.num:w<x.w;}
    };
    multiset<node> s0,s1;//0:add;1:del
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline int get(int x,int k)
    {
        if (x<k||k==0) return inf;
        int base=x/k;
        return base*base*(k-x%k)+(base+1)*(base+1)*(x%k);
    }
    inline void add()
    {
        multiset<node>::iterator p=s0.begin();
        node A=*p;
        ans+=A.w;
        int id=A.num;
        s1.erase(s1.find(node(id,get(a[id],k[id]-1)-get(a[id],k[id]))));
        s0.erase(p);
        k[id]++;
        s0.insert(node(id,get(a[id],k[id]+1)-get(a[id],k[id])));
        s1.insert(node(id,get(a[id],k[id]-1)-get(a[id],k[id]))); 
    }
    inline void del()
    {
        multiset<node>::iterator p=s1.begin();
        node A=*p;
        ans+=A.w;
        int id=A.num;
        s0.erase(s0.find(node(id,get(a[id],k[id]+1)-get(a[id],k[id]))));
        s1.erase(p);
        k[id]--;
        s0.insert(node(id,get(a[id],k[id]+1)-get(a[id],k[id])));
        s1.insert(node(id,get(a[id],k[id]-1)-get(a[id],k[id])));
    }
    signed main()
    {
        n=read();q=read();cut=read();
        for (int i=1;i<=n;i++)
        {
            a[i]=read();
            ans+=a[i]*a[i];
            k[i]=1;
            s0.insert(node(i,get(a[i],2)-get(a[i],1)));
            s1.insert(node(i,get(a[i],0)-get(a[i],1)));
        }
        for (int i=1;i<=cut;i++) add();
        printf("%lld
    ",ans);
        while(q--)
        {
            opt=read();
            if (opt==1) add();
            if (opt==2) del(); 
            if (opt==3)
            {
                a[++n]=read();
                ans+=a[n]*a[n];
                k[n]=1;
                s0.insert(node(n,get(a[n],2)-get(a[n],1)));
                s1.insert(node(n,get(a[n],0)-get(a[n],1)));
                int last=ans;
                while(1)
                {
                    del();
                    add();
                    if (ans==last) break;
                    last=ans;
                }
            }
            printf("%lld
    ",ans);
        }
        return 0;
        
    }
  • 相关阅读:
    7人脸识别
    1图片视频文件操作基础
    3直方图与二值化,图像梯度
    6模板匹配(人脸匹配案例)
    基础习题
    碎片知识点整理
    详解:MySQL数据表损坏的正确修复方案
    前端开发:模块化 — 高效重构
    分享几个基于jQuery不错的前端相册展示插件代码
    程序员编程10年的心得和体会
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13530546.html
Copyright © 2011-2022 走看看