7.14比赛题解

比赛题很水呀

T1.同行

题目描述

给定一个 N 个点 M 条边的无向图,其中 Bessie 在 1 号点,Elsie 在 2 号点,
它们的目的地为 N 号点。Bessie 每经过一条边需要消耗 B 点能量,Elsie 每经过一条
边需要消耗 E 点能量。当它们相遇时,它们可以一起行走,此时它们每经过一条边需要消
耗 P 点能量。求它们两个到达 N 号点时最少消耗多少能量?

输入

第一行,五个整数 B, E, P, N,M (B,E,P<=40,000)
下面 m 行每行两个数 u,v 表示一条无向边(u,v)。 (1<=u,v<=n)

输出

一个整数,表示最小能量消耗。。

不说了,水的一匹。建无向图然后跑三个最短路即可。

代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#define LL long long
using namespace std;
const LL maxn=40010,inf=1e18;
vector <pair<LL,LL> > ma1[maxn],ma2[maxn],fanma[maxn];
priority_queue <pair<LL,LL> > heap;
LL dis1[maxn],dis2[maxn],disn[maxn],n,m;
void dij(LL x,vector <pair<LL,LL> > *ma,LL *dis){
    LL i,w;vector <pair<LL,LL> > ::iterator it;
    for(i=1;i<=n;i++)dis[i]=inf;
    while(!heap.empty())heap.pop();
    dis[x]=0;heap.push(make_pair(0,x));
    while(!heap.empty()){
        w=-heap.top().first;x=heap.top().second;heap.pop();
        if(w!=dis[x])continue;
        for(it=ma[x].begin();it!=ma[x].end();it++)if(dis[x]+it->first<dis[it->second]){
            dis[it->second]=dis[x]+it->first;
            heap.push(make_pair(-dis[it->second],it->second));
        }
    }
}
int main(){
    //freopen("walk.in","r",stdin);
    //freopen("walk.out","w",stdout);
    LL i,j,b,e,p,x,y,ans=inf;
    scanf("%lld%lld%lld%lld%lld",&b,&e,&p,&n,&m);
    for(i=1;i<=m;i++){
        scanf("%lld%lld",&x,&y);
        ma1[x].push_back(make_pair(b,y));
        ma1[y].push_back(make_pair(b,x));
        ma2[y].push_back(make_pair(e,x));
        ma2[x].push_back(make_pair(e,y));
        fanma[y].push_back(make_pair(p,x));
        fanma[x].push_back(make_pair(p,y));//存边
    }
    dij(1,ma1,dis1);
    dij(2,ma2,dis2);
    dij(n,fanma,disn);//三次dijstar()
    for(i=1;i<=n;i++)ans=min(ans,dis1[i]+dis2[i]+disn[i]);
    printf("%lld",ans);
    return 0;
}

T2.长跑

问题描述

在二维平面上有 N 个点,从(x1,y1)到(x2,y2)的代价为|x1-x2|+|y1-y2|。
求从 1 号点出发,按从 1 到 N 的顺序依次到达每个点的最小总代价。
你有 K 次机会可以跳过某个点,不允许跳过 1 号点或 N 号点。。

输入

第一行 N,K (2<=N<=500,0<=K<=N-2)
接下来 N 行每行两个数(x,y)表示第 i 个点的座标。
(-1000 <= x <= 1000, -1000 <= y <= 1000)

输出

一行,1 整实数,表示最小代价。

水水水题。O(n3) dp暴力乱搞即可。以为要爆(开玩笑一个小目标的复杂度),还想了很多优化,最后还是把动归写起。
状态:f[i][j] 表示跑到第i点跳了j次使用的最小代价。
方程:f[i][j]=min(f[l][il1]+dis(pointi,pointl)) 注意il1>=0
根本没有优化的余地了。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define LL long long
using namespace std;
const LL maxn=510,inf=1e18;
LL f[maxn][maxn],x[maxn],y[maxn],n;
LL getdis(LL a,LL b){
    return abs(x[a]-x[b])+abs(y[a]-y[b]);
}
int main(){
    //freopen("run.in","r",stdin);
    //freopen("run.out","w",stdout);
    LL i,j,k,o;
    scanf("%lld%lld",&n,&k);
    for(i=1;i<=n;i++)scanf("%lld%lld",&x[i],&y[i]);
    memset(f,3,sizeof(f));
    f[1][0]=0;
    for(i=2;i<=n;i++)
        for(j=1;j<i;j++){
            for(o=0;o+i-j-1<=k;o++){
                f[i][o+i-j-1]=min(f[i][o+i-j-1],f[j][o]+getdis(i,j));
            }
        }//动归部分
    printf("%lld",f[n][k]);
    return 0;   
}

T3.糖果

问题描述

幼儿园的小孩们收到了一个有 M 颗糖果的大包裹,现在要把这些糖果分给 N
个小孩。 每一个小孩都给出了一个期望的糖果数,如果没有达到他的期望值
a[i],小孩就会生气。每差一个糖果,小孩的生气指数就会增加。他生气的程
度等于他少得到的糖果数的平方。比如,Mirko 想要得到 32 个糖果,但是只得
到了 29 个。他少了 3 个,所以他的生气指数是 9。
不幸的是,糖果数不足以满足所有小孩的期望。所以我们应该采取最优的分
配方法,使得最后小孩们的生气指数之和最小。

