NOIP2018模拟试题

所有试题限制都为512MB,1Sec

试题1

新的开始

【题目描述】

发展采矿业当然首先得有矿井, 小FF花了上次探险获得的千分之一的财富请人在岛上挖了n口矿井, 但他似乎忘记考虑的矿井供电问题……
为了保证电力的供应, 小FF想到了两种办法:

  1. 在这一口矿井上建立一个发电站, 费用为v(发电站的输出功率可以供给任意多个矿井)。
  2. 将这口矿井与另外的已经有电力供应的矿井之间建立电网, 费用为p。
    小FF希望身为”NewBe_One" 计划首席工程师的你帮他想出一个保证所有矿井电力供应的最小花费。

【输入格式】

第一行一个整数n, 表示矿井总数。
第2~n+1行,每行一个整数, 第i个数v[i]表示在第i口矿井上建立发电站的费用。
接下来为一个n*n的矩阵P, 其中p[ i , j ]表示在第i口矿井和第j口矿井之间建立电网的费用(数据保证有p[ i, j ] = p[ j, i ], 且 p[ i, i ]=0)。

【输出格式】

仅一个整数, 表示让所有矿井获得充足电能的最小花费。

【输入样例】

4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0

【输出样例】

9

输出样例说明:
小FF可以选择在4号矿井建立发电站然后把所有矿井都与其建立电网,总花费是3+2+2+2 = 9。

【数据范围】

对于30%的数据: \(1 \leq n \leq 50\);
对于100%的数据: \(1 \leq n \leq 300; 0 \leq v[i], p[i,j] \leq 10^5.\)

分析

法一:考场做法

可以用堆来维护贪心跑最短路,把点的距离初值设为v[i],不同的是松弛的时候只改为边权,且要用vis记录是否已经计算过了,计算过了不能再计算以防循环松弛出错,时间复杂度\(O(M \log M)\)

#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
    T data=0;
    int w=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            w=-1;
        ch=getchar();
    }
    while(isdigit(ch))
        data=10*data+ch-'0',ch=getchar();
    return x=data*w;
}
typedef long long ll;
typedef pair <int,int> pii;
const int INF=0x7fffffff;
/*
4
5
4
4
3 
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
*/
const int MAXN=307,MAXM=90007;

struct Edge
{
    int nx,to,w;
}E[MAXM];
int head[MAXN],ecnt;

void addedge(int x,int y,int w)
{
    E[++ecnt].to=y,E[ecnt].w=w;
    E[ecnt].nx=head[x],head[x]=ecnt;
}

bool vis[MAXN];
int d[MAXN];
priority_queue <pii> Q;

int main()
{
  freopen("newstart.in","r",stdin);
  freopen("newstart.out","w",stdout);
    int n;
    read(n);
    for(int i=1;i<=n;++i)
        read(d[i]);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
        {
            int w;
            read(w);
            if(i==j)
                continue;
            addedge(i,j,w);
        }
    for(int i=1;i<=n;++i)
        Q.push(pii(-d[i],i));
    while(!Q.empty())
    {
        int x=Q.top().second;
        Q.pop();
        if(vis[x])
            continue;
        vis[x]=1;
        for(int i=head[x];i;i=E[i].nx)
        {
            int y=E[i].to,w=E[i].w;
            if(!vis[y]&&d[y]>w)
            {
                d[y]=w;
                Q.push(pii(-d[y],y));
            }
        }
    }
    ll ans=0;
    for(int i=1;i<=n;++i)
        ans+=d[i];
    printf("%lld\n",ans);
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}

法二:标解

题目很像一个最小生成树问题,但又不同于一般的最小生成树,因为某些点可以不与其他点相连而单独建造发电站。 那么我们是否能将这个问题转化为最小生成树呢?
答案显然。
我们可以新建一个0号节点,并与1~n号点相连,边权为当前节点建造发电站的费用, 这样便解决了只建电站而不与其他点连边的问题,那么我们对于这个新的图做一次最小生成
树便能得到答案了。
标程是用Kruskal算法,时间复杂度为\(O(M\log M+N)\), \(M\)为边数。然而std是Prim

#include<cstdio>
#include<algorithm>

using namespace std;

#define For( i , _Begin , _End ) for( int i = (_Begin) , i##END = (_End) ; i <= (i##END) ; i++ )

