【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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM