另把天梯賽所有題解內容全部打包成了一個文檔,可以自行下載:https://download.csdn.net/download/daixinliangwyx/11170075
L3-001 湊零錢
解法:dfs
坑點:對於零錢總和小於m的,就不需要進行dfs了。如果不進行這個特判,最後一個數據點會超時。
代碼:
#include<bits/stdc++.h>
using namespace std;
int n, k;
int a[10010], b[10010];
int dfs(int i, int sum, int c) {
int flag = 0;
if(sum > k) return 0;
if(sum == k) {
for(int pp = 0; pp < c; pp++) {
printf(pp == 0 ? "%d" : " %d", b[pp]);
}
printf("\n");
return 1;
}
for(int j = i + 1; j < n; j++) {
b[c] = a[j];
flag = dfs(j, sum + a[j], c+1);
if(flag) return 1;
}
return 0;
}
int main() {
scanf("%d%d", &n, &k);
int flag = 0, sum = 0;
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
sum += a[i];
}
sort(a, a+n);
if(sum >= k) {
for(int i = 0; i < n; i++) {
if(a[i] > k) break;
b[0] = a[i];
flag = dfs(i, a[i], 1);
if(flag) break;
}
}
if(flag == 0) printf("No Solution\n");
return 0;
}
L3-002 特殊堆棧
解法:用stack來模擬入棧出棧,用vector來取中間值
代碼:
#include<bits/stdc++.h>
using namespace std;
int n, num;
stack<int> s;
vector<int> v;
string op;
int main() {
cin >> n;
vector<int>::iterator it;
while(n--) {
cin >> op;
if(op == "Push") {
cin >> num;
s.push(num);
it = lower_bound(v.begin(), v.end(), num);
v.insert(it, num);
} else if (op == "Pop") {
if(s.empty()) cout << "Invalid" << endl;
else {
cout << s.top() << endl;
v.erase(lower_bound(v.begin(), v.end(), s.top()));
s.pop();
}
} else {
if(s.empty()) cout << "Invalid" << endl;
else {
cout << v[(s.size()+1)/2-1] << endl;
}
}
}
return 0;
}
L3-003 社交集羣
解法:裸並查集
代碼:
#include<bits/stdc++.h>
using namespace std;
int n, num, k, sum;
vector<int> v[1010];
int tot[1010], pre[1010], ans[1010];
int find(int x) {
if(x == pre[x]) return pre[x];
return pre[x] = find(pre[x]);
}
void join(int x, int y) {
int fx = find(x), fy = find(y);
if(fx != fy) {
pre[fx] = fy;
tot[fy] += tot[fx];
}
}
int main() {
scanf("%d", &n);
sum = 0;
for(int i = 1; i <= n; i++) {
pre[i] = i;
tot[i] = 1;
}
for(int i = 1; i <= n; i++) {
scanf("%d:", &num);
for(int j = 0; j < num; j++) {
scanf("%d", &k);
v[k].push_back(i);
}
}
for(int i = 1; i <= 1000; i++) {
int len = v[i].size();
for(int j = 0; j < len-1; j++) {
join(v[i][j], v[i][j+1]);
}
}
for(int i = 1; i <= n; i++) {
if(i == find(i)) ans[sum++] = tot[i];
}
printf("%d\n", sum);
sort(ans, ans+sum);
for(int i = sum-1; i >= 0; i--) {
printf(i == sum-1 ? "%d" : " %d", ans[i]);
}
printf("\n");
return 0;
}
L3-004 腫瘤診斷
解法:就是個三維立體的bfs求聯通塊數量,我是直接將三維的座標信息放在了二維數組裏(像題目輸入數據的形式那樣存儲),也就是將下一層平面的座標信息接在了上一層的座標信息後面(下一層平面的第一行接在上一平面的最後一行後面)。方向則要改成前後左右上下這六個方向。
坑點:判斷下一個點的x、y的範圍時,對於next.y只用判斷左右是否越界即可,但對於next.x不僅要判越界還要判前後方向(與前一個點now要在同一個平面)和上下方向(與前一個點now不能在同一個平面)的範圍。
代碼:
#include<bits/stdc++.h>
using namespace std;
long long n, m, l, t, num, vv, sum;
long long v[80000][130];
long long dir[6][2];
struct point {
long long x, y;
};
void bfs(long long sx, long long sy) {
queue<point> q;
point now, next;
now.x = sx;
now.y = sy;
q.push(now);
v[sx][sy] = 0;
while(!q.empty()) {
now = q.front();
q.pop();
vv++;
for(long long i = 0; i < 6; i++) {
next.x = now.x + dir[i][0];
next.y = now.y + dir[i][1];
if(next.y < 0 && next.y >= m) continue;//判斷左右是否越界,越界就跳過
if(next.x < 0 || next.x >= n*l) continue;//判斷前後、上下是否越界,越界就跳過
else {//如果在前後和上下的總範圍內
if(i == 2 || i == 3) {//前後
if(next.x / n != now.x / n) continue;//不在一個平面就跳過
}
if(i == 4 || i == 5) {//上下
if(next.x / n == now.x / n) continue;//在一個平面就跳過
}
}
if(v[next.x][next.y] == 1) {
v[next.x][next.y] = 0;
q.push(next);
}
}
}
}
int main() {
sum = 0;
scanf("%lld%lld%lld%lld", &n, &m, &l, &t);
dir[0][0] = 0;
dir[0][1] = 1;
dir[1][0] = 0;
dir[1][1] = -1;
dir[2][0] = 1;
dir[2][1] = 0;
dir[3][0] = -1;
dir[3][1] = 0;
dir[4][0] = n;
dir[4][1] = 0;
dir[5][0] = -n;
dir[5][1] = 0;
for(long long i = 0; i < n*l; i++) {
for(long long j = 0; j < m; j++) {
scanf("%lld", &v[i][j]);
}
}
for(long long i = 0; i < n*l; i++) {
for(long long j = 0; j < m; j++) {
if(v[i][j]) {
vv = 0;
bfs(i, j);
if(vv >= t) sum += vv;
}
}
}
printf("%lld\n", sum);
return 0;
}
L3-005 垃圾箱分佈
解法:裸迪傑斯特拉。將每個垃圾箱編號接在n個居民點後面,對每個垃圾箱進行迪傑斯特拉然後進行選擇就行了。
坑點:1、因爲輸入點包含垃圾箱(帶'G')所以輸入的時候要按字符串輸入,然後再轉成數值,注意垃圾箱是<=10的,所以垃圾箱號碼可能是兩位哦,一開始沒注意這個範圍,WA在了最後一個測試點上。2、一開始讀題有誤,題目上說“垃圾箱的位置必須選在到所有居民點的最短距離最長的地方”,也就是說選出的垃圾箱i到每個居民點j的最短距離(dis[i][j])中最短的那一段距離要是最長的,我一開始理解成了要讓總的dis[i][j]之和最長(因爲題目說的是到所有居民點,我就以爲是求和,我說樣例怎麼對不上,還是自己讀錯題了呀)。(ps:平均距離加上0.0000001是爲了%lf四捨五入時候的浮點誤差,其實好像這裏加不加都能過)
代碼:
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
int n, m, k, ds, len, uu, vv, flag, sumdis, mindis, realsumdis, ans, maxmindis;
int way[1030][1030], to[1030], dis[1030][1030], vis[1030];
char u[10], v[10];
void Dijstl(int s) {
to[s] = 0;
for(int i = 1; i <= n+m; i++) {
int minn = inf, next = -1;
for(int j = 1; j <= n+m; j++) {
if(vis[j] == 0 && to[j] < minn) {
minn = to[j];
next = j;
}
}
if(next == -1) break;
else
vis[next] = 1;
for(int j = 1; j <= n+m; j++)
if(vis[j] == 0 && to[next] + dis[next][j] < to[j]) to[j] = to[next] + dis[next][j];
}
}
int main() {
flag = 0;
maxmindis = -1;
scanf("%d%d%d%d", &n, &m, &k, &ds);
for(int i = 1; i <= n+m; i++) {
for(int j = 1; j <= n+m; j++) {
if(i == j) way[i][j] = 0;
else
way[i][j] = inf;
}
}
for(int i = 0; i < k; i++) {
scanf("%s%s%d", u, v, &len);
if(u[0] == 'G') {
uu = 0;
for(int j = 1; j < strlen(u); j++)
uu = uu * 10 + (int)(u[j] - '0');
uu += n;
} else {
uu = atoi(u);
}
if(v[0] == 'G') {
vv = 0;
for(int j = 1; j < strlen(v); j++)
vv = vv * 10 + (int)(v[j] - '0');
vv += n;
} else {
vv = atoi(v);
}
way[uu][vv] = len;
way[vv][uu] = len;
}
for(int i = n+1; i <= n+m; i++) {
memset(vis, 0, sizeof(vis));
memset(to, inf, sizeof(to));
for(int ii = 1; ii <= n+m; ii++) {
for(int jj = 1; jj <= n+m; jj++) {
dis[ii][jj] = way[ii][jj];
}
}
Dijstl(i);
sumdis = 0;
mindis = inf;
int flag2 = 0;
for(int j = 1; j <= n; j++) {
if(to[j] > ds || to[j] == inf) {
flag2 = 1;
break;
}
sumdis += to[j];
if(i != j) mindis = min(mindis, to[j]);
}
if(flag2 == 0) {
flag = 1;
if(mindis > maxmindis) {
ans = i;
maxmindis = mindis;
realsumdis = sumdis;
} else if(mindis == maxmindis) {
if(sumdis < realsumdis) {
ans = i;
maxmindis = mindis;
realsumdis = sumdis;
}
}
}
}
if(flag) printf("G%d\n%d.0 %.1lf\n", ans-n, maxmindis, realsumdis*1.0/n);
else
printf("No Solution\n");
return 0;
}
L3-006 迎風一刀斬
天吶,計算幾何的數學題,看到就頭大,太繁瑣了,得考慮很多種情況,看了網上別人的代碼,都是100多行起步的。碼起來沒有樂趣,告辭!
L3-007 天梯地圖
解法:因爲起點固定,就直接對長度和時間分別進行迪傑斯特拉。
坑點:在處理“如果最快到達路線不唯一,則輸出幾條最快路線中最短的那條”這個問題時,在時間的Dijistrl裏面是可以直接用一個一維數組來記錄到j點的最短長度的(因爲源點唯一,就可以當作路徑的迪傑斯特拉處理);也可以用Floyd記錄任意兩個點的最短路徑(不會超時),但是在時間相等時進行判斷的地方:fdis[s][next] + fdis[next][j] <= fdis[s][j]這裏必須要有等號否則只能得23分(測試點2錯誤)。
另外提供兩組自造數據:
input:
5 4
1 2 1 2 5
1 3 1 2 4
2 4 1 5 1
3 4 1 4 2
1 4
output:
Time = 6; Distance = 6: 1 => 3 => 4
input:
5 5
2 1 1 4 4
1 0 1 2 2
2 3 1 1 1
3 4 1 2 2
4 0 1 3 3
2 0
output:
Time = 6: 2 => 3 => 4 => 0
Distance = 6: 2 => 1 => 0
代碼:
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
int n, m, v1, v2, oneway, length, ti, s, e, tmp, flag;
int disto[510], dis[510][510], timeto[510], tme[510][510], disvis[510], timevis[510], sum[510];
int dispre[510], timepre[510];
int a[510], b[510], ka, kb;
int fdisto[510];
void Dijstrldis() {
disto[s] = 0;
for(int i = 0; i < n; i++) {
int minn = inf, next = -1;
for(int j = 0; j < n; j++) {
if(disvis[j] == 0 && disto[j] < minn) {
minn = disto[j];
next = j;
}
}
if(next == -1) break;
else
disvis[next] = 1;
for(int j = 0; j < n; j++) {
if(disvis[j] == 0 && disto[next] + dis[next][j] < disto[j]) {
disto[j] = disto[next] + dis[next][j];
dispre[j] = next;
sum[j] = sum[next] + 1;
} else if(disvis[j] == 0 && disto[next] + dis[next][j] == disto[j]) {
if(sum[next] + 1 < sum[j]) {
sum[j] = sum[next] + 1;
dispre[j] = next;
}
}
}
}
}
void Dijstrltime() {
timeto[s] = 0;
for(int i = 0; i < n; i++) {
int minn = inf, next = -1;
for(int j = 0; j < n; j++) {
if(timevis[j] == 0 && timeto[j] < minn) {
minn = timeto[j];
next = j;
}
}
if(next == -1) break;
else
timevis[next] = 1;
for(int j = 0; j < n; j++) {
if(timevis[j] == 0 && timeto[next] + tme[next][j] < timeto[j]) {
timeto[j] = timeto[next] + tme[next][j];
timepre[j] = next;
fdisto[j] = fdisto[next] + dis[next][j];
} else if(timevis[j] == 0 && timeto[next] + tme[next][j] == timeto[j]) {
if(fdisto[j] > fdisto[next] + dis[next][j]) {
timepre[j] = next;
fdisto[j] = fdisto[next] + dis[next][j];
}
}
}
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
dis[i][j] = inf;
tme[i][j] = inf;
}
}
for(int i = 0; i < n; i++) {
sum[i] = 1;
disto[i] = inf;
timeto[i] = inf;
}
for(int i = 0; i < m; i++) {
scanf("%d%d%d%d%d", &v1, &v2, &oneway, &length, &ti);
dis[v1][v2] = length;
tme[v1][v2] = ti;
if(oneway == 0) {
dis[v2][v1] = length;
tme[v2][v1] = ti;
}
}
scanf("%d%d", &s, &e);
Dijstrldis();
tmp = e;
ka = 0;
a[ka++] = tmp;
while(tmp != s) {
tmp = dispre[tmp];
a[ka++] = tmp;
}
Dijstrltime();
tmp = e;
kb = 0;
b[kb++] = tmp;
while(tmp != s) {
tmp = timepre[tmp];
b[kb++] = tmp;
}
flag = 1;
if(ka == kb) {
for(int i = 0; i < ka; i++) {
if(a[i] != b[i]) {
flag = 0;
break;
}
}
} else {
flag = 0;
}
if(flag) {
printf("Time = %d; Distance = %d: ", timeto[e], disto[e]);
for(int i = ka-1; i >= 0; i--)
printf(i == ka-1 ? "%d" : " => %d", a[i]);
printf("\n");
} else {
printf("Time = %d: ", timeto[e]);
for(int i = kb-1; i >= 0; i--)
printf(i == kb-1 ? "%d" : " => %d", b[i]);
printf("\nDistance = %d: ", disto[e]);
for(int i = ka-1; i >= 0; i--)
printf(i == ka-1 ? "%d" : " => %d", a[i]);
printf("\n");
}
return 0;
}
L3-008 喊山
解法:簡單bfs,將每個山頭的鄰近山頭存在一個鄰接表裏,每次查詢山頭時,直接從該山頭開始bfs,一層層往外搜(也就是從近到遠)。在bfs裏面定義一個step記錄此時搜到了第幾層,從隊列裏每取出一個山頭就通過山頭所處層數是否大於step來判斷一下取出的山頭是否爲更外一層,如果是則記錄該山頭同時更新step。最後bfs結束得到的也就是最外一層的山頭編號了。
坑點:感覺這個題目出鍋了啊,題目上說“若這樣的山頭不只一個,則輸出編號最小的那個”,我一開始是用優先隊列來維護同一層的山頭編號小的先取出來,但只得了21分;而去掉優先隊列採用普通隊列卻得了滿分,普通隊列顯然無法滿足題目條件,佛了佛了~~
獻上自造的出鍋數據:
input:
7 5 4
3 1
1 2
2 3
4 5
5 6
1 4 5 7
滿分代碼output:
3
6
4
0
優先隊列21分代碼output:
2
6
4
0
這組數據我只是對題目給出的輸入樣例調換了m對山頭的輸入順序,滿分代碼得到的答案卻與輸出樣例不同(顯然滿分代碼沒有實現題目所說的要求),與輸出樣例一致的用優先隊列來維護較小編號的代碼反而只得了21分。
代碼:
#include<bits/stdc++.h>
using namespace std;
int n, m, k, u1, u2, num;
int vis[10010];
vector<int> v[10010];
struct shan {
int id, sum;
// friend bool operator <(shan a, shan b) {//21分
// return a.id > b.id;
// }
};
void bfs(int s) {
// priority_queue<shan> q;//21分
queue<shan> q;
shan now, next;
now.id = s;
now.sum = 0;
vis[s] = 1;
q.push(now);
int ans = 0, step = 0;
while(!q.empty()) {
// now = q.top();//21分
now = q.front();
q.pop();
if(now.sum > step) {
ans = now.id;
step++;
}
for(int i = 0; i < v[now.id].size(); i++) {
next.id = v[now.id][i];
next.sum = now.sum + 1;
if(vis[next.id] == 0) {
q.push(next);
vis[next.id] = 1;
}
}
}
printf("%d\n", ans);
}
int main() {
scanf("%d%d%d", &n, &m, &k);
for(int i = 0; i < m; i++) {
scanf("%d%d", &u1, &u2);
v[u1].push_back(u2);
v[u2].push_back(u1);
}
while(k--) {
memset(vis, 0, sizeof(vis));
scanf("%d", &num);
bfs(num);
}
return 0;
}
L3-010 是否完全二叉搜索樹
解法:用map容器來存左右孩子,從根結點開始遞歸分治來建二叉搜索樹,然後通過bfs來輸出層序序列。至於判斷是否爲完全二叉樹,只需要在bfs裏面給每個結點的左右孩子按層序編上號,然後比對最後一個結點的編號是否等於n就行了(如果最後一個結點的編號爲n則說明該結點前面的結點都是按序編滿的,屬於完全二叉樹)。
代碼:
#include<bits/stdc++.h>
using namespace std;
int n, a[25];
map<int, int> L, R, num;
void build(int root, int value) {
if(value > root) {
if(L[root] == 0) {
L[root] = value;
return;
} else {
build(L[root], value);
}
} else {
if(R[root] == 0) {
R[root] = value;
return;
} else {
build(R[root], value);
}
}
}
void bfs(int root) {
queue<int> Q;
Q.push(root);
int cnt = 0, lastpoint = -1, p = 1;
num[root] = p;
while (!Q.empty()) {
int tn = Q.front();
lastpoint = tn;
Q.pop();
printf(cnt++ == 0 ? "%d" : " %d", tn);
if (L[tn]) {
Q.push(L[tn]);
}
if (R[tn]) {
Q.push(R[tn]);
}
num[L[tn]] = ++p;
num[R[tn]] = ++p;
}
if(num[lastpoint] == n) printf("\nYES\n");
else
printf("\nNO\n");
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if(i > 1) build(a[1], a[i]);
}
bfs(a[1]);
return 0;
}
L3-011 直搗黃龍
解法:迪傑斯特拉+打印路徑。由於題目給的是城鎮的代號(字符串),因此需要給每個城鎮一個整型的編號,至於城鎮代號跟城鎮編號的一一映射可以用兩個map來存儲。
代碼:
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
int n, k, len;
int to[210], dis[210][210], sump[210], sum[210], num[210], pre[210], vis[210], sumpath[210];
string s1, s2, s, u, v;
map<string, int> m1;
map<int, string> m2;
void Dijstr(int start) {
to[start] = 0;
for(int i = 1; i <= n; i++) {
int minn = inf, next = -1;
for(int j = 1; j <= n; j++) {
if(vis[j] == 0 && to[j] < minn) {
minn = to[j];
next = j;
}
}
if(next == -1) break;
else
vis[next] = 1;
for(int j = 1; j <= n; j++) {
if(vis[j] == 0 && to[next] + dis[next][j] < to[j]) {
to[j] = to[next] + dis[next][j];
sump[j] = sump[next] + 1;
sum[j] = sum[next] + num[j];
sumpath[j] = sumpath[next];
pre[j] = next;
} else if(vis[j] == 0 && to[next] + dis[next][j] == to[j]) {
sumpath[j]= sumpath[j] + sumpath[next];
if(sump[next] + 1 > sump[j]) {
sump[j] = sump[next] + 1;
sum[j] = sum[next] + num[j];
pre[j] = next;
} else if(sump[next] + 1 == sump[j]) {
if(sum[j] < sum[next] + num[j]) {
sum[j] = sum[next] + num[j];
pre[j] = next;
}
}
}
}
}
}
int main() {
cin >> n >> k >> s1 >> s2;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
dis[i][j] = inf;
}
}
for(int i = 1; i <= n; i++) {
sump[i] = 1;
sumpath[i] = 1;
to[i] = inf;
}
m1[s1] = 1;
m2[1] = s1;
for(int i = 2; i <= n; i++) {
cin >> s >> num[i];
m1[s] = i;
m2[i] = s;
sum[i] = num[i];
}
for(int i = 0; i < k; i++) {
cin >> u >> v >> len;
dis[m1[u]][m1[v]] = len;
dis[m1[v]][m1[u]] = len;
}
Dijstr(1);
int t = m1[s2], k = 0, path[210];
path[k++] = t;
while(t != 1) {
t = pre[t];
path[k++] = t;
}
for(int i = k-1; i >= 0; i--) {
if(i == k-1) cout << m2[path[i]];
else
cout << "->" << m2[path[i]];
}
printf("\n%d %d %d\n", sumpath[m1[s2]], to[m1[s2]], sum[m1[s2]]);
return 0;
}
L3-012 水果忍者
解法:總體思想就是先在給出的某一條線段上固定一個端點,以該端點來遍歷其他所有線段,通過該端點與其它線段的兩個端點相連來確定斜率範圍,看是否存在一個斜率範圍可以同時穿過所有線段(也就是這個斜率範圍,是在該端點與其他任意線段的兩個端點連線的斜率範圍內的)。
代碼:
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
int n;
double maxk, mink, ansmaxk, ansmink, ansx, ansy;
struct line {
double x, maxy, miny;
}l[10010];
int cmp(line a, line b) {
if(a.x < b.x) return 1;
return 0;
}
int main() {
scanf("%d", &n);
for(int i = 0; i < n; i++)
scanf("%lf%lf%lf", &l[i].x, &l[i].maxy, &l[i].miny);
sort(l, l+n, cmp);//排一下序,使所有線段按x座標從小到大排好
for(int i = 0; i < n; i++) {//把該線段上的點作爲答案直線上的一個點
ansmaxk = inf;//初始化經過該線段上點的答案斜率範圍
ansmink = -inf;
int j;
for(j = 0; j < n; j++) {
if(i != j) {
if(i < j) {//得到可以同時穿過線段i和j的答案直線的斜率範圍
maxk = (l[i].miny - l[j].maxy) / (l[i].x - l[j].x);
mink = (l[i].miny - l[j].miny) / (l[i].x - l[j].x);
} else {
maxk = (l[j].miny - l[i].miny) / (l[j].x - l[i].x);
mink = (l[j].maxy - l[i].miny) / (l[j].x - l[i].x);
}
if(ansmaxk < mink || ansmink > maxk) break;//以i線段爲基礎的答案斜率不滿足當前同時穿過兩線段的斜率範圍,說明不能穿過所有線段,直接break
if(maxk < ansmaxk) {
ansmaxk = maxk;
ansx = l[j].x;
ansy = l[j].maxy;
}
ansmink = max(mink, ansmink);
}
}
if(j == n) {//存在經過i線段上點的一個斜率範圍所得到的答案直線可以同時穿過所有線段
printf("%.0lf %.0lf %.0lf %.0lf\n", l[i].x, l[i].miny, ansx, ansy); //線段i上取了最低點,則另一條線段要取最高點
return 0;
}
}
}
L3-013 非常彈的球
解法:設拋出角爲θ,由E=m*v*v/2,得到拋出速度v=sqrt(2*E/m),水平方向上的速度Vx=v*cosθ,垂直方向上的速度Vy=v*sinθ,由gt=Vy,得到上升到最高點所需時間爲Vy/g,由Vt^2-Vo^2=2*g*H(Vt爲末速度0,Vo爲初速度Vy,此時g爲方向加速度-g),得到上升的最高高度H=Vt*Vt/(2*g),那麼又由H=g*t*t/2得到從最高點下落到地面的時間就爲Vt/g即Vy/g,所以從球拋出到第一次下落到地面上用時爲t=2*Vy/g,忽略了空氣阻力那麼水平方向上的投擲距離則爲X=Vx*t=2*Vx*Vy/g=2*v*cosθ*v*sinθ/g,由sin(2*θ)=sinθ*cosθ/2得到X=v*v*sin(2*θ)/g,當θ取45度角時可以使X最大,X=v*v/g=2*E/(m*g)。這就是第一次掉落到地面時的投擲距離,因爲反彈後只損失p%的動能,後續的水平投擲距離就仍然按上面的方法計算,這就是一個循環的過程了,直到動能變爲0爲止。
坑點:測試點1、3超時或者答案錯誤:循環終止條件不能爲E>0,因爲存在浮點數誤差,要用E > 1e-9。
代碼:
#include<bits/stdc++.h>
using namespace std;
const double g = 9.8;
double w, p, E = 1000, sum = 0, s;
int main() {
scanf("%lf%lf", &w, &p);
w /= 100;
do {
sum += 2 * E / (w * g);
E = E * (100 - p) / 100;
} while(E > 1e-9);
printf("%.3lf\n", sum);
return 0;
}
L3-014 周遊世界
解法:因爲相鄰站點之間的線路只屬於一家公司,所以在存儲這個信息的時候可以用vector容器,容器下標作爲起點站點,往容器裏面push結構體,結構體存的是起點站點的相鄰站點以及這兩相鄰站點所屬公司。在查詢的時候,就用暴搜就可以了,從起始站點開始搜,不斷走到可以到達的相鄰站點,走到終點進行比較更新再回溯,這樣肯定是能到達滿足條件的最終路線的。至於路線存儲就可以用兩個vector分別來存,每當進行換乘的時候(更換了公司),就把新公司和該公司的起始站點(換乘站點)存入,也就是說,vector每一項都是一個換乘站點,最後輸出的每一個公司的路線就是第i項(起始站點)到達第i+1項(換乘站點),因此dfs是需要一個參數nowc來保存前一站點到達當前站點所屬公司的,在每次搜索時判斷一下目前所屬公司與當前站點到下一站點所屬公司是否相同,就可以知道當前站點是不是換乘站點。
代碼:
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
int n, m, t, s, e, qs, qe, stationnum, companynum, vis[10000];
struct info {
int next, company;//下一個站點編號,該區間所屬公司編號
};
vector<info> q[10000];
vector<int> path, anspath, c, ansc;
void dfs(int now, int snum, int cnum, int nowc) {//當前站點編號,站點數量,涉及的公司數量,當前線路所屬公司
if(now == qe) {
if(snum < stationnum || (snum == stationnum && cnum < companynum)) {
stationnum = snum;
companynum = cnum;
ansc = c;
anspath = path;
}
return;
}
for(int i = 0; i < q[now].size(); i++) {
if(!vis[q[now][i].next]) {
vis[q[now][i].next] = 1;
if(nowc == q[now][i].company) dfs(q[now][i].next, snum+1, cnum, nowc);//當前站點和下一站點是同一公司的
else {//不是同一個公司,說明需要換乘了
c.push_back(q[now][i].company);//將換乘公司存入
path.push_back(now);//存入當前站點,也就是所走線路上每次換乘()的第一個站點
dfs(q[now][i].next, snum+1, cnum+1, q[now][i].company);
c.pop_back();//回溯
path.pop_back();
}
vis[q[now][i].next] = 0;
}
}
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &m);
scanf("%d", &qs);//先輸入一個區間起點
for(int j = 1; j < m; j++) {
scanf("%d", &qe);//與區間起點相鄰的區間終點
struct info io;
io.next = qe;
io.company = i;
q[qs].push_back(io);//存儲了qs到qe區間(相鄰兩個站點)的信息
io.next = qs;
q[qe].push_back(io);//存儲了qe到qs區間(相鄰兩個站點)的信息
qs = qe;//滑動更新區間起點
}
}
scanf("%d", &t);
while(t--) {
stationnum = inf;
companynum = inf;
memset(vis, 0, sizeof(vis));
path.clear(), anspath.clear(), c.clear(), ansc.clear();
scanf("%d%d", &qs, &qe);
vis[qs] = 1;//標記訪問數組,防止不斷走環進入死循環
dfs(qs, 0, 0, 0);
if(stationnum == inf) printf("Sorry, no line is available.\n");
else {
printf("%d\n", stationnum);
anspath.push_back(qe);//因爲存的是每一次換乘線路的起點站,所以需要最後把終點存進去(終點之後是沒有換乘的)
for(int i = 0; i < companynum; i++)
printf("Go by the line of company #%d from %04d to %04d.\n", ansc[i], anspath[i], anspath[i+1]);
}
}
return 0;
}
L3-015 球隊“食物鏈”
解法:dfs+剪枝。
坑點:一開始用全排列函數對鏈的順序進行排列然後對每組排列進行判斷,T了最後兩個測試點。再然後直接dfs暴搜,T了測試點4。才考慮對dfs進行剪枝優化,必須在每次進行搜索的時候,判斷一下後面的球隊有沒有哪個球隊贏過第一個球隊,如果沒有就不需要進行下一次搜索了(後面的點沒有能與第一個點相連的,即組不了環鏈),直接return節省時間。
代碼:
#include<bits/stdc++.h>
using namespace std;
int n, flag;
int order[25], vis[25];
char res[25][25];
vector<int> v[25];
void dfs(int p, int k) {
if(flag) return;
if(k >= n) {
if(res[p][order[1]] == 'W' || res[order[1]][p] == 'L') {
flag = 1;
for(int i = 1; i <= n; i++)
printf(i == 1 ? "%d" : " %d", order[i]);
}
return;
}
int i;
for(i = 1; i <= n; i++) //剪枝優化,如果剩下的點沒有能和起點相連的,就直接return
if(!vis[i] && (res[i][order[1]] == 'W' || res[order[1]][i] == 'L')) break;
if(i == n+1) return;
for(i = 1; i <= n; i++) {
if(vis[i] == 0 && (res[p][i] == 'W' || res[i][p] == 'L')){
order[k+1] = i;
vis[i] = 1;
dfs(i, k+1);
vis[i] = 0;
}
}
}
int main() {
flag = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
getchar();
for(int j = 1; j <= n; j++) {
scanf("%c", &res[i][j]);
}
}
for(int i = 1; i <= n; i++) {
memset(vis, 0, sizeof(vis));
order[1] = i;
vis[i] = 1;
dfs(i, 1);
if(flag) break;
}
if(!flag) printf("No Solution");
return 0;
}
L3-016 二叉搜索樹的結構
解法:先遞歸建二叉搜索樹,然後用bfs看層數和雙親結點。
坑點:測試點2:查詢的點有可能不在樹裏面,即輸入的建樹點裏面沒有需要查詢的點。測試點3:查詢a是a的雙親,應該輸出No。
代碼:
#include<bits/stdc++.h>
using namespace std;
int n, t, num1, num2, a[105];
char s1[100], s2[100], s3[100], s4[100], s5[100], s6[100];
map<int, int> L, R, ceng, pre, vis;
struct tree {
int num, step;
};
void build(int root, int value) {
if(value < root) {
if(L[root] == -1) {
L[root] = value;
return;
} else {
build(L[root], value);
}
} else {
if(R[root] == -1) {
R[root] = value;
return;
} else {
build(R[root], value);
}
}
}
void bfs(int root) {
queue<tree> Q;
tree now, next;
now.num = root;
now.step = 1;
Q.push(now);
while (!Q.empty()) {
now = Q.front();
Q.pop();
ceng[now.num] = now.step;
if (L[now.num] != -1) {
pre[L[now.num]] = now.num;
next.num = L[now.num];
next.step = now.step + 1;
Q.push(next);
}
if (R[now.num] != -1) {
pre[R[now.num]] = now.num;
next.num = R[now.num];
next.step = now.step + 1;
Q.push(next);
}
}
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
L[a[i]] = -1;
R[a[i]] = -1;
vis[a[i]] = 1;
if(i > 1) build(a[1], a[i]);
}
bfs(a[1]);
scanf("%d", &t);
while(t--) {
scanf("%d%s", &num1, s1);
if(s1[0] == 'a') {
scanf("%d%s%s", &num2, s2, s3);
if(!vis[num1] || !vis[num2]) {
printf("No\n");
continue;
}
if(s3[0] == 's') {
if(pre[num1] == pre[num2]) printf("Yes\n");
else
printf("No\n");
} else {
scanf("%s%s%s", s4, s5, s6);
if(ceng[num1] == ceng[num2]) printf("Yes\n");
else
printf("No\n");
}
} else {
scanf("%s%s", s2, s3);
if(s3[1] == 'o') {
if(num1 == a[1]) printf("Yes\n");
else
printf("No\n");
} else if(s3[1] == 'a') {
scanf("%s%d", s4, &num2);
if(!vis[num1] || !vis[num2]) {
printf("No\n");
continue;
}
if(pre[num2] == num1) printf("Yes\n");
else
printf("No\n");
} else if(s3[1] == 'e') {
scanf("%s%s%d", s4, s5, &num2);
if(!vis[num1] || !vis[num2]) {
printf("No\n");
continue;
}
if(L[num2] == num1) printf("Yes\n");
else
printf("No\n");
} else {
scanf("%s%s%d", s4, s5, &num2);
if(!vis[num1] || !vis[num2]) {
printf("No\n");
continue;
}
if(R[num2] == num1) printf("Yes\n");
else
printf("No\n");
}
}
}
return 0;
}
L3-020 至多刪三個字符
解法:DP。參考別人思路得到:
dp[i][j]表示從前i個字符中刪除恰好j個字符,剩下字符按序構成的字符串的種類數。
狀態轉移方程:
dp[i][j] = dp[i-1][j] + dp[i-1][j-1]; (j>1,不刪除第i個字符的情況+刪除第i個字符的情況)
dp[i][j] = 1;(j==0,即初始狀態)
注意初始狀態的i要從0開始,因爲前i個字符,當算i=1時需要用到i=0的值。
但是上面的狀態轉移實際上是有重複計算的:
比如abcdec 一共6個字符長,dp[6][3] = dp[5][2] + dp[5][3];
dp[5][2]代表前5個字符裏刪2個字符,這肯定包括了刪除第4和第5個字符("de")這種情況,然後再把第6個字符也刪除形成dp[6][3]的一份子,這相當於刪除了dec,如右所示 abcdec ;而dp[5][3]代表前5個字符裏刪3個字符,這裏面肯定包括了刪除第3,4,5個字符的情況,如右所示 abcdec; 這樣的dp[6][3]就對得到字符串"abc"進行了兩次統計計算,造成了重複。
至於減去重複計算部分的話,需要在每遞推到一個dp[i][j]時,就從第i-1到第1個字符裏從後往前找有沒有等於第i個字符的,找到第一個後停下,假設這個位置是k,那麼[k,i-1]和[k+1,i]這兩種刪除方式得到的剩下字符串就是相同的,參考上面紅色部分,而且從[k+1,i-1]這段是兩種刪除方式中都會刪掉的,所以[k,i]這段裏有i-k個字符被刪掉了,要滿足此時j的dp[i][j],就需要從[1,k-1]這段裏面再刪掉(j-(i-k))個字符,而dp[k-1][j-(i-k))]就是從前k-1個字符中刪除(j-(i-k))個字符能形成多少不同子串,這個值貢獻到了dp[i][j]的計算中,而且這個值在計算dp[i][j]累加時被加了兩次(即參考上面紅色部分的兩次刪除情況,dp[5][2]計算了dp[2][0],dp[5][3]也計算了dp[2][0]),現在減掉一倍的它就可以了,然後就break;進行下一次狀態轉移。
爲什麼找到一個相同的後就break呢,因爲i和j都是從小到大狀態轉移的,對於前面串的去重是已經進行過了的,不需要繼續找。
坑點:總個數可能非常大,需要開long long。
代碼:
#include<bits/stdc++.h>
using namespace std;
long long dp[1000010][5];
string s;
int main() {
cin >> s;
int len = s.size();
for(int i = 0; i <= len; i++)
dp[i][0] = 1;
for(int i = 1; i <= len; i++) {
for(int j = 1; j <= 3; j++) {//不能從0開始,否則可能會修改初態的值
dp[i][j] = dp[i-1][j] + dp[i-1][j-1];
for(int k = i-1; k > 0 && j >= i-k; k--) {
if(s[i-1] == s[k-1]) {
dp[i][j] -= dp[k-1][j-(i-k)];
break;
}
}
}
}
printf("%lld\n", dp[len][0]+dp[len][1]+dp[len][2]+dp[len][3]);
return 0;
}
L3-021 神壇
解法:先按照極角排序,再用相鄰向量求面積來不斷更新最小值,O(n^2)。
極角排序相關學習推薦:https://www.cnblogs.com/aiguona/p/7248311.html
坑點:因爲兩個點的相應座標相乘可能超出int範圍,所以要開long long來存。
代碼:
#include<bits/stdc++.h>
#define inf 1ll<<60
using namespace std;
typedef long long ll;
int n, k;
struct point {
ll x, y;
} p[5010], xl[5010];
int cmp(point a, point b) {
if(b.y * a.x > b.x * a.y) return 1;
return 0;
}
int main() {
ll minn = inf;
scanf("%d", &n);
for(int i = 0; i < n; i++)
scanf("%lld%lld", &p[i].x, &p[i].y);
for(int i = 0; i < n; i++) {
k = 0;
for(int j = 0; j < n; j++) {
if(i != j) {
xl[k].x = p[j].x - p[i].x;
xl[k].y = p[j].y - p[i].y;
k++;
}
}
sort(xl, xl+k, cmp);
for(int j = 1; j < k; j++)
minn = min(minn, abs(xl[j].y*xl[j-1].x-xl[j].x*xl[j-1].y));
}
printf("%.3lf\n", minn/2.);
return 0;
}