template< typename Type >inline void Read( Type &In ){
    In = 0;char ch = getchar();
    for( ;ch> '9'||ch< '0';ch=getchar() );
    for( ;ch>='0'&&ch<='9';ch=getchar() )In = In * 10 + ch - '0';
}

static const int Inf = 0x7fffffff;
static const int maxn = 302;

int n , ans;
int mp[maxn][maxn];

namespace preparation{
    
    void Init(){
        Read( n );
        For( i , 1 , n )Read( mp[ n + 1 ][i] ) , mp[i][ n + 1 ] = mp[ n + 1 ][i];
        For( i , 1 , n )For( j , 1 , n )Read( mp[i][j] );
    }
    
};

namespace working{
    
    bool Vis[maxn];
    int Dis[maxn];
    
    void prim(){
        For( i , 0 , n )Dis[i] = Inf;
        For( i , 0 , n ){
            int p = 0;
            For( j , 1 , n + 1 )
                if( !Vis[j] && Dis[j] < Dis[p] )
                    p = j;
            Vis[p] = true;
            For( j , 1 , n + 1 )
                if( !Vis[j] )
                    Dis[j] = min( Dis[j] , mp[p][j] );
        }
        For( i , 1 , n )ans += Dis[i];
    }
    
};

#define Judge

int main(){
    
    #ifdef Judge
        freopen("newstart.in","r",stdin);
        freopen("newstart.out","w",stdout);
    #endif
    
    preparation :: Init();
    
    working :: prim();
    
    printf("%d\n",ans);
    
    return 0;
}

试题二

工业时代

【试题描述】

小FF的第一片矿区已经开始运作了, 他着手开展第二片矿区……
小FF的第二片矿区, 也是”NewBe_One“计划的核心部分, 因为在这片矿区里面有全宇宙最稀有的两种矿物,科学家称其为NEW矿和BE矿。
矿区是被划分成一个n*m的矩形区域。 小FF探明了每一小块区域里的NEW矿和BE矿的蕴藏量, 并且小FF还在矿区的北边和西边分别设置了NEW矿和BE矿的收集站。你的任务是设计一个管道运输系统,使得运送的NEW矿和BE矿的总量最多。
管道的型号有两种,一种是东西向,一种是南北向。在一个格子内你能建造一种管道,但不能两种都建。如果两个同类型管道首位相接,它们就可以被连接起来。
另外这些矿物都十分不稳定,因此它们在运送过程中都不能拐弯。这就意味着如果某个格子上建有南北向管道,但是它北边的格子建有东西向管道,那么这根南北向管道内运送的任何东西都将丢失。进一步地,运到NEW矿收集站的BE矿也会丢失,运到BE矿收集站的NEW矿也会丢失。

【输入格式】

第一行包含两个整数n和m,表示矿区大小。
以下n行,每行m个整数,其中第i行第j个整数G[ i , j ] 描述各个格子上的BE矿数量。接下来以类似的矩阵表示各个格子上的NEW矿数量。

【输出格式】

仅一个整数, 表示最多可以采集到的NEW矿和BE矿的总量。

【输入样例】

4 4
0 0 10 9
1 3 10 0
4 2 1 3
1 1 20 0
10 0 0 0
1 1 1 30
0 0 5 5
5 10 10 10

【输出样例】

98

【数据范围】

对于30%的数据: $ 0 \leq n,m \leq 100$;
对于100%的数据: $ 0 \leq n, m \leq 1000$; \(0 \leq G[ i, j ] \leq 1000\).

分析

法一:考场做法

\(f(i,j,0/1)\)表示前i行,前j列,其中第(i,j)决策向南北0(东西1)的最大收益
\(g(i,j,0/1)\)表示前i行,前j列,其中第(i,j)决策向南北0(东西1)时转移无关的那个格子的管道安装方向。
然后就可以做出复杂的状态转移,时间复杂度\(O(NM)\)

#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
    T data=0;
    int w=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            w=-1;
        ch=getchar();
    }
    while(isdigit(ch))
        data=10*data+ch-'0',ch=getchar();
    return x=data*w;
}
typedef long long ll;
const int INF=0x7fffffff;

