小白月賽33題解(全)
A.字符統計
題目鏈接:https://ac.nowcoder.com/acm/contest/11210/A
直接讀入字符串進行遍歷即可。用getline()
讀一行字符串,注意:cin讀入后的回車會留在輸入緩沖區,getline()的不會,所以在使用cin后,getline()之前需要清空輸入緩沖區
注意行開頭空格和空行的特殊處理。
AC代碼:
#include <iostream>
using namespace std;
string s;
int r, dc, zf;
int main()
{
int t;
cin >> t;
getline(cin, s);
while(t--)
{
r = dc = zf = 0;
while(getline(cin, s) && s != "=====")
{
r++;
for(int i = 0; i < s.size(); i++)
{
if(s[i] == ' ' && i) dc++;
zf++;
}
if(s.size())dc++;
}
cout << r << ' ' << dc << ' ' << zf << endl;
}
return 0;
}
B.連分數
題目鏈接:https://ac.nowcoder.com/acm/contest/11210/B
對一般的\(p/q\),\(p / q = a + 1 / m, 其中m = q / (p \% q)\),然后令分子 = q, 分母 = p % q,繼續上述操作,直到$ p % q == 0$時結束操作。
細節處理:
- 沒輸出一個a,如果p、q更新后可以進行操作,則輸出“+1/{”,並且記錄輸出的’{‘的數量
- 如果p、q更新后不可進行操作,即\(p \% q == 0\),則說明到最后一個數了,不用輸出大括號,並且下一輪會結束循環
- 最后輸出等量的’}‘。
AC代碼:
#include <iostream>
#include <cstdio>
using namespace std;
int t, p, q;
int main()
{
cin >> t;
while(t--)
{
scanf("%d%d", &p, &q);
printf("%d/%d = ", p, q);
int kh = 0;
while(q)
{
printf("%d", p / q);
int t = p % q;
p = q, q = t;
if(q)
{
if(p % q != 0)
{
printf("+1/{");
kh++;
}
else printf("+1/");
}
}
while(kh--) printf("}");
puts("");
}
return 0;
}
C.挪酒瓶
題目鏈接:https://ac.nowcoder.com/acm/contest/11210/C
參考題解:https://www.nowcoder.com/discuss/643079、https://blog.nowcoder.net/n/efd1e10b345a42e1ae0231fc4ae9e7b4
對於一個有 n 個元素的置換群p,從最后一個 a往前開始換,則費用為 \((w_a+w_b)+(w_a+w_c)+...=(n−2)w_a+\sum_{i\in p}w_i\)
最優的策略則是選擇較小的 \(w_a\) 開始換。
對於多個置換群而言,單獨內部交換並不是最優解,可以把目前最輕的酒瓶拿進來替換,最后再換回去。令置換群 p 內最輕重量為 \(m_p=min_{i\in p}wi\),令全部的酒里面最輕重量為 \(m_{all}\)。這個策略的花費為 \(2(m_{all}+m_p)+(n−2)m_{all}+\sum_{i\in p}w_i - m_p + m_{all}\)(交換兩次最小值花費:\(2(m_{all}+m_p)\),交換后的所有費用和為:\(\sum_{i\in p}w_i - m_p + m_{all}\))
最后對於每一個置換群,考慮兩個 Case 如下,都選擇最優即可:
- \((n−2)w_p+\sum_{i\in p}w_i\)
- \(2(m_{all}+m_p)+(n−2)m_{all}+∑_{iεp}w_i - m_p + m_{all}\) = \((n + 1)*m_{all} + m_p + \sum_{i\in p}w_i\)
AC代碼:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5+10;
int a[N], w[N];
bool st[N];
int main()
{
int t;
cin >> t;
while(t--)
{
memset(st, 0, sizeof st);
int n, ans = 0, min_all = 1e9;
cin >> n;
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 1; i <= n; i++)
{
scanf("%d", &w[i]);
//找出全局最小值
min_all = min(min_all, w[i]);
}
for(int i = 1; i <= n; i++)
if(!st[i])
{
int cnt = 0, minv = 1e9;
//求一個置換群
for(int j = i; !st[j]; j = a[j])
{
cnt++;
ans += w[j];
minv = min(minv, w[j]);
st[j] = true;
}
//最優解更新
ans += min((cnt - 2) * minv, (cnt + 1) * min_all + minv);
}
cout << ans << endl;
}
return 0;
}
D.購物
題目鏈接:https://ac.nowcoder.com/acm/contest/11210/D
將物品名稱映射到數字,用數組記錄每一個物品的數量,然后遍歷每個人,將其沒有的物品的數量-1,最后遍歷計算物品數量大於0的個數。
AC代碼:
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <cstring>
using namespace std;
const int N = 110;
int t, s, n;
int a[N];
int main()
{
cin >> t;
while(t--)
{
memset(a, 0, sizeof a);
unordered_map<string, int> goods;
string str;
int x, idx = 0;
scanf("%d%d", &s, &n);
for(int i = 0; i < s; i++)
{
cin >> str >> x;
goods.insert({str, idx++});
a[goods[str]] = x;
}
//for(int i = 0; i < idx; i++) cout << a[i] << ' ';
//puts("");
for(int i = 0; i < n; i++)
{
bool has[N];
memset(has, false, sizeof has);
cin >> x;
for(int j = 0; j < x; j++)
{
cin >> str;
has[goods[str]] = true;
}
for(int i = 0; i < idx; i++)
if(!has[i]) a[i]--;
}
int ans = 0;
for(int i = 0; i < idx; i++)
if(a[i] > 0) ans++;
if(ans) cout << ans << endl;
else puts("Need to be lucky");
}
return 0;
}
E.喝可樂
題目鏈接:https://ac.nowcoder.com/acm/contest/11210/E
直接枚舉每種可樂買多少瓶的所有情況,對每種情況計算能喝到的瓶數,記錄最大值即可。
AC代碼:
#include <iostream>
#include <algorithm>
using namespace std;
int t, n, a, b;
int main()
{
cin >> t;
while(t--)
{
int ans = 0;
cin >> n >> a >> b;
for(int i = 0; i <= n; i++)
{
int Max = n, an = i, bn = n - i;
while(an >= a || bn >= b)
{
if(an >= a)
{
Max += an / a;
bn += an / a;
an = an % a;
}
if(bn >= b)
{
Max += bn / b;
an += bn / b;
bn = bn % b;
}
}
ans = max(Max, ans);
}
cout << ans << endl;
}
return 0;
}
F.天旋地轉
題目鏈接:https://ac.nowcoder.com/acm/contest/11210/F
模擬,把坐標系的四種不同情況下的移動偏移量存下來,然后按步驟一步一步操作即可。
注意在計算逆時針旋轉時,標記坐標軸方向的數字fx需要變為非負數
AC代碼:
#include <iostream>
#include <algorithm>
#include <unordered_map>
#define x first
#define y second;
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
int t, n;
PII Move[4][4] = {{{0, 1}, {-1, 0}, {1, 0}, {0, -1}},
{{-1, 0}, {0, -1}, {0, 1}, {1, 0}},
{{0, -1}, {1, 0}, {-1, 0}, {0, 1}},
{{1, 0}, {0, 1}, {0, -1}, {-1, 0}}};
unordered_map<char, int> f {{'w', 0}, {'a', 1}, {'d', 2}, {'s', 3}};
int main()
{
cin >> t;
while(t--)
{
LL xx = 0, yy = 0, fx = 0, k;
cin >> n;
while(n--)
{
char c;
cin >> c >> k;
if(c == 'r') fx = (fx + k) % 4;
else if(c == 'l') fx = ((fx - k) % 4 + 4) % 4;
else xx += k * Move[fx][f[c]].x, yy += k * Move[fx][f[c]].y;
//cout << fx << ' ' << xx << ',' << yy << endl;
}
cout << xx << ' ' << yy << endl;
}
return 0;
}
G.切圈圈
題目鏈接:https://ac.nowcoder.com/acm/contest/11210/G
首先預處理出前綴和數組。可以知道性質:前綴和數組中相等的兩點,之間的區間和一定是0。題目已經給出整個數組的和為0,那么只要確定數組中的一段和為0的區間,則另外一段(包含首尾的那一段)也就一定區間和為0了。所以可以直接在一維線性結構上進行求解。
當已經確定了一個區間和為0的區間時,這區間的端點前綴和數組的值一定相等。如果該區間內可以再分,由性質可得,區間內的切分點的前綴和數組值一定和兩個端點的值相等。(環的另外一段也是如此)
由此可以進一步推出:最后分割出來的區間,所有端點的前綴和數組值一定相等。
所以,只要求出前綴和數組值相等的最大數量,即為答案。
AC代碼:
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
const int N = 10010;
int t, n;
int a[N];
int main()
{
cin >> t;
while(t--)
{
cin >> n;
int ans = 0;
map<int, int> m;
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
//預處理前綴和數組
a[i] += a[i - 1];
//記錄值為a[i]的點有多少個
m[a[i]]++;
ans = max(ans, m[a[i]]);
}
cout << ans << endl;
}
return 0;
}
H.貨物運輸
題目鏈接:https://ac.nowcoder.com/acm/contest/11210/H
根據題目建有向圖,然后直接套最短路模板即可。
關於建圖,難點在處理好邊權:
邊權:w[i] = c * min(f, d) + max(0, f - d) * cc
AC代碼:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 110;
int t, n, m, s, ee, f;
int a, b, c, d, cc;
int h[N], e[N * N], ne[N * N], idx;
LL w[N * N], dist[N];
bool st[N];
void add(int a, int b, LL c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dij(int u)
{
dist[u] = 0;
for(int i = 0; i < n; i++)
{
int k = -1;
for(int j = 1; j <= n; j++)
if(!st[j] && (k == -1 || dist[k] > dist[j]))
k = j;
for(int j = h[k]; j != -1; j = ne[j])
{
dist[e[j]] = min(dist[e[j]], dist[k] + w[j]);
}
st[k] = true;
}
}
int main()
{
cin >> t;
while(t--)
{
idx = 0;
memset(h, -1, sizeof h);
memset(dist, 0x3f, sizeof dist);
memset(st, false, sizeof st);
scanf("%d%d%d%d%d", &n, &m, &s, &ee, &f);
for(int i = 0; i < m; i++)
{
scanf("%d%d%d%d%d", &a, &b, &c, &d, &cc);
//注意d、f的大小
add(a, b, (LL)c * min(d, f) + (LL)max(0, f - d) * cc);
}
dij(s);
//for(int i = 1; i <= n; i++)
// cout << i << ':' << dist[i] << endl;
printf("%lld\n", dist[ee]);
}
return 0;
}
I.三角尼姆
題目鏈接:https://ac.nowcoder.com/acm/contest/11210/I
首先手動模擬發現:N = 1時Alice必贏,此時一共有1個空位;N = 2時Alice必贏,此時一共有3個空位;N = 3時Bob必贏,此時一共有6個空位;N = 4時Bob必贏,此時一共有10個空位。。。。。。
大膽猜測出:當空位總數為偶數個是,先手必贏,否則先手必輸
經代碼檢驗猜測正確。
證明:最后輸的條件一定是只剩1個空位,往前推一下就是倒數第二次放棋子一定是3個空位,即最后面對的必輸局面是剩下奇數個空位。
由:奇數 - 奇數 = 偶數,偶數 - 奇數 = 奇數,且每次減少空位一定是1或者3(奇數)
可得:剩余空位數一定是以“奇偶奇偶...”或者“偶奇偶奇...”的順序出現的
進一步可得:如果玩家第一次面對的是偶數個空位,則一直是面對偶數個空位,則一定不會碰到奇數的情況,就一定不會輸
AC代碼:
#include <iostream>
#include <algorithm>
using namespace std;
int t, n;
int main()
{
cin >> t;
while(t--)
{
cin >> n;
n = (1 + n) * n / 2;
if(n & 1) puts("Alice");
else puts("Bob");
}
return 0;
}
J.線段的交
題目鏈接:https://ac.nowcoder.com/acm/contest/11210/J
分兩步:判斷線段是否相交、求面積。
解法一:利用向量叉積判斷線段相交及求四邊形面積。
AC代碼:
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
double x1, yy1, x2, y2, x3, y3, x4, y4;
struct Point
{
double x, y;
}p1, p2, q1, q2;
double cj(Point p, Point q)
{
return p.x * q.y - q.x * p.y;
}
double area(Point p1, Point p2, Point q1)
{
Point xl1 = {p2.x - p1.x, p2.y - p1.y};
Point xl2 = {q1.x - p1.x, q1.y - p1.y};
return cj(xl1, xl2);
}
int main()
{
scanf("%lf%lf%lf%lf%lf%lf%lf%lf", &x1, &yy1, &x2, &y2, &x3, &y3, &x4, &y4);
p1 = {x1, yy1}, p2 = {x2, y2}, q1 = {x3, y3}, q2 = {x4, y4};
double s1 = area(p1, p2, q1);
double s2 = area(p1, p2, q2);
double s3 = area(q1, q2, p1);
double s4 = area(q1, q2, p2);
if(s1 * s2 <= 0 && s3 * s4 <= 0) printf("%.8f\n", (fabs(s1) + fabs(s2)) / 2);
else cout << 0 << endl;
return 0;
}
解法二:利用快速排斥實驗和跨立實驗判斷直線相交,在用向量叉積求四邊形面積
WA代碼(case通過率92%,bug未知):
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
double x1, yy1, x2, y2, x3, y3, x4, y4;
struct Point
{
double x, y;
}p1, p2, q1, q2;
//快速排斥實驗
bool kspc(Point p1, Point p2, Point q1, Point q2)
{
double minRx = min(p1.x, p2.x), minRy = min(p1.y, p1.y);
double maxRx = max(p1.x, p2.x), maxRy = max(p1.y, p2.y);
double minTx = min(q1.x, q2.x), minTy = min(q1.y, q2.y);
double maxTx = max(q1.x, q2.x), maxTy = max(q1.y, q2.y);
double minFx = max(minRx, minTx), minFy = max(minRy, minTy);
double maxFx = min(maxRx, maxTx), maxFy = min(maxRy, maxTy);
return (minFx <= maxFx && minFy <= maxFy);
}
//求向量叉積
double cj(Point p, Point q)
{
return p.x * q.y - q.x * p.y;
}
//跨立實驗
bool kl(Point p1, Point p2, Point q1, Point q2)
{
Point xl1 = {p1.x - q1.x, p1.y - q1.y}, xl2 = {q2.x - q1.x, q2.y - q1.y}, xl3 = {p2.x - q1.x, p2.y - q1.y};
Point xl4 = {q1.x - p1.x, q1.y - p1.y}, xl5 = {p2.x - p1.x, p2.y - p1.y}, xl6 = {q2.x - p1.x, q2.y - p1.y};
return (cj(xl1, xl2) * cj(xl2, xl3) >= 0 && cj(xl4, xl5) * cj(xl5, xl6) >= 0);
}
//求面積
void area()
{
/*
double s1 = fabs(p1.x * p2.y - p2.x * p1.y + p2.x * q1.y - q1.x * p2.y + q1.x * p1.y - p1.x * q1.y) / 2;
double s2 = fabs(p1.x * p2.y - p2.x * p1.y + p2.x * q2.y - q2.x * p2.y + q2.x * p1.y - p1.x * q2.y) / 2;
*/
Point xl1 = {p2.x - p1.x, p2.y - p1.y};
Point xl2 = {q1.x - p1.x, q1.y - p1.y};
Point xl3 = {q2.x - p1.x, q2.y - p1.y};
printf("%.8lf\n", (fabs(cj(xl1, xl2)) + fabs(cj(xl1, xl3))) / 2);
}
int main()
{
scanf("%lf%lf%lf%lf%lf%lf%lf%lf", &x1, &yy1, &x2, &y2, &x3, &y3, &x4, &y4);
p1 = {x1, yy1}, p2 = {x2, y2}, q1 = {x3, y3}, q2 = {x4, y4};
if(kspc(p1, p2, q1, q2) && kl(p1, p2, q1, q2)) area();
else cout << 0 << endl;
return 0;
}