返回顶部

【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  阅读(1330)  评论(0编辑  收藏  举报