ABC199 A~E
- [A - Square Inequality](https://atcoder.jp/contests/abc199/tasks/abc199_a)
- [B - Intersection](https://atcoder.jp/contests/abc199/tasks/abc199_b)
- [C - IPFL](https://atcoder.jp/contests/abc199/tasks/abc199_c)
- [D - RGB Coloring 2](https://atcoder.jp/contests/abc199/tasks/abc199_d)
- [E - Permutation](https://atcoder.jp/contests/abc199/tasks/abc199_e)
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 0≤A,B,C≤1000
输入格式
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 4 | Yes |
10 10 10 | 10 10 10 | 10 10 10 | No |
3 3 3 | 4 4 4 | 5 5 5 | No |
分析
直接按题意计算即可。
代码
#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 1≤i≤N, A i ≤ x ≤ B i A_i\le x\le B_i Ai≤x≤Bi。
1
≤
N
≤
100
1\le N\le 100
1≤N≤100
1
≤
A
i
≤
B
i
≤
1000
1\le A_i\le B_i\le 1000
1≤Ai≤Bi≤1000
输入格式
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 1≤i≤N, A i ≤ x A_i\le x Ai≤x。
- 对于所有的 1 ≤ i ≤ N 1\le i\le N 1≤i≤N, x ≤ B i x\le B_i x≤Bi。
这时,我们可以进一步简化条件:
- ( 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,minB−maxA+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
1≤N≤2×105
∣
S
∣
=
2
N
|S|=2N
∣S∣=2N
1
≤
Q
≤
3
×
1
0
5
1\le Q\le 3\times 10^5
1≤Q≤3×105
1
≤
T
i
≤
2
1\le T_i\le 2
1≤Ti≤2,如果
T
i
=
1
T_i=1
Ti=1,
1
≤
A
i
<
B
i
≤
2
N
1\le A_i < B_i\le 2N
1≤Ai<Bi≤2N;如果
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} FLIP→IPFL→LPFI
样例输入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:=1−flipped;
- 当 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 x−N。
这种算法的时间复杂度为 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
1≤N≤20
0
≤
M
≤
N
(
N
−
1
)
2
0\le M\le \frac{N(N-1)}2
0≤M≤2N(N−1)
1
≤
A
i
,
B
i
≤
N
1\le A_i,B_i\le N
1≤Ai,Bi≤N
输入格式
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
我们用R
、G
、B
分别代表三种不同的颜色,则有
6
6
6中不同的着色方法,它们分别是RGB
、RBG
、GRB
、GBR
、BRG
、BGR
。
样例输入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×2N−1,因为使用
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 1≤i≤M,这个排列的前 X i X_i Xi个数中不超过 Y i Y_i Yi的最多有 Z i Z_i Zi个。
2
≤
N
≤
18
2\le N\le 18
2≤N≤18
0
≤
M
≤
100
0\le M\le 100
0≤M≤100
1
≤
X
i
,
Y
i
<
N
1\le X_i,Y_i < N
1≤Xi,Yi<N
0
≤
Z
i
<
N
0\le Z_i < N
0≤Zi<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}
dp2N−1(全选)。
代码
我这里做了一个小优化,即忽略
Z
i
≥
Y
i
Z_i\ge Y_i
Zi≥Yi的条件。当然,我们也可以不用优化,但不能不用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;
}