const int MAXN=1e3+7;
int ne[MAXN][MAXN],be[MAXN][MAXN];
int f[MAXN][MAXN][2],g[MAXN][MAXN][2];
/*
2 2
5 2
3 4
1 0
99 6
*/
int main()
{
  freopen("industry.in","r",stdin);
  freopen("industry.out","w",stdout);
    int n,m;
    read(n);read(m);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            read(be[i][j]);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            read(ne[i][j]);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
        {
            g[i][j][0]=f[i][j-1][0]<f[i][j-1][1];
            f[i][j][0]=f[i-1][j][0]-f[i-1][j-1][g[i-1][j][0]]+ne[i][j]+f[i][j-1][g[i][j][0]];
            g[i][j][1]=f[i-1][j][0]<f[i-1][j][1];
            f[i][j][1]=f[i][j-1][1]-f[i-1][j-1][g[i][j-1][1]]+be[i][j]+f[i-1][j][g[i][j][1]];
//          cerr<<i<<" "<<j<<" f0="<<f[i][j][0]<<" f1="<<f[i][j][1]<<endl;
        }
    printf("%d\n",max(f[n][m][0],f[n][m][1]));
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}

法二:标解

这个题可以用动态规划来解决。
令A[ i , j ] 为第j列1~i个格子New矿总和, B[ i , j ]为第i行前j个格子Be矿总和。
F[ i, j, 0] 定义为以(i, j) 为右下角,第( i , j )号格子建立BE矿管道的最大采矿量。
F[ i, j, 1] 定义为以(i, j) 为右下角,第( i , j )号格子建立NEW矿管道的最大采矿量。
那么可得到如下转移方程:
\(F[i,j,0] := \max\{ F[ i-1 , j , 0 ], F[ i-1, j, 1 ]\} + B[ i, j ]\);
\(F[i,j,1] := \max\{ F[ i , j-1 , 0 ], F[ i, j-1, 1 ]\} + A[ i, j ]\);
而最终答案便是\(\max\{F[ n, m, 0 ], F[ n, m, 1 ]\}\)
时间复杂度: \(O(NM)\)

#include<cstdio>
#include<algorithm>
using namespace std;

const int sm = 1000+10;

int n,m;
int nw[sm][sm],be[sm][sm],nn[sm][sm];
int f[sm][sm][2];

char ch;
void read(int &x) {
    x=0,ch=getchar();
    while(ch>'9'||ch<'0') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
int Max(int a,int b) { return a>b?a:b; }

int main() {

    freopen("industry.in","r",stdin);
    freopen("industry.out","w",stdout);

    read(n),read(m);
    
    for(int i=1,x;i<=n;++i)
        for(int j=1;j<=m;++j)
            read(x),be[i][j]=be[i][j-1]+x;
            
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            read(nn[i][j]);
    for(int j=1;j<=m;++j) 
        for(int i=1;i<=n;++i)
            nw[i][j]=nw[i-1][j]+nn[i][j];
    
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j) {
            f[i][j][0]=Max(f[i-1][j][0],f[i-1][j][1])+be[i][j];
            f[i][j][1]=Max(f[i][j-1][0],f[i][j-1][1])+nw[i][j];
        }   
    
    printf("%d\n",Max(f[n][m][0],f[n][m][1]));
    
    return 0;
}

试题三

杀蚂蚁

【题目描述】

说“善有善报,恶有恶报,不是不报……”。 小FF一心只顾自己企业的壮大而没顾及自己的采矿业对Greed Island上生态环境的破坏, Greed Island的环境日益恶劣。 终于,岛上的蚂蚁们变异了,它们决定对小FF的矿区进行攻击,欲将岛上的人类驱逐出去……面对蚂蚁们的进攻, 人类节节败退。无奈之下, 小FF请来了全宇宙最强的防御系统制造商派来的工程机器人——SCV,希望能够阻挡蚂蚁的攻势。
经过小FF的研究,他发现蚂蚁们每次都走同一条长度为n个单位的路线进攻, 且蚂蚁们的经过一个单位长度所需的时间为T秒。也就是说,只要小FF在这条路线上布防且给蚂蚁造成沉痛伤害就能阻止蚂蚁的进军。
SCV擅长制造的防御塔有三种,分别是激光塔,放射塔和干扰塔, 他们可以在一个单位长度内修建一座防御塔。三种防御塔的作用如下:
激光塔: 使用高能激光,当蚂蚁从塔前经过时每秒对蚂蚁造成r点伤害。
放射塔: 释放放射性元素, 当蚂蚁经过这座塔后,每一秒受到g点伤害。
干扰塔: 干扰塔负责干扰蚂蚁们的信息素,使得蚂蚁在经过这座塔后,经过之后每一个单位长度的时间变成T+b。
当然, 放射塔和干扰塔的效果是可以叠加的, 也就是说如果蚂蚁经过x座放射塔,那么蚂蚁每秒钟会受到xg点伤害; 同理,如果蚂蚁经过y座干扰塔, 那么蚂蚁经过一个单位长度的时间将变为T+yb。
现在距离蚂蚁的下一轮进攻还有足够长的时间,你这个“NewBe_One”计划的首席工程师现在被任命为战略总参谋长, 因此你必须设计一个给蚂蚁们造成最大伤害的布塔方案。

