[SinGuaRiTy] 2017-07-24 NOIP2015 模拟赛

【SinGuLaRiTy-1030】 Copyright (c) SinGuLaRiTy 2017. All Rights Reserved.

对于所有题目: Time Limit: 1s | Memory Limit: 256MB

               

旅行 (travel)

题目描述

Mr_H 旗下的 n 个OIer 坐船外出旅行!
但是他们只有一艘船,虽然船能装下全部的Oier,但太拥挤将会影响众OIer 的心情,所以Mr_H决定选择一部分Oier 去。我们假设,每个人单独坐船的快乐程度是Ci,而船上每多一个人,他的快乐程度会减去Di。
现在你的任务是帮助Mr_H 计算,选择那些人,才能使船上所有人的快乐程度之和达到最大。

输入

第 1 行是一个整数n,表示OIer 的人数;
第 2 行有n 个整数,第i 个整数表示第i 个人人单独坐船的快乐程度Ci(1<=Ci<=10000);
第 3 行有n 个整数,第i 个整数表示每多1 人,第i 个人快乐程度的下降值Di(1<=Di<=10)。

输出

第 1 行一个整数,是最大的快乐程度之和;
第 2 行一个整数,是最大的快乐程度之和所对应的汽艇上的人数(若有多种方案,则输出人数最多的)。

样例数据

样例输入样例输出

6
10 10 10 10 10 9
2 2 2 2 2 3

18
3

 

 

 

 

 

<数据范围>

对于30%的数据,n<=20;
对于100%的数据,n<=1000。

解析

很明显,直接枚举是没有必要的。我们可以枚举人数 K,然后算出在这种情况下每个人的快乐程度。然后贪心地取前K 大的数,这样就得到了 O(n^2*logn) 的算法。

Code

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<cmath>

#define MAXN 1010
#define INF 2000000000

using namespace std;

int n,d[MAXN][MAXN];
int ans=-INF;

void init()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    scanf("%d",&d[1][i]);
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        for(int j=2;j<=n;j++)
        d[j][i]=d[j-1][i]-x;
    }
}

int main()
{
    init();
    int s;
    for(int i=1;i<=n;i++)
    {
        sort(d[i]+1,d[i]+1+n);
        if(d[i][n-i+1]<=0)
            break;
        int x=0;
        for(int j=0;j<i;j++)
            x+=d[i][n-j];
        if(ans<=x)
        {
            ans=x;
            s=i;
        }
    }
    printf("%d\n%d",ans,s);
    return 0;
}

数据 (data)

题目描述

Mr_H 出了一道信息学竞赛题,就是给 n 个数排序。输入格式是这样的:
试题有若干组数据。每组数据的第一个是一个整数 n,表示总共有 n 个数待排序;接下来 n 个整数,分别表示这n 个待排序的数。
例如:3 4 2 –1 4 1 2 3 4,就表示有两组数据。第一组有3 个数(4,2,-1),第二组有4个数(1,2,3,4)。可是现在Mr_H 做的输入数据出了一些问题。例如:2 1 9 3 2 按理说第一组数据有2 个数(1,9),第二组数据有3 个数,可是“3”后面并没有出现三个数,只出现了一个数“2”而已!
现在 Mr_H 需要对数据进行修改,改动中“一步”的含义是对文件中的某一个数+1 或-1,写一个程序,计算最少需要多少步才能将数据改得合法。

输入

第一行一个整数m,表示Mr_H 做的输入数据包含的整数个数。第二行包含m 个整数a[i],每个整数的绝对值不超过10000。

输出

一个整数,表示把数据修改为合法的情况下,最少需要多少步。

样例数据

样例输入1样例输出1

4
1 9 3 2

2
样例输入2样例输出2

10
4 4 3 5 0 -4 -2 -1 3 5

3

 

 

 

 

 

 

 

 

<数据范围>

