11月9日考试 题解(贪心+背包+最短路+树形DP)

T1 游戏

题目大意:给定$n$个二元组$(a_i,b_i)$,多次询问,每次给定$k,m$,求前$m$大$a_i\times k+b_i$之和。$a_i\leq 10^4,b_i\leq 10^3,10^3\leq k\leq 10^9$。

唬人题。看到数据范围可以发现只跟$a_i$有关。所以按照$a$排序然后前缀和优化一下即可。时间复杂度$O(n\log n+q)$。

然而这题要双关键字排序,成功爆成$0$分……

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
const int N=500005;
int n,q,k,m;
int sum1[N],sum2[N];
struct node{
    int a,b;
}s[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.a==y.a?x.b>y.b:x.a>y.a;
}
signed main()
{
    n=read();
    for (int i=1;i<=n;i++)
        s[i].a=read(),s[i].b=read();
    sort(s+1,s+n+1,cmp);
    for (int i=1;i<=500000;i++)
    {
        sum1[i]=sum1[i-1]+s[i].a,
        sum2[i]=sum2[i-1]+s[i].b;
    }
    q=read();
    while(q--)
    {
        k=read(),m=read();
        printf("%lld\n",sum1[m]*k+sum2[m]);
    }
    return 0;
}

T2 保温箱

原题目:CF730J

设$f_{i,j}$表示选了$i$个箱子,其总体积为$j$,最多能装多少$a_i$。然后这题就转化成了一个$01$背包问题。直接搞就行了。注意按照$b$从大到小排序(虽然我不知道为什么……)时间复杂度$O(n\sum b)$。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=109;
struct Bot
{
    int a,b;
    bool operator < (const Bot &x)const
    {
        return b>x.b;
    }
} p[N];
int n,f[N*N][N],k,t,sum,ans;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&p[i].a),sum+=p[i].a;
    for(int i=1;i<=n;i++) scanf("%d",&p[i].b);
    sort(p+1,p+1+n);int tmp=0;
    for(int i=1;i<=n;i++)
    {
        if(tmp>=sum) break;
        tmp+=p[i].b;k=i;
    }
    memset(f,-0x3f,sizeof f);
    f[0][0]=0;
    printf("%d ",k);
    for(int i=1;i<=n;i++)
        for(int j=tmp;j>=p[i].b;j--)
            for(int K=1;K<=k;K++)
                f[j][K]=max(f[j][K],f[j-p[i].b][K-1]+p[i].a);
    for(int i=sum;i<=tmp;i++)
        ans=max(ans,f[i][k]);
    printf("%d",sum-ans);
    return 0;
}  

T3 树林

题目大意:在一个$n\times m$的图中有一个四联通的树林。给定起点$(sx,sy)$,你可以在图上八联通地走动。现要求从起点走出一个环再回到起点使得这个环能将树林包住。求最小步数。

首先一个暴力的想法就是求出一个恰好能将树林包住的点然后枚举两个点求最短路。然而我没有调出来……正解是在其基础上优化:我们不妨将一个点彻底堵住,这样路径就分成了两部分,然后我们分别求出对于两部分的最短路,加起来就是总的最短路。

时间复杂度$O(nm)$。

