NOI Online 入門組

一共三道題, 三道我都不會, 入門組??????

所謂的“入門組”題目

1. 文具訂購 題目鏈接:普及難度

題目描述

小明的班上共有n元班費, 同學們準備集體用這些班費購買3種物品:
1.圓規, 每個7元
2.筆,每隻4元
3.筆記本,每本3元
小明負責訂購文具, 設圓規, 筆, 筆記本的訂購數量分別爲a, b, c, 他訂購的原則依次
如下:
1.n元錢必須正好用光, 即7a+4b+3c=n7a+4b+3c = n
2.在滿足以上條件的情況下, 成套的數量級儘可能地大, 即maxsize(min(a,b,c))maxsize(min(a, b, c))
3.在滿足以上要求的前提下, 物品的總數儘可能地大, 即maxsize(a+b+c)maxsize(a+b+c)
請你幫助小明求出滿足條件的最優方案。可以證明最優方案存在且唯一。

題目分析

我們來一條一條分析(記住有優先級)
第一個條件:就是說, 我們要用, 7, 4, 3這3個數正好湊出n來;
可見, 除了1, 2, 5之外, 其他的都可以湊出來。
第二個條件:最小值儘量大, 那麼其他兩個都要比他大, 可見, 貪心策略就是湊出儘量多的成套的(一套14元)的文具來。
第三個條件, 因爲總數要儘量多, 所以當成套的湊完之後, 還剩下的錢, 就要往3元和4元去湊, 因爲第三個條件要求文具數量儘可能多, 所以如果能湊出一個7元的, 7 = 3+4, 所以, 可以轉換爲一個3元的加上一個4元的, 這樣文具數量就比之前多1了.
現在有一個問題, 萬一湊完整套之後餘下的錢是1, 2, 5怎麼辦??
把一套的錢拆開, 加入到現在剩下的錢裏, 也就是餘下的錢加14, 不就能拆開了嗎。

代碼

#include <cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<cstdlib>
#include<ctime>
#include<cctype>

using namespace std;

int n;
int main()
{
	cin >> n;
	if(n == 1 || n == 2 || n == 5)
	{
		cout << -1 << endl;
		return 0;
	}
	int a, b, c;
	int j = n % 14;
	int k = n / 14;
	if(j == 0)
	{
		a = b = c = k;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 1)
	{
		a = b = k-1;
		c = k+4;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 2)
	{
		a = k-1;
		b = k;
		c = k + 3;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 3)
	{
		a = b = k;
		c = k+1;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 4)
	{
		a = c = k;
		b = k+1;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 5)
	{
		a = k-1;
		b = k;
		c = k+4;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 6)
	{
		a = b = k;
		c = k+2;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 7)
	{
		a = k;
		b = c = k+1;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 8)
	{
		a = c = k;
		b = k+2;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 9)
	{
		a = b = k;
		c = k + 3;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 10)
	{
		a = k;
		b = k+1;
		c = k+2;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 11)
	{
		a = k;
		b = k+2;
		c = k+1;
		cout << a << " " << b << " " << c << endl;
	}
	else if(j == 12)
	{
		a = b = k;
		c = k+4;
			 cout << a << " " << b << " " << c << endl;
	}
	else
	{
		a = k;
		b = k+1;
		c = k+3;
		cout << a << " " << b << " " << c << endl;
	}
	return 0;
}

2.跑步 題目網址 :省選難度

題目描述