对于 20%的数据,m<=10, |a[i]|<=5;
对于60%的数据,m<=5000, |a[i]|<=10000
对于100%的数据,m<=100000, |a[i]|<=10000

解析

先来谈谈图论的解法,简单的说就是连边然后找最短路:若a[i]>0,那么就说明这个数可以用来表示数列的长度,我们就从a[i]连一条边到下一个数列i+a[i]+1,并设定其权值为0——因为这里实际上不需要耗费任何操作。如果i+a[i]+1超过了n怎么办呢?我们就在任意两个相邻的数间连一条权值为1的边——这里的左移右移就相当于加和减的操作,是需要消耗布数的。注意:第一个点和第二个点不能连,会出毒的。另外,若a[1]<0,我们显然要将其变为正数,于是这样的话我们需要在ans后面直接加上a[1],即使a[1]变为正数的步数。至于最后的答案嘛,直接跑1~n的最短路就行了。

不过,这么玄(zhèng) 学(cháng) 的东西怎么能是标准解法呢?要看DP+优化的同学还是直接 [下载PDF题解] 好了。

Code

#include<cstdio>
#include<queue>
#include<vector>
#include<iostream>

#define need 110003
#define inf 1e9

using namespace std;

int n,nn;

inline void Read(int &d)
{
    char t=getchar();bool mark=false;
    while(t<'0'||t>'9') {if(t=='-') mark=true;t=getchar();}
    for(d=0;!(t<'0'||t>'9');t=getchar()) d=(d<<1)+(d<<3)+t-'0';
    if(mark) d=-d;
}

struct bian
{
    int la,en,len;
};
vector<bian> w;

int tot,fi[need];

void add(const int &a,const int &b,const int &c)
{
    tot++;
    w.push_back((bian){fi[a],b,c});
    fi[a]=tot;
}

int s,e;

struct fy
{
    int dis,id;
    bool operator< (const fy &b) const
    {
        return dis>b.dis;
    }
};
priority_queue<fy> q;

bool getans[need];
int dis[need];

void dijkstra(int s)
{
    for(int i=1;i<=nn;i++)
    {
        dis[i]=inf;
        getans[i]=false;
    }
    q.push((fy){0,s});
    dis[s]=0;
    int x,y,t;
    while(!q.empty())
    {
        while(!q.empty()&&getans[q.top().id])
            q.pop();
        x=q.top().id;
        if(x==e)
            return ;
        getans[x]=true;
        q.pop();
        for(t=fi[x];t;t=w[t].la)
        {
            y=w[t].en;
            if(getans[y])
                continue;
            if(dis[y]>dis[x]+w[t].len)
            {
                dis[y]=dis[x]+w[t].len;
                q.push((fy){dis[y],y});
            }
        }
    }
}

int main()
{
    w.resize(1);
    int n;
    scanf("%d",&n);
    int dans=0;
    int a;
    Read(a);
    if(a<0)
        dans=-a,add(1,2,0);
    else
        add(1,a+2,0);
    nn=max(nn,a+2);
    for(int i=2,a;i<=n;i++)
    {
        Read(a);
        if(a>=0)
            add(i,i+a+1,0);
        nn=max(nn,i+a+1);
        add(i,i+1,1);
        add(i+1,i,1);
    }
    for(int i=n+1;i<=nn;i++)
    {
        add(i,i+1,1);
        add(i+1,i,1);
    }
    s=1;
    e=n+1;
    dijkstra(s);
    cout<<dis[e]+dans;
    return 0;
}

<DP+优化 Code>

#include<iostream>//乱搞
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;

const int Max = 100000;
const int INF = 0x3f3f3f3f;

struct node{
    int val, pos;
    node(){}
    node(int a, int b){ val = a, pos = b;}
    bool operator < (const node & X) const{
        return val > X.val;
    }
};

int N;
int A[Max + 5], Dp[Max + 5];
priority_queue<node>Q;

