AtCoder Beginner Contest 218 A~F 题解

本场链接:AtCoder Beginner Contest 218

A - Weather Forecast

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
#define x first
#define y second

int main()
{
	int n;string s;cin >> n >> s;
	if(s[n - 1] == 'o')	cout << "Yes" << endl;
	else cout << "No" << endl;
	return 0;
}

B - qwerty

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
#define x first
#define y second

int main()
{
	forn(i,1,26)
	{
		int x;cin >> x;
		cout << char(x + 'a' - 1);
	}
	cout << endl;
	return 0;
}

C - Shapes

首先考虑实现逆时针旋转整个字符数组:不难观察到相当于把每一列换到行上,并且第一列换到最后一行,第二列换到倒数第二行...如此可以将四种形态直接求出来,剩下的问题为:是否存在一种平移方式使得:两个图形重叠。可以以一定的顺序遍历所有黑点,如果存在一种平移的方式使得两个图形重叠,那么所有点相对偏移的坐标全部相同,抠出所有点进行check即可。

注意形态一共有四种,可以不旋转,并且两个图形内的点数的个数不一定是相同的。甚至可以没有。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
#define x first
#define y second

const int N = 205;
char s[N][N],t[N][N],r[N][N];

bool solve(int n)
{
	vector<pii> target;	forn(i,1,n)	forn(j,1,n)	if(t[i][j] == '#')	target.push_back({i,j});
	vector<pii> cur;	forn(i,1,n)	forn(j,1,n)	if(s[i][j] == '#')	cur.push_back({i,j});
	if(target.size() != cur.size())	return 0;
	if(target.empty())	return 1;
	int dx = target[0].x - cur[0].x,dy = target[0].y - cur[0].y;
	forn(i,0,target.size() - 1)	if(target[i].x - cur[i].x != dx || target[i].y - cur[i].y != dy)	return 0;
	return 1;
}

int main()
{
	int n;scanf("%d",&n);
	forn(i,1,n)	scanf("%s",s[i] + 1);
	forn(i,1,n)	scanf("%s",t[i] + 1);

	bool ok = 0;
	forn(_,1,4)
	{
		forn(j,1,n)	forn(i,1,n)	r[n - j + 1][i] = s[i][j];
		forn(i,1,n)	forn(j,1,n)	s[i][j] = r[i][j];
		ok |= solve(n);
	}

	if(ok)	puts("Yes");
	else puts("No");
	return 0;
}

D - Rectangles

显然考虑枚举两个点 \((i,j)\),并且首先保证两个点满足:\(y_i == y_j\) 这样相当于枚举一条竖线。同时可以保证 \(x_i < x_j\) 这样 \(i\) 点一定在 \(j\) 点的上方,现在剩下的问题相当于求:有多少个二元组\((z,k)\)满足这四个点可以形成一个矩形,那么首先前面的两个点 \((z,k)\) 显然也必须要是一条竖线,并且保证 \(x_z < x_k\) 的前提下需要有: \(x_z == x_i\)\(x_k == x_j\)。如此可以直接用 map 维护二元组的数量,统计答案即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
#define x first
#define y second

const int N = 2005;
pii p[N];

int main()
{
	int n;scanf("%d",&n);
	forn(i,1,n)	scanf("%d%d",&p[i].x,&p[i].y);

	map<pii,int> st;
	ll res = 0;
	forn(i,1,n)	forn(j,1,n)
	{	
		int x1 = p[i].x,x2 = p[j].x,y1 = p[i].y,y2 = p[j].y;
		if(y1 != y2 || x1 > x2 || i == j)	continue;
		res += st[{x1,x2}];
		++st[{x1,x2}];
	}

	printf("%lld\n",res);
	return 0;
}

E - Destruction

