NOIP模拟16:「Star Way To Heaven·God Knows·Loost My Music」

T1:Star Way To Heaven

基本思路:

  最小生成树。
  假如我们将上边界与下边界看作一个点,然后从上边界经过星星向下边界连边,会发现,他会形成一条线将整个矩形分为左右两个部分。
  并且很明显从左边界走到右边界一定会经过这条线的某一部分。
  为了与星星和上下边界尽可能远,我们直接走两点连边的中点,这应该很好理解。
  他要求最小距离,所以我们可以跑最小生成树。
  然后在最小生成树的边里拣大的输出他的\(1/2\)即可。
  我们将所有星星连边,并与上下边界连边,与上下边界连边的权值是\(y\)以及\(m-y\)
  然后,我们还面临一个问题,就是我们生成的是树,对于一个完全图来说,这棵树不可能退化成链,就是说,他至少有两支,而我们要的是这棵树上最大权值最小的那一支上的最大值,总不可能求出树后在\(DFS\)吧。
  考虑最小生成树的原理:贪心。
  他每次都会选权值最小加入生成树,那么当代表下界的点第一次被加入生成树时直接return即可,之前得到的那一支就是我们要的,记得在之前不断对边取max即可。
  最后输出时记得除以2.
  代码:

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
    #define ll long long
    #define rr register 
    const int SIZEK=6006;
    int k;
    double n,m;
    struct Star{double x,y;}star[SIZEK];
    bool operator<(Star a_,Star b_)
    {
        if(a_.x==b_.x) return a_.y<b_.y;
        return a_.x<b_.x;
    }
    double ans=-999999;
    int read()
    {
        rr int x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9')
        {
            if(c_read=='-') y_read=-1;
            c_read=getchar();
        }
        while(c_read<='9'&&c_read>='0')
        {
            x_read=(x_read*10)+(c_read^48);
            c_read=getchar();
        }
        return x_read*y_read;
    }
    struct edge{int f,t;double w;}a[SIZEK*SIZEK/2];
    bool operator<(const edge a_,const edge b_){return a_.w<b_.w;}
    int cnt;
    double len(int i,int j)
    {
        double l;
        l=(star[i].x-star[j].x)*(star[i].x-star[j].x);
        l+=(star[i].y-star[j].y)*(star[i].y-star[j].y);
        l=sqrt(l);
        return l;
    }
    int f[SIZEK];
    int find(int x)
    {
        if(f[x]==x)
            return x;
        return f[x]=find(f[x]);
    }
    void join(int x,int y)
    {
        if(f[x]!=f[y])
            f[f[y]]=f[x];
    }
};
using namespace STD;
int main()
{
    int x=scanf("%lf%lf",&n,&m);
    k=read();
    for(rr int i=1;i<=k;i++) x=scanf("%lf%lf",&star[i].x,&star[i].y),f[i]=i;
    f[k+1]=k+1,f[k+2]=k+2;
	//最近对优化连边
    sort(star+1,star+1+k);
    for(rr int i=1;i<=k;i++)
        for(rr int j=i+1;j<=min(k,i+500);j++)
        {
            a[++cnt].f=i;
            a[cnt].t=j;
            a[cnt].w=len(i,j);
        }
	//
    for(rr int i=1;i<=k;i++)
    {
        a[++cnt].f=i;
        a[cnt].t=k+1;
        a[cnt].w=star[i].y;
        a[++cnt].f=i;
        a[cnt].t=k+2;
        a[cnt].w=m-star[i].y;
    }
    sort(a+1,a+1+cnt);
    for(rr int i=1;i<=cnt;i++)
    {
        if(find(k+1)==find(k+2)) break;
        int f=a[i].f;
        int t=a[i].t;
        if(find(f)!=find(t))
        {
            join(f,t);
            ans=max(ans,a[i].w);
        }
    }   
    printf("%.9lf",ans/2.00);
}

  改题途中:
  正解用的是Prim,因为这样的话就不用把边真正建出来了,但是我更熟悉kruscal。所以用的kruscal。但是MLE了。
  后来战神教我用点对减少连边我才A的。
  这也从一个侧面印证了kruscal在稠密图上的局限性。

T2:God Knows

基本思路:

  数据结构优化DP
  我们可以发现,与边\((i,p[i])\)相交的边\((j,p[j])\)一定满足:

  i<j&&p[i]>p[j] 或 i>j&&p[i]<p[j]

  如果您急于看正解,接下来这一段可以直接跳过
  
  

  先说一个考场上想的稀奇思路:
  我们可以给所有相交的的边的i与j连边,然后问题转化成了以最小的代价将整个图里的边盖。
  为什么这个思路是值得考虑的呢?
  我们可以发现,我们发现,将图中的边删去只需要将与他相交的边删去或将它上的点删去即可。
  这很像将图中边覆盖时至少要将他的一个端点选中。
  所以,这个思路值得考虑。
  但是他在这个题中实际上是不可行的。因为图的边完全覆盖是NP问题。。。。。
  就是下面链接里提到的广播问题。。。。。。。
  不知道啥是NP问题??戳这里
  总之1s内是解决不了了。
  但是假如图退化为树,就可以树规了。
  这个思路还有一个可能的实现就是将建出的图中的点改为边,边改为点,然后跑最小欧拉路。
  至于建边,归并排序的逆序对算法也许可以做到\(O(nlogn)\)实现。
  至于是否真的可以,作者没有精力实现。
  留给读者思考。
  如果您将他证伪或解除,请您告诉我。
  这个思路只是作为一个启发提出,希望也许可以得到一些其他的灵感。

  
  
  然后说正解。
  第一行已经说了,数据结构优化DP。
  我们可以发现,这道题与线性DP中的有好城市问题类似。
  传送门:友好城市
  我们观察可以发现答案的边一定是不相交的,那么他们的端点一定成上升序列。
  因为只有边没有被删去的点才会有贡献。
  所以相交的边的点一定只有其一做出贡献。
  我们可以求出图里所有的极长上升子序列。
  然后取他们c的和的最小值,即可。
  复杂度\(O(n^2)\)
  考虑优化。
  用线段树维护p的范围,线段树里插入的是i。
  那么我们每次在线段树里查询1->p[i]-1即可。
  还要考虑相交的线都可能对同一点作出贡献。所以我们要循环查询p[j]->p[i]-1,将j更新为查询结果。
  因为我是从小到大插入的i所以查询到的结果一定与之前的j相交或平行,而平行的话也是最大i,所以一定可以贡献。
  上代码:

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
    #define ll long long
    #define rr register 
    #define inf INT_MAX
    const int SIZE=2e5+4;
    int n,ans=inf;
    int p[SIZE],cost[SIZE];
    int c[SIZE];
    int read()
    {
        rr int x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9')
        {
            if(c_read=='-') y_read=-1;
            c_read=getchar();
        }
        while(c_read<='9'&&c_read>='0')
        {
            x_read=(x_read*10)+(c_read^48);
            c_read=getchar();
        }
        return x_read*y_read;
    }
    inline int max(int x,int y){return x>y?x:y;}
    inline int min(int x,int y){return x<y?x:y;}
    bool vis[SIZE];
    class Line_tree
    {
        private:
            #define lc id<<1
            #define rc id<<1|1
            int maxn[SIZE<<2];
        public:
            Line_tree(){}
            void insert(int id,int l,int r,int pos,int v)
            {
               // if(id>=(SIZE<<2))
                if(l==r){maxn[id]=v;return;}
                int mid=(l+r)>>1;
                if(pos<=mid) insert(lc,l,mid,pos,v);
                else insert(rc,mid+1,r,pos,v);
                maxn[id]=max(maxn[lc],maxn[rc]);
            }
            int query(int id,int l,int r,int st,int en)
            {
                if(st<=l&&r<=en) return maxn[id];
                int mid=(l+r)>>1;
                rr int ret=-inf;
                if(st<=mid) ret=max(ret,query(lc,l,mid,st,en));
                if(mid<en) ret=max(ret,query(rc,mid+1,r,st,en));
                return ret;
            }
    }t;
};
using namespace STD;
int main()
{
    n=read();
    for(rr int i=1;i<=n;i++) p[i]=read();
    for(rr int i=1;i<=n;i++) 
    {
        cost[i]=read();
        c[i]=inf;
    }
    int id=0;
    for(rr int i=1;i<=n;i++)
    {
        id=0;
        if(p[i]-1>=1)
            id=t.query(1,1,n,1,p[i]-1);
        while(id)
        {
            c[i]=min(c[i],c[id]+cost[i]);
            vis[id]=1;
            if(p[id]+1>p[i]-1)
                break;
            id=t.query(1,1,n,p[id]+1,p[i]-1);
        }
        if(c[i]==inf) c[i]=cost[i];
        t.insert(1,1,n,p[i],i);
    }
    for(rr int i=1;i<=n;i++)
        if(!vis[i])
            ans=min(ans,c[i]);
    printf("%d",ans);
}

  最后在解释下为什么是极大上升序列吧,因为他可以保证平行,并且使得每一组相交线里都会有一条直线被选中

T3:Loost My Music

  斜率优化DP·可持久化栈(主席树)·二分·凸包
  将这个题的式子取反。会得到:

\[-\frac{c_{u}-c_{v}}{depth_{u}-depth_{v}} \]

假如我们以c为y轴,depth为x轴,会发现,这其实是相邻两点连线的斜率取反。

  因此,我们可以考虑用单调栈维护下凸包。然后将栈顶与当前值计算即可。
  注意当回溯后要继续向下搜,所以要恢复栈状态,因此考虑可持久化栈。
  上代码:

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
    #define ll long long
    #define rr register 
    const int SIZE=5e5+4;
    int n;
    int top[SIZE];
    int fa[SIZE],to[SIZE];
    int dire[SIZE],head[SIZE];
    double val[SIZE],depth[SIZE];
    double ans[SIZE];
    inline void add(int f,int t)
    {
        static int num1=0;
        to[++num1]=t;
        dire[num1]=head[f];
        head[f]=num1;
    }
    namespace Prisident_tree
    {
        struct node
        {
            int id;
            node *l,*r;
        }root[SIZE];
        void build(node &now,int l,int r)
        {
            if(l==r) {if(l==1) now.id=1;return;}
            rr int mid=(l+r)>>1;
            now.l=new node;
            now.r=new node;
            build(*now.l,l,mid),build(*now.r,mid+1,r);
        }
        void insert(node &before ,node &now,int l,int r,int pos,int id)
        {
            if(l==r){now.id=id;return;}
            rr int mid=(l+r)>>1;
            if(pos<=mid)
            {
                now.r=before.r;
                now.l=new node;
                insert(*before.l,*now.l,l,mid,pos,id);
            }
            else 
            {
                now.l=before.l;
                now.r=new node;
                insert(*before.r,*now.r,mid+1,r,pos,id);
            }
        }
        int query(rr node now,rr int l,rr int r,rr int pos)
        {
            if(l==r) return now.id;
            rr int mid=(l+r)>>1;
            if(pos<=mid) 
                return query(*now.l,l,mid,pos);
            else 
                return query(*now.r,mid+1,r,pos);
        }  
    };
    using namespace Prisident_tree;
    int read()
    {
        rr int x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9')
        {
            if(c_read=='-') y_read=-1;
            c_read=getchar();
        }   
        while(c_read<='9'&&c_read>='0')
        {
            x_read=(x_read*10)+(c_read^48);
            c_read=getchar();
        }
        return x_read*y_read;
    }
    inline double rate(rr int id1,rr int id2){return (val[id1]-val[id2])/(depth[id1]-depth[id2]);}
    int find(int f,int now)
    {
        int l=1,r=top[f];
        rr double a1,a2;
        while(l<r)
        {
            rr int mid=(l+r)>>1;
            rr int id1=query(root[f],1,n,mid);
            rr int id2=query(root[f],1,n,mid+1);
            a1=rate(id2,id1);
            a2=rate(now,id2);
            if(a1>=a2) r=mid;
            else l=mid+1;
        }
        return l;
    }
    void dfs(int now)
    {
        if(depth[now]==1.00)
        {
            ans[now]=rate(now,1);
            insert(root[fa[now]],root[now],1,n,2,now);
            top[now]=2;
        }
        else
        {
            if(now!=1)
            {
                int pos=find(fa[now],now);
                top[now]=pos+1;
                int id=query(root[fa[now]],1,n,pos);
                ans[now]=rate(now,id);
                insert(root[fa[now]],root[now],1,n,top[now],now);
            }
        }
        for(rr int i=head[now];i;i=dire[i])
        {
            depth[to[i]]=depth[now]+1.00;
            dfs(to[i]);
        }
    }
};
using namespace STD;
int main()
{
    n=read();
    for(rr int i=1;i<=n;i++) int x=scanf("%lf",val+i);
    for(rr int i=2;i<=n;i++) fa[i]=read(),add(fa[i],i);
    build(root[1],1,n);
    dfs(1);
    for(rr int i=2;i<=n;i++) printf("%.10lf\n",-ans[i]);
}

2021.7.16 现役

posted @ 2021-07-16 09:11  Geek_kay  阅读(68)  评论(1编辑  收藏  举报