1. 程式人生 > >2018 BNUZ IT 節 ACM程式設計競賽網路賽題解

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);
	}
}