小H是一個熱愛運動的孩子, 某天他想給自己指定一個跑步計劃。計劃一共跑N米, 其中 第i(i>1)i(i>1)分鐘要跑xix_i米(xix_i是正整數, 但沒有確定好總時長。
由於隨着跑步時間的增加, 小H會越來越累, 所以小H的計劃必須滿足對於任意i(i>1)i(i>1)都滿足xixi1x_i \leq x_{i-1}.
現在小H想知道一共存在多少個不同的滿足條件的計劃, 請你幫助他,兩個計劃不同當且僅當跑步的總時長不同, 或者存在一個i, 是的兩個計劃中的xix_i不相同。
由於最後的答案可能很大, 你只需要輸出答案對p取模的結果。

題目分析

一道很經典的正整數拆分問題 ,首先搞明白什麼時候算作一種情況:當且僅當每個數字的組合沒有與之前重複的即可, 不考慮順序。也沒有數字個數上的要求。
考慮dp;設fi,jf_{i,j}表示用了前i個數和爲j方案數。
則他就有第i個數選和不選兩種選擇:fi,j=fi1,ji+fi1,jf_{i,j} = f_{i-1, j-i} + f_{i-1, j}非常像完全揹包有木有???
因此我們可以是他變成一維的。
但是這樣容易超時, 所以我們決定實行分塊操作。
m=nm = \sqrt{n}則小於m的數組成得方案我們可以用f數組去處理。大於m的數組成的方案則設一個g數組。
gi,jg_{i,j}表用了i個大於等於m的數和爲j的方案數。
兩種操作:
在拆分序列中增加一個數m;
把當前在拆分序列每個數都加上一;
兩種情況一加, 就得到轉移方程:gi,j=gi1,jm+gi,jig_{i,j} = g_{i-1, j-m} + g_{i, j-i}i最大爲m\sqrt{m}
合併答案時,首先明白, 答案一定是兩種情況加起來。
枚舉第一種情況和爲j, 那麼另一種情況和爲n-j;兩者乘起來, 加到總方案中;即爲j=0n(fm1,j × i=0mgi,nj)\sum_{j=0}^{n}(f_{m-1, j} \ \times\ \sum_{i=0}^{m}g_{i, n-j})

代碼

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<cstdlib>
#include<ctime>
#include<cctype>

using namespace std;

int n, p;
int m;
int f[100010];
int g[400][100010]
int main()
{
	cin >> n >> p;
	cin >> m;
	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 = 1; i <= n; i++)
    {
    	int 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;
}

3.魔法 題目鏈接:省選難度

題目描述

C國有n座城市與m條有向道路組成, 城市和道路都從1開始編號, 經過i號道路需要tit_i的費用。
現在你要從1號城市出發去n號城市, 你可以施展最多k次魔法,使得通過下一條道路時, 需要的費用變爲原來的相反數,即費用從tit_i變成ti-t_i。請你算一算, 至少要花費多少費用才能完成這次旅程。
注意:使用魔法只是改變一次的花費, 而不改變一條道路自身的tit_i;最終的費用可以爲負,並且一個城市可以經過多次, 包括n號城市。

題目分析

有k次魔法可使用;

task 1: k = 0;

在一張權值爲正的圖上要使1點到n點最短, 很顯然, 跑最短路。
Floyd一波帶走~~

task 2: k = 1;

考慮怎樣從task1推到task2;
在改進的二維Floyd方程上加以改造;
fk,i,jf_{k,i,j}表示使用不超過k次魔法的情況下, 從i到j的最短路。
則當k = 1時,轉移思路就是不走和走了之間小的那個。枚舉唯一使用魔法的邊(u,v)(u, v)
方程:f1,i,j=min(f0,i,j , f0,i,u+f0,v,jwu,v)f_{1, i, j} = min(f_{0, i, j}\ , \ f_{0, i, u} + f_{0, v, j} - w_{u, v})

task 3: k = 2時

從k = 1推唄;
採用Floyd的本質思想, 枚舉一箇中轉點p。
i -> p最多用1次魔法, p -> j 最多用一次魔法
於是這樣就合成答案了;
方程長這樣子:
f2,i,j=min(f1,i,p+f1,p,j)f_{2, i, j} = \min(f_{1, i, p} + f_{1, p, j})
把min忽略, 有沒有一點像矩陣乘法的樣子, 只不過把乘換加, 加換min而已了。。

task 4:k > 2時

矩陣快速冪。。。。。。。

代碼

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<cstdlib>
#include<ctime>
#include<cctype>

using namespace std;

int f[110][110];
int n, m, k;
struct edge
{
	int u, v, w;
}ed[2510];
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 <= m; i++)
	{
		int x, y, z;
		cin >> x >> y >> z;
		ed[i].u = x;
		ed[i].v = y;
		ed[i].w = z;
		f[x][y] = z;
	}
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			for(int k = 1; k <= n; k++)
			{
				f[j][k] = min(f[j][k], f[j][i] + f[i][k]);
				}
		}
	}
	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;
}

本次寫的代碼, 沒有經過評測, 不一定對哦~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章