結果
B組 C/C++ 河北 第一名
賽情
莫名其妙就到比賽時間了,沒有啥特別准備,考場在自己學校,沒啥心情波動
吸取去年沒東西吃的教訓,這次買了一個面包和一瓶酸奶,居然要二十塊 555
A: 空間
問題描述
小藍准備用 \(256MB\) 的內存空間開一個數組,數組的每個元素都是 \(32\) 位二進制整數,如果不考慮程序占用的空間和維護內存需要的輔助空間,請問 \(256MB\) 的空間可以存儲多少個 \(32\) 位二進制整數?
解答
\(256MB=256*2^{10}KB=256*2^{20}B=256*8*2^{20}Bit=2147483648Bit\)
\(2147483648Bit/32=67108864\)
答案:67108864
B: 卡片
問題描述
小藍有很多數字卡片,每張卡片上都是數字 \(0\) 到 \(9\)。
小藍准備用這些卡片來拼一些數,他想從 \(1\) 開始拼出正整數,每拼一個,就保存起來,卡片就不能用來拼其它數了。
小藍想知道自己能從 \(1\) 拼到多少。
例如,當小藍有 \(30\) 張卡片,其中 \(0\) 到 \(9\) 各 \(3\) 張,則小藍可以拼出 \(1\) 到 \(10\),但是拼 \(11\) 時卡片 \(1\) 已經只有一張了,不夠拼出 \(11\)。
現在小藍手里有 \(0\) 到 \(9\) 的卡片各 \(2021\) 張,共 \(20210\) 張,請問小藍可以從 \(1\) 拼到多少?
提示:建議使用計算機編程解決問題。
解答
使用 \(cnt[]\) 數組記錄手上剩余的不同數字的卡片數,用 while 循環迭代每一個待拼的數,按位拆分后依次在 \(cnt\) 減去
當發現 \(cnt[i]\) 數量不足時,說明當前數字無法拼出,跳出循環,答案為前一個拼好的數字
代碼如下
#include <bits/stdc++.h>
using namespace std;
int cnt[11],c=0;
bool fl=false;
int main() {
for (int i=0;i<=9;i++) cnt[i]=2021;
while (!fl && ++c) {
int t=c;
while (t) cnt[t%10]--,t/=10;
for (int i=0;i<=9;i++)
if (cnt[i]<0) { fl=true; break; }
}
printf("%d",c-1);
return 0;
}
答案:3181
C: 直線
問題描述
在平面直角坐標系中,兩點可以確定一條直線。如果有多點在一條直線上,那么這些點中任意兩點確定的直線是同一條。
給定平面上 \(2 × 3\) 個整點 \({(x, y) | 0 ≤ x < 2, 0 ≤ y < 3, x ∈ Z, y ∈ Z}\),即橫坐標是 \(0\) 到 \(1\) (包含 \(0\) 和 \(1\)) 之間的整數、縱坐標是 \(0\) 到 \(2\) (包含 \(0\) 和 \(2\)) 之間的整數的點。
這些點一共確定了 \(11\) 條不同的直線。
給定平面上 \(20 × 21\) 個整點 \({(x, y) | 0 ≤ x < 20, 0 ≤ y < 21, x ∈ Z, y ∈ Z}\),即橫坐標是 \(0\) 到 \(19\) (包含 \(0\) 和 \(19\)) 之間的整數、縱坐標是 \(0\) 到 \(20\) (包含 \(0\) 和 \(20\)) 之間的整數的點。
請問這些點一共確定了多少條不同的直線。
解答
數據范圍比較小,先不考慮斜率為 \(0\) 或不存在的情況,直接用 \(O(n^2*m^2)\) 的枚舉兩個點組成的其他直線,
每找到一條新的直線都用數組存起來,使用點斜式 \(y-y_0=k(x-x_0)\) 標識直線,
遇新直線的時候逐個判斷是否與先前重復(先判斷斜率是否相同,再判斷點是否在直線上,即代入表達式值為 \(0\))。
最后答案要加上斜率不存在的 \(n\) 條和斜率為 \(0\) 的 \(m\) 條
代碼如下
#include <bits/stdc++.h>
#define N 20
#define M 21
#define eps 0.00001
using namespace std;
struct Line { double x,y,xl; };
vector<Line> v;
int main() {
for (int i=0;i<N;i++) {
for (int j=0;j<M;j++) {
for (int k=0;k<N;k++) {
for (int l=0;l<M;l++) {
if (i==k || j==l) continue;
double xl=double((1.0*(l-j))/(1.0*(k-i)));
bool fl=true;
for (int g=0;g<(int)v.size();g++) {
if ((abs(v[g].xl-xl)<eps) && (abs(double(j)-v[g].y-(v[g].xl*(double(i)-v[g].x)))<eps))
{ fl=false; break; }
}
if (fl) v.push_back((Line){double(i),double(j),double(xl)});
}
}
}
}
cout<<(int)v.size()+N+M;
return 0;
}
答案:40257
D: 貨物擺放
小藍有一個超大的倉庫,可以擺放很多貨物。
現在,小藍有 \(n\) 箱貨物要擺放在倉庫,每箱貨物都是規則的正方體。小藍規定了長、寬、高三個互相垂直的方向,每箱貨物的邊都必須嚴格平行於長、寬、高。
小藍希望所有的貨物最終擺成一個大的立方體。即在長、寬、高的方向上分別堆 \(L、W、H\) 的貨物,滿足 \(n = L × W × H\)。
給定 \(n\),請問有多少種堆放貨物的方案滿足要求。
例如,當 \(n = 4\) 時,有以下 \(6\) 種方案:\(1×1×4、1×2×2、1×4×1、2×1×2、2 × 2 × 1、4 × 1 × 1\)。
請問,當 n = \(2021041820210418\) (注意有 \(16\) 位數字)時,總共有多少種方案?
提示:建議使用計算機編程解決問題。
解答
這么大的 \(n\) 也太恐怖了,一般方法做的話復雜度是要 \(O(logn)\) 以下的,但是太困難了
於是想着先把 \(n\) 分解質因數看看,代碼跑出來質因數數列是 \(2,3,3,3,17,131,2857,5882353\),發現只有 \(8\) 個
要使長寬高的乘積為 \(n\),上述數列的每個數都要被分別分配到三個維度,這個過程可以用 \(dfs\) 實現,注意要去重(考試用的 vector,重載 set 也可以)
代碼如下
#include <bits/stdc++.h>
#define ll long long
#define MAXN 1000007
using namespace std;
ll n,x=1,y=1,z=1; int vis[9]={0,2,3,3,3,17,131,2857,5882353};
struct node {
ll a,b,c;
bool operator == (const node &T) const {
return a==T.a && b==T.b && c==T.c;
}
};
vector<node> s;
void dfs(int now) {
if (now==9) {
bool fl=true;
node tp=(node){x,y,z};
for (int i=0;i<(int)s.size();i++) {
if (s[i]==tp) { fl=false; break; }
}
if (fl) s.push_back(tp);
return;
}
x*=vis[now],dfs(now+1),x/=vis[now];
y*=vis[now],dfs(now+1),y/=vis[now];
z*=vis[now],dfs(now+1),z/=vis[now];
}
int main() {
dfs(1);
cout<<(int)s.size()<<'\n';
return 0;
}
答案:2430
E: 路徑
問題描述
小藍學習了最短路徑之后特別高興,他定義了一個特別的圖,希望找到圖中的最短路徑。
小藍的圖由 \(2021\) 個結點組成,依次編號 \(1\) 至 \(2021\)。
對於兩個不同的結點 \(a, b\),如果 \(a\) 和 \(b\) 的差的絕對值大於 \(21\),則兩個結點之間沒有邊相連;如果 \(a\) 和 \(b\) 的差的絕對值小於等於 \(21\),則兩個點之間有一條長度為 \(a\) 和 \(b\) 的最小公倍數的無向邊相連。
例如:結點 \(1\) 和結點 \(23\) 之間沒有邊相連;結點 \(3\) 和結點 \(24\) 之間有一條無向邊,長度為 \(24\);結點 \(15\) 和結點 \(25\) 之間有一條無向邊,長度為 \(75\)。
請計算,結點 \(1\) 和結點 \(2021\) 之間的最短路徑長度是多少。
提示:建議使用計算機編程解決問題。
解答
題意已經說得很明白了,就是判斷兩個點之間是否符合連邊條件,如果符合就連一條指定長度的邊
看知乎上有些選手把最小公倍數(lcm)看成最大公因數的(gcd)做了,這樣錯了有點可惜
公式:\(lcm(a,b)=a*b/gcd(a,b)\)
考場上 Dijkstra 寫了一小半了發現這個似乎這個用 Floyd 跑時間是夠的,反正最后交的也是一個答案
吐槽一下考場的 Dev C++ 版本太舊,同一個主進程只能 fork 一個程序窗口,跑 Flyod 的時候不能運行其他代碼
#include <bits/stdc++.h>
#define MAXN 3007
#define ll long long
using namespace std;
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; }
ll d[MAXN][MAXN];
vector<ll> G[MAXN];
int main() {
memset(d,0x3f,sizeof(d));
for (int i=1;i<=2021;i++) d[i][i]=0;
for (ll i=1;i<=2021;i++)
for (ll j=1;j<=2021;j++)
if (abs(i-j)<=21) d[i][j]=d[j][i]=i*j/gcd(i,j);
for (ll k=1;k<=2021;k++)
for (ll i=1;i<=2021;i++)
for (ll j=1;j<=2021;j++)
if (d[i][j]>d[i][k]+d[k][j]) d[i][j]=d[i][k]+d[k][j];
cout<<d[1][2021];
return 0;
}
答案:10266837
F: 時間顯示
問題描述
小藍要和朋友合作開發一個時間顯示的網站。在服務器上,朋友已經獲取了當前的時間,用一個整數表示,值為從 \(1970\) 年 \(1\) 月 \(1\) 日 \(00:00:00\) 到當前時刻經過的毫秒數。
現在,小藍要在客戶端顯示出這個時間。小藍不用顯示出年月日,只需要顯示出時分秒即可,毫秒也不用顯示,直接舍去即可。
給定一個用整數表示的時間,請將這個時間對應的時分秒輸出。
輸入格式
輸入一行包含一個整數,表示時間。
輸出格式
輸出時分秒表示的當前時間,格式形如 \(HH:MM:SS\),其中 \(HH\) 表示時,值為 \(0\) 到 \(23\),\(MM\) 表示分,值為 \(0\) 到 \(59\),\(SS\) 表示秒,值為 \(0\) 到\(59\)。時、分、秒不足兩位時補前導 \(0\)。
對於所有評測用例,給定的時間為不超過 \(10^{18}\) 的正整數
樣例
樣例輸入 1
46800999
樣例輸出 1
13:00:00
樣例輸入 2
1618708103123
樣例輸出 2
01:08:23
解答
考場上很笨比,老是以為秒和毫秒之間是 \(60\) 或 \(100\) 的關系,搞了半天搞不出樣例的答案
第 \(N\) 次做的時候突然才發現 \(1s=1000ms\),計算時分秒直接除對應的倍數再模一下就可以了
代碼如下
#include <bits/stdc++.h>
#define ll unsigned long long
using namespace std;
ll ms,s,m,h;
int main() {
cin>>ms;
s=ms/1000,m=ms/(1000*60),h=ms/(1000*60*60);
s%=60,m%=60,h%=24;
if (h<10) cout<<"0",cout<<h<<":";
if (m<10) cout<<"0",cout<<m<<":";
if (s<10) cout<<"0",cout<<s;
return 0;
}
G: 砝碼稱重
問題描述
你有一架天平和 \(N\) 個砝碼,這 \(N\) 個砝碼重量依次是 \(W_1, W_2, · · · , W_n\)。
請你計算一共可以稱出多少種不同的重量?
注意砝碼可以放在天平兩邊。
輸入格式
輸入的第一行包含一個整數 \(N\)。
第二行包含 \(N\) 個整數:\(W_1, W_2, W_3, · · · , W_N\)。
輸出格式
輸出一個整數代表答案。
樣例
輸入
3
1 4 6
輸出
10
樣例說明
能稱出的 \(10\) 種重量是:\(1、2、3、4、5、6、7、9、10、11\)。
\(1 = 1;\)
\(2 = 6 − 4\) (天平一邊放 \(6\),另一邊放 \(4\));
\(3 = 4 − 1;\)
\(4 = 4;\)
\(5 = 6 − 1;\)
\(6 = 6;\)
\(7 = 1 + 6;\)
\(9 = 4 + 6 − 1;\)
\(10 = 4 + 6;\)
評測用例規模與約定
對於 \(50\%\) 的評測用例,\(1 ≤ N ≤ 15\)。
對於所有評測用例,\(1 ≤ N ≤ 100\),\(N\) 個砝碼總重不超過 \(100000\)。
解答
還算比較簡單的 DP,用 \(f[i][j]\) 表示使用前 \(i\) 個砝碼,能否稱出重量為 \(j\) 的物品,值為 \(1\) 則可以,\(0\) 則不可以
對於每個狀態,我們只需要關注砝碼兩邊的差值,不需要關注兩邊具體的數值
那么狀態轉移方程為 \(f[i][j]=max(f[i-1][j],f[i-1][abs(j-s[i])],f[i-1][j+s[i])\),對應以下四種情況
\((1)\) 取 \(f[i-1][j]\):不使用 \(s[i]\)
\((2)\) 取 \(f[i-1][abs(j-s[i])]\) 並且 \(j>=s[i]\):將 \(s[i]\) 放在 \(j-s[i]\) 的一邊
\((3)\) 取 \(f[i-1][abs(j-s[i])]\) 並且 \(j<s[i]\):將 \(s[i]\) 放在空的一邊
\((4)\) 取 \(f[i-1][j+s[i]]\) :將 \(s[i]\) 放在空的一邊
三種候選碼如果有任意一個為 \(1\),那么 \(f[i][j]=1\)
這個數組可以滾動,但是要注意每次新得到的需要用數組保存起來等內循環結束再賦值給 \(f\),防止用一塊砝碼使用兩次
一開始看錯了數據范圍,看成了單個砝碼的重量不超過 \(100000\),想着這樣空間受不了,於是用 set 記錄當前稱出的重量了一遍,后來才改回用數組來的
代碼如下
#include <bits/stdc++.h>
#define MAXN 107
#define MAXM 1000007
using namespace std;
int ans,sum,n,w[MAXN];
bool f[MAXM*10];
vector<int> v;
int main() {
cin>>n,f[0]=true;
for (int i=1;i<=n;i++) cin>>w[i],sum+=w[i];
for (int i=1;i<=n;i++) {
v.clear();
for (int j=0;j<=sum;j++) {
if (f[j]) {
v.push_back(abs(j-w[i]));
v.push_back(j+w[i]);
}
}
for (int i=0;i<(int)v.size();i++) f[v[i]]=true;
}
for (int i=1;i<=sum;i++) if (f[i]) ans++;
cout<<ans;
return 0;
}
H: 楊輝三角形
問題描述
下面的圖形是著名的楊輝三角形:
如果我們按從上到下、從左到右的順序把所有數排成一列,可以得到如下數列:
\(1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, ...\)
給定一個正整數 \(N\),請你輸出數列中第一次出現 \(N\) 是在第幾個數?
輸入格式
輸入一個整數 \(N\)。
輸出格式
輸出一個整數代表答案。
樣例
輸入
6
輸出
13
評測用例規模與約定
對於 \(20\%\) 的評測用例,\(1 ≤ N ≤ 10\);
對於所有評測用例,\(1 ≤ N ≤ 1000000000\)。
解答
這個題做得最不好惹,先嘗試數學推導發現搞不出來啥規律,做着做着就犯傻覺得 \(O(n^2)\) 推一個組合數就可以出來了
(原因是算了以下 \(C_{2000}^{1000}\) 大於 \(1000000000\),就覺得 \(2000\) 階把全部數據范圍覆蓋了,笨比了233)
正解肯定是數學方法,等知道了再來補充
代碼如下(錯誤的)
#include <bits/stdc++.h>
#define ll long long
#define MAXN 2007
using namespace std;
ll n,C[MAXN][MAXN];
int main() {
cin>>n,C[0][0]=1;
for (int i=1;i<MAXN;i++) {
for (int j=1;j<=i;j++) {
C[i][j]=C[i-1][j]+C[i-1][j-1];
if (C[i][j]==n) {
cout<<i*(i-1)/2+j;
return 0;
}
}
}
return 0;
}
I: 雙向排序
問題描述
給定序列 \((a_1, a_2, · · · , a_n) = (1, 2, · · · , n)\),即 \(a_i = i\)。
小藍將對這個序列進行 \(m\) 次操作,每次可能是將 \(a_1, a_2, · · · , a_{qi}\) 降序排列,或者將 \(a_{qi}, a_{qi+1}, · · · , a_n\) 升序排列。
請求出操作完成后的序列。
輸入格式
輸入的第一行包含兩個整數 \(n, m\),分別表示序列的長度和操作次數。
接下來 \(m\) 行描述對序列的操作,其中第 \(i\) 行包含兩個整數 \(pi, qi\) 表示操作類型和參數。
當 \(pi = 0\) 時,表示將 \(a_1, a_2, · · · , a_{qi}\) 降序排列;當 \(pi = 1\) 時,表示將 \(a_{qi}, a_{qi+1}, · · · , an\) 升序排列。
輸出格式
輸出一行,包含 \(n\) 個整數,相鄰的整數之間使用一個空格分隔,表示操作完成后的序列。
樣例
輸入
3 3
0 3
1 2
0 2
輸出
3 1 2
樣例說明
原數列為 \((1, 2, 3)\)。
第 \(1\) 步后為 \((3, 2, 1)\)。
第 \(2\) 步后為 \((3, 1, 2)\)。
第 \(3\) 步后為 \((3, 1, 2)\)。與第 \(2\) 步操作后相同,因為前兩個數已經是降序了。
評測用例規模與約定
對於 \(30\%\) 的評測用例,\(n, m ≤ 1000\);
對於 \(60\%\) 的評測用例,\(n, m ≤ 5000\);
對於所有評測用例,\(1 ≤ n, m ≤ 100000,0 ≤ ai ≤ 1,1 ≤ bi ≤ n\)。
解答
顯然對於前 \(30\%\) 的數據只需要進行 \(m\) 次 sort
考慮到數列中的數取值范圍是 \([1,n]\) ,可以用一個數組 \(num[i]\) 記錄數字 \(i\) 在數列中的位置
對於 \(pi==0\) 的情況,在內循環中找到位置小於等於 \(qi\) 的數並記錄,即記錄 \(num[i]<=qi\) 的所有 \(i\),用 \(tong[]\) 標識
然后按真實值的正序遍歷 \(tong[val]==true\) 的數,分別賦予新的位置值,\(cnt\) 每次減一,這樣就完成了一次降序排序
\(pi==1\) 的情況類似處理即可,最后輸出的時候要 for (int i=1;i<=n;i++) pri[num[i]]=i;
改變一下結構
這樣做時間復雜度是 \(O(n*m)\),可以過掉前 \(60\%\) 的數據
#include <bits/stdc++.h>
#define MAXN 5007
using namespace std;
int n,m,num[MAXN],pri[MAXN];
bool tong[MAXN];
int main() {
cin>>n>>m;
for (int i=1;i<=n;i++) num[i]=i;
while (m--) {
for (int i=1;i<=n;i++) tong[i]=false;
int pi,qi; cin>>pi>>qi;
if (!pi) {
for (int i=1;i<=n;i++)
if (num[i]<=qi) tong[i]=true;
int cnt=qi;
for (int i=1;i<=n;i++)
if (tong[i]) num[i]=cnt--;
}
else {
for (int i=1;i<=n;i++)
if (num[i]>=qi) tong[i]=true;
int cnt=qi;
for (int i=1;i<=n;i++)
if (tong[i]) num[i]=cnt++;
}
for (int i=1;i<=n;i++) pri[num[i]]=i;
}
for (int i=1;i<=n;i++) pri[num[i]]=i;
for (int i=1;i<=n;i++) cout<<pri[i]<<" ";
return 0;
}
J: 括號序列
問題描述
給定一個括號序列,要求盡可能少地添加若干括號使得括號序列變得合法,當添加完成后,會產生不同的添加結果,請問有多少種本質不同的添加結果。
兩個結果是本質不同的是指存在某個位置一個結果是左括號,而另一個是右括號。
例如,對於括號序列 \(((()\),只需要添加兩個括號就能讓其合法,有以下幾種不同的添加結果:\(()()()、()(())、(())()、(()())\) 和 \(((()))\)。
輸入格式
輸入一行包含一個字符串 \(s\),表示給定的括號序列,序列中只有左括號和右括號。
輸出格式
輸出一個整數表示答案,答案可能很大,請輸出答案除以 \(1000000007\) (即 \(10^9 + 7\) ) 的余數。
輸入
((()
輸出
5
評測用例規模與約定
對於 \(40\%\) 的評測用例,\(|s| ≤ 200\)。
對於所有評測用例,\(1 ≤ |s| ≤ 5000\)。
解答
想了一半沒做出來的 DP...因為沒有存代碼,只能回憶起考場上的一些思路
要盡可能少得添加括號,所以添加的括號只可能是左右中的一種,並且數量為原字符串中左右括號數量差值
對於添加右括號的情況,用 \(f[i][j]\) 表示前 \(i\) 個字符,添加 \(j\) 個右括號的本質不同的添加后結果數
但是由於前 \(i\) 的左右括號差值是一定的,所以轉移時 \(j\) 的范圍也被限定了
添加左括號的話需要從后往前做,其他是一樣的
狀態轉移方程在考場上寫假了,以后再來補