華南師大 2017 年 ACM 程序設計競賽新生初賽題解
華南師范大學第很多屆 ACM 程序設計競賽新生賽(初賽)在 2017 年 11 月 20 日 - 27 日成功舉行,共有 146 名同學有效參賽(做出 1 題)。進入決賽的資格初定為完成並通過 5 題或以上,決賽時間是 12 月 3 日,地點未定。
題解
被你們虐了千百遍的題目和 OJ 也很累的,也想要休息,所以你們別想了,行行好放過它們,我們來看題解吧。。。
A. 詭異的計數法
Description
cgy 太喜歡質數了以至於他計數也需要用質數表示!在他看來,2(第一小質數)表示1,3(第二小質數)表示2……可正因為他的計數方法太奇葩,所以他的數學成績非常差而且拖累了他的績點。 lxy 得知 cgy 的績點排名之后打算告訴他,可是必須以極度麻煩的 cgy 質數計數法的方式表示。 lxy 出重金 ¥(10000000 mod 10) 請你來為他解決這個詭異的計數。
Input
輸入包括多組數據,每組數據各占一行,每一行有一個正整數 n ,表示 cgy 的績點排名,輸入到文件結尾結束( \(n \leq 100000\) ,數據組數 \(t \leq 1000000\) )。
Output
對應每組數據輸出一個質數,為 cgy 績點排名的質數計數法。
Sample Input
1
2
28
13
Sample Output
2
3
107
41
Hint
- 本場比賽是網絡初賽,除了不能抄襲其他選手代碼外,對任何知識和資料的檢索獲取不作限制;
- A 題並不是最簡單的,題目難度不按順序,請量力而為~
- 如有其他疑問請看補充說明 https://d.wps.cn/v/8pBAU
題目大意
求第 \(K\) 個質數的值
參考思路
可以選用埃氏篩法(Sieve of Eratosthenes)、歐拉篩法(Sieve of Euler)來做預處理,之后就可以直接輸出。
注意先計算出第 100000 個素數是 1299709 ,所以記錄數組只要開到 MaxN ,但標記數組要開到 MaxP 。
時間復雜度 \(O(NloglogN)\) 或 \(O(N)\) 。
參考代碼
標程使用了埃氏篩法:
#include <stdio.h>
#include <math.h>
int n,cnt;
int notprime[10000010];
int prime[5000010];
int main(void)
{
for (int i=2;i<=sqrt(10000000)+1;++i)
if (!notprime[i])
for (int j=2*i;j<=10000000;j+=i)
notprime[j]=1;
for (int i=2;i<=10000000;++i)
if (!notprime[i])
{
++cnt;
prime[cnt]=i;
}
while (~scanf("%d",&n))
printf("%d\n",prime[n]);
return 0;
}
ZYJ 的歐拉篩解:
#include <stdio.h>
#include <string.h>
const int MaxN = 1E5;
const int MaxP = 1299709;
int primes[MaxN + 1], cnt;
char isPrime[MaxP + 1];
void init() {
memset(isPrime, 0xff, sizeof(isPrime));
for (int i = 2; i <= MaxP; ++i) {
if (isPrime[i]) {
primes[++cnt] = i;
}
for (int j = 1; j <= cnt && i * primes[j] <= MaxP; ++j) {
isPrime[i * primes[j]] = 0;
if (!(i % primes[j])) {
break;
}
}
}
}
int main() {
int N;
init();
while (~scanf("%d", &N)) {
printf("%d\n", primes[N]);
}
return 0;
}
了解到計院方面教的是 cin/cout 的輸入輸出方法,由於某人出題事故導致 cin/cout 會被卡超時,公平起見放松了數據;進而導致優化的試除法可以險過(過濾偶數、循環外開方、懶計算),時間復雜度 \(O(N \sqrt{N})\) :
#include <stdio.h>
#include <math.h>
const int MaxN = 1E5;
int cnt = 1, primes[MaxN + 1] = {-1, 2};
int isPrime(const int& n) {
int q = (int)sqrt(n) + 1;
for (int i = 2; i < q; ++i) {
if (!(n % i)) {
return 0;
}
}
return 1;
}
int getPrime(const int& n) {
if (cnt < n) {
for (int i = primes[cnt] + 1; cnt < n; ++i) {
if ((n & 1) && isPrime(i)) {
primes[++cnt] = i;
}
}
}
return primes[n];
}
int main() {
int N;
while (~scanf("%d", &N)) {
printf("%d\n", getPrime(N));
}
return 0;
}
B. 數發票
Description
cgy 組織了某外出活動准備到學院報銷,學院給了他一定的差旅費報銷金額。而 cgy 平時是一個十分注重收集公交車發票的人,他的手上有無數張不同面值的發票,現在他想計算最少要多少張發票可以恰好達到報銷金額。 cgy 忙着整理這些發票,於是把這個任務交給了一起外出的你,你需要仔細計算否則就拿不到外出的車費了。
Input
輸入第一行是一個正整數 T 表示數據組數。接下來是多組數據,每組數據第一行是正整數 n ,分別表示 cgy 有 n 種面值的發票(每種都有無數張),接下來一行有 n 個由空格隔開的正整數,表示這 n 種面值的大小,再下一行是一個正整數 x 表示報銷金額(T,x<=1000; n<=100)。
Output
對於每一組數據,輸出能湊齊報銷金額的最少的發票張數,如果不能正好湊齊則輸出 -1 。
Sample Input
2
3
1 3 5
11
2
3 5
7
Sample Output
3
-1
Hint
樣例解釋:
第一組數據中 11=5+5+1=5+3+3 ,最少要用 3 張
第二組數據中無論多少張都湊不齊 7 元,輸出 -1
題目大意
有 \(N\) 種物品和一個容量為 \(X\) 的背包,每種物品都有無限件可以使用。若存在一種方案使得剛好放滿背包,求這種方案中所使用物品的最小件數。
參考思路
動態規划,完全背包問題變種,時間復雜度 \(O(NX)\) 等。
參看《背包九講》。
參考代碼
標程:
#include <stdio.h>
#include <string.h>
int kase,n,x,c[1010];
int dp[1010];
int main(void)
{
scanf("%d",&kase);
while (kase--)
{
memset(dp,0,sizeof(dp));
scanf("%d",&n);
for (int i=0;i<n;++i)
scanf("%d",&c[i]);
scanf("%d",&x);
for (int i=0;i<=x;++i)
for (int j=0;j<n;++j)
if (i>c[j] && dp[i-c[j]])
{
if (!dp[i])
dp[i]=dp[i-c[j]]+1;
else
dp[i]=dp[i-c[j]]+1<dp[i]?dp[i-c[j]]+1:dp[i];
}
else if (i==c[j])
dp[i]=1;
if (dp[x])
printf("%d\n",dp[x]);
else
printf("%d\n",-1);
}
return 0;
}
ZYJ:
#include <stdio.h>
inline void getMin(int& a, const int b) {
if (a > b) a = b;
}
const int INF = 0x3f3f3f3f;
const int MAXN = 101;
const int MAXX = 1010;
int N, amount;
int cost[MAXN];
int dp[MAXX];
void read() {
scanf("%d", &N);
for (int i = 0; i < N; ++i) {
scanf("%d", cost + i);
}
scanf("%d", &amount);
}
void work() {
dp[0] = 0;
for (int i = 1; i <= amount; ++i) {
int tmp = INF;
for (int j = 0; j < N; ++j) {
if (cost[j] <= i) {
getMin(tmp, dp[i - cost[j]]);
}
}
dp[i] = tmp + 1;
}
printf("%d\n", dp[amount] >= INF ? -1 : dp[amount]);
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
read();
work();
}
return 0;
}
C. ljc 吃糖
Description
ljc 喜歡吃糖,每一顆糖能夠給他提供一個單位的腦能量。對於每道題,他至少要有 m1 能量才會考慮去做,且 AC 這道題需要消耗 m2 能量(即 ljc 能量數大於等於 m1 時才能去 AC 這道題,AC 完成后 ljc 剩余(當前能量值-m2)能量)。
現在 ljc 的初始能量為 n,比賽題目數為 k,問若 ljc 想要 AK (All-Killed,即 AC 所有題目),則他至少要准備多少顆糖?
Input
第一行輸入一個正整數 T 表示數據組數,每組第 1 行兩個數 n, k,接下來 k 行為兩個正整數 m1, m2。
保證所有數據 \(T, n, k, m1, m2 \leq 1000\),且 \(m1 \geq m2\) 。
Output
每組數據占一行,輸出 ljc 至少要准備的糖的數目。
Sample Input
1
0 8
4 2
6 4
8 6
10 8
12 10
14 12
16 14
18 16
Sample Output
74
Hint
ljc 做題的順序可以任意排列,順序不同, AK 所需要的糖數不一定相同。
如果要用到排序,你可以調用 C 語言的 qsort 函數或者 C++ 的 sort 函數。
題目大意
給定一系列若干個任務,每個任務都有一個先驗條件 precondition 和完成消耗 cost ,現在擁有有限的不可再生資源數量 N ,問最少需要補充多少資源才能完成所有任務。
該問題是“給定不可補充資源,問最多能完成哪些任務”的變種,難度持平。
參考思路
貪心算法。時間復雜度 \(O(klogk)\) 。
假定當前最大可用能量為 N ,任務隊列為 prob[] ,如果一個任務 \(prob_i\) 不能被完成,一定是它的先驗需求 \(prob_i.m1 > N\) ,否則由於 \(m1 \leq m2\) 一定成立,任何一個任務都可以被選擇完成。
再假定存在兩個任務 a 和 b 能夠按順序完成,那么一定要 \(a.m2 + b.m1 < b.m2 + a.m1\) ,因為先完成的任務會先消耗能量,若前面任務消耗得太多,則會導致不滿足后面任務的先驗條件,進而需要不斷地補充能量。如果一系列任務都能滿足這一條件,就可以盡量少地補充能量,使得能量利用效率最大化。因此我們可以基於這一思想給任務排序。
進一步也可以轉化為 \(a.m1 - a.m2 > b.m1 - b.m2\) 即對差值排序,思想也是類似的。
參考代碼
沒找到標程,以下由 ZYJ 提供:
#include <stdio.h>
#include <algorithm>
using std::sort;
const int MaxN = 1000;
struct Problem {
int precond, cost;
int priority;
bool operator<(const Problem& cmp) const {
return priority > cmp.priority;
}
} prob[MaxN];
int n, k;
void read() {
scanf("%d%d", &n, &k);
for (int i = 0; i < k; ++i) {
scanf("%d%d", &prob[i].precond, &prob[i].cost);
prob[i].priority = prob[i].precond - prob[i].cost;
}
}
void work() {
sort(prob, prob + k);
int res = 0;
for (int i = 0; i < k; ++i) {
if (n >= prob[i].precond) {
n -= prob[i].cost;
} else {
res += prob[i].precond - n;
n = prob[i].precond - prob[i].cost;
}
}
printf("%d\n", res);
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
read();
work();
}
return 0;
}
D. 姓名字母排列
Description
自古以來,如果如何排列一份無序名單是一個具有爭議性、頗為麻煩的問題。通常的做法是按照姓名拼音的英文字母排字典序。
某一天 Zyj 突發奇想,能不能把所有人的名字拼在一起成為一個串,並且這個串在所有拼接方案中字典序是最小的呢?比如有三個人的名字:zyh、zy、zyj,顯然會有以下六種組合(加入空格是便於閱讀,實際排列不應有空格):
zyh zy zyj
zyh zyj zy
zy zyh zyj
zy zyj zyh
zyj zyh zy
zyj zy zyh
顯然只有第二種 zyhzyjzy 是所有方案中字典序最小的。
現在給你一份名單,你能找出這個最小方案嗎?
Input
第一行輸入一個正整數 \(N < 10^6\) 代表名字數量。
接下來 \(N\) 行,每行輸入一個只含小寫字母的字符串(當然也不含空格),代表每個人的名字。
保證一個人的名字最大長度不超過 20 ,保證所有人名字長度的總和小於 \(10^6\) 。
Output
輸出名字排列拼接方案,要在所有可行方案中滿足字典序最小。
Sample Input
3
hello
world
zyj
Sample Output
helloworldzyj
Hint
如果要用到排序,你可以調用 C 語言的 qsort 函數或者 C++ 的 sort 函數。
題目大意
原原題:《最大數》,1998 年 NOIP 全國聯賽提高組,codevs 1860;
原題:《ZLM 的撲克牌》,2015 年華南師范大學 ACM 新生賽(逃;
給定若干個小寫的英文字母串,求一種方案使得所有串按該方案的順序相鄰首尾拼接后,得到的新串在所有方案中字典序最小。
參考思路
貪心算法,需要先嚴格弱序化(strict weak ordering)。
- 單串字典序不決定拼接串字典序。如果就英文字母串的屬性來說,顯然每個串都有一個“A-Za-z”的“字典序”,既然有一個約定成俗的字典序,它們之間當然是可比的,也可以排序;但是並不能證明按照字典序排好的串拼接起來,得到的新串在所有拼接方案中,它也是按字典序的極小元或極大元。很容易找到反例: bacd 和 b 可以拼接成 bacdb 和 bbacd 兩種, 雖然 b 是原串集合 {b, bacd} 中的極小元,但 bbacd 不是拼接串集合 {bacdb, bbacd} 的極小元,因此 b 不能排在 bacd 的前面。
- 建立新關系。基於上面的思考,很容易想到將原串集合任意元素兩兩拼接比較。對於任意串 A 和 B ,大概有 3 種情況:
- A 和 B 在開頭處沒有公共子串。直接按字典序排就好了,比如 AB < BA 顯然能決定出 A < B ;
- A 和 B 在開頭處有一個短的公共子串。這時 A 可以分解為 PQ 而 B 可以分解為 PR ,對於 Q 和 R 的比較又回到了第一種情況;
- A 是 B 在開頭的子串,或反過來。假如是前者,則 B 可以分解為 AP ,對 AAP 和 APA 的比較實際上是第二種情況,對 A 和 P 的比較。但如果 A 和 P 又在開頭處有公共子串,甚至是循環的呢?比如 A 和 B ,而 B = AA..AP 甚至 B = AA..AQR 且 A = QSQS..QS 呢?這時 A 比較短,先補齊到 B 的長度,有可能是 A/P 甚至是 S/Q 、 S/R 、 Q/R 的比較,取決於循環節的長度。
參考代碼
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
const int MaxCount = 1E6 + 1;
const int MaxLength = 20 + 1;
int N;
char names[MaxCount][MaxLength] = {0};
void read() {
scanf("%d", &N);
for (int i = 0; i < N; ++i) {
scanf("%s", names[i]);
}
}
int namesCmp(const void* a, const void* b) {
static char p[MaxLength << 1], q[MaxLength << 1];
strcat(strcpy(p, (char*)a), (char*)b);
strcat(strcpy(q, (char*)b), (char*)a);
return strcmp(p, q);
}
void work() {
qsort(names, N, MaxLength, namesCmp);
for (int i = 0; i < N; ++i) {
printf("%s", names[i]);
}
putchar('\n');
}
int main() {
read();
work();
return 0;
}
E. Substring
Description
一個字符串的子串表示原字符串中一段連續的部分。例如,字符串"121"的子串總共有六個,它們分別是"1"(前一個),"2","1"(后一個),"12","21"和"121"。但"11"不是"121"的子串,因為兩個'1'在原串中不連續。我們認為空串也不是子串。
現給你一個只含有'0'和'1'兩種字符的字符串,問它含有相同'0'和'1'的子串有幾個。例如,字符串"0101"中符合條件的子串有4個。它們分別是"01"(前一個),"10","01"(后一個),"0101"。
Input
輸⼊只有一行,為給定的字符串。字符串長度不超過100,且只包含'0'和'1'兩種字符。
Output
輸出為一個數,表示符合條件的子串的個數。注意最后有一個換行符。
Sample Input
00110011
Sample Output
10
Hint
字符串"00110011"中符合條件的子串分別為"01"(前一個),"10","01"(后一個),"0011" (前一個),"0110","1100","1001","0011"(后一個),"011001","00110011",共10個。
題目大意
給定任意 01 串,求含有相同數量的 '0' 和 '1' 的連續子串有多少個。
參考思路
遍歷計數。時間復雜度 \(O(L^2)\) 或 \(O(L^3)\) ,其中 \(L = |S|\) 即字符串的長度。
從左到右掃一遍字符串,統計中間掃過的 '0' 和 '1' 的數量,若數量相等則掃過的子串符合條件,進行累加。
由於一個字符串的子串有 \(L + (L - 1) + \ldots + 2 + 1 = \frac{L(L + 1)}{2}\) 個,所以可能要遍歷 \(L\) 次起點, \(1 \ldots (L - i)\) 次終點。
參考代碼
\(O(L^3)\) 復雜度的解法:
#include <stdio.h>
#include <string.h>
char str[101];
void read() {
scanf("%s", str);
}
void work() {
int len = strlen(str);
int res = 0;
for (int i = 0; i < len; ++i) {
for (int j = i; j < len; ++j) {
int zeroCnt = 0, oneCnt = 0;
for (int k = i; k <= j; ++k) {
str[k] == '0' ? ++zeroCnt : ++oneCnt;
}
if (zeroCnt == oneCnt) {
++res;
}
}
}
printf("%d\n", res);
}
int main() {
read();
work();
return 0;
}
\(O(L^2)\) 復雜度的解法只在一念之間,只要你願意想:
void work() {
int len = strlen(str);
int res = 0;
for (int i = 0; i < len; ++i) {
int zeroCnt = 0, oneCnt = 0;
for (int j = i; j < len; ++j) {
str[j] == '0' ? ++zeroCnt : ++oneCnt;
if (zeroCnt == oneCnt) {
++res;
}
}
}
printf("%d\n", res);
}
F. 法特狗
Description
眾所周知, cgq 因為臉黑而沒有去玩法特狗這一款游戲,不過他老板 gou4shi1 經常問他某個英靈厲不厲害,為了一勞永逸, cgq 決定寫個程序給所有英靈打個分。
在法特狗里,每張卡牌都對應着一個英靈。設 \(n\) 為卡池里所有卡牌的數目, \(m\) 為卡池里有多少張卡牌 y 使得,英靈 x 的所有屬性都大於等於英靈 y 的對應屬性(注意, y 可以是 x 本身),則英靈x的評分為 \(\frac{m}{n}\) 。
現在認為每個英靈只有四個屬性(耐久、敏捷、魔力、幸運),每個屬性只有四個等級(ABCD , A 最強, D 最弱)。給出一個卡池所有英靈的屬性,要求給所有英靈評分。
Input
輸入第一行是一個整數 n ( \(1 \le n \le 100000\) ),代表卡池里所有卡牌的數目。
接下來是 n 行,每行四個大寫字母,分別代表每個英靈的耐久、敏捷、魔力、幸運。
Output
輸出 n 行,每行一個浮點數(小數點后保留兩位),代表每個英靈的評分。
Sample Input
4
AAAA
AAAD
CBCD
DDDD
Sample Output
1.00
0.75
0.50
0.25
Hint
1.00 = 4/4
0.75 = 3/4
0.50 = 2/4
0.25 = 1/4
題目大意
給定若干個物品,每個物品含有四個屬性,每個屬性又有四種值,當且僅當一個物品所有屬性的值都小於等於另一個物品,才可以說這個物品比另一個物品弱。求對於任意一個物品,比它弱的物品數。
參考思路
四維偏序關系(partial ordering relation)計數。
雖然有四個維度,但由於每一個維度的范圍較小,所以狀態數有限,只有 \(4^4 = 256\) 個,完全可以用標記數組記錄一下:先預處理出同一種狀態的卡牌有多少張,對於每種卡牌,找出比它弱的卡牌數量,最后根據卡牌的輸入順序直接計算輸出答案。時間復雜度 \(O(256 \times 256 + N)\) 或 \(O(256N)\) 。
L2M 說可以將每個狀態加起來,時間復雜度 \(O(4 \times 256 + N)\) 。
標記數組可以是 'D' - 'A' 變基映射成 0 - 3 的四維屬性數組,這樣大概會有 5 或 8 個 for 循環;也可以是存儲哈希(hash)成四進制數的一維狀態數組;當然某些同學直接打表算狀態也可以,就是有點奇怪hhh。
易錯點解析
- 偏序關系不可比:由於 C/C++ 函數庫里的排序方法要求嚴格弱序,而這里是四維偏序,即任意兩個物品 \(X\) 和 \(Y\) ,當且僅當 \(X.p1 \leq Y.p1\) 且 \(X.p2 \leq Y.p2\) 且 \(X.p3 \leq Y.p3\) 且 \(X.p4 \leq Y.p4\) 都滿足時才有 \(X \preccurlyeq Y\) ,比如 \(ABCD \preccurlyeq AABB\) ,但 \(ABCD\) 和 \(BACD\) 不可比較。你可以根據偏序關系的定義來自己證明一下,總之是不可以直接進行比較排序(comparison sort)的,即使 hash 成四進制數也不行(數字本身也是嚴格序);
- 數據漂移:由於要計算並輸出浮點數,部分用 float 的同學有可能會遇到浮點數數據漂移問題;
- 卡牌可重復:一張卡牌只可能是有限的 256 種狀態,卡池里 \(N\) 張牌肯定有重復的;但最后卻要根據卡牌的輸入順序來輸出(即相等可重復但不相同);
- 反向映射:因為原來的屬性值是 'A' - 'D' ,如果按這個順序映射成 0 - 3 的話,大概就是 'D' < 'A' 等價於 3 > 0 ,這樣要小心,對屬性值的關系比較要反着來;如果 0 - 3 代表 'D' - 'A' 就不用費這腦細胞了。
參考代碼
標程 1 (\(O(256N)\)):
#include <stdio.h>
const int N = 1e5 + 10;
const int M = 4;
int a0[N], a1[N], a2[N], a3[N];
int b[M][M][M][M];
int main() {
int n;
scanf("%d\n", &n);
for (int i = 0; i < n; ++i) {
scanf("%c%c%c%c\n", &a0[i], &a1[i], &a2[i], &a3[i]);
a0[i] = 'D' - a0[i]; a1[i] = 'D' - a1[i]; a2[i] = 'D' - a2[i]; a3[i] = 'D' - a3[i];
b[a0[i]][a1[i]][a2[i]][a3[i]]++;
}
for (int i = 0; i < n; ++i) {
int ans = 0;
for (int j0 = 0; j0 <= a0[i]; ++j0)
for (int j1 = 0; j1 <= a1[i]; ++j1)
for (int j2 = 0; j2 <= a2[i]; ++j2)
for (int j3 = 0; j3 <= a3[i]; ++j3)
ans += b[j0][j1][j2][j3];
printf("%.2f\n", (double)ans/n);
}
return 0;
}
標程 2 (\(O(256 \times 256 + N)\)):
#include <stdio.h>
const int N = 1e5 + 10;
const int M = 4;
int a0[N], a1[N], a2[N], a3[N];
int b[M][M][M][M], c[M][M][M][M];
int main() {
int n;
scanf("%d\n", &n);
for (int i = 0; i < n; ++i) {
scanf("%c%c%c%c\n", &a0[i], &a1[i], &a2[i], &a3[i]);
a0[i] = 'D' - a0[i]; a1[i] = 'D' - a1[i]; a2[i] = 'D' - a2[i]; a3[i] = 'D' - a3[i];
b[a0[i]][a1[i]][a2[i]][a3[i]]++;
}
for (int i0 = 0; i0 < M; ++i0)
for (int i1 = 0; i1 < M; ++i1)
for (int i2 = 0; i2 < M; ++i2)
for (int i3 = 0; i3 < M; ++i3)
for (int j0 = 0; j0 <= i0; ++j0)
for (int j1 = 0; j1 <= i1; ++j1)
for (int j2 = 0; j2 <= i2; ++j2)
for (int j3 = 0; j3 <= i3; ++j3)
c[i0][i1][i2][i3] += b[j0][j1][j2][j3];
for (int i = 0; i < n; ++i)
printf("%.2f\n", (double)c[a0[i]][a1[i]][a2[i]][a3[i]]/n);
return 0;
}
ZYJ 的四進制標記解法(O\((4 \times 256 \times 256 + N)\),雖然多了個常數 4 但就是自豪地比標程快(驕傲.jpg)):
#include <stdio.h>
int N;
int cardSet[100100];
int cardMap[256];
int leqCount[256];
void addCard(int idx, const char* card) {
int val = 0;
for (const char *p = card + 3; p >= card; --p) {
val <<= 2;
val |= 'D' - *p;
}
cardSet[idx] = val;
++cardMap[val];
}
void read() {
char card[5];
scanf("%d", &N);
for (int i = 0; i < N; ++i) {
scanf("%s", card);
addCard(i, card);
}
}
int leqCard(int a, int b) {
for (int i = 4; i > 0; --i) {
if ((a & 3) > (b & 3)) {
return 0;
}
a >>= 2;
b >>= 2;
}
return 1;
}
void work() {
for (int i = 0; i < 256; ++i) {
if (cardMap[i]) for (int j = 0; j < 256; ++j) {
if (cardMap[j] && leqCard(j, i)) {
leqCount[i] += cardMap[j];
}
}
}
for (int i = 0; i < N; ++i) {
printf("%.2lf\n", (double)leqCount[cardSet[i]] / N);
}
}
int main() {
read();
work();
return 0;
}
G. zyj 打印獎狀
Description
zyj 又被叫去打印獎狀啦!但是學院辦公室的打印機實在太辣雞,經常把獎狀文字打歪。為了讓學院更換打印機, zyj 把自己在學院得的所有獎狀都拿出來,用以展示這打印機打出來的獎狀到底歪成什么樣子。為了方便表示, zyj 決定把每一張獎狀的文字范圍中心和獎狀紙張中心的幾何距離列舉出來,用以表觀地顯示打印機的誤差。 zyj 天天都要打印獎狀,日理萬機,所以他請求你幫他完成這個任務。
Input
zyj 得的獎狀數不勝數,所以輸入文件包括若干組獎狀數據。每組獎狀數據包含兩行,第一行是兩個浮點數 a, b(a, b > 0) ,表示獎狀的大小;第二行是四個浮點數 x, y, l, w(x, y >= 0; l, w > 0) ,表示獎狀上文字范圍左上角的坐標和文字范圍的長和寬。輸入數據保證文字范圍一定完全在獎狀面積內。輸入到文件結尾結束。
Output
對應每一組獎狀數據輸出獎狀中心和文字區域中心的幾何距離,保留 3 位小數。
Sample Input
3.0 4.0
1.0 1.0 1.0 1.0
2.0 2.0
1.0 1.0 1.0 1.0
3.0 3.0
0.0 0.0 3.0 3.0
Sample Output
0.500
0.707
0.000
Hint
double 類型的輸入輸出格式: scanf("%lf") / printf("%f")
本題數據量大,請使用 scanf/printf ,使用 cin/cout 會導致超時
題目大意
給定兩個矩形,其中小矩形一定被套在大矩形之中。給出大矩形的長寬,小矩形的左上角頂點坐標和長寬,求出兩個矩形中心的幾何距離。
參考思路
簽到題,公式亂搞就行。
易錯點解析
- 這題沒有說清楚長是平行於 x 軸的長,寬是平行於 y 軸的寬,舉報出題人;
- 這題沒有說清楚 a / x / l 一定對應以及 b / y / w 一定對應,舉報出題人;
- 輸入輸出:如果用 scanf / printf 作輸入輸出,前者要指定具體是 %f / %lf / %Lf 的哪個,而后者的 float / double 都是用 %f 輸出,當然 long double 還是用 %Lf ;
- 數據漂移:由於要計算並輸出浮點數,部分用 float 的同學有可能會遇到浮點數數據漂移問題;
參考代碼
#include <stdio.h>
#include <math.h>
double a, b;
double x, y, width, height;
inline double dist(double x1, double y1, double x2, double y2) {
return sqrt(pow(x1 - x2, 2.f) + pow(y1 - y2, 2.f));
}
int main() {
while (~scanf("%lf%lf", &a, &b)) {
scanf("%lf%lf%lf%lf", &x, &y, &width, &height);
double res = dist(a / 2.f, b / 2.f, x + width / 2.f, y + height / 2.f);
printf("%.3f\n", res);
}
return 0;
}
H. CG 之爭
Description
眾所周知, cgq 和 cgy 名字中只差一個字。為了保住 CG 這個名號, cgq 作為擂主給 cgy 出了個難題:要是 cgy 能在 1s 內答出 \(\frac{(\frac{1+\sqrt{5}}{2})^{n}-(\frac{1-\sqrt{5}}{2})^{n}}{\sqrt{5}}\) ,就能獲得一次正式挑戰 cgq 的機會。 cgy 好菜啊,而且他的 1s 十分寶貴,於是求助於你,請你幫他求出這個數。
Input
多組數據,每一組一行是 cgq 給出的一個正整數 n ,輸入到文件結尾結束(\(n \leq 80\))。
Output
對應於每一組 n 求出 \(\frac{(\frac{1+\sqrt{5}}{2})^{n}-(\frac{1-\sqrt{5}}{2})^{n}}{\sqrt{5}}\) ,保留 3 位小數。
Sample Input
1
2
Sample Output
1.000
1.000
Hint
double 和 long double 精度都有限,求 80 次方會存不下。
題目大意
求斐波那契數列的第 \(k\) 項值。
參考思路
打表 or 記憶化遞推。遞推的時間復雜度 \(O(80)\) ,還有 \(O(N)\) 的輸入輸出。
因為浮點數類型存不下 80 次方這么大的數(存儲位數不夠 or 表示精度不夠),可以搜索一下題目給出的公式,容易知道是斐波那契數列的通項公式;也可以搜索一下 C/C++ 語言是怎么處理大數計算、高精度科學計算的,用高精算法求 80 次方也可以。
易錯點解析
- 保留 3 位小數是騙你的。因為斐波那契是個整數數列,就算用公式算,考慮上精度損失等問題后得到的還是個整數;
- 不作記憶化處理的遞歸是會超時的。遞歸空間是一棵二叉樹,不作記憶化處理的話最大有 80 層,復雜度高達 O(2^N) 指數爆炸算到地老天荒;
- 不作記憶化處理的遞推是可能會超時的。因為有多組輸入數據呀,你的復雜度是 O(80T) 哦;
- 用浮點數類型遞推到最后精度不夠是會 WA 的。有些同學就做了優化,從奇怪的地方拿到了第 79 、 80 項直接用字符串輸出, emmm... 也是可以的。
參考代碼
#include <stdio.h>
#define LL long long
#define lld "%lld"
const int MaxN = 80;
LL fib[MaxN + 1] = {0, 1};
inline void init() {
for (int i = 2; i <= MaxN; ++i) {
fib[i] = fib[i - 1] + fib[i - 2];
}
}
int main() {
int N;
init();
while (~scanf("%d", &N)) {
printf(lld".000\n", fib[N]);
}
return 0;
}
I. 人形質量單位
Description
每個人的質量或許相同,或許不同。於是乎,如果以人們的體重作為一個計量單位,那么可以有多少種不同的組合呢?
設有 n(\(1 \leq n \leq 10\)) 種不同的質量,第 i 種質量大小為 w[i](\(w[i] \leq 300\)),第 i 個質量有 x[i](\(1 \leq x[i] \leq 100\)) 個人(其總重量不超過 300000kg ),要求編程輸出這些人形能稱出不同質量的種數,但不包括一個人也不用的情況。
Input
第一行輸入質量的種數 n
第二行輸入每種質量的質量
第三行輸入每種質量的人的人數
Output
輸出可以稱量的質量種數
Sample Input
3
1 2 3
2 1 2
Sample Output
10
Hint
無
題目大意
有 \(N\) 種物品,每種物品有 \(X_i\) 個且價值為 \(W_i\) 。可以任意選取物品,求使得所取物品總價值不同的方案數。
參考思路
動態規划,或記憶化搜索。
如果限定了人數,求最大總質量,那就是多重背包問題,參看《背包九講》。
這里只是要求方案數,拿個標記數組記錄一下就好了,時間復雜度 \(O(N\sum{W_iX_i})\) 等。
易錯點解析
- 可能會有同學想到枚舉的方法,即假設總共有 P 個人,從選 1 個人到選 P 個人不斷組合出來,看有多少種不同的總質量不就好了嗎?但要注意,最壞情況下可選方案數是小於等於 \(C^1_P + C^2_P + \ldots + C^P_P = 2^P - 1\) 的(當且僅當所有種類質量值 \(W_i\) 互質,且不存在 \(p \leq X_j \land q \leq X_i\) 使得 \(W_i = p \land W_j = q\) 時取等號),考慮到有 10 種質量且每種質量有 100 個人,總人數最多高達 1000 這是要算到地老天荒的節奏;
參考代碼
標程(\(O(N\sum{X_i\sum{W_iX_i}})\)):
#include<iostream>
using namespace std;
int main()
{
int max=0;
int n,w[305],x[105];
bool p[300*100*10+5];
cin>>n;
for(int i=1;i<=n;i++)
cin>>w[i];
for(int i=1;i<=n;i++)
{
cin>>x[i];
max+=x[i]*w[i];
}
for(int i=1;i<=max;i++) p[i]=false;
p[0]=true;
for(int k=1;k<=n;k++)
{
for(int i=1;i<=x[k];i++)
for(int j=max;j>=w[k];j--)
{
if(p[j]==false && p[j-w[k]]==true) p[j]=true;
}
}
int ans=0;
for(int i=1;i<=max;i++)
{
if(p[i]) {ans++; }
}
cout<<ans;
}
\(O(N\sum{W_iX_i})\) :
#include <stdio.h>
#include <string.h>
const int MaxN = 10, MaxW = 301, MaxX = 101;
const int MaxM = MaxN * MaxW * MaxX;
int N, maxWeight;
int W[MaxW], X[MaxX], flag[MaxM];
void read() {
scanf("%d", &N);
for (int i = 0; i < N; ++i) {
scanf("%d", W + i);
}
for (int i = 0; i < N; ++i) {
scanf("%d", X + i);
maxWeight += W[i] * X[i];
}
}
int curr[MaxM];
void work() {
int res = 0;
flag[0] = 1;
for (int i = 0; i < N; ++i) {
memset(curr, 0, sizeof(curr));
for (int j = W[i]; j <= maxWeight; ++j) {
if (!flag[j] && flag[j - W[i]] && curr[j - W[i]] < X[i]) {
flag[j] = 1;
curr[j] = curr[j - W[i]] + 1;
++res;
}
}
}
printf("%d\n", res);
}
int main() {
read();
work();
return 0;
}
J. 訓練 ZetaGo
Description
L2M 未來科技開發公司研制了一款新的智能機器人 ZetaGo ,它可以把任何事物都計算出一個特征值,在經過重排后可以據此做出一系列的決策。
為了檢驗 ZetaGo 的重排能力,需要人工進行監督學習(supervised learning)訓練。 LLM 作為 L2M 手下的一名實驗員,就要每天進行這樣的苦差事。
今天的重排規則是這樣的:已知一組特征值 A ,要求重新排列后的組合 B 可以滿足任意兩個相鄰元素的乘積(\(B_i * B_{i+1}\))為 4 的倍數。 ZetaGo 經過決策運算后會給出是否存在這樣一種重排方案, LLM 要根據決策結果告訴它對不對,以便糾正學習模型。
今天 LLM 實在太累啦,想拜托你幫幫忙。
Input
第一行輸入一個正整數 \(T \leq 10\) 代表着訓練組數。
接下來 \(3T\) 行,每 \(3\) 行首先是一個正整數 \(2 \leq N \leq 1000\) 代表着特征值的個數,接下來一行是用空格隔開的特征值 \(A_i\) (其中 \(1 \leq A_i \leq 10^9\) 且 \(0 \leq i < N\) ),最后一行 ZetaGo 會給出它的判定結果 "yes" 或者 "no" 。
Output
請你告訴 ZetaGo 它的判定結果對不對,輸出 "AC" 或者 "WA" 表示對或錯(不包括引號)。
Sample Input
3
2
4 4
no
3
1 2 4
yes
4
1 2 2 4
yes
Sample Output
WA
AC
AC
Hint
樣例 2 解釋:1 4 2
樣例 3 解釋:1 4 2 2
題目大意
給定一組數據,問是否存在這樣一種排列,使得重排后的數據,任意相鄰元素兩兩相乘的值是 4 的倍數,再跟機器人輸入的“它的判斷結果”作比較。
參考思路
規律題,統計數量關系。時間復雜度 \(\theta(N)\) 。
分類討論,可以將數字分為三類: 4 的倍數、是 2 的倍數但不是 4 的倍數、奇數。
- 顯然,任何數和 4 的倍數相乘,其結果仍然是 4 的倍數,因此是萬能搭配;
- 顯然,任意 2 的倍數只有一個因子 2 ,因此它們只能兩兩間相乘才能得到 4 的倍數,必須相鄰地放一起;
- 對於任意奇數,和 2 的倍數相乘不符合題意,只能和 4 的倍數相間搭配。
因此可以先排列所有 cnt1 個奇數,每兩個奇數之間插入一個 4 的倍數,需要 \(cnt1 - 1\) 個 4 的倍數;若存在 2 的倍數,需要拿出一個 4 來連接它們,這樣需要 \(cnt1\) 個 4 的倍數。判斷一下 4 的倍數有多少個,是否滿足條件,並將判斷結果跟機器人的相比較,得出答案。
易錯點解析
- 有些同學規律找對了,可惜忘記每次都要初始化;
- 有些同學規律找對了,可惜跟機器人結果的對比反了;
- 有些同學規律找對了,可惜統計 4 的倍數時也順帶算進 2 的倍數了;
- 有些同學規律找對了,可惜忘記是倍數;
參考代碼
#include <stdio.h>
int T, N;
int cnt1, cnt2, cnt4;
char ans[4] = "yes";
void init() {
cnt1 = cnt2 = cnt4 = 0;
}
void read() {
int rd;
scanf("%d", &N);
for (int i = 0; i < N; ++i) {
scanf("%d", &rd);
if (rd & 3) {
(rd & 1) ? ++cnt1 : ++cnt2;
} else {
++cnt4;
}
}
scanf("%s", ans);
}
void work() {
puts(ans[0] == "ny"[cnt4 >= cnt1 - !cnt2] ? "AC" : "WA");
}
int main() {
scanf("%d", &T);
while (T--) {
init();
read();
work();
}
}
K. 強迫症患者
Description
cgy是一個很有強迫症的人,他一看到小數就渾身難受。然而有一天,他碰到了這么一條只有整數和除號構成的式子:1/2/4/16 ,他的強迫症又來了,加上了括號讓式子變成了 (1/2)/(4/16)=2 把它變成了整數。但是走着走着,他又碰到了一個牆上寫滿了這個風格的式子,cgy突然發現有些式子可以通過加括號來讓結果變為整數,有些卻不行。但是他手邊沒有紙和筆,於是就沒有把這個想法寫下來,現在你能通過這一系列數字來判斷他們是否可以通過加括號變成整數么
Input
一個整數 n(n<=100) 表示有幾組數據
接下來輸入 m(m<=1000) 表示有 m 個數字,再下去一行輸入 m 個正整數(每個數字不大於 1000 ),表示式子中的數字。
Output
對於每一行輸入,如果可以通過加括號變成整數就輸出 Yes ,否則輸出 No 。
Sample Input
3
3
1 2 4
4
1 5 6 9
4
1 4 2 2
Sample Output
Yes
No
Yes
Hint
無
題目大意
給定一串除法式子 a / b / c / ... ,可以對該式子任意加括號以改變其中某些運算符(除號變乘號),問是否存在這樣一種加括號方案,使得整個式子的分子可以整除分母。
參考思路
貪心 + 最大公約數,時間復雜度 \(O(N)\) 。
因為括號可以任意加,一定存在一種最優方案,使得除了第二個數只能作為除數外,其他數字的乘積為被除數,若它們的乘積跟第二個數的最大公約數為 1 ,則說明所有的數乘起來都不能整除第二個數,否則第二個數除以這個最大公約數后其值為 1 (被除掉了,也可以理解為被約分了)。
貪心策略證明:假如從最優方案中刪去某一個括號,使得還有另外一組數跟第二個數相乘作為除數,若其他剩余的數的乘積不能整除第二個數,當然也不能整除這個新的更大的除數;若剩余的數的乘積能整除這個新除數,根據乘法結合律,它們也能整除單獨的第二個數,因此得證。
易錯點解析
- 乘法溢出:因為 int 乘法有可能溢出,為了避免高精計算,干脆每次都讓第二個數除以某個數跟它的最大公約數,只要到最后時第二個數變為 1 ,說明它在這個過程中被除掉啦;
- 約分:整個過程其實就相當於約分,我們在約分時就是在找分子和分母的最大公約數並同除,這個應該非常好理解;
- 運算結合性:現代人類設計出來的算術乘法、算術除法符號是左結合的,也就是一定是從左往右算的,這點應該沒問題吧,所以 a / b / c 一定等價於 ((a / b) / c) 也沒問題吧....
參考代碼
#include <stdio.h>
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
void work() {
int M, a, b;
scanf("%d%d%d", &M, &a, &b);
b /= gcd(a, b);
for (int i = 2; i < M; ++i) {
scanf("%d", &a);
b /= gcd(a, b);
}
puts(b == 1 ? "Yes" : "No");
}
int main() {
int N;
scanf("%d", &N);
while (N--) {
work();
}
return 0;
}
L. Oyk 捏泡泡紙
Description
著名的剪紙大師 Oyk 今年不剪紙了,最近工作壓力大,他改為捏泡泡紙來放松心情。由於 Czj 每天上班摸魚,他被抓過來陪 Oyk 捏泡泡紙。
已知一張完好的泡泡紙上會有 \(N\) 個泡泡,他們約定每次每個人都可以 paji 掉 2 的冪次(1,2,4,8,16,...,\(2^k\))個泡泡,並且總是由 Oyk 先捏,輪流下去直到捏完。他們都想由自己來捏完整張泡泡紙,且他們都足夠聰明,總會試圖選擇最有利於自己的策略。
看他們捏泡泡太無聊了,我想預先知道他們誰能夠剛好在最后捏完整張泡泡紙。
Input
可能會有多行輸入,請你一直處理到文件尾。
每行會輸入一個正整數 \(N \leq 10^6\) 。
Output
對於每張泡泡紙,請你告訴我故事的結局。
如果是 Oyk 最后捏完,輸出 "Oyk Oyk!" (不包括引號,下同)。
如果是 Czj 最后捏完,輸出 "diu,Czj!" 。
Sample Input
4
5
6
Sample Output
Oyk Oyk!
Oyk Oyk!
diu,Czj!
Hint
對於樣例 3 ,無論 Oyk 先捏 1 / 2 / 4 個都改變不了泡泡紙最后被 Czj 捏完。
題目大意
原題:與 hdu 1847 幾乎一樣。
只有一堆若干數量的物品,規定每人每次可以取 \(2^{\mathbb{k}}\) 個,取完者勝,問先手是否能贏。
參考思路
巴什博弈變種,找出先手必敗態,發現 \(N = 3\mathbb{k}\) 先手必敗。時間復雜度 \(O(1)\) 。
可以先枚舉出前幾個數比如 1 ~ 10 觀察先手的勝負情況,發現 \(N = 3\) 是第一個先手必敗態,想辦法將必敗態轉移給對方就能獲得勝利。發現任何數總能加 1 或 2 成為 3 的倍數,而任何 \(3\mathbb{k}\) 都不能整除 \(2^{\mathbb{p}}\) ,剩下的數總會是 \(3\mathbb{m} + 1\) 或 \(3\mathbb{m} + 2\) ,這樣只需要拿走 1 個或 2 個就能將 \(3\mathbb{m}\) 的狀態轉移給對方,直到 \(N = 3\) ,因此我們可以確定 \(N = 3\mathbb{k}\) 是本游戲的先手必敗態。
由於數據較小,也允許遞推的做法,復雜度 \(o(NlogN)\) 。原理是這樣的,假設當前有 \(N\) 個,如果取走 \(2^{\mathbb{p}}\) 個后對手是必敗態,說明這一局先手是必勝的,遍歷一遍所有的取法即可。這一思想其實類似於 SG 函數,各位可以自行去了解 SG 函數的原理,把這個題當作 SG 的入門題hhh。
易錯點解析
- 不知道有沒有同學把 Oyk 看成了 Qyk ...(逃;
參考代碼
標程:
#include <stdio.h>
int main() {
int N;
while (~scanf("%d", &N)) {
puts(N % 3 ? "Oyk Oyk!" : "diu,Czj!");
}
return 0;
}
遞推解法(改自一個同學的提交):
#include <stdio.h>
const int MAXN = 1E6;
int win[MAXN + 1];
void init() {
for (int i = 1; i <= MAXN; ++i) {
for (int j = 1; j <= i; j <<= 1) {
if (!win[i - j]) {
win[i] = 1;
break;
}
}
}
}
int main() {
init();
int N;
while (~scanf("%d", &N)) {
puts(win[N] ? "Oyk Oyk!" : "diu,Czj!");
}
return 0;
}
M. 光棍節活動
Description
一年一度的光棍節活動中,cgy 的班級想搞一個別出心裁的活動,要求將班上的男生和女生完美分組,規則如下:
- 每個組之間男生數要相同,女生數也要相同;
- 同一個組中的男生數不能等於女生數;
- 每組中男生和女生分別要有 2 名或以上;
- 必須要分成 2 個或以上的組,組數也要盡可能的多才好玩;
如果不能完美分組,將隨機抽出一名男生和一名女生組成一隊進行假扮情侶活動,重復進行(盡量少的次數)抽人直到剩下的男女生人數恰好可以完美分組。
如果始終不存在完美分組,那么除了成功組隊進行假扮情侶的外,可能會多出若干男生或者女生沒有組隊哦(同性才是真愛!)。
cgy 安排 lxy 實施這個方案,但 lxy 臉紅心跳,想請你幫幫忙。
Input
第一行輸入正整數 \(T \leq 100\) 代表總共有 \(T\) 種情況。
接下來 \(T\) 行,每行包含兩個正整數 \(N < 10^5\) 和 \(M < 10^5\) 代表着該種情況中有 \(N\) 名男生和 \(M\) 名女生。
Output
對於每種情況,請你幫忙計算出完美分組數和假扮情侶數,在一行中輸出,用空格隔開。
如果存在沒有組隊的幸運兒,你可以再為 ta 們輸出且只輸出一行 "happy single dog!" (不包括引號)聊表慶祝。
Sample Input
3
18 24
19 25
18 19
Sample Output
6 0
6 1
0 18
happy single dog!
Hint
樣例解釋:
對於樣例 1 ,可以完美分組為最多 6 組,每組有 3 名男生 4 名女生,不需要再抽人進行假扮情侶活動。
對於樣例 2 ,先抽出一對送給 FFF 團,之后就跟樣例 1 一樣啦。
對於樣例 3 ,只能抽出 18 對玩假扮情侶活動,然后下一行為剩下的可憐妹紙輸出 happy single dog! 。
題目大意
有兩堆數量不等的物品,要求對其進行分組,且做到組數最大化;
不能只分一個組,否則不叫分組;
不能任意分組,每個組的情況要相同;
加入規則 2 ,一個組中不同種類物品數量不同;
加入規則 3 ,一個組中任意一種物品數量不為 1 。
(由於我原來的程序忘記考慮規則 3 ,所以比賽中臨時刪掉了,后來發現數據也沒卡掉,改不改都是對的)
參考思路
數量關系規律題,枚舉到能求最大公約數為止。
如果兩個數量相等則直接全部算作假扮情侶。
如果兩個數量相差 1 則一定不能求 gcd 。
如果枚舉過程中最大數可以整除最小數,則不滿足規則 3 。
如果枚舉失敗,那就不能完美分組了。
參考代碼
下面的代碼已經考慮了規則 3 :
#include <stdio.h>
int N, M;
void swap(int &a, int &b) {
const int t = a;
a = b;
b = t;
}
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
void read() {
scanf("%d %d", &N, &M);
}
void work() {
int perfect = 0, fake = 0;
bool hasSingle = false;
if (N > M) {
swap(N, M);
}
if (N == M) {
fake = N;
} else if (N + 1 == M) {
hasSingle = true;
fake = N;
} else {
int groupCnt = 1;
while (N && (1 == (groupCnt = gcd(M, N)) || N == groupCnt)) {
++fake;
--N;
--M;
}
if (!N) {
hasSingle = true;
} else {
perfect = groupCnt;
}
}
printf("%d %d\n", perfect, fake);
if (hasSingle) {
puts("happy single dog!");
}
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
read();
work();
}
return 0;
}
初賽復盤
一開始我是沒想過要把題目排成這個順序的,我們的背鍋俠忘記調啦!於是只好在 7 分鍾發了條@全員說難度不按順序,然而大家還是喜歡按順序做(攤手.jpg)。於是就又有了一堆被打擊信心的爆零啦 qwq
接着就由於沒有充分驗題,陸續發現了卡 cin 之類的奇怪問題,改了兩次數據重判了若干次,又把原來 AC 的給重判成 TLE 了“咦重判完 AC 反而變少了”(葯丸.jpg)。
然后安利一下 qu 老師家的可能還算好用的金山 WPS 寫得;借助這個奇怪的寫作(?)工具臨時對發現的一些問題,一些沒說清楚的事項作了個補充(雖然感覺並沒有人看),於是像 system("pause")
之類的到后期似乎沒在選手們的提交中出現過了。
整體區分度良好,從最終排名來看,做對題目數呈階段性分布;除了 A 題是個看起來簡單其實大家都沒學過的大坑外,對 H 題《CG 之爭》難度估算錯誤,顯然大家都沒聽說過斐波那契的通項公式;對 M 題《光棍節活動》難度估算錯誤,雖然與 K 題《強迫症患者》一樣是要用到求最大公約數的算法,但題面明顯較復雜,陳述有待優化,甚至出現出題人自己都漏考慮其中一個條件的情況;對 L 題《Oyk 捏泡泡紙》難度估算錯誤,顯然大家找規律的能力還是挺強的,之前是認為這個博弈題比較難證明,然而大家敢交題。。
整體題目質量良好,除了個別題目題意有待加強、條件描述不准確或其他小問題外,涉及的知識點較全,沒有重復出題;除了個別選手在 F 題用到批處理生成長段代碼,正解解答代碼量適中,涉及的少數程序語言知識大致符合教xue學xi進度。
出題總結
題號 | 題目 | ZYJ 預xia估cai難度 | 正確率(正確/提交) | 通過率(正確人數/有效人數) | 出題人 |
---|---|---|---|---|---|
A | 詭異的計數法 | 2.8 | 9.88%(100/1012) | 61.64%(90/146) | Cgy |
B | 數發票 | 4.0 | 11.43%(28/245) | 13.01%(19/146) | Cgy |
C | Ljc 吃糖 | 2.5 | 8.82%(30/340) | 14.38%(21/146) | Ljc |
D | 姓名字母排列 | 2.5 | 9.59%(21/219) | 8.22%(12/146) | Zyj |
E | Substring | 2.0 | 51.08%(118/231) | 74.66%(109/146) | Zlm |
F | 法特狗 | 3.0 | 7.29%(25/343) | 12.33%(18/146) | Cgq |
G | Zyj 打印獎狀 | 1.0 | 26.97%(140/519) | 88.36%(129/146) | Cgy |
H | Cg 之爭 | 1.8 | 21.55%(86/399) | 53.42%(78/146) | Cgy |
I | 人形質量單位 | 4.0 | 20.00%(16/80) | 6.85%(10/146) | Lxy |
J | 訓練 ZetaGo | 3.0 | 21.02%(78/371) | 47.95%(70/146) | Zyj |
K | 強迫症患者 | 2.5 | 21.26%(44/207) | 27.40%(40/146) | Lxy |
L | Oyk 捏泡泡紙 | 3.0 | 59.90%(118/197) | 74.66%(109/146) | Zyj |
M | 光棍節活動 | 2.2 | 11.35%(43/379) | 26.03%(38/146) | Zyj |
呃,文字性的東西辣么煩,怎么可能會有出題總結這種東西嘛。
最后
初賽並不是為了刁難大家,而是讓大家體驗 ACM 程序設計競賽是怎樣的,算法和數據結構是個什么東西,對於一個已知的問題,如何抽象出它的模型並找到合適的方法,在較好的性能內解決它。
今年應該也是第一年允許轉專業的同學參加,一開始還挺擔心他們多學了半學期《數據結構》會不會太強了的(逃)。可能對於多數同學來說,參加 ACM 新生賽或許是第一次這么高密度地思考和解決問題,但不論是萌新也好,還是轉專業的同學也好,希望我們這次比賽可以是你踏上專業成長路線的第一步,相信也只是各位的許多生活歷程的一小段,未來的生活還很長,可能你會像我一樣莫名其妙地轉了前端開發,可能你會做一些更有趣的東西,可能你以后會做一名教師,也可能你會繼續深入計算機科學的研究中,甚至可能愛上算法競賽以及算法研究與設計,往小了說后面還有 CTF 競賽(逃)。
不管怎樣,希望這次比賽能夠讓你感受到計算機科學的藝術與程序設計的魅力,讓你的編程能力有所鍛煉和提高,讓你的思考空間和知識面有所擴充,而不僅僅是停留在表面上的“這個太難了”“這個有什么用嘛”“我以后總會學到的”,謝謝!
本文基於
知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議發布,歡迎引用、轉載或演繹,但是必須保留本文的署名BlackStorm以及本文鏈接http://www.cnblogs.com/BlackStorm/p/SCNUCPCFF-2017-Solutions.html,且未經許可不能用於商業目的。如有疑問或授權協商請與我聯系。