AtCoder Beginner Contest 199 (Sponsored by Panasonic) A~E 题解

46 篇文章 7 订阅
43 篇文章 6 订阅
这篇博客详细解析了ABC199比赛中的五道题目,包括平方不等式、序列交集、IPFL问题、RGB着色和排列问题。每道题目都给出了关键思路和高效算法实现,涵盖了数学计算、数组操作和图论策略,适合提升编程竞赛技巧。
摘要由CSDN通过智能技术生成

A - Square Inequality

题目大意

给定三个整数 A , B , C A,B,C A,B,C。判断 A 2 + B 2 < C 2 A^2+B^2<C^2 A2+B2<C2是否成立。

0 ≤ A , B , C ≤ 1000 0\le A,B,C\le 1000 0A,B,C1000

输入格式

A   B   C A~B~C A B C

输出格式

如果 A 2 + B 2 < C 2 A^2+B^2<C^2 A2+B2<C2,输出Yes;否则,输出No

样例

A A A B B B C C C输出
2 2 2 2 2 2 4 4 4Yes
10 10 10 10 10 10 10 10 10No
3 3 3 4 4 4 5 5 5No

分析

直接按题意计算即可。

代码

#include <cstdio>
using namespace std;

int main()
{
	int a, b, c;
	scanf("%d%d%d", &a, &b, &c);
	puts(a * a + b * b < c * c? "Yes": "No");
	return 0;
}

B - Intersection

题目大意

给定两个长度为 N N N的序列: A = ( A 1 , A 2 , A 3 , … , A N ) A = (A_1, A_2, A_3, \dots, A_N) A=(A1,A2,A3,,AN) B = ( B 1 , B 2 , B 3 , … , B N ) B = (B_1, B_2, B_3, \dots, B_N) B=(B1,B2,B3,,BN)
找到符合如下条件的整数 x x x的个数:

  • 对于所有的 1 ≤ i ≤ N 1\le i\le N 1iN A i ≤ x ≤ B i A_i\le x\le B_i AixBi

1 ≤ N ≤ 100 1\le N\le 100 1N100
1 ≤ A i ≤ B i ≤ 1000 1\le A_i\le B_i\le 1000 1AiBi1000

输入格式

N N N
A 1   A 2   …   A N A_1~A_2~\dots~A_N A1 A2  AN
B 1   B 2   …   B N B_1~B_2~\dots~B_N B1 B2  BN

输出格式

输出答案。

样例

样例输入1

2
3 2
7 5

样例输出1

3

x x x可以取 3 , 4 , 5 3,4,5 3,4,5

样例输入2

3
1 5 3
10 7 3

样例输出2

0

没有 x x x符合条件。

样例输入3

3
3 2 5
6 9 8

样例输出3

2

分析

我们将 x x x的限制条件拆解为:

  • 对于所有的 1 ≤ i ≤ N 1\le i\le N 1iN A i ≤ x A_i\le x Aix
  • 对于所有的 1 ≤ i ≤ N 1\le i\le N 1iN x ≤ B i x\le B_i xBi

这时,我们可以进一步简化条件:

  • ( max ⁡ A ) ≤ x (\max A)\le x (maxA)x
  • x ≤ ( min ⁡ B ) x\le (\min B) x(minB)

从而得到 ( max ⁡ A ) ≤ x ≤ ( min ⁡ B ) (\max A)\le x\le (\min B) (maxA)x(minB),所以合法的 x x x的个数为 max ⁡ ( 0 , min ⁡ B − max ⁡ A + 1 ) \max(0,\min B-\max A+1) max(0,minBmaxA+1)

代码

#include <cstdio>
using namespace std;

int main()
{
	int n, maxa = 1, minb = 1000;
	scanf("%d", &n);
	for(int i=0; i<n; i++)
	{
		int a;
		scanf("%d", &a);
		if(a > maxa) maxa = a;
	}
	while(n--)
	{
		int b;
		scanf("%d", &b);
		if(b < minb) minb = b;
	}
	if(maxa > minb) puts("0");
	else printf("%d\n", minb - maxa + 1);
	return 0;
}

C - IPFL

题目大意

给定长度为 2 N 2N 2N且只由大写英文字母组成的字符串 S S S
你要处理 Q Q Q个请求。
i i i个请求中由三个整数 T i , A i T_i,A_i Ti,Ai B i B_i Bi组成:

  • 如果 T i = 1 T_i=1 Ti=1:交换 S S S中第 A i A_i Ai B i B_i Bi个字符;
  • 如果 T i = 2 T_i=2 Ti=2,交换 S S S中的前 N N N个和后 N N N个字符(如:FLIP → \to IPFL)。

输出执行所有请求后的 S S S

1 ≤ N ≤ 2 × 1 0 5 1\le N\le 2\times 10^5 1N2×105
∣ S ∣ = 2 N |S|=2N S=2N
1 ≤ Q ≤ 3 × 1 0 5 1\le Q\le 3\times 10^5 1Q3×105
1 ≤ T i ≤ 2 1\le T_i\le 2 1Ti2,如果 T i = 1 T_i=1 Ti=1 1 ≤ A i < B i ≤ 2 N 1\le A_i < B_i\le 2N 1Ai<Bi2N;如果 T i = 2 T_i=2 Ti=2 A i = B i = 0 A_i=B_i=0 Ai=Bi=0