代码:

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2005;
const int dx[]={-1,-1,-1,0,0,1,1,1};
const int dy[]={-1,0,1,-1,1,-1,0,1};
char mp[N][N]; 
int dis[N][N],sx,sy,kx,ky,n,m,ans=INT_MAX;
inline bool judge(int x,int y)
{
    if (x<1||x>n||y<1||y>m) return 0;
    if (mp[x][y]=='X') return 0;
    return 1;
}
inline void work()
{
    queue< pair<int,int> > q;
    memset(dis,0x3f,sizeof(dis));
    dis[sx][sy]=0;
    q.push(make_pair(sx,sy));
    while(!q.empty())
    {
        int x=q.front().first,y=q.front().second;q.pop();
        for (int i=0;i<8;i++)
        {
            int xx=x+dx[i],yy=y+dy[i];
            if (judge(xx,yy)&&dis[xx][yy]>dis[x][y]+1)
            {
                dis[xx][yy]=dis[x][y]+1;
                q.push(make_pair(xx,yy));
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%s",mp[i]+1);
        for (int j=1;j<=m;j++)
        {
            if (mp[i][j]=='*') sx=i,sy=j;
            if (mp[i][j]=='X')
                if (!kx||kx>i) kx=i,ky=j;
        }
    }
    for (int i=1;i<kx;i++) mp[i][ky]='X';
    work();
    for (int i=1;i<kx;i++)
        ans=min(ans,min(dis[i][ky-1],min(dis[i-1][ky-1],dis[i+1][ky-1]))+min(dis[i][ky+1],min(dis[i-1][ky+1],dis[i+1][ky+1]))+2);
    printf("%d",ans);
    return 0;
}

T4 扮猪吃老虎

又臭又长的sb题面……

题目大意:给定一棵$n$个节点的无根树,每个节点有权值$Power_i$和$Point_i$。现在给定初始值$Point_i$和最低分数$low$,选择两个叶子节点(可以相同)作为起点和终点然后依次经过路径上每个节点。当经过一个节点时,设人的分数为$Point_1$,力量为$Power_1$,节点的分数为$Point_2$,力量为$Power_2$。设$\Delta Power=Power_1-Power_2,\Delta Point=Point_1-Point_2,delta=2\times sig(\Delta Power)(\sqrt{|\Delta Power|+1}-1)-A\times sig(\Delta Point)(\sqrt{|\Delta Point+1|}-1)$。那么人的分数将会变为$Point_1+delta$,节点的分数将会变为$Point_2-delta$。求一个最小的力量值$Power$使得人最后的分数不小于$low$。

观察$delta$这个式子,它是关于$Power_1$单增的。所以我们可以二分这个$Power$。然后考虑树形DP:设$f_i$表示以$i$为根的子树中从叶子节点走到$i$分数的最大值。同时记录一下次大值,然后进行换根DP。比较一下以每个叶子节点为根时的最大值即可。注意特判起点和终点为同一叶子节点的情况。

时间复杂度$O(n\log t)$。

代码:

#include<cmath>
#include<cstdio>
#include<iostream>
using namespace std;
const int N=200005;
const int inf=1e9;
const double eps=1e-9;
int pw[N],pi[N],head[N],du[N],leaf[N],tot,cnt,id,n,v1,v2,A,rt;
double f[N],g[N],l=-inf,r=inf,now,ans,mx;
struct node{int next,to;}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)
{
    edge[++cnt]=(node){head[from],to};
    head[from]=cnt;
}
inline double sig(double x)
{
    if (fabs(x)<eps) return 0;
    return x>0?1:-1;
}
inline double calc(double v,double p1,double p2)
{
    double d1=now-p1,d2=v-p2;
    return 2*sig(d1)*(sqrt(fabs(d1)+1)-1)-A*sig(d2)*(sqrt(fabs(d2)+1)-1);
}
inline void dfs1(int x,int fa)
{
    if (du[x]==1) f[x]=(double)v1+calc(v1,pw[x],pi[x]),g[x]=-inf;
    else f[x]=g[x]=-inf;
    for (int i=head[x];i;i=edge[i].next)
    {
        int y=edge[i].to;
        if (y==fa) continue;
        dfs1(y,x);
        double val=f[y]+calc(f[y],pw[x],pi[x]);
        if (val>f[x]) g[x]=f[x],f[x]=val;
        else if (val>g[x]) g[x]=val;
    }
}
inline void dfs2(int x,int fa)
{
    if (du[x]==1) mx=max(mx,f[x]);
    double tmp=f[x];
    for (int i=head[x];i;i=edge[i].next)
    {
        int y=edge[i].to;
        if (y==fa) continue;
        double val=f[y]+calc(f[y],pw[x],pi[x]);
        if (val==f[x]) f[x]=g[x];
        val=f[x]+calc(f[x],pw[y],pi[y]);
        if (val>f[y]) g[y]=f[y],f[y]=val;
        else if (val>g[y]) g[y]=val;
        dfs2(y,x);
        f[x]=tmp;
    }
}
inline bool check(double v)
{
    mx=-inf,now=v;
    if (n==1) mx=(double)v1+calc(v1,pw[1],pi[1]);
    else
    {
        dfs1(rt,0);dfs2(rt,0);
        for (int i=1;i<=tot;i++)
            mx=max(mx,v1+calc(v1,pw[leaf[i]],pi[leaf[i]]));
    }
    return mx>=(double)v2;
}
int main()
{
    id=read();n=read();v1=read();v2=read();A=read();
    for (int i=1;i<n;i++)
    {
        int x=read(),y=read();
        add(x,y);add(y,x);
        du[x]++;du[y]++;
    }
    for (int i=1;i<=n;i++)
    {
        pw[i]=read(),pi[i]=read();
        if (du[i]==1) leaf[++tot]=i;
        else rt=i;
    }
    while(r-l>=eps)
    {
        double mid=(l+r)/2.0;
        if (check(mid)) ans=mid,r=mid-eps;
        else l=mid+eps;
    }
    printf("%.6lf",ans);
    return 0;
}

 

posted @ 2020-11-09 18:10  我亦如此向往  阅读(141)  评论(0编辑  收藏  举报