CodeForces Round #594(Div.2) 题解

CodeForces Round #594(Div.2)题解

最近CF有点炸。。。打比赛都好麻烦啊。。。

A. Integer Points

题目大意

N N N条形如 y = x + p i ( 0 ≤ p i ≤ 1 0 9 ) y=x+p_i(0\le p_i\le 10^9) y=x+pi(0pi109)的直线, M M M条形如 y = − x + q i ( 0 ≤ q I ≤ 1 0 9 ) y=-x+q_i(0\le q_I\le10^9) y=x+qi(0qI109)的直线。求这些直线一共有多少个交点横纵坐标均为整点。

分析

显然只有 y = x + p i y=x+p_i y=x+pi y = − x + q j y=-x+q_j y=x+qj才可能有一个交点,那么我们联立解析式,解一下这个方程组得到: { x = q j − p i 2 y = p i + q j 2 \begin{cases}x=\frac{q_j-p_i}{2}\\y=\frac{p_i+q_j}{2}\end{cases} {x=2qjpiy=2pi+qj

显然只有当 q j q_j qj p i p_i pi奇偶性相同的时候,方程组的解才是整数,于是统计 p i , q j p_i,q_j pi,qj的奇偶性即可。

参考代码

#include 
#include 
using namespace std;

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	int T;
	scanf("%d", &T);
	while(T--) {
		int N, M;
		int cnt0 = 0, cnt1 = 0;
		scanf("%d", &N);
		for(int i = 1; i <= N; i++) {
			int p;
			scanf("%d", &p);
			if(p % 2 == 0) cnt0++;
			else cnt1++;
		}
		scanf("%d", &M);
		long long ans = 0;
		for(int i = 1; i <= M; i++) {
			int p;
			scanf("%d", &p);
			if(p % 2) ans += cnt1;
			else ans += cnt0;
		}
		printf("%lld\n", ans);
	}
	return 0;
}

B. Grow The Tree

题目大意

给定 N N N根木棍的长度,现在要求将这 N N N根木棍平行于 x x x轴或者平行于 y y y轴放置,要求不能够有连续两根以上的木棍状态是一样的(即只能够横竖横竖或者竖横竖横地放),求最后放的木棍的终点到原点 ( 0 , 0 ) (0,0) (0,0)的距离的最大值的平方。

分析

通过简单的数学推理可以得到,我们需要将这 N N N根木棍分成两部分。设第一部分的总长为 X X X,第二部分的总长为 Y Y Y,我们的目的是最大化 X 2 + Y 2 X^2+Y^2 X2+Y2

结论: 当我们最大化 X X X或者 Y Y Y中的一个值时,答案一定是最大的。

证明: 不妨设 X ≤ Y X\le Y XY。我们从 X X X中拿出总长度为 p p p的木棍放进 Y Y Y中,那么新的距离的平方为 ( X − p ) 2 + ( Y + p ) 2 = X 2 − 2 X p + p 2 + Y 2 + 2 Y p + p 2 = X 2 + Y 2 + 2 p ( Y − X + p ) (X-p)^2+(Y+p)^2=X^2-2Xp+p^2+Y^2+2Yp+p^2=X^2+Y^2+2p(Y-X+p) (Xp)2+(Y+p)2=X22Xp+p2+Y2+2Yp+p2=X2+Y2+2p(YX+p)。显然答案增大。

由于要求只能够横竖横竖地放,我们只需要将前 N 2 \frac{N}{2} 2N小的木棍的长度和作为 X X X,剩余的作为 Y Y Y就可以了。

参考代码

#include 
#include 
using namespace std;

typedef long long ll;
const int Maxn = 100000;

int N, A[Maxn + 5];
ll s[Maxn + 5];

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	scanf("%d", &N);
	for(int i = 1; i <= N; i++)
		scanf("%d", &A[i]);
	sort(A + 1, A + N + 1);
	for(int i = 1; i <= N; i++)
		s[i] = s[i - 1] + A[i];
	int mid = N / 2;
	ll x = s[mid] - s[0], y = s[N] - s[mid];
	printf("%lld\n", x * x + y * y);
	return 0;
}

C. Ivan the Fool and the Probability Theory

题目大意

给定一个 N × M N\times M N×M的方格图,现在要求将每个格子染成黑白两种颜色,要求每个格子至多和四个方向(上下左右)上的一个格子同色,问有多少种方案。

分析

我们可以知道,当我们确定了第一行和第一列,那么整个方格图就确定了。

现在我们开始统计一行和一列的方案数。

考虑仅确定一行的方案数。

我们设 f ( i ) f(i) f(i)为长度为 i i i的方格的合法方案数。

由于我们可以在方格后加上一个黑色或者白色的方格(强制与最后一个方格不同色),所以我们可以用 f ( i ) f(i) f(i)去更新 f ( i + 1 ) f(i+1) f(i+1),而且这个方格的颜色是唯一的,所以 f ( i + 1 ) + = f ( i ) f(i+1)+=f(i) f(i+1)+=f(i)