【输入格式】

输入数据仅一行, 5个整数 n, r, g, b, T中间用一个空格隔开。 它们分别表示你可以布防的总长度, 激光塔的效果、 放射塔的效果和干扰塔的效果。

【输出格式】

输出仅一个整数, 代表你的方案给蚂蚁带来的最大伤害值。

【输入样例】

5 4 3 2 1

【输出样例】

82

输出样例解释:
第1号位置为放射塔, 第2,3号位置建造干扰塔,第4,5号位置建造激光塔。
1号-2号 时间为1秒 伤害值为0
2号-3号 时间为1秒 伤害值为31=3
3号-4号 时间为3秒 伤害值为3
3=9
4号-5号 时间为5秒 伤害值为45+35=35
5号格子走出去 时间为5秒 伤害值为45+35=35
总伤害值为:0+3+9+35+35=82

【数据范围】

对于30%的数据: $ 1 \leq n \leq 20$;
对于60%的数据: \(1 \leq n \leq 1024; 0 \leq r, g, b \leq 65536; 0 \leq T \leq 3\);
对于另外40%的数据: \(1 \leq n \leq 400; 0 \leq r, g, b \leq 2^{31}-1; 0 \leq T \leq 1000\).

分析

法一:考场部分分

容易发现激光塔肯定在最后放,枚举激光塔数量,再二进制枚举前面的决策,最后检验。复杂度\(O(N^2 2^N)\)

#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<ctime>
#include<iostream>
#include<string>
#include<vector>
#include<list>
#include<deque>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<complex>
#pragma GCC optimize ("O0")
using namespace std;
template<class T> inline T read(T&x){
    T data=0;
    int w=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            w=-1;
        ch=getchar();
    }
    while(isdigit(ch))
        data=10*data+ch-'0',ch=getchar();
    return x=data*w;
}
typedef long long ll;
const int INF=0x7fffffff;

int n,r,g,b,t;
ll ans;

void cal(int x,int s)
{
    ll sum=0,G=0,T=t;
    for(int i=0;i<n-x;++i)
    {
        sum+=G*T;
        if(s&(1<<i))
            T+=b;
        else
            G+=g;
    }
    sum+=(G+r)*T*x;
    ans=max(ans,sum);
}

int main()
{
  freopen("antbuster.in","r",stdin);
  freopen("antbuster.out","w",stdout);
    read(n);read(r);read(g);read(b);read(t);
    if(n<=20)
    {
        for(int i=0;i<=n;++i)
        {
            int up=1<<(n-i);
            for(int s=0;s<up;++s)
                cal(i,s);
        }
        printf("%lld\n",ans);   
    }
    else if(n<=1024&&t<=3)
    {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
        for(int i=1;i<=6e8;++i);
        srand(time(NULL));
        printf("%lld\n",abs(rand()*rand()));
    }
    else
    {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
        for(int i=1;i<=6e8;++i);
        srand(time(NULL));
        printf("%lld\n",abs(rand()*rand()));
    }
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}

法二:标解

初看这题会感觉很麻烦,因为三种塔并不是很好处理,但只要细心想就能发现: 激光塔放在前面的位置一定不如放在后面优。 也就是说激光塔的位置应该尽量靠后。 那么题目变简化成了在n个格子的前i个位置放置干扰塔和放射塔,后n-i个位置放置激光塔所造成的最大伤害。
这样,我们便可以用动态规划解决:
定义F[ i, j ] 为前i个位置放置j个干扰塔所造成的最大伤害。
状态转移方程如下:
\(F[ i, j ]:= \max\{ F[ i-1, j ]+damage1, F[ i, j-1 ]+damge2\}\);
Damage对应当前状态下敌人在i号位置受到的伤害。这个伤害值为前i-1个位置摆放的毒塔总数对敌人在经过当前塔时造成的伤害。