bool getint(int & num){
    char c; int flg = 1;    num = 0;
    while((c = getchar()) < '0' || c > '9'){
        if(c == '-')    flg = -1;
        if(c == -1) return 0;
    }
    while(c >= '0' && c <= '9' ){
        num = num * 10 + c - 48;
        if((c = getchar()) == -1)   return 0;
    }
    num *= flg;
    return 1;
}

int main(){
    getint(N);
    for(int i = 1; i <= N; ++ i)    getint(A[i - 1]), A[i - 1] += i;
    Q.push(node(A[0], A[0]));
    memset(Dp, 0x3f, sizeof Dp );
    int minnum = INF;
    for(int i = 1; i <= N; ++ i){
        while(! Q.empty() && Q.top().pos < i){
            minnum = min(minnum, Q.top().val - 2 * Q.top().pos);
            Q.pop();
        }
        Dp[i] = min(Dp[i], minnum + i);
        if(! Q.empty()) Dp[i] = min(Dp[i], Q.top().val - i);
        Q.push(node(Dp[i] + A[i], A[i]));
    }
    printf("%d\n", Dp[N]);
    return 0;
}
View Code

业务 (business)

题目描述

Mr_H 谋得一份兼职——货车司机,从此以后他将会开着货车穿行在C 国的各大城市之间。
C 国中有n 座城市(编号为1~n),并且有m 条双向公路,每条公路连接两座不同的城市。货车从任意一座城市出发都可以抵达任意另一座城市。在每条公路上,都有一个收费站,通过的车辆需要交纳一定过路费。可能有多条公路连接相同的两座城市。
为了增加财政收入,C 国还在每座城市也设置了收费站。并且规定,车辆从一座城市到另一座城市的费用是,所经过公路费用和,加上所经过的城市中费用的次大值(这里的次大可以和最大相同,但是城市不同)。
现在Mr_H 告诉你今年k 次业务运送货物的起点、终点城市列表,请你帮忙计算,每次业务需要交纳的最低过路费。

输入

第一行包含三个用一个空格隔开的整数:n,m,k。其意义如题目描述。
第 2 到第 n+1 行:第i+1 行包含一个单独的整数 c(1<=c<=100000),表示城市i 的费用。
接下来的m 行,每行包含三个整数a,b,w,表示一条公路连接城市a 和城市b(1<=a,b<=n),其过路费为w(1<=w<=100000)。
最后的 k 行,每行包含两个整数:s,t,表示一次业务的起点和终点(1<=s,t<=n 且 s!=t)。

输出

共k 行,每行一个整数,表示从城市s 到t 的最少过路费。

样例数据

样例输入样例输出

5 7 3
2
5
3
3
4
1 2 3
1 3 2
2 5 3
5 3 1
5 4 1
2 4 3
3 4 4
1 3
1 4
2 3

4
7
8

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<样例解释>

包含 5 个城市的样例图形如下:

●城市1 到城市3 的道路的“边过路费”为 2,“点过路费”为 2(城市2 的费用为次大)。所以总的花费为 2+2=4 。
●要从城市1 走到城市4 ,可以从城市1 走到城市3,再走到城市5,最后到达城市4 。如果这么走的话,需要的“边过路费”为 2+1+1=4,需要的点过路费为 3(城市3 或城市4 的点过路费次大),所以总的花费为 4+3=7。
●从城市2 走到城市3 的最佳路径是从城市2 出发,抵达城市5,最后到达城市3,这么走的话,边过路费为 3+1=4,点过路费为4,总花费为 4+4=8。

<数据范围>

对于 20%的数据,n<=10, m<=20
对于50%的数据,n<=100, m<=5000
对于100%的数据,1<=n<=250 , 1<=m<=10000 , 1<=k<=10000 ,其中有50%的数据点权没有重复。

解析

(考试的时候用的DFS,没D出来)