我们也可以加上两个连续的黑色或者白色的方格。这样,我们可以用 f ( i ) f(i) f(i)去更新 f ( i + 2 ) f(i+2) f(i+2),所以有 f ( i + 2 ) + = f ( i ) f(i+2)+=f(i) f(i+2)+=f(i)

简单整理一下上面两个方程,我们可以得到 f ( i ) = f ( i − 1 ) + f ( i − 2 ) f(i)=f(i-1)+f(i-2) f(i)=f(i1)+f(i2)

这不就是斐波那契数列的递推公式吗?

又因为当行和列拼在一起时会导致格子 ( 1 , 1 ) , ( 1 , 2 ) , ( 2 , 1 ) (1,1),(1,2),(2,1) (1,1),(1,2),(2,1)三个格子同色,所以我们的答案是 f ( N ) + f ( M ) − 1 f(N)+f(M)-1 f(N)+f(M)1

又由于黑白可以反色,所以总答案为 2 ( f ( N ) + f ( M ) − 1 ) 2(f(N)+f(M)-1) 2(f(N)+f(M)1)

参考代码

#include 
#include 
using namespace std;

typedef long long ll;
const int Maxn = 100000;
const ll Mod = 1e9 + 7;

ll fib[Maxn + 5];

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	ll N, M;
	scanf("%lld %lld", &N, &M);
	fib[0] = fib[1] = 1;
	for(int i = 2; i <= max(N, M); i++)
		fib[i] = (fib[i - 1] + fib[i - 2]) % Mod;
	printf("%lld\n", 2 * (fib[N] + fib[M] - 1 + Mod) % Mod);
	return 0;
}

D1/D2. The World Is Just a Programming Task

题目大意

有一个括号序列,你需要选择两个位置,将位置上的括号交换,使得最后得到的循环位置最大。

循环位置的定义是:我们将前 i i i个位置上的字符串取出放到字符串的最后,所得的括号序列是一个合法的序列。

分析

首先我们可以知道,对于一个左右括号数量并不相同的序列,它没有循环位置。我们将这种情况直接特判掉。

我们先将整个括号序列转成一个合法的序列。容易证明这是一定存在的。

那么这个序列的循环节位置就长这样,就是一级括号所在的位置:
CodeForces Round #594(Div.2) 题解_第1张图片

那么我们可以发现,对于若我们交换一个一级括号的左右括号,如下:CodeForces Round #594(Div.2) 题解_第2张图片

若我们记翻转的一级括号内的二级括号数量为 a a a,那么这个操作的答案就是 a + 1 a+1 a+1

我们也可以交换一个二级括号的左右括号。

简单画图可以知道答案是一级括号的数量+翻转的二级括号内的数量+1。

通过画图可以发现翻转一个三级括号是没用的。

O ( N ) O(N) O(N)扫一遍就行了。

参考代码

#include 
#include 
using namespace std;

const int Maxn = 300000;

int N, s[Maxn * 2 + 5];
char str[Maxn + 5];
int lef[Maxn * 2 + 5], dep[Maxn * 2 + 5];
int siz[Maxn * 2 + 5], sum[Maxn * 2 + 5];
int stk[Maxn + 5], tp;

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	scanf("%d %s", &N, str + 1);
	int cnt = 0;
	for(int i = 1; i <= N; i++) {
		s[i] = s[i + N] = (str[i] == '(' ? 1 : -1);
		cnt += s[i];
	}
	if(cnt) {
		puts("0\n1 1");
		return 0;
	}
	int pos = 1;
	for(int i = 1; i <= N * 2; i++) {
		cnt += s[i];
		if(cnt < 0) pos = i + 1, cnt = 0;
		else if(i - pos + 1 >= N) break;
	}
	for(int i = pos; i <= pos + N - 1; i++)
		if(s[i] == -1) {
			int p = stk[tp--];
			sum[tp]++, lef[i] = p, dep[i] = tp;
			siz[i] = sum[tp + 1], sum[tp + 1] = 0;
		} else stk[++tp] = i;
	int ans = sum[0], l = 1, r = 1;
	for(int i = pos; i <= pos + N - 1; i++) {
		if(dep[i] == 0)
			if(siz[i] + 1 > ans)
				ans = siz[i] + 1, l = lef[i], r = i;
		if(dep[i] == 1)
			if(sum[0] + siz[i] + 1 > ans)
				ans = sum[0] + siz[i] + 1, l = lef[i], r = i;
	}
	printf("%d\n%d %d\n", ans, (l - 1) % N + 1, (r - 1) % N + 1);
	return 0;
}

E.Queue in the Train

题目大意

N N N个人在火车上坐成一列,第一个位置旁边有一个水龙头。

i i i个人会在 t i t_i ti时刻去接水。每个人去接水都会用 p p p分钟。如果他前面有人在排队或者接水,那么他会等待,否则就去接水。问每个人接完水的时间。

分析

我们用两个队列来模拟这个过程。一个用来存当前正在排队的人,另一个用来存在座位上等待的人。

我们先把人按照前去接水的顺序排好。

当排队队列不是空的时候,我们将队头取出,更新它的答案。