输入格式

N N N
S S S
Q Q Q
T 1   A 1   B 1 T_1~A_1~B_1 T1 A1 B1
T 2   A 2   B 2 T_2~A_2~B_2 T2 A2 B2
⋮ \hspace{18pt}\vdots
T Q   A Q   B Q T_Q~A_Q~B_Q TQ AQ BQ

样例

样例输入1

2
FLIP
2
2 0 0
1 1 4

样例输出1

LPFI

FLIP → IPFL → LPFI \text{FLIP}\to\text{IPFL}\to\text{LPFI} FLIPIPFLLPFI

样例输入2

2
FLIP
6
1 1 3
2 0 0
1 1 2
1 2 3
2 0 0
1 1 4

样例输出2

ILPF

分析

首先, O ( N Q ) \mathcal O(NQ) O(NQ)的模拟法肯定行不通,会TLE
我们考虑优化。
我们很容易发现, T i = 1 T_i=1 Ti=1的交换操作肯定是 O ( 1 ) \mathcal O(1) O(1)的,但 T i = 2 T_i=2 Ti=2的翻转操作是 O ( n ) \mathcal O(n) O(n)的,所以需要优化。
我们可以用一个变量 f l i p p e d \mathrm{flipped} flipped记录目前是否已经前后翻转( 1 1 1表示已经翻转, 0 0 0表示没有翻转),这时,操作变为如下:

  • T i = 2 T_i=2 Ti=2 f l i p p e d : = 1 − f l i p p e d \mathrm{flipped}:=1-\mathrm{flipped} flipped:=1flipped
  • T i = 1 T_i=1 Ti=1 f l i p p e d = 0 \mathrm{flipped}=0 flipped=0时,我们直接交换 A i A_i Ai B i B_i Bi
  • T i = f l i p p e d = 1 T_i=\mathrm{flipped}=1 Ti=flipped=1时,我们发现,一个位置 x x x如果 < N <N <N,则实际位置在 x + N x+N x+N;否则,实际位置在 x − N x-N xN

这种算法的时间复杂度为 O ( N + Q ) \mathcal O(N+Q) O(N+Q),可轻松通过此题。

代码

#include <cstdio>
#define maxn 400005
using namespace std;

char s[maxn];
int n;

inline void swap(char& x, char& y) { x ^= y ^= x ^= y; }
inline char& calc(int pos) { return s[pos < n? pos + n: pos - n]; }

int main()
{
	scanf("%d%s", &n, s);
	int q;
	scanf("%d", &q);
	bool flipped = false;
	while(q--)
	{
		int t, a, b;
		scanf("%d%d%d", &t, &a, &b);
		a --, b --;
		if(t == 2) flipped = !flipped;
		else if(flipped) swap(calc(a), calc(b));
		else swap(s[a], s[b]);
	}
	if(flipped)
		for(int i=0; i<n; i++)
			swap(s[i], s[n + i]);
	puts(s);
	return 0;
}

D - RGB Coloring 2

题目大意

我们有一个有 N N N个点和 M M M条边的简单无向图,第 i i i条边连接着顶点 A i A_i Ai B i B_i Bi
我们要给这个图用三种不同的颜色着色,使得相邻的顶点有不同的颜色。
有多少种合法的着色方法?不一定要使用所有颜色。

1 ≤ N ≤ 20 1\le N\le 20 1N20
0 ≤ M ≤ N ( N − 1 ) 2 0\le M\le \frac{N(N-1)}2 0M2N(N1)
1 ≤ A i , B i ≤ N 1\le A_i,B_i\le N 1Ai,BiN

输入格式

N   M N~M N M
A 1   B 1 A_1~B_1 A1 B1
A 2   B 2 A_2~B_2 A2 B2
⋮ \hspace{12pt}\vdots
A M   B M A_M~B_M AM BM

输出格式

输出答案。

样例

样例输入1

3 3
1 2
2 3
3 1

样例输出1

6

我们用RGB分别代表三种不同的颜色,则有 6 6 6中不同的着色方法,它们分别是RGBRBGGRBGBRBRGBGR

样例输入2

3 0

样例输出2

27

这个图没有边,所以任意着色都是可行的,一共有 3 N = 27 3^N=27 3N=27种方法。

样例输入3

4 6
1 2
2 3
3 4
2 4
1 3
1 4

样例输出3

0

这里没有合法方案。

样例输入4

20 0

样例输出4

3486784401

分析

我们将图中的每个连通块依次暴力算出所有可能的着色方案数,再相乘即可。
其实,这里我们最大的总尝试数不是 3 N 3^N 3N,而是 3 × 2 N − 1 3\times 2^{N-1} 3×2N1,因为使用 DFS \text{DFS} DFS时每个点的前一个点的颜色已经定好了,只需要尝试两种可能即可。

代码

似乎没人发现可以用unsigned int吧……