删除一条边显然比较无理,考虑把问题逆向成建边,相当于一开始就把所有的边删掉,增加最少的边使得答案减少的最少,即答案的最大值。因为在这样反过来的问题上,正权边相当于减少权值,负权边相当于增加权值,所以所有负权边全部加入,并且正权边尽可能少,这样的问题答案等价于求 MST。直接套版子即可 \(res = sum - MST\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
#define x first
#define y second

const int N = 2e5+7,M = 2 * N;
struct Edge
{
	int u,v,w;
	bool operator<(const Edge& rhs)	const
	{
		return w < rhs.w;
	}
}edges[M];
int fa[N];
int n,m;

int find(int x)
{
	if(fa[x] == x)	return x;
	return fa[x] = find(fa[x]);
}

ll kruskal()
{
	sort(edges + 1,edges + m + 1);
	ll res = 0;
	forn(i,1,m)
	{
		int u = edges[i].u,v = edges[i].v,w = edges[i].w;
		u = find(u),v = find(v);
		if(u == v)	
		{
			if(w < 0)	res += w;
			continue;
		}
		fa[u] = v;
		res += w;
	}
	return res;
}

int main()
{
	scanf("%d%d",&n,&m);
	forn(i,1,n)	fa[i] = i;

	ll sum = 0;
	forn(i,1,m)
	{
		int u,v,w;scanf("%d%d%d",&u,&v,&w);
		edges[i] = {u,v,w};
		sum += w;
	}

	printf("%lld\n",sum - kruskal());
	return 0;
}
		

F - Blocked Roads

因为所有边权为 \(1\),所以单次求解 SSSP 的复杂度是 \(O(n + m)\)的,由于 \(m = n^2\)故单次求解为 \(O(n^2)\)。那么直接暴力枚举删除所有边再拓展 BFS 求最短路的复杂度就是 \(O(n^4)\)

考虑首先求出一条最短路,不难想到:如果删去的边不在这条最短路上,那么删去他不会对最短路产生任何影响,所以答案不变。如果边就在最短路上,则在删除他的前提下直接 BFS,由于 \(1 - n\) 的最短路上至多有 \(n-1\) 条边,所以最多会额外跑 \(n - 1\) 次 BFS,这样产生的额外代价是 \(O(n^3)\) 的。整体的复杂度即 \(O(n^3)\)。可以通过本题。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
#define x first
#define y second

const int N = 405,M = N * N;
int edge[M],succ[M],ver[N],ID[M],idx;
int dist[N];
int n,m;
pii from[N];
bool st[M];

void add(int u,int v,int id)
{
	edge[idx] = v;
	ID[idx] = id;
	succ[idx] = ver[u];
	ver[u] = idx++;
}

int bfs(int del = 0)
{
	memset(dist,0x3f,sizeof dist);dist[1] = 0;
	queue<int> q;q.push(1);
	while(!q.empty())
	{
		int u = q.front();q.pop();
		for(int i = ver[u];~i;i = succ[i])
		{
			if(ID[i] == del)	continue;
			int v = edge[i];
			if(dist[v] > dist[u] + 1)
			{
				dist[v] = dist[u] + 1;
				from[v] = {u,ID[i]};
				q.push(v);
			}
		}
	}
	return dist[n] == 0x3f3f3f3f ? -1 : dist[n];
}

int main()
{
	memset(ver,-1,sizeof ver);
	scanf("%d%d",&n,&m);
	forn(i,1,m)
	{
		int u,v;scanf("%d%d",&u,&v);
		add(u,v,i);
	}

	int res = bfs(),cur = n;
	if(res == -1)
	{
		forn(i,1,m)	puts("-1");
		return 0;
	}
	while(cur != 1)
	{
		int u = from[cur].x,id = from[cur].y;
		st[id] = 1;
		cur = u;
	}
	
	forn(i,1,m)
	{
		if(!st[i])	printf("%d\n",res);
		else printf("%d\n",bfs(i));
	}
	return 0;
}
posted @ 2021-09-13 16:05  随处可见的阿宅  阅读(104)  评论(0编辑  收藏  举报