输入

第 1 行:2 个整数 M,N
第 2..N+1 行:第 i+1 行表示第 i 个小朋友期望值 a[i]。

输出

第 1 行:1 个整数,表示最小的生气指数总和。

可以转化为另一个问题:将一个数分成n 个数使它们的平方和尽量小即可。用柯西不等式证明得最小时所有数均相同(而且这很显然吧
这时我们算出有的糖果与需要得糖果的差值记为del=ma[i] 。将del 首先平均分配最优地在每个小朋友身上(注意必须是整数),即:

ping=del/n
rest=delpingn
ans=rest(ping+1)2+(nrest)ping2.....(1)

完了?没有。我们考虑到有的小朋友需要的糖果数小于平均数,而我们不可能使他得到的糖果数为负,所以我们还要将原来的a[i] 排序后一个个扫,若存在需要数比平均数小则更新答案和平均数,最后再将答案加上(1) 式即可。

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
const LL maxn=100010;
LL sum=0,n,m,a[maxn];
int main(){
    freopen("candy.in","r",stdin);
    freopen("candy.out","w",stdout);
    LL ping,sheng,i,j,t,del,ans=0,cnt;
    scanf("%lld%lld",&m,&n);
    for(i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        sum+=a[i];
    }
    sort(a+1,a+n+1);
    del=sum-m;
    if(del<=0){
        printf("0");return 0;
    }
    ping=del/n;
    i=1;
    cnt=n;
    while(a[i]<ping){
        ans+=a[i]*a[i];
        cnt--;del-=a[i];
        ping=del/cnt;
        i++;
    }//处理比平均数小的小朋友的情况
    sheng=del-ping*cnt;
    printf("%lld",sheng*(ping+1)*(ping+1)+(cnt-sheng)*ping*ping+ans);
    return 0;
}

T4.抗议

问题描述

约翰家的 N 头奶牛正在排队游行抗议。一些奶牛情绪激动,约翰测算下来,
排在第 i 位的奶牛的理智度为 Ai,数字可正可负。
约翰希望奶牛在抗议时保持理性,为此,他打算将这条队伍分割成几个小组,
每个抗议小组的理智度之和必须大于或等于零。奶牛的队伍已经固定了前后顺
序,所以不能交换它们的位置,所以分在一个小组里的奶牛必须是连续位置的。
除此之外,分组多少组,每组分多少奶牛,都没有限制。
约翰想知道有多少种分组的方案,由于答案可能很大,只要输出答案除以
1000000009 的余数即可。

输入格式

第一行:单个整数 N
接下来 N 行,每行有一个整数 Ai。

输出格式

一个整数:表示分组方案数模 1000000009 的余数

数据范围

对于 50% 的数据,1<= N <=200
对于 100%的数据,1 ≤ N ≤ 105 −105 ≤ Ai ≤ 105

很好的一道动归递推套树状数组的题。
状态:f[i] 表示前i 头奶牛能有的方案总数。
方程:f[i]=0i1f[j](sum[i]sum[j]>=0) 其中f[0]=1,sum 表示奶牛理智值的前缀和。
时间复杂度:O(n2)
显然超时,但考虑条件限制中可变成sum[i]>=sum[j] 可用树状数组维护前sum[j]f[j] 和(类似于逆序对的样子)。
将前缀和排序后编号维护树状数组即可。
时间复杂度:O(nlog2n)

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
const LL mod=1000000009,maxn=100010;
LL tree[maxn],f[maxn],a[maxn],c[maxn],k[maxn],n;
LL lowbit(LL x){
    return x&(-x);
}
void modify(LL x,LL del){
    for(;x<=n+1;x+=lowbit(x))tree[x]=(tree[x]+del)%mod;
}
LL getsum(LL x){
    LL sum=0;
    for(;x>0;x-=lowbit(x))
        sum=(sum+tree[x])%mod;
    return sum;
}
struct node{
    LL x,w;
}p[maxn];
bool cmp(node a,node b){
    return a.w<b.w;
}
int main(){
    //freopen("protest.in","r",stdin);
    //freopen("protest.out","w",stdout);
    LL i,j;
    scanf("%lld",&n);
    for(i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(i=1;i<=n;i++){
        p[i].w=p[i-1].w+a[i];
        p[i].x=i;
    }
    sort(p,p+n+1,cmp);
    for(i=0;i<=n;i++){
        if(i&&p[i].w==p[i-1].w)k[i]=k[i-1];
        else k[i]=i+1;
        c[p[i].x]=k[i];//离散化编号 
    }
    modify(c[0],1);
    for(i=1;i<=n;i++){
        f[i]=getsum(c[i]);
        modify(c[i],f[i]);
    }
    printf("%lld",f[n]);
    return 0;
}

总结与反思:

需要改进的:
T1:考试时没写vector指针,代码很冗杂,现在学会了。
T2:最开始不都不敢写暴力dp,还去想优化。暴力出奇迹,必须用暴力呀。
T4:一定要检查自己是否忘记取余了!!!!!并且树状数组要避免负数与0。博主现在都没搞懂自己之前代码哪里错了。差点AK,唉。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章