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;
}
原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13777467.html