#include <cstdio>
#include <vector>
#define maxn 25
using namespace std;

vector<int> G[maxn];
int col[maxn], dep[maxn];

inline int next(int c) { return (c + 1) % 3; }

int paint(int v)
{
	for(int u: G[v])
		if(col[v] == col[u])
			return 0;
	int ans = 1;
	for(int u: G[v])
	{
		if(dep[u] == -1) dep[u] = dep[v] + 1;
		if(dep[u] == dep[v] + 1)
		{
			col[u] = next(col[v]);
			int res = paint(u);
			col[u] = next(col[u]);
			res += paint(u);
			col[u] = -1;
			if(res == 0) return 0;
			ans *= res;
		}
	}
	return ans;
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		G[--x].push_back(--y);
		G[y].push_back(x);
	}
	for(int i=0; i<n; i++)
		col[i] = dep[i] = -1;
	unsigned int ans = 1;
	for(int i=0; i<n; i++)
		if(dep[i] == -1)
		{
			col[i] = dep[i] = 0;
			ans *= 3U * paint(i);
		}
	printf("%u\n", ans);
	return 0;
}

E - Permutation

题目大意

求符合如下条件的 ( 1 , 2 , … , N ) (1,2,\dots,N) (1,2,,N)的排列的个数:

  • 对于每个 1 ≤ i ≤ M 1\le i\le M 1iM,这个排列的前 X i X_i Xi个数中不超过 Y i Y_i Yi的最多有 Z i Z_i Zi个。

2 ≤ N ≤ 18 2\le N\le 18 2N18
0 ≤ M ≤ 100 0\le M\le 100 0M100
1 ≤ X i , Y i < N 1\le X_i,Y_i < N 1Xi,Yi<N
0 ≤ Z i < N 0\le Z_i < N 0Zi<N

输入格式

N   M N~M N M
X 1   Y 1   Z 1 X_1~Y_1~Z_1 X1 Y1 Z1
X 2   Y 2   Z 2 X_2~Y_2~Z_2 X2 Y2 Z2
⋮ \hspace{18pt}\vdots
X M   Y M   Z M X_M~Y_M~Z_M XM YM ZM

输出格式

输出一个整数,即符合条件的排列的个数。

样例

样例输入1

3 1
2 2 1

样例输出1

4

四个符合条件的排列分别为: ( 1 , 2 , 3 ) (1,2,3) (1,2,3) ( 2 , 3 , 1 ) (2,3,1) (2,3,1) ( 3 , 1 , 2 ) (3,1,2) (3,1,2) ( 3 , 2 , 1 ) (3,2,1) (3,2,1)

样例输入2

5 2
3 3 2
4 4 3

样例输出2

90

样例输入3

18 0

样例输出3

6402373705728000

由于没有限制条件,所有的 18 ! = 6402373705728000 18!=6402373705728000 18!=6402373705728000个排列都可行。这也是本题的最大输出。

分析

首先,还是先排除 O ( N ! ∑ X ) \mathcal O(N!\sum X) O(N!X)的暴力算法,这样做的时间复杂度太高了。
我们考虑状压 DP \text{DP} DP。令 d p m a s k \mathrm{dp}_\mathrm{mask} dpmask表示从 ( 1 , 2 , … , N ) (1,2,\dots,N) (1,2,,N)中选出子序列 m a s k \mathrm{mask} mask(二进制第 i i i位是 0 0 0表示不选 i i i 1 1 1表示选 i i i)。
则, d p 0 = 1 \mathrm{dp}_0=1 dp0=1,动态转移方程为 d p m a s k = m a s k \mathrm{dp}_\mathrm{mask}=\mathrm{mask} dpmask=mask中所有为的 1 1 1位上把 1 1 1变成 0 0 0 d p \mathrm{dp} dp中的和,详见代码。
写代码时注意判断合法性,最终答案应为 d p 2 N − 1 \mathrm{dp}_{2^N-1} dp2N1(全选)。

代码

我这里做了一个小优化,即忽略 Z i ≥ Y i Z_i\ge Y_i ZiYi的条件。当然,我们也可以不用优化,但不能不用long long!!!

#include <cstdio>
#include <vector>
#define maxn 20
using namespace std;

using LL = long long;

vector<pair<int, int>> lim[maxn];
LL dp[1 << maxn];

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int x, y, z;
		scanf("%d%d%d", &x, &y, &z);
		if(z < y) lim[x].emplace_back(y, z);
	}
	int mx = 1 << n;
	dp[0] = 1LL;
	for(int st=0; st<mx; st++)
	{
		vector<int> s;
		for(int i=0; i<n; i++)
			if(st >> i & 1)
				s.push_back(i);
		int cnt = __builtin_popcount(st);
		bool ok = true;
		for(auto [y, z]: lim[cnt])
		{
			int tot = 0;
			for(auto x: s)
				if(x < y && ++tot > z)
				{
					ok = false;
					break;
				}
			if(!ok) break;
		}
		if(ok) for(int x: s) dp[st] += dp[st ^ (1 << x)];
	}
	printf("%lld\n", dp[mx - 1]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值