2018 BNUZ IT 節 ACM程式設計競賽網路賽題解
A. 歐幾里德的微笑
這道題要做到在一個二維空間上放置三個點,問這三個點是否能繞某個旋轉點轉一定角度後,a到b的位置,b到c的位置。
解法其實很簡單,既然要求a到b,b到c,那麼必然是點 a 到點 b 的距離要等於點b到點c 的距離的,這樣它們才能夠對稱,並且不能三點共線就可以了,可以看作是以這個三角形做一個外切圓的原理。
程式碼:
#include<bits/stdc++.h> using namespace std; #define ll long long struct point{ ll x,y; }; ll getDis(point p1,point p2){ return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y); } int main(){ point p1,p2,p3; int cas = 1; int T; scanf("%d",&T); while(T--){ printf("Case #%d: ",cas++); scanf("%lld %lld %lld %lld %lld %lld",&p1.x,&p1.y,&p2.x,&p2.y,&p3.x,&p3.y); ll d1 = getDis(p1,p2); ll d2 = getDis(p2,p3);; if(d1 != d2){ puts("No"); }else if((p2.x - p3.x) * (p1.y - p2.y) == (p1.x - p2.x) * (p2.y - p3.y)){ puts("No"); }else{ puts("Yes"); } } }
B. 貓叔的計算器
這道題就是一個模擬計算器的操作,不過是計算機中的整數運算規則,在寫法上也沒有什麼困難的,這道題的特點就是有的人寫的特別長,有的人寫的特別短
下面貼上最短的小Q同學的程式碼:
#include <bits/stdc++.h> #define ll long long using namespace std; int main() { int T, cas = 1; scanf("%d", &T); while (T--) { ll ans = 0, tmp; scanf("%d", &ans); getchar(); char c; while ((c = getchar()) != '\n') { scanf("%lld", &tmp); if (c == '+') { ans += tmp; } else if (c == '-') { ans -= tmp; } else if (c == '*') { ans *= tmp; } else { ans /= tmp; } } printf("Case #%d:%lld\n", cas++, ans); } }
C.switch
這道題是說有n個小鎮,其中有k個是有倉庫的,現在要在一個沒有倉庫的小鎮上開實體店,要求至少可到達一個倉庫,讓求到達倉庫的最小距離
這道題其實是逗你玩得,根本就不需要什麼最短路演算法,那些寫了Dijkstra的同學們被耍了…
因為它要求一定要到達倉庫,那麼如果能夠到達倉庫,最近的一定是與倉庫直接相鄰的,所以對於輸入進來的資料,我們只要標記下所有的倉庫,然後去找與倉庫相鄰的小鎮,找到最小值就可以了。
程式碼:
#include <bits/stdc++.h> using namespace std; #define maxn 100005 #define ll long long #define mem(a,x) memset(a,x,sizeof(a)) const ll inf = 1e9 + 1; struct node{ int v; ll w; node(int vv = 0,ll ww = 0ll):v(vv),w(ww){} }; int flag[maxn]; vector<node>edge[maxn]; void init(int n){ for(int i = 0;i <= n;i++){ edge[i].clear(); flag[i] = 0; } } void add(int u,int v,ll w){ edge[u].push_back(node(v,w)); } int main(){ int t,n,m,k,u,v,cas = 1; ll w; scanf("%d",&t); while(t--){ scanf("%d %d %d",&n,&m,&k); init(n); for(int i = 1;i <= m;i++){ scanf("%d %d %lld",&u,&v,&w); add(u,v,w); add(v,u,w); } for(int i = 1;i <= k;i++){ scanf("%d",&u); flag[u] = 1; } ll ans = inf; for(int i = 0;i <= n;i++){ if(flag[i] == 1){ int sz = edge[i].size(); for(int j = 0;j < sz;j++){ if(flag[edge[i][j].v] == 0){ ans = min(ans,edge[i][j].w); } } } } if(ans == inf){ printf("Case #%d: -1\n",cas++); }else{ printf("Case #%d: %lld\n",cas++,ans); } } return 0; }
D. 未來日記
這道題原本是一道防AK題,但是無奈同學們的檢索功力太強,都找到了原題,並且是一毛一樣的…這怪我…
言歸正傳,這道題是一道經典Tarjan演算法,也就是求強連通分量的演算法,不懂強連通的同學可以自行百度一波
整體思路就是用Tarjan求出有多少個強連通分量,那麼只要把這些強連通分量用最少的邊連起來,就成了一個完整的強連通分量,就滿足所有人可達了。求出強連通分量後,在每個強連通分量之間統計出度和入度,一個強連通分量至少要有一個出度和入度,只要每個強連通分量都有一個出度和一個入度,就一定可以構成一個完整的強連通分量了。
統計總共缺多少出度和多少入度,取大的那個值作為答案(因為有可能有好幾個強連通分量指向了一個強連通分量 比如只有ABC三個點的圖,線索有 A->B C->B 的這種,總共卻一個出度和兩個入度,那麼答案肯定是需要兩個線索的)(即可多不可少)
程式碼:
#include <bits/stdc++.h>
using namespace std;
#define mem(a,x) memset(a,x,sizeof(a))
#define ll long long
#define maxn 50005
struct edge {
int nxt,to;
} e[maxn];
int low[maxn],dfn[maxn],vis[maxn],head[maxn],pre[maxn],out[maxn],in[maxn];
int dep,num,cnt,n,m;
stack<int>sta;
void add(int u,int v) {
e[cnt].to = v;
e[cnt].nxt = head[u];
head[u] = cnt++;
}
void tarjan(int u){
dfn[u] = low[u] = ++dep;
sta.push(u);
vis[u] = 1;
for(int i = head[u];i != -1;i = e[i].nxt){
int v = e[i].to;
if(!dfn[v]){
tarjan(v);
low[u] = min(low[u],low[v]);
}else if(vis[v]){
low[u] = min(low[u],dfn[v]);
}
}
int v;
if(low[u] == dfn[u]){
num++;
do{
v = sta.top();
sta.pop();
pre[v] = num;
vis[v] = 0;
}while(u != v);
}
}
void init() {
cnt = num = dep = 0;
while(!sta.empty())
sta.pop();
mem(head,-1);
mem(low,0);
mem(dfn,0);
mem(vis,0);
mem(pre,0);
mem(out,0);
mem(in,0);
}
int main() {
int u,v,Case = 1,T;
scanf("%d",&T);
while(T--) {
scanf("%d %d",&n,&m);
init();
for(int i = 1; i <= m; i++) {
scanf("%d %d",&u,&v);
add(u,v);
}
for(int i = 1; i <= n; i++) {
if(!dfn[i])
tarjan(i);
}
if(num == 1)
printf("Case #%d: 0\n",Case++);
else {
for(u = 1; u <= n; u++) {
for(int i = head[u]; i != -1; i = e[i].nxt) {
v = e[i].to;
if(pre[u] != pre[v]) {
out[pre[u]]++;
in[pre[v]]++;
}
}
}
int ans1 = 0,ans2 = 0;
for(int i = 1; i <= num; i++) {
if(in[i] == 0)
ans1++;
if(out[i] == 0)
ans2++;
}
printf("Case #%d: %d\n",Case++,max(ans1,ans2));
}
}
return 0;
}
E. 尋找旅館的學長
水題一枚,寫兩個 cmp 就好了,然後根據最後輸入的是0 還是 1 選擇cmp進行一個排序就ok了
程式碼:
#include<bits/stdc++.h>
using namespace std;
struct node{
int m;
int s;
}datas[100000];
bool cmp1(node data1,node data2) {
if(data1.s != data2.s)
return data1.s < data2.s;
else
return data1.m < data2.m;
}
bool cmp2(node data1,node data2) {
if(data1.m != data2.m)
return data1.m < data2.m;
else
return data1.s < data2.s;
}
int main() {
int t,m,s,f;
while(~scanf("%d",&t)) {
for(int i = 1; i <= t; i++) {
int count = 0;
while(scanf("%d %d",&m,&s)&&(m||s)) {
datas[count].m = m;
datas[count++].s = s;
}
scanf("%d",&f);
if(f) {
sort(datas,datas+count,cmp1);
} else {
sort(datas,datas+count,cmp2);
}
cout<<"Case #"<<i<<":"<<endl;
for(int j = 0; j < 3; j++)
cout<<datas[j].m<<" "<<datas[j].s<<endl;
}
}
return 0;
}
F. 命運石之門的選擇
這是一道裸廣搜題,只需要在廣搜的過程中根據他是α世界線分支或是β世界線分支控制好跳躍路徑經可以了,真的很裸很暴力
程式碼:
#include <bits/stdc++.h>
#define MAXN 100005
#define pii pair<int, int>
#define mp(a,b) make_pair(a, b)
using namespace std;
int a[MAXN];
queue<pii> q;
bool mark[MAXN];
int n;
bool check(int i) {
if (i >= 0 && i <= n && !mark[i]) {
mark[i] = true;
return true;
}
return false;
}
int bfs(int s, int k) {
memset(mark, false, sizeof(mark));
while (!q.empty()) {
q.pop();
}
q.push(mp(s, 0));
while (!q.empty()) {
int u = q.front().first;
int t = q.front().second;
mark[u] = true;
if (u == k) {
return t;
}
q.pop();
if (a[u]) {
if (check(u - 2)) {
q.push(mp(u - 2, t + 1));
}
if (check(u + 2)) {
q.push(mp(u + 2, t + 1));
}
if (check(u * 2)) {
q.push(mp(u * 2, t + 1));
}
} else {
if (check(u - 1)) {
q.push(mp(u - 1, t + 1));
}
if (check(u + 1)) {
q.push(mp(u + 1, t + 1));
}
}
}
return -1;
}
int main() {
int T, cas = 1;
scanf("%d", &T);
int s, k;
while (T--) {
scanf("%d %d %d", &n, &s, &k);
for (int i = 0; i <= n; i++) {
scanf("%d", &a[i]);
}
printf("Case #%d: ", cas++);
printf("%d\n", bfs(s, k));
}
}
G. 細胞分裂
簽到題
2^n 只要注意 pow 出來的結果強轉為longlong,否則會有精度丟失
程式碼:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;
const ll mod = 1e9+7;
int main(){
ll T,n;
int cas = 1;
scanf("%lld",&T);
while(T--){
scanf("%lld",&n);
ll ans = pow(2,n);
printf("Case #%d: %lld\n",cas++,ans);
}
return 0;
}
H. 四月是你的謊言
一道沒什麼可講只是教你使用STL的模擬題
下面貼上依舊是最短的小Q的程式碼:
#include<bits/stdc++.h>
#define MAXN 105
using namespace std;
map<string, int> title;
bool mark[MAXN][35];
queue<int> q[MAXN][35];
void init() {
memset(mark, false, sizeof(mark));
title.clear();
for (int i = 0; i < 105; i++) {
for (int j = 0; j < 35; j++) {
while (!q[i][j].empty()) {
q[i][j].pop();
}
}
}
}
int main() {
int T, x, n, cas = 1;
string y;
int Q, tmp;
scanf("%d", &T);
while (T--) {
printf("Case #%d:\n", cas++);
init();
int cnt = 0;
scanf("%d", &Q);
while (Q--) {
getchar();
char opt;
scanf("%c", &opt);
if (opt == 's') {
cin >> x >> y;
if (!title[y]) {
title[y] = ++cnt;
}
mark[x][title[y]] = 1;
} else if (opt == 'p') {
cin >> y >> n;
if (!title[y]) {
title[y] = ++cnt;
}
int id = title[y];
for (int i = 0; i < n; i++) {
scanf("%d", &tmp);
for (int j = 0; j < MAXN; j++) {
q[j][id].push(tmp);
}
}
} else {
cin >> x >> y >> n;
int id = title[y];
if (!mark[x][id]) {
puts("No Subscription");
continue;
}
bool flag = false;
while (!q[x][id].empty() && n--) {
if (flag) {
printf(" ");
}
printf("%d", q[x][id].front());
q[x][id].pop();
flag = true;
}
puts("");
}
}
}
}
I. 聰明的學長
一道水題,找第k大的數,暴力做法排序之後從後往前找,但是不推薦
建議使用分治法
但是隻給出暴力程式碼:
#include<bits/stdc++.h>
using namespace std;
bool cmp(int a,int b) {
return a > b;
}
int main() {
int t,n,id;
int num[100000];
while(~scanf("%d",&t)) {
for(int i = 1; i <= t; i++) {
scanf("%d",&n);
for(int j = 0; j < n; j++) {
scanf("%d",&num[j]);
}
scanf("%d",&id);
cout<<"Case #"<<i<<": ";
if(id > n)
cout<<"-1"<<endl;
else {
sort(num,num+n,cmp);
cout<<num[id-1]<<endl;
}
}
}
return 0;
}
J. 貓叔的煩惱
一個二維平面上的塗點題,由於資料不大,輸入完後只要把會覆蓋目標點的地毯找出來,最後一個就是最上面的,簡單粗暴
程式碼:
#include <bits/stdc++.h>
#define MAXN 100005
using namespace std;
struct node {
int x, y, h, w;
} a[MAXN];
int main() {
int T, n, cas = 1;
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d %d %d %d", &a[i].x, &a[i].y, &a[i].h, &a[i].w);
}
int x, y;
int ans = -1;
scanf("%d %d", &x, &y);
for (int i = 0; i < n; i++) {
if (a[i].x <= x && a[i].x + a[i].h >= x
&& a[i].y <= y && a[i].y + a[i].w >= y) {
ans = i + 1;
}
}
printf("Case #%d: %d\n", cas++, ans);
}
}