2018 BNUZ IT 节 ACM程序设计竞赛网络赛题解

A.   欧几里德的微笑

这道题要做到在一个二维空间上放置三个点,问这三个点是否能绕某个旋转点转一定角度后,a到b的位置,b到c的位置。

解法其实很简单,既然要求a到b,b到c,那么必然是点 a 到点 b 的距离要等于点b到点c 的距离的,这样它们才能够对称,并且不能三点共线就可以了,可以看作是以这个三角形做一个外切圆的原理。

代码:

#include
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 

#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 
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];

vectoredge[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 

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;
stacksta;
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
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 #"<

F. 命运石之门的选择

这是一道裸广搜题,只需要在广搜的过程中根据他是α世界线分支或是β世界线分支控制好跳跃路径经可以了,真的很裸很暴力

代码:

#include 

#define MAXN 100005
#define pii pair 
#define mp(a,b) make_pair(a, b)

using namespace std;

int a[MAXN];
queue 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 
#include 
#include 
#include 
#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

#define MAXN 105

using namespace std;

map title;

bool mark[MAXN][35];
queue 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
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 #"< n)
				cout<<"-1"<

J. 猫叔的烦恼

一个二维平面上的涂点题,由于数据不大,输入完后只要把会覆盖目标点的地毯找出来,最后一个就是最上面的,简单粗暴

代码:

#include 

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

你可能感兴趣的:(ACM-)