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,唉。

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