返回顶部
  • 管理
  • 【NOI online 2020入门组题解】 A,B,C题

    题目跳转:

    A.文具订购
    B.跑步
    C.魔法
    到底部评论+点赞

    A.文具订购(order)

    题目描述

    小明的班上共有 n 元班费,同学们准备使用班费集体购买 3 种物品:

    1. 圆规,每个 7 元。
    2. 笔,每支 4 元。
    3. 笔记本,每本 3 元。

    小明负责订购文具,设圆规,笔,笔记本的订购数量分别为 a,b,c,他订购的原则依次如下:

    1. n 元钱必须正好用光,即 7a+4b+3c=n。
    2. 在满足以上条件情况下,成套的数量尽可能大,即 a,b,c中的最小值尽可能大。
    3. 在满足以上条件情况下,物品的总数尽可能大,即 a+b+c 尽可能大。

    请你帮助小明求出满足条件的最优方案。可以证明若存在方案,则最优方案唯一。

    输入格式

    输入仅一行一个整数,代表班费数量 n。

    输出格式

    如果问题无解,请输出 -1。

    否则输出一行三个用空格隔开的整数 a, b, c,分别代表圆规、笔、笔记本的个数。

    输入输出样例

    输入 #1

    1
    

    输出 #1

    -1
    

    输入 #2

    14
    

    输出 #2

    1 1 1
    

    输入 #3

    33
    

    输出 #3

    1 2 6
    

    说明/提示

    样例输入输出 3 解释

    a=2,b=4,c=1 也是满足条件 1,2 的方案,但对于条件 3,该方案只买了 7 个物品,不如 a=1,b=2,c=6 的方案。

    数据规模与约定

    • 对于测试点 1 ~ 6,保证n≤14。

    • 对于测试点 7 ~ 12,保证 n是 14 的倍数。

    • 对于测试点 13 ~18,保证 n≤100。

    • 对于 100% 的数据,保证 0≤n\(10^5\)

    题解部分

    【题目大意】给定三件物品a,b,c的价格以及总金额n,求问订购原则的情况下,最优的购买方案是什么?

    【思路分析】如果在分析题目样例的时候,能够人工模拟拼凑出它的购买方案,那不难想到使用枚举算法解决。但是你全部枚举a,b,c的话肯定会超时的,所以我们还需要对枚举算法进行一定的优化。那么怎么来优化呢?

    从题目的三个条件来入手:

    1. n 元钱必须正好用光,即 7a+4b+3c=n。
    2. 在满足以上条件情况下,成套的数量尽可能大,即 a,b,c中的最小值尽可能大。
    3. 在满足以上条件情况下,物品的总数尽可能大,即 a+b+c 尽可能大。

    首先,第一个条件,n元钱必须用完,那就要我们保证不能剩下钱,这里没什么好优化的。

    再来看第二个条件,成套的数量尽可能大,那就是说明尽可能地整体买入这三样东西,他们的总额是14元。那么我们可以先处理一个maxt,maxt=n/14,代表n元钱最多能卖maxt套。接下来就可以从maxt枚举到0,看有没有合法方案。

    最后,第三个条件,物品总数尽可能大,总数尽可能多,那么我就要尽可能买价格便宜的,那才能尽可能多。所以买完一整套后,剩余的money,money=n-(maxt*14),就先全部买入c,套数为maxc=money/3。接着再循环maxc~0,其中b的套数就可以通过计算得到,就不要枚举了,其实这道题有点像百钱买百鸡的变形。

    通过以上几个条件的约束,枚举范围就变得很小了,就可以通过此题,并且注意枚举都是从大到小倒着枚举的

    Talk is cheap,上代码

    【参考代码】

    #include <bits/stdc++.h>
    using namespace std;
    
    int n,ta,tb,tc;  //n为总的金钱,ta,tb,tc存买的套数 
    int money,moneyb,maxc; //money是整套买了之后的剩下的钱,c是枚举的笔和笔记本套数,moneyb是买了a、c之后剩下的钱 
    int main()
    {
    	int flag=0;  //0代表当前没有方案 
    	cin>>n;
    	int maxt=n/14;  //为了满足第二个条件,先3件物品整套买 
    	for(int i=maxt;i>=0;i--){  //从最大套数枚举到最小 
    		ta = tb = tc = i ; //套数初始为i 
    		money=n-(i*14); //算出剩下的钱 
    		maxc=money/3; //剩下的钱能买入的最多c的套数 
    		for(int c=maxc;c>=0;c--){  //为了满足第三个条件,那么就要让3块钱的物品尽可能的多 
     			moneyb=(money-3*c); //算出能够买下物品b剩余的钱 
    			if(moneyb%4==0){ //如果剩下的钱能买完物品b 
    				flag=1; //那么就能产生方案 
    				if(3*i+moneyb/4+c>ta+tb+tc){ //更新ta,tb,tc的值 
    					ta=i;
    					tb=i+moneyb/4;
    					tc=i+c;
    				}
    			}
    			if(flag){  //输出方案 
    				printf("%d %d %d\n",ta,tb,tc);
    				return 0;
    			}
    		}
    	}	
    	printf("-1"); //否则没有方案 
    	return 0;
    }
    

    B.跑步(running)

    题目描述

    小 H 是一个热爱运动的孩子,某天他想给自己制定一个跑步计划。小 H 计划跑 \(n\)米,其中第$ i(i \geq 1)$ 分钟要跑 \(x_i\)米($x_i $是正整数),但没有确定好总时长。

    由于随着跑步时间增加,小 H 会越来越累,所以小 H 的计划必须满足对于任意 \(i(i >1)\) 都满足$ x_i \leq x_{i-1}$。

    现在小 H 想知道一共有多少个不同的满足条件的计划,请你帮助他。两个计划不同当且仅当跑步的总时长不同,或者存在一个 \(i\),使得两个计划中 \(x_i\) 不相同。

    由于最后的答案可能很大,你只需要求出答案对 \(p\) 取模的结果。

    输入格式

    输入只有一行两个整数,代表总米数 \(n\) 和模数 \(p\)

    输出格式

    输出一行一个整数,代表答案对 \(p\) 取模的结果。

    输入输出样例

    输入 #1

    4 44
    

    输出 #1复制

    5
    

    输入 #2

    66 666666
    

    输出 #2

    323522
    

    输入 #3

    66666 66666666
    

    输出 #3

    45183149
    

    说明/提示

    样例输入输出 1 解释

    五个不同的计划分别是:{1,1,1,1},{2,1,1},{3,1},{2,2},{4}。


    数据规模与约定

    本题共 10 个测试点,各测试点信息如下表。

    测试点编号 \(n \leq\) 测试点编号 \(n \leq\)
    1 5 6 \(2\times 10^3\)
    2 10 7 \(5\times 10^3\)
    3 50 8 \(2\times 10^4\)
    4 100 9 \(5\times 10^4\)
    5 500 10 \(10^5\)

    对于全部的测试点,保证$ 1 \leq n \leq 10^5$,\(1 \leq p < 2^{30}\)

    题解部分

    【题目大意】给出一个数n,求多少种拆分方案?

    【思路分析】裸的拆分数问题,这道题的满分解法超出了大家的知识范围,建议大家可以去做简单版【FZOJ 1297 数的划分】一题。

    满分解法要求时间复杂度控制在\(O(n\sqrt(n))\)

    满分解法一:完全背包DP+分块

    满分解法二:数学知识——五边形数定理

    【参考代码】

    下面代码借鉴自洛谷

    解法一:

    //完全背包+分块
    #include <bits/stdc++.h>
    using namespace std;
    const int N = 100005;
    
    int f[N];
    int g[400][N];
    int main() {
        int n, p;
        cin >> n >> p;
        int m = sqrt(n) + 1;
        f[0] = 1;
        for (int i = 1; i < m; i++) {
            for (int j = i; j <= n; j++) {
                f[j] += f[j - i];
                f[j] %= p;
            }
        }
        g[0][0] = 1;
        for (int i = 1; i < m; i++) {
            for (int j = i; j <= n; j++) {
                g[i][j] = g[i][j - i];
                if (j >= m) g[i][j] += g[i - 1][j - m];
                g[i][j] %= p;
            }
        }
        int ans = 0;
        for (int i = 0; i <= n; i++) {
            LL sum = 0;
            for (int j = 0; j < m; j++) sum += g[j][n - i];
            sum %= p;
            ans = (ans + f[i] * sum) % p;
        }
        cout << ans << endl;
        return 0;
    }
    

    解法二:

    //五边形数定理
    #include<bits/stdc++.h>
    int T,n,m,mod,f[100010],g[100010];
    int main(){
        f[0]=1,scanf("%d%d",&n,&mod);
        for(int i=1;i*(3*i-1)/2<=n;i++)g[m++]=i*(3*i-1)/2,g[m++]=i*(3*i+1)/2;
        for(int i=1;i<=n;i++)for(int j=0;j<m&&g[j]<=i;j++)f[i]=(f[i]+(((j>>1)&1)?-1ll:1ll)*f[i-g[j]])%mod;
        printf("%d\n",(f[n]+mod)%mod);
    }
    

    C.魔法(magic)

    题目描述

    C 国由 \(n\) 座城市与 \(m\) 条有向道路组成,城市与道路都从 1 开始编号,经过 \(i\) 号道路需要 \(t_i\) 的费用。

    现在你要从 1 号城市出发去 \(n\) 号城市,你可以施展最多 \(K\) 次魔法,使得通过下一条道路时,需要的费用变为原来的相反数,即费用从 \(t_i\) 变为 -\(t_i\)。请你算一算,你至少要花费多少费用才能完成这次旅程。注意:使用魔法只是改变一次的花费,而不改变一条道路自身的$ t_i$;最终的费用可以为负,并且一个城市可以经过多次(包括 \(n\) 号城市)。

    输入格式

    从文件 magic.in 中读入数据。

    第一行三个整数$ n, m, K$,表示城市数、道路数、魔法次数限制。

    接下来 m 行每行三个整数 \(u_i,v_i,t_i\),第 \(i\) 行描述 \(i\) 号道路,表示一条从 \(u_i\)\(v_i\) 的有向道路,经过它需要花费 \(t_i\)

    输出格式

    输出到文件 magic.out 中。

    仅一行一个整数表示答案。

    样例1输入

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

    样例1输出

    -8

    样例1解释

    依次经过 1 号道路、2 号道路、3 号道路,并在经过 1、2 号道路前使用魔法。

    样例2输入

    2 2 2
    1 2 10
    2 1 1

    样例2输出

    -19

    样例2解释

    依次经过 1 号道路、2 号道路、1 号道路,并在两次经过 1 号道路前都使用魔法。

    数据范围与提示

    对于所有测试点和样例满足:

    1 ≤ n ≤ 100,1 ≤ m ≤ 2500,0 ≤ K ≤ 106,1 ≤ ui,vi ≤ n,1 ≤ ti ≤ 109。

    数据保证图中无自环,无重边,至少存在一条从 1 号城市到达 n 号城市的路径。

    每个测试点的具体限制见下表:

    在这里插入图片描述

    题解部分

    【前置知识】快速幂、矩阵乘法、最短路floyed

    【题目大意】n个城市,m条路,构成一张图。但是在这张图上,边权允许有k次改变,问最少的花费(最短路)。

    【思路分析】一眼看完题,心中大呼:这不就是分层图裸题吗?

    后来稍微分析了一下时间复杂度和数据规模,特别是看了k的最大值\(10^6\),显然分层图不可行,时间和空间都会爆炸。

    这道题目的时间复杂度肯定不能与\(k\)相关,或者尽可能把\(k\)

    变成\(\sqrt k\)或者\(\log k\),这样才能保证通过。

    所以就有了Floyed求最短路+矩阵优化快速幂的做法

    【参考代码】

    下面代码借鉴自洛谷

    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    struct edge
    {
     int u,v,w;
    }e[2505];
    int n,m,k;
    struct mat
    {
     long long a[105][105];
     mat(int x=63)
     {
      memset(a,x,sizeof(a));
     }
     mat operator*(const mat&b)const
     {
      mat ans;
      for(int k=1;k<=n;k++)
       for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
         ans.a[i][j]=min(ans.a[i][j],a[i][k]+b.a[k][j]);
      return ans;
     }
    }a;
    long long f[105][105];
    mat fpow(mat x,int y)
    {
     mat ans;
     for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
       ans.a[i][j]=f[i][j];
     while(y)
     {
      if(y&1)ans=ans*x;
      x=x*x;
      y>>=1;
     }
     return ans;
    }
    int main()
    {
     memset(f,63,sizeof(f));
     cin>>n>>m>>k;
     for(int i=1;i<=n;i++)
      f[i][i]=0;
     for(int i=1;i<=m;i++)
     {
      cin>>e[i].u>>e[i].v>>e[i].w;
      f[e[i].u][e[i].v]=e[i].w;
     }
     for(int k=1;k<=n;k++)
      for(int i=1;i<=n;i++)
       for(int j=1;j<=n;j++)
        f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
     for(int k=1;k<=m;k++)
     {
      int u=e[k].u,v=e[k].v,w=e[k].w;
      for(int i=1;i<=n;i++)
       for(int j=1;j<=n;j++)
        a.a[i][j]=min(a.a[i][j],min(f[i][j],f[i][u]+f[v][j]-w));
     }
     if(k==0)cout<<f[1][n]<<endl;
     else cout<<fpow(a,k).a[1][n]<<endl;
     return 0;
    }
    

    posted @ 2020-03-08 10:33  QYcccccccc  阅读(1453)  评论(0)    收藏  举报