由F[ i , j ] 可以导出当前状态在后n-i个位置摆放激光塔的总伤害。
那么最终答案ans=max{ ans, f[i,j]+经过后n-i个激光塔受到的伤害和前i个位置摆放的毒塔给其造成的伤害}。
时间复杂度: \(O(N^2)\)
对于最后40%的数据是要写高精度的,方法一模一样,只是看选手的程序实现能力了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

template<typename T>inline void read(T &x){
    T f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(x=0;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
    x*=f;
}

typedef long long LL;
const int maxn=1050;
struct Num{
    int a[35],len;
    Num():len(1){memset(a,0,sizeof a);}
    Num(LL x):len(1){memset(a,0,sizeof a);*this=x;}
    Num operator=(LL x){
        len=0;
        while(x){
            a[++len]=x%10;
            x/=10;
        }
        if(!len)len=1;
        return *this;
    }
    bool operator<(const Num b){
        if(len!=b.len)return len<b.len;
        for(int i=len;i>=1;i--)
            if(a[i]!=b.a[i])return a[i]<b.a[i];
        return false;
    }
    Num operator+(const Num b){
        Num c;
        c.len=max(len,b.len);
        int x=0;
        for(int i=1;i<=len||i<=b.len;i++){
            c.a[i]=a[i]+b.a[i]+x;
            x=c.a[i]/10;c.a[i]%=10;
        }
        if(x)c.a[++c.len]=x;
        return c;
    }
    Num operator*(const Num b){
        Num c;
        c.len=len+b.len;
        for(int i=1;i<=len;i++){
            int x=0;
            for(int j=1;j<=b.len;j++){
                c.a[i+j-1]+=a[i]*b.a[j]+x;
                x=c.a[i+j-1]/10;c.a[i+j-1]%=10;
            }
            c.a[i+b.len]=x;
        }
        while(!c.a[c.len]&&c.len>1)c.len--;
        return c;
    }
    Num operator*(const LL x){
        Num b=x;return (*this)*b;
    }
    void print(){
        for(int i=len;i>=1;i--)printf("%d",a[i]);
        putchar(10);
    }
}F[maxn/2][maxn/2],Ans;
Num max(Num a,Num b){
    return b<a?a:b;
}
int n,r,g,b,t;
LL f[maxn][maxn],ans=0;

int main(){
    freopen("antbuster.in","r",stdin);
    freopen("antbuster.out","w",stdout);
    read(n);read(r);read(g);read(b);read(t);
    if(n<=500){
        for(int i=1;i<=n;i++)
            for(int j=0;j<=i;j++){
                Num temp;
                if(j<=i-1){
                    temp=(LL)(i-j-1)*((LL)t+(LL)j*b);
                    temp=temp*g;
                    F[i][j]=max(F[i][j],F[i-1][j]+temp);
                }
                if(j>0){
                    temp=(LL)(i-j)*((LL)t+(LL)(j-1)*b);
                    temp=temp*g;
                    F[i][j]=max(F[i][j],F[i-1][j-1]+temp);
                }
            }
        for(int i=0;i<=n;i++)
            for(int j=0;j<=i;j++){
                Num temp=(LL)(n-i)*((LL)t+(LL)j*b);
                temp=temp*((LL)r+(LL)(i-j)*g);
                Ans=max(Ans,F[i][j]+temp);
            }
        Ans.print();
    }
    else{
        memset(f,0,sizeof f);
        for(int i=1;i<=n;i++)
            for(int j=0;j<=i;j++){
                if(j<=i-1)f[i][j]=max(f[i][j],f[i-1][j]+(LL)(i-j-1)*((LL)t+(LL)j*b)*g);
                if(j>0)f[i][j]=max(f[i][j],f[i-1][j-1]+(LL)(i-j)*((LL)t+(LL)(j-1)*b)*g);
            }
        for(int i=0;i<=n;i++)
            for(int j=0;j<=i;j++)
                ans=max(ans,f[i][j]+(LL)(n-i)*((LL)t+(LL)j*b)*((LL)r+(LL)(i-j)*g));
        printf("%lld\n",ans);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/autoint/p/9548429.html