補題補題
牛客網給出的題解:https://www.nowcoder.com/discuss/39219
我自己目前只有前 7 題,后面一題先放着。我自己的解析也發在牛客網的題目下了。。不知能不能騙騙贊 233
魔法幣
Description
小易准備去魔法王國采購魔法神器,購買魔法神器需要使用魔法幣,但是小易現在一枚魔法幣都沒有,但是小易有兩台魔法機器可以通過投入x(x可以為0)個魔法幣產生更多的魔法幣。
魔法機器1:如果投入x個魔法幣,魔法機器會將其變為2x+1個魔法幣
魔法機器2:如果投入x個魔法幣,魔法機器會將其變為2x+2個魔法幣
小易采購魔法神器總共需要n個魔法幣,所以小易只能通過兩台魔法機器產生恰好n個魔法幣,小易需要你幫他設計一個投入方案使他最后恰好擁有n個魔法幣。
Input
輸入包括一行,包括一個正整數n(1 ≤ n ≤ 10^9),表示小易需要的魔法幣數量。
Output
輸出一個字符串,每個字符表示該次小易選取投入的魔法機器。其中只包含字符'1'和'2'。
Sample Input
10
Sample Output
122
解題思路
水題,求 x 經過若干次 \(f(x)=2x+c\) 的變換恰好到 n ,顯然次數不超過 \(log(n)+1=32\) 次,而 \(c=1 || 2\) 使得每次 n' 具有奇偶特征,直接遞歸出來。
AC 代碼
#include <stdio.h>
int n;
void read() {
scanf("%d", &n);
}
void magic(int n) {
if (n <= 0) return;
if (n & 1) {
magic((n - 1) / 2);
putchar('1');
} else {
magic((n - 2) / 2);
putchar('2');
}
}
void work() {
magic(n);
putchar('\n');
}
int main() {
read();
work();
return 0;
}
相反數
Description
為了得到一個數的"相反數",我們將這個數的數字順序顛倒,然后再加上原先的數得到"相反數"。例如,為了得到1325的"相反數",首先我們將該數的數字順序顛倒,我們得到5231,之后再加上原先的數,我們得到5231+1325=6556.如果顛倒之后的數字有前綴零,前綴零將會被忽略。例如n = 100, 顛倒之后是1.
Input
輸入包括一個整數n,(1 ≤ n ≤ 10^5)
Output
輸出一個整數,表示n的相反數
Sample Input
1325
Sample Output
6556
解題思路
水題,可以算程序設計入門課作業題。
AC 代碼
#include <stdio.h>
int n;
void read() {
scanf("%d", &n);
}
int reverse(int n) {
int res = 0;
while (n) {
res = res * 10 + n % 10;
n /= 10;
}
return res;
}
void work() {
printf("%d\n", n + reverse(n));
}
int main() {
read();
work();
return 0;
}
字符串碎片
Description
一個由小寫字母組成的字符串可以看成一些同一字母的最大碎片組成的。例如,"aaabbaaac"是由下面碎片組成的:'aaa','bb','c'。牛牛現在給定一個字符串,請你幫助計算這個字符串的所有碎片的平均長度是多少。
Input
輸入包括一個字符串s,字符串s的長度length(1 ≤ length ≤ 50),s只含小寫字母('a'-'z')
Output
輸出一個整數,表示所有碎片的平均長度,四舍五入保留兩位小數。
如樣例所示: s = "aaabbaaac"
所有碎片的平均長度 = (3 + 2 + 3 + 1) / 4 = 2.25
Sample Input
aaabbaaac
Sample Output
2.25
解題思路
水題,所有塊的總長度就是字符串的長度,數一下有多少塊。
AC 代碼
#include <stdio.h>
char str[55];
void read() {
scanf("%s", str);
}
void work() {
int cnt = 1, i;
for (i = 1; str[i]; ++i) {
if (str[i] != str[i-1]) {
++cnt;
}
}
double res = (double)(i) / cnt;
printf("%.2lf\n", res);
}
int main() {
read();
work();
return 0;
}
游歷魔法王國
Description
魔法王國一共有n個城市,編號為0~n-1號,n個城市之間的道路連接起來恰好構成一棵樹。
小易現在在0號城市,每次行動小易會從當前所在的城市走到與其相鄰的一個城市,小易最多能行動L次。
如果小易到達過某個城市就視為小易游歷過這個城市了,小易現在要制定好的旅游計划使他能游歷最多的城市,請你幫他計算一下他最多能游歷過多少個城市(注意0號城市已經游歷了,游歷過的城市不重復計算)。
Input
輸入包括兩行,第一行包括兩個正整數n(2 ≤ n ≤ 50)和L(1 ≤ L ≤ 100),表示城市個數和小易能行動的次數。
第二行包括n-1個整數parent[i](0 ≤ parent[i] ≤ i), 對於每個合法的i(0 ≤ i ≤ n - 2),在(i+1)號城市和parent[i]間有一條道路連接。
Output
輸出一個整數,表示小易最多能游歷的城市數量。
Sample Input
5 2
0 1 2 3
Sample Output
3
解題思路
原本以為是樹上 dp ,其實是貪心。
畫個圖可以知道,可把 parent[i] 當作 (i+1) 的父親節點(因為 parent[i] 是可以重復的)。之前看漏了 parent[i] 的范圍限制了父節點標號比子節點小 這個條件,我用了 鏈式前向星 來建圖。
建好圖之后,就可以從樹根擴散出每個節點所在最長樹鏈的長度,選出最長的一條樹鏈,記其長度為 maxLen 。
分類討論:
- 若 L ≤ maxLen ,顯而易見得結果;
- 若 L > maxLen ,意味着可以往回走,要知道越短的樹鏈往回走的代價越低。如果從末端往回走,消耗的代價非常高,最壞情況是較短的樹鏈都連接在最遠的樹根上,整條最長鏈都要回走;如果已經知道最終步數會有剩余,則可以先消耗富余的步數走短鏈,最后才走最長鏈;
- 繼續對 rest = L - maxLen 進行討論:
- 若樹鏈上存在某個節點擁有另一條子鏈,其長度 x 必定小於或等於該祖先到原鏈末端的長度,考察樹鏈上每個節點到葉子的一條最短子鏈:
- 當 x > rest/2 可以在中途預先用掉 rest 步而不影響要走的 maxLen 最長鏈,可達城市增加 rest/2 個;
- 當 x ≤ rest/2 可以在中途預先用掉 2x 步而不影響要走的 maxLen 最長鏈,可達城市增加 x 個;
- 當所有的 x 總和 sum(x) ≤ rest/2 說明富余的步數足夠把最短鏈到次最長鏈都走一遍,可達城市為全部 n 個。
- 本小節討論可知 rest/2 決定了能多走的城市數量,總共能走 min(n, 1 + rest/2 + maxLen) 個城市。
- 若樹鏈上存在某個節點擁有另一條子鏈,其長度 x 必定小於或等於該祖先到原鏈末端的長度,考察樹鏈上每個節點到葉子的一條最短子鏈:
AC 代碼
#include <stdio.h>
#include <string.h>
#define MAXN 55
#define MAXM 55
inline void getMax(int& n, int x) {
n < x && (n = x);
}
inline void getMin(int& n, int x) {
n > x && (n = x);
}
struct Edge {
int to;
int next;
} edge[MAXM];
int cnt;
int head[MAXN], len[MAXN];
void init() {
memset(head, 0xff, sizeof(head));
}
void addEdge(int u, int v) {
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt++;
}
int n, L;
void read() {
int parent;
scanf("%d%d", &n, &L);
for (int i = 1; i < n; ++i) {
scanf("%d", &parent);
addEdge(parent, i);
}
}
void walk(int u) {
for (int i = head[u]; ~i; i = edge[i].next) {
len[edge[i].to] = len[u] + 1;
walk(edge[i].to);
}
}
void work() {
walk(0);
int maxLen = 0;
for (int i = 0; i < n; ++i) {
getMax(maxLen, len[i]);
}
if (L <= maxLen) {
printf("%d\n", L + 1);
} else {
int res = n;
getMin(res, maxLen + (L - maxLen) / 2 + 1);
printf("%d\n", res);
}
}
int main() {
init();
read();
work();
return 0;
}
重排數列
Description
小易有一個長度為N的正整數數列A = {A[1], A[2], A[3]..., A[N]}。
牛博士給小易出了一個難題:
對數列A進行重新排列,使數列A滿足所有的A[i] * A[i + 1] (1 ≤ i ≤ N - 1)都是4的倍數。
小易現在需要判斷一個數列是否可以重排之后滿足牛博士的要求。
Input
輸入的第一行為數列的個數t(1 ≤ t ≤ 10),
接下來每兩行描述一個數列A,第一行為數列長度n(1 ≤ n ≤ 10^5)
第二行為n個正整數A[i](1 ≤ A[i] ≤ 10^9)
Output
對於每個數列輸出一行表示是否可以滿足牛博士要求,如果可以輸出Yes,否則輸出No。
Sample Input
2
3
1 10 100
4
1 2 3 4
Sample Output
Yes
No
解題思路
分類討論下。
- 顯然,任意數和 4 的倍數相乘,其結果仍是 4 的倍數;
- 顯然,若存在任意數量 2 的倍數,兩兩之間乘起來就是 4 的倍數;
- 如果存在一個數不是 2 的倍數,即它是一個奇數:
- 放在 2 的倍數旁邊,一定不符合要求;
- 放在 4 的倍數旁邊,相乘結果仍是 4 的倍數。
因此符合要求的排列分兩種情況:
- 存在 2 的倍數,所有 2 的倍數相鄰排列,需要一個 4 的倍數連接剩下的數,奇數最多和 4 的倍數數量相等,要求 countMod4 >= countOdd
- 沒有 2 的倍數,原本放 2 的倍數一端可以改放一個奇數,countMod4 >= countOdd - 1
AC 代碼
#include <stdio.h>
int n;
int arr[100100];
int countMod4, countMod2;
void read() {
countMod4 = 0;
countMod2 = 0;
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%d", arr + i);
if (arr[i] % 4 == 0) {
++countMod4;
} else if (arr[i] % 2 == 0) {
++countMod2;
}
}
}
void work() {
int countOdd = n - countMod4 - countMod2;
if ((n == 1 && countMod4) || countMod4 >= countOdd - !countMod2) {
puts("Yes");
} else {
puts("No");
}
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
read();
work();
}
return 0;
}
最長公共子括號序列
Description
一個合法的括號匹配序列被定義為:
- 空串""是合法的括號序列
- 如果"X"和"Y"是合法的序列,那么"XY"也是一個合法的括號序列
- 如果"X"是一個合法的序列,那么"(X)"也是一個合法的括號序列
- 每個合法的括號序列都可以由上面的規則生成
例如"", "()", "()()()", "(()())", "(((()))"都是合法的。
從一個字符串S中移除零個或者多個字符得到的序列稱為S的子序列。
例如"abcde"的子序列有"abe","","abcde"等。
定義LCS(S,T)為字符串S和字符串T最長公共子序列的長度,即一個最長的序列W既是S的子序列也是T的子序列的長度。
小易給出一個合法的括號匹配序列s,小易希望你能找出具有以下特征的括號序列t:
1、t跟s不同,但是長度相同
2、t也是一個合法的括號匹配序列
3、LCS(s, t)是滿足上述兩個條件的t中最大的
因為這樣的t可能存在多個,小易需要你計算出滿足條件的t有多少個。
如樣例所示: s = "(())()",跟字符串s長度相同的合法括號匹配序列有:
"()(())", "((()))", "()()()", "(()())",其中LCS( "(())()", "()(())" )為4,其他三個都為5,所以輸出3.
Input
輸入包括字符串s(4 ≤ |s| ≤ 50,|s|表示字符串長度),保證s是一個合法的括號匹配序列。
Output
輸出一個正整數,滿足條件的t的個數。
Sample Input
(())()
Sample Output
3
解題思路
根據題意,當且僅當修改距離為 1 時 LCS 最大。很容易證明對於兩種基本序列 (()) 和 ()() 都有距離為 1 的合法修改。
原本想的是對每個左括號,跟每個右括號替換,判斷合法后累計。
后來發現會漏掉一些情況,那就暴力得干脆一點,把每個符號插入到任意位置,判合法,去重,累計。
AC 代碼
#include <stdio.h>
#include <algorithm>
#include <string>
#include <set>
using namespace std;
char str[55];
void read() {
scanf("%s", str);
}
bool test(const string& s) {
int cnt = 0;
for (int i = 0; s[i]; ++i) {
s[i] == '(' ? ++cnt : --cnt;
if (cnt < 0) {
return false;
}
}
return true;
}
void work() {
set<string> record;
for (int i = 1; str[i+1]; ++i) {
string tmp(str);
tmp.erase(i, 1);
for (int j = 1; str[j]; ++j) {
if (str[i] == str[j]) continue;
string s(tmp);
s.insert(j, 1, str[i]);
if (test(s)) {
record.insert(s);
}
}
}
printf("%lu\n", record.size());
}
int main() {
read();
work();
return 0;
}
合唱
Description
小Q和牛博士合唱一首歌曲,這首歌曲由n個音調組成,每個音調由一個正整數表示。
對於每個音調要么由小Q演唱要么由牛博士演唱,對於一系列音調演唱的難度等於所有相鄰音調變化幅度之和, 例如一個音調序列是8, 8, 13, 12, 那么它的難度等於|8 - 8| + |13 - 8| + |12 - 13| = 6(其中||表示絕對值)。
現在要對把這n個音調分配給小Q或牛博士,讓他們演唱的難度之和最小,請你算算最小的難度和是多少。
如樣例所示: 小Q選擇演唱{5, 6}難度為1, 牛博士選擇演唱{1, 2, 1}難度為2,難度之和為3,這一個是最小難度和的方案了。
Input
輸入包括兩行,第一行一個正整數n(1 ≤ n ≤ 2000) 第二行n個整數v[i](1 ≤ v[i] ≤ 10^6), 表示每個音調。
Output
輸出一個整數,表示小Q和牛博士演唱最小的難度和是多少。
Sample Input
5
1 5 6 2 1
Sample Output
3
解題思路
正推的話,容易想到一個人繼續演唱或換人演唱的時候發生狀態轉移:
- 設 dp[i][j] 表示當前小Q唱到第 i 個音調,牛博士唱到第 j 個音調的難度和;
- 不妨設當前 i > j :
- 若 i - 1 == j 則發生換人,由於不知道上一次 i 唱到哪里,狀態由 min{ dp[k][j] + abs(v[i] - v[k]) }, k < j 轉移來;
- 若 i - 1 > j 則表示當前是從 i - 1 唱到 i 的,沒有換人,狀態由 dp[i-1][j] + abs(v[i] - v[i-1]) 累加;
- 不妨設 dp[i][j] 表示當前演唱到第 i 個,上一個人演唱到第 j 個,則狀態轉移方程為
dp[i][j] = dp[i-1][j] + abs(v[i] - v[i-1]), j < i - 1
dp[i][i -1] = min{ dp[i-1][k] + abs(v[i] - v[k]) }, k < i - 1 - 邊界情況是一個人唱第一個,后面所有音調讓另一個人唱
dp[i][0] = dp[i-1][0] + abs(v[i] - v[i-1]), i ≥ 2
或者一個人唱前面所有音調,最后一個音調讓另一個人唱
dp[i][i-1] = dp[i-1][i-2] + abs(v[i-1] - v[i-2]), i ≥ 2
反推:
- 設 dp[i][j] 表示從當前小Q唱到第 i + 1 個音調,牛博士唱到第 j + 1 個音調開始,直到所有音調演唱完的難度和;邊界情況 i = 0 或 j = 0 表示一個人還沒開始唱;
- 容易知道下一個音調 next = max{ i, j } ,若讓小Q唱下一個音調,會得到 abs(v[next] - v[i]) 的貢獻;若讓牛博士唱會得到 abs(v[next] - v[j]) 的貢獻;
- 狀態轉移方程
dp[i][j] = min{ dp[next][j] + abs(v[next] - v[i]), dp[i][next] + abs(v[next] - v[j]) }, i ≠ j < n
AC 代碼
正推:
#include <stdio.h>
#include <stdlib.h>
typedef long long llong;
inline void getMin(llong& n, llong x) {
n > x && (n = x);
}
#define MAXN 2020
int n;
int v[MAXN], cost[MAXN];
void read() {
scanf("%d%d", &n, v);
for (int i = 1; i < n; ++i) {
scanf("%d", v + i);
cost[i] = abs(v[i] - v[i - 1]);
}
}
llong dp[MAXN][MAXN];
void work() {
llong res = (1ll << 63) - 1;
for (int i = 2; i < n; ++i) {
// dp[i][0] = dp[i - 1][0] + cost[i];
dp[i][i - 1] = dp[i - 1][i - 2] + cost[i - 1];
}
for (int i = 2; i < n; ++i) {
for (int j = 0; j < i - 1; ++j) {
dp[i][j] = dp[i - 1][j] + cost[i];
getMin(dp[i][i - 1], dp[i - 1][j] + abs(v[i] - v[j]));
}
}
for (int i = 0; i < n - 1; ++i) {
getMin(res, dp[n - 1][i]);
}
printf("%lld\n", res);
}
int main() {
read();
work();
return 0;
}
反推要做一些變動:
using std::max;
using std::min;
int n, v[MAXN];
void read() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", v + i);
}
}
int cost(int a, int b) {
return a && b ? abs(v[a] - v[b]) : 0;
}
記憶化遞歸搜索:
llong solve(int i, int j) {
int next = max(i, j) + 1;
if (next == n + 1) {
return 0;
}
if (!~dp[i][j]) {
dp[i][j] = min(solve(next, j) + cost(next, i), solve(i, next) + cost(next, j));
}
return dp[i][j];
}
void work() {
memset(dp, 0xff, sizeof(dp));
llong res = solve(0, 0);
printf("%lld\n", res);
}
用循環更簡單:
void work() {
for (int i = n - 1; i >= 0; --i) {
for (int j = n - 1; j >= 0; --j) {
int next = max(i, j) + 1;
dp[i][j] = min(dp[next][j] + cost(next, i), dp[i][next] + cost(next, j));
}
}
printf("%lld\n", dp[0][0]);
}
本文基於
知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 發布,歡迎引用、轉載或演繹,但是必須保留本文的署名 BlackStorm 以及本文鏈接 http://www.cnblogs.com/BlackStorm/p/7499974.html ,且未經許可不能用於商業目的。如有疑問或授權協商請 與我聯系 。

知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議