令:ans[x][y]=从x 到y 的最小过路费!计算ans[x][y]可以用DIJKstra 算法或SPFA 算法!
令:dist[i][0]=从k 出发到i 点经过的点费用最大值为a[k]的情况的最小边费用和;dist[i][1]=从k 出发到i 点经过的点费用次大值为a[k]的情况的最小边费用和
于是有: ans[x][y]=min{dist[x][0]+dist[y][1],dist[x][1]+dist[y][0]}+a[k]}其中 1<=k<=n
计算出ans[x][y]后,回答询问就很简单了。
时间复杂度 O(n2logm+k) (DIJKstra 堆优化后的时间复杂度)

Code

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<queue>

#define MAXN 250
#define MAXM
#define DIST(x)(tmp[x.pos][x.hm])
#define VIS(x)(vis[x.pos][x.hm])
#define INF 0x3f3f3f3f

using namespace std;

typedef long long int LL;

int getint()
{
    int rn=0;
    char c=getchar();
    while(c<'0'||'9'<c)
        c=getchar();
    while('0'<=c&&c<='9')
    {
        rn=rn*10+c-'0';
        c=getchar();
    }
    return rn;
}

int G[MAXN+10][MAXN+10];
int Val[MAXN+10];
int dist[MAXN+10][MAXN+10];
int N,M,K;

int tmp[MAXN+10][2];
bool vis[MAXN+10][2];

struct point
{
    int pos,w;
    bool hm;
    point(){}
    point(int a,bool b,int c){pos=a,hm=b,w=c;}
    bool operator < (const point &a)const
    {
        return w>a.w;
    }
};

priority_queue<point>que;
void insert(point newed)
{
    if(DIST(newed)<newed.w)
        return;
    DIST(newed)=newed.w;
    que.push(newed);
}

void Dijkstra(int sc)
{
    memset(tmp,0x3f,sizeof(tmp));
    memset(vis,0,sizeof(vis));
    insert(point(sc,0,0));
    point now,newed;
    while(!que.empty())
    {
        now=que.top();
        que.pop();
        if(VIS(now))
            continue;
        else
            VIS(now)=1;
        for(int v=1;v<=N;++v)
            if(G[now.pos][v])
            {
                newed.pos=v;
                newed.hm=now.hm;
                newed.w=DIST(now)+G[now.pos][v];
                if(Val[v]>Val[sc])
                {
                    if(now.hm)
                        continue;
                    else
                    {
                        newed.hm=1;
                        insert(newed);
                    }
                }
                else if(Val[v]==Val[sc])
                {
                    if(v==sc)
                        insert(newed);
                    else
                    {
                        insert(newed);
                        newed.hm=1;
                        insert(newed);
                    }
                }
                else
                    insert(newed);
            }
    }
}

int main()
{
    scanf("%d%d%d",&N,&M,&K);
    int i,j;
    for(i=1;i<=N;++i)
        scanf("%d",&Val[i]);
    int a,b,c;
    for(i=1;i<=M;++i)
    {
        scanf("%d%d%d",&a,&b,&c);
        G[a][b]=(!G[a][b]||G[a][b]>c?c:G[a][b]);
        G[b][a]=(!G[b][a]||G[b][a]>c?c:G[b][a]);
    }
    memset(dist,0x3f,sizeof(dist));
    for(int sc=1;sc<=N;++sc)
    {
        Dijkstra(sc);

        for(i=1;i<=N;++i)
            for(j=1;j<=N;++j)
                dist[i][j]=min(dist[i][j],min(tmp[i][0]+tmp[j][1],tmp[i][1]+tmp[j][0])+Val[sc]);
    }
    for(i=1;i<=K;++i)
    {
        scanf("%d%d",&a,&b);
        printf("%d\n",dist[a][b]);
    }
    return 0;
}

 

Time: 2017-07-24

转载于:https://www.cnblogs.com/SinGuLaRiTy2001/p/7229673.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值