否则当当前时间大于他前去接水时,检查队列,若队列为空或者他前面没有人去排队时就把他塞进队列中,否则放到优先队列里。

否则从优先队列中取出一个元素并更新它的答案。若优先队列也是空的,那么就将时间倒回当前这个人去接水的时间即可。

参考代码

#include 
#include 
#include 
using namespace std;

typedef long long ll;
const int Maxn = 100000;

struct Node {
	int t;
	int id;
};
bool cmp(Node lhs, Node rhs) {return lhs.t == rhs.t ? lhs.id < rhs.id : lhs.t < rhs.t;}

int N, P;
Node A[Maxn + 5];
queue<int> q1;
priority_queue<int, vector<int>, greater<int> > q2;
ll ans[Maxn + 5];

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	scanf("%d %d", &N, &P);
	for(int i = 1; i <= N; i++) {
		scanf("%d", &A[i].t);
		A[i].id = i;
	}
	sort(A + 1, A + N + 1, cmp);
	ll now = 0;
	int last = N + 1;
	for(int i = 1; i <= N;) {
		if(!q1.empty()) {
			int id = q1.front();
			q1.pop();
			ans[id] = now + P;
			now += P, last = id;
		} else if(A[i].t <= now) {
			if(q1.empty() && A[i].id < last)
				q1.push(A[i].id);
			else if(!q1.empty() && q1.back() > A[i].id && A[i].id < last)
				q1.push(A[i].id);
			else q2.push(A[i].id);
			i++;
		} else {
			if(!q2.empty()) {
				int id = q2.top();
				q2.pop();
				ans[id] = now + P;
				now += P, last = id;
			} else now = A[i].t, last = N + 1;
		}
	}
	while(!q1.empty()) {
		int id = q1.front();
		q1.pop(), ans[id] = now + P;
		now += P;
	}
	while(!q2.empty()) {
		int id = q2.top();
		q2.pop(), ans[id] = now + P;
		now += P;
	}
	for(int i = 1; i <= N; i++)
		printf("%lld ", ans[i]);
	return 0;
}

F. Catowice City

题目大意

N N N个人和 N N N只猫,每个人都认识一些猫(其中包括自己的猫)。要求选出 j j j裁判和 p p p只猫,使得 j + p = N j+p=N j+p=N,且选出的人和猫都互相不认识。输出任意一种方案。

分析

我们将一个人和一只猫拆成两个点,每个猫点向他们对应的点连一条有向边,就像这样:
CodeForces Round #594(Div.2) 题解_第3张图片

然后再对于一对认识关系,由人向猫连一条有向边:
CodeForces Round #594(Div.2) 题解_第4张图片

容易看出裁判集合无法到达选手集合。

我们就可以找出所有的强连通分量。如果只有一个强连通分量,那么就不存在答案。

否则我们随便找一个强连通分量作为裁判,剩余的作为选手即可。

其实我也不会证明这个结论。。。

参考代码

#include 
#include 
#include 
#include 
using namespace std;

typedef long long ll;
const int Maxn = 1e6;

int N, M;
vector<int> G[Maxn + 5];

int dfn[Maxn + 5], low[Maxn + 5], dcnt;
int col[Maxn + 5], ccnt;
bool vis[Maxn + 5];
int stk[Maxn + 5], tp;

void Tarjan_scc(int u) {
	dfn[u] = ++dcnt, low[u] = dcnt, vis[u] = true;
	stk[++tp] = u;
	for(int i = 0; i < (int)G[u].size(); i++) {
		int v = G[u][i];
		if(!dfn[v]) {
			Tarjan_scc(v);
			low[u] = min(low[u], low[v]);
		} else if(vis[v]) low[u] = min(low[u], low[v]);
	}
	if(dfn[u] == low[u]) {
		++ccnt;
		while(true) {
			col[stk[tp]] = ccnt;
			vis[stk[tp]] = false;
			if(stk[tp--] == u)
				break;
		}
	}
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	int T;
	scanf("%d", &T);
	while(T--) {
		scanf("%d %d", &N, &M);
		ccnt = 0;
		for(int i = 1; i <= N; i++)
			dfn[i] = 0, G[i].clear();
		for(int i = 1; i <= M; i++) {
			int u, v;
			scanf("%d %d", &u, &v);
			if(u == v) continue;
			G[u].push_back(v);
		}
		for(int i = 1; i <= N; i++)
			if(!dfn[i]) Tarjan_scc(i);
		if(ccnt == 1) puts("No");
		else {
			puts("Yes");
			int cnt_j = 0, cnt_p = 0;
			for(int i = 1; i <= N; i++)
				if(col[i] == 1) cnt_j++;
				else cnt_p++;
			printf("%d %d\n", cnt_j, cnt_p);
			for(int i = 1; i <= N; i++)
				if(col[i] == 1) printf("%d ", i);
			puts("");
			for(int i = 1; i <= N; i++)
				if(col[i] > 1) printf("%d ", i);
			puts("");
		}
	}
	return 0;
}

你可能感兴趣的:(#,CodeForces)