• 10月7日考试 题解(贪心+动态规划+DFS序+分块)


    T1 倾斜的线

    题目大意:给定两个正整数 $P$ 和 $Q$。在二维平面上有 $n$ 个整点。现在请你找到一对点使得经过它们的直线的斜率在数值上最接近 $frac{P}{Q}$(即这条直线的斜率与$frac{P}{Q}$的差最小),请输出经过它们直线的斜率 $frac{p}{q}$。如果有两组点的斜率的接近程度相同,请 输出较小的斜率。保证答案的 $frac{p}{q}>0$,即输出的 $p$ 和 $q$ 都是正整数。

    题目的要求是让$frac{y_1-y_2}{x_1-x_2}-frac{P}{Q}$最小,那么我们通分一下,得到:$frac{Q(y_1-y_2)-P(x_1-x_2)}{Q(x_1-x_2)}$,然后把$(Qy-Px,Qx)$看作新的横纵坐标,按照纵坐标排个序,$O(n)$扫一下即可。

    题解的做法也很妙:对于每个点,我们把斜率为$frac{P}{Q}$且经过这些点的直线在$y$轴上的截距排序。对于一个三元组$(i,j,k)$经过$(i,j)$的直线或经过$(j,k)$的直线比经过$(i,k)$的直线更优。画出图来长这样:

    时间复杂度$O(nlog n+n)$。

    代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #define int long long
    using namespace std;
    const int N=200005;
    const int inf=1e18;
    int n,P,Q,ans;
    struct node
    {
        int id,x,y;
        bool operator < (const node &x) const
        {
            return y<x.y;
        }
        double operator / (const node &ff)
        {
            return (double)(ff.y-y)/(double)(ff.x-x);
        }
    }a[N],b[N];
    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 gcd(int a,int b){
        return (b==0)?a:gcd(b,a%b);
    }
    signed main()
    {
        n=read();P=read();Q=read();
        for (int i=1;i<=n;i++)
        {
            a[i].x=read(),a[i].y=read();
            b[i].id=i;
            b[i].y=a[i].y*Q-a[i].x*P;
            b[i].x=a[i].x*P;
        }
        sort(b+1,b+n+1);
        double mi=inf;
        for (int i=1;i<=n;i++)
        {
            if (abs(b[i]/b[i+1])<mi)
                ans=i,mi=abs(b[i]/b[i+1]);
            else if (abs(b[i]/b[i+1])==mi)
                ans=(b[ans+1]/b[ans])<(b[i+1]/b[i])?ans:i;
        }
        int p=abs(a[b[ans+1].id].y-a[b[ans].id].y),q=abs(a[b[ans+1].id].x-a[b[ans].id].x);
        int g=gcd(p,q);
        printf("%lld/%lld",p/g,q/g);
        return 0;
    }

    T2 扭动的树

    题目大意:有一棵以 $key$ 为键值以$val$ 为权值的二叉查找树,定义其某个节点的 $sum$ 为 它的子树内节点的 $val$ 之和。给出 $n$ 个$<key, val>$正整数对,现需保证这棵树上任 意一条边的两个端点的 $key$值的最大公约数不为 $1$,询问这棵树上所有节点的 $sum$ 之和最大可能是多少。如果这棵树不存在任意一个合法形态,输出$-1$。

    考虑到二叉搜索树的中序遍历是一段$key$单调递增的区间,所以我们不妨以$key$为关键字从小到大排序。根据此性质,我们可以从区间中提取出一个数作为此区间的根然后区间DP。一个区间$[l,r]$的根的父亲只可能是$l-1$或者$r+1$,所以DP的状态数是$n^2$的。转移是$n^3$的。

    代码:

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #define int long long
    using namespace std;
    const int N=305;
    const int inf=1e18;
    int n,f[N][N][2],sum[N],ans,g[N][N];
    struct node
    {
        int key,val;
    }a[N];
    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;
    }
    bool cmp(node x,node y)
    {
        return x.key<y.key;
    }
    inline int gcd(int a,int b){
        return (b==0)?a:gcd(b,a%b);
    }
    signed main()
    {
        n=read();ans=-inf;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                for (int k=0;k<=1;k++)
                    f[i][j][k]=-inf;
        for (int i=1;i<=n;i++)
            a[i].key=read(),a[i].val=read();
        sort(a+1,a+n+1,cmp);
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                g[i][j]=gcd(a[i].key,a[j].key);
        for (int i=1;i<=n;i++)
        {
            sum[i]=sum[i-1]+a[i].val;
            if (i!=1&&g[i][i-1]!=1) f[i][i][0]=a[i].val;
            if (i!=n&&g[i][i+1]!=1) f[i][i][1]=a[i].val;
        }
        for (int len=2;len<=n;len++)
            for (int l=1;l+len-1<=n;l++)
            {
                int r=l+len-1,res;
                for (int k=l;k<=r;k++)
                {
                    if (k==l) res=f[l+1][r][0]+(sum[r]-sum[l-1]);
                    else if (k==r)  res=f[l][r-1][1]+(sum[r]-sum[l-1]);
                    else res=f[l][k-1][1]+f[k+1][r][0]+(sum[r]-sum[l-1]);
                    if (l!=1&&g[k][l-1]!=1) f[l][r][0]=max(f[l][r][0],res);
                    if (r!=n&&g[k][r+1]!=1) f[l][r][1]=max(f[l][r][1],res);
                    if (len==n) ans=max(ans,res);
                }
            }
        if (ans<0) printf("-1");
        else printf("%lld",ans);
        return 0;
    }

    T3 打铁的匠

    题目大意:给定一棵含有$n$个结点的边带权的树,其根为$1$。一个结点的权值为根到此结点的距离。有$q$次询问,每次给出$u,k$,求以$u$为根的子树内与$u$权值之差大于等于$k$的点的差值之和。

    同机房大佬写的树状数组还跑的比我快QAQ,题解写的平衡树码风感人。我自己yy出了一个分块的做法,复杂度也是正确的。先把树的$dfs$序搞出来,然后分块。预处理的同时要保证块内元素是有序的。询问时对于每个块二分找出第一个差值大于等于$k$的位置,然后前缀和搞一搞就可以了。

    询问的复杂度$O(qsqrt n log sqrt n)$。时限3s,卡卡常还是可以跑过去的。

    代码:

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    const int N=100005;
    long long ans,sum[N],g[N],b[N],w[N];
    int size[N],son[N],dep[N],fa[N],n,q;
    int dfn[N],top[N],rev[N],tot;
    int belong[N],l[N],r[N],num,block;
    int head[N],cnt;
    struct node
    {
        int next,to,dis;
    }edge[N*2];
    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 void add(int from,int to,int dis)
    {
        edge[++cnt]=(node){head[from],to,dis};
        head[from]=cnt;
    }
    inline void dfs_son(int now,int f)
    {
        size[now]=1;
        dep[now]=dep[f]+1;fa[now]=f;
        for (int i=head[now];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if (to==f) continue;
            w[to]=w[now]+edge[i].dis;
            dfs_son(to,now);
            size[now]+=size[to];
            if (size[to]>size[son[now]]) son[now]=to;
        }
    }
    inline void dfs_chain(int now,int topf)
    {
        dfn[now]=++tot;
        top[now]=topf;
        rev[tot]=now;
        if (!son[now]) return;
        dfs_chain(son[now],topf);
        for (int i=head[now];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if (dfn[to]) continue;
            dfs_chain(to,to);
        }
    }
    inline void build()
    {
        for (int i=1;i<=n;i++) 
            b[i]=w[rev[i]];
        block=sqrt(n);num=n/block;
        if (n%block) num++;
        for (int i=1;i<=n;i++)
        {
            g[i]=b[i];
            belong[i]=((i-1)/block)+1;
        }
        for (int i=1;i<=num;i++)
        {
            l[i]=(i-1)*block+1;
            r[i]=i*block;
        }
        r[num]=n;
        for (int i=1;i<=num;i++)
            sort(g+l[i],g+r[i]+1);
        for (int i=1;i<=n;i++) 
            sum[i]=sum[i-1]+g[i];
    }
    inline void query(int x,int y,long long k)
    {
        if (belong[x]==belong[y])
        {
            for (int i=x;i<=y;i++)
                if (b[i]-b[x]>=k) ans+=(b[i]-b[x]);
            return;
        }
        for (int i=x;i<=r[belong[x]];i++)
            if (b[i]-b[x]>=k) ans+=(b[i]-b[x]);
        for (int i=l[belong[y]];i<=y;i++)
            if (b[i]-b[x]>=k) ans+=(b[i]-b[x]);
        for (int i=belong[x]+1;i<=belong[y]-1;i++)
        {
            int L=l[i],R=r[i],mid;
            while(L<R)
            {
                mid=(L+R)>>1;
                if (g[mid]-b[x]>=k) R=mid;
                else L=mid+1;
            }
            if (L==r[i]){
                if (g[L]-b[x]>=k)
                    ans+=g[L]-b[x];
                continue;
            }
            ans+=(sum[r[i]]-sum[L-1]-b[x]*(r[i]-L+1));
        }
    }
    int main()
    {
        n=read();
        for (int i=2;i<=n;i++)
        {
            fa[i]=read();int w=read();
            add(fa[i],i,w);add(i,fa[i],w);
        }
        dfs_son(1,0);
        dfs_chain(1,1);
        build();
        q=read();
        while(q--)
        {
            ans=0;
            int u=read(),goal=read();
            query(dfn[u],dfn[u]+size[u]-1,goal);
            printf("%lld
    ",ans);
        }
        return 0;
    }
  • 相关阅读:
    (数论选拔)联盟阵容
    ai变成bi(递增)最小次数
    状压dp
    dp被3整除的子序列
    离散化+莫队
    dp+哈希
    set的应用
    NOIP 2016 明明的随机数
    洛谷背景更改
    zzulioj 1734 堆
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13777467.html
Copyright © 2020-2023  润新知