ABC260 A~F
- [A - A Unique Letter](https://atcoder.jp/contests/abc260/tasks/abc260_a)
- [B - Better Students Are Needed!](https://atcoder.jp/contests/abc260/tasks/abc260_b)
- [C - Changing Jewels](https://atcoder.jp/contests/abc260/tasks/abc260_c)
- [D - Draw Your Cards](https://atcoder.jp/contests/abc260/tasks/abc260_d)
- [E - At Least One](https://atcoder.jp/contests/abc260/tasks/abc260_e)
- [F - Find 4-cycle](https://atcoder.jp/contests/abc260/tasks/abc260_f)
A - A Unique Letter
题目大意
给定一个长度为
3
3
3的字符串
S
S
S。
输出
S
S
S中出现正好一次的字母(任意,如abc
中,三个字母都可为答案)。
如果没有,输出-1
。
数据保证 S S S的长为 3 3 3,且由小写英文字母组成。
输入格式
S S S
输出格式
输出任意符合条件的答案。
样例
S S S | 输出 |
---|---|
pop | o |
abc | a /b /c |
xxx | -1 |
分析
我们设输入的3个字母分别为a
、b
、c
。
首先,如果
a
=
b
=
c
a=b=c
a=b=c,那么输出
−
1
-1
−1。
其次,我们依次尝试找到两个相同的字母:
xxy
形式( a = b a=b a=b):输出 c c cxyx
形式( a = c a=c a=c):输出 b b byxx
形式( b = c b=c b=c):输出 a a axyz
形式( a ≠ b ≠ c a\ne b\ne c a=b=c):输出任意一个
代码
这里,我把最后两种情况合并了(一个else
搞定,都输出
a
a
a):
#include <cstdio>
using namespace std;
int main()
{
char a = getchar(), b = getchar(), c = getchar();
if(a == b && b == c) puts("-1");
else if(a == c) putchar(b);
else if(a == b) putchar(c);
else putchar(a);
return 0;
}
B - Better Students Are Needed!
题目大意
N
N
N个员工参加了一场选聘考试。
第
i
i
i个员工数学考了
A
i
A_i
Ai分,英语
B
i
B_i
Bi分。
公司按如下的方式选聘员工:
- 数学分数在前 X X X的被直接录取;
- 剩下的人中,英语分数在前 Y Y Y的被录取;
- 最后,总分在前 Z Z Z的被录取,剩下的人被淘汰。
注意:分数相同的员工按编号排序。
输出被录取的所有员工的编号,按升序排列。
1
≤
N
≤
1000
1\le N\le 1000
1≤N≤1000
0
≤
X
,
Y
,
Z
≤
N
0\le X,Y,Z\le N
0≤X,Y,Z≤N
1
≤
X
+
Y
+
Z
≤
N
1\le X+Y+Z\le N
1≤X+Y+Z≤N
0
≤
A
i
,
B
i
≤
100
0\le A_i,B_i\le 100
0≤Ai,Bi≤100
输入格式
N
X
Y
Z
N~X~Y~Z
N X Y Z
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
输出格式
输出被录取的所有员工的编号,按升序排列,每行一个。
样例
略,请自行前往AtCoder查看
分析
本题主要有两种思路:
- 用
pair<int, int>
代表一个员工,再使用vector
+sort
或priority_queue
执行三次分别排序数学、英语、总分; - 用
struct { int math, english, id; }
表示员工,存储一次,排序三次(使用不同的排序依据)
详见代码1、代码2。
代码
代码1
vector
+sort
实现#include <cstdio> #include <vector> #include <algorithm> #define maxn 1005 using namespace std; int a[maxn], b[maxn]; bool used[maxn]; int main() { int n, x, y, z; scanf("%d%d%d%d", &n, &x, &y, &z); for(int i=0; i<n; i++) scanf("%d", a + i); for(int i=0; i<n; i++) scanf("%d", b + i); // Math vector<pair<int, int>> sel_a; for(int i=0; i<n; i++) sel_a.emplace_back(-a[i], i); sort(sel_a.begin(), sel_a.end()); for(int i=0; i<x; i++) used[sel_a[i].second] = true; // English vector<pair<int, int>> sel_b; for(int i=0; i<n; i++) if(!used[i]) sel_b.emplace_back(-b[i], i); sort(sel_b.begin(), sel_b.end()); for(int i=0; i<y; i++) used[sel_b[i].second] = true; // Total vector<pair<int, int>> sel_t; for(int i=0; i<n; i++) if(!used[i]) sel_t.emplace_back(-(a[i] + b[i]), i); sort(sel_t.begin(), sel_t.end()); for(int i=0; i<z; i++) used[sel_t[i].second] = true; for(int i=0; i<n; i++) if(used[i]) printf("%d\n", i + 1); return 0; }
priority_queue
实现#include <cstdio> #include <queue> #define maxn 1005 using namespace std; int a[maxn], b[maxn], c[maxn]; bool used[maxn]; inline void selectOnce(int* scores, int n, int snum) { priority_queue<pair<int, int>> sel; for(int i=0; i<n; i++) if(!used[i]) { sel.emplace(-scores[i], i); if(sel.size() > snum) sel.pop(); } while(!sel.empty()) used[sel.top().second] = true, sel.pop(); } int main() { int n, x, y, z; scanf("%d%d%d%d", &n, &x, &y, &z); for(int i=0; i<n; i++) scanf("%d", a + i); for(int i=0; i<n; i++) scanf("%d", b + i); for(int i=0; i<n; i++) c[i] = a[i] + b[i]; selectOnce(a, n, x); selectOnce(b, n, y); selectOnce(c, n, z); for(int i=0; i<n; i++) if(used[i]) printf("%d\n", i + 1); return 0; }
代码2
#include <cstdio>
#include <vector>
#include <algorithm>
#define maxn 1005
using namespace std;
struct Emp { // Employee
int math, eng, id;
} emps[maxn];
inline bool cmp1(const Emp& e1, const Emp& e2) {
return e1.math == e2.math?
e1.id < e2.id:
e1.math > e2.math;
}
inline bool cmp2(const Emp& e1, const Emp& e2) {
return e1.eng == e2.eng?
e1.id < e2.id:
e1.eng > e2.eng;
}
inline bool cmp3(const Emp& e1, const Emp& e2) {
int tot1 = e1.math + e1.eng, tot2 = e2.eng + e2.math;
return tot1 == tot2?
e1.id < e2.id:
tot1 > tot2;
}
inline bool cmp4(const Emp& e1, const Emp& e2) {
return e1.id < e2.id;
}
int main()
{
// Input
int n, x, y, z;
scanf("%d%d%d%d", &n, &x, &y, &z);
for(int i=0; i<n; i++)
scanf("%d", &emps[i].math),
emps[i].id = i;
for(int i=0; i<n; i++)
scanf("%d", &emps[i].eng);
// Sort
auto last = emps + n;
sort(emps, last, cmp1);
sort(emps + x, last, cmp2);
sort(emps + x + y, last, cmp3);
sort(emps, emps + x + y + z, cmp4); // 按编号升序排序
// Output
for(int i=0; i<x+y+z; i++)
printf("%d\n", emps[i].id + 1);
return 0;
}
C - Changing Jewels
题目大意
Takahashi有一个
N
N
N级的红色宝石。
他可以重复下列操作任意次数:
- 将一个 N N N级的红色宝石转换为“一个 ( N − 1 ) (N-1) (N−1)级的红色宝石和 X X X个 N N N级的蓝色宝石”。
- 将一个 N N N级的蓝色宝石转换为“一个 ( N − 1 ) (N-1) (N−1)级的红色宝石和 Y Y Y个 N − 1 N-1 N−1级的蓝色宝石”。
Takahashi最后最多能得到几个 1 1 1级的蓝色宝石?
1
≤
N
≤
10
1\le N\le 10
1≤N≤10
1
≤
X
,
Y
≤
5
1\le X,Y\le 5
1≤X,Y≤5
输入格式
N X Y N~X~Y N X Y
输出格式
输出一个整数,即最终蓝色宝石的数量。
样例
N N N | X X X | Y Y Y | 输出 |
---|---|---|---|
2 2 2 | 3 3 3 | 4 4 4 | 12 12 12 |
10 10 10 | 5 5 5 | 5 5 5 | 3942349900 3942349900 3942349900 |
注意小心
32
32
32位整数(int/int32
)溢出。
分析
要获得
(
N
−
1
)
(N-1)
(N−1)级的蓝宝石,必须先尽可能多的获得
N
N
N级的蓝宝石。
而要达到这个目的,就需要有尽可能多的
N
N
N级红宝石。
以此类推,我们可以按顺序进行操作
1
1
1,操作
2
2
2……直到所有宝石全部为
1
1
1级(也就是循环
(
N
−
1
)
(N-1)
(N−1)次)。维护两个变量
red
\text{red}
red(初始为
1
1
1)和
blue
\text{blue}
blue(初始为
0
0
0),分别表示当前的红、蓝宝石的数目。
每次循环,先将
blue
\text{blue}
blue加上
red
×
X
\text{red}\times X
red×X(操作
1
1
1),再将
red
\text{red}
red加上
blue
\text{blue}
blue、
blue
\text{blue}
blue乘上
Y
Y
Y(操作
2
2
2)。
时间复杂度 O ( n ) \mathcal O(n) O(n),如有读不懂的地方,可参考代码。
代码
注意使用long long
。
#include <cstdio>
using namespace std;
int main()
{
int n, x, y;
scanf("%d%d%d", &n, &x, &y);
long long red = 1LL, blue = 0LL;
while(--n)
{
blue += red * x;
red += blue, blue *= y;
}
printf("%lld\n", blue);
return 0;
}
D - Draw Your Cards
题目大意
有
N
N
N张牌,上面分别写着数字
P
1
,
P
2
,
…
,
P
N
P_1,P_2,\dots,P_N
P1,P2,…,PN。
按照这个顺序,我们进行
N
N
N个操作,第
i
i
i个操作的具体步骤如下:
- 取出第 i i i张牌,令 X = P i X=P_i X=Pi;
- 找到存堆中顶牌 ≥ X ~\ge X ≥X的最小一张,将这张牌置于其上;
- 如果没有符合条件的牌,将 X X X放入一新堆;
- 当某堆牌数达到 K K K时,把这堆的牌全部吃掉。
求每张牌被吃掉的时间(若没有被吃掉,输出-1
,详见输出格式)。
1
≤
K
≤
N
≤
2
×
1
0
5
1\le K\le N \le 2\times 10^5
1≤K≤N≤2×105
P
P
P是
(
1
,
2
,
…
,
N
)
(1,2,\dots,N)
(1,2,…,N)的一种排列。
输入格式
N
K
N~K
N K
P
1
P
2
…
P
N
P_1~P_2~\dots~P_N
P1 P2 … PN
输出格式
输出
N
N
N行,第
i
i
i行表示卡片
i
i
i被吃掉的时间(如果没被吃掉,输出-1
)。
样例
略,就是懒
分析
首先肯定不能用vector<stack<int>>
这种数据结构,效率太低,容易写错,还不好用。可以用一个类似于并查集的数据结构,每次叠放操作都可看作“把下面的牌的父亲设置为上面的牌”。我们还需要记录并查集中每个连通分量的大小,方便模拟“吃掉”操作。
最终对于每个节点,输出其祖宗被吃掉的时间(咋听起来有点怪)。
目前的时间复杂度是
O
(
N
2
)
\mathcal O(N^2)
O(N2),因为每次操作都需要用
O
(
n
)
\mathcal O(n)
O(n)的时间,找到最小的符合条件的牌堆。
很容易想到,可以使用set
优化。
set
是自动排序的集合,常用的的操作有插入(insert
)、删除(erase
)、二分查找(lower_bound
/upper_bound
),一次操作的时间复杂度均为
O
(
log
n
)
\mathcal O(\log n)
O(logn)。
这时,使用一个set<int>
维护每个堆顶的卡牌编号,就可以把时间复杂度降到
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn)以内。
至此,此题完。注意对 K = 1 K=1 K=1的特判。
代码
#include <cstdio>
#include <set>
#define maxn 200005
using namespace std;
int fa[maxn], eat[maxn], sz[maxn];
int find(int x) {
return fa[x] == x? x: fa[x] = find(fa[x]);
}
int main()
{
int n, k;
scanf("%d%d", &n, &k);
set<int> cards;
for(int i=0; i<n; i++)
{
int x;
scanf("%d", &x);
x --;
eat[x] = -1, fa[x] = x;
if(k == 1)
{
eat[x] = i + 1;
continue;
}
auto it = cards.upper_bound(x);
if(it == cards.end())
cards.insert(x), sz[x] = 1;
else
{
fa[*it] = x;
cards.erase(it);
if((sz[x] = sz[*it] + 1) == k)
eat[x] = i + 1;
else cards.insert(x);
}
}
for(int i=0; i<n; i++)
printf("%d\n", eat[find(i)]);
return 0;
}
E - At Least One
题目大意
给定整数
M
M
M和
N
N
N对整数:
(
A
1
,
B
1
)
,
(
A
2
,
B
2
)
,
…
,
(
A
N
,
B
N
)
(A_1,B_1),(A_2,B_2),\dots,(A_N,B_N)
(A1,B1),(A2,B2),…,(AN,BN)。
题目保证对于任意
i
i
i,
1
≤
A
i
<
B
i
≤
M
1\le A_i<B_i\le M
1≤Ai<Bi≤M。
符合如下条件的整数序列 S S S被称作好的序列:
- S S S是 ( 1 , 2 , … , M ) (1,2,\dots,M) (1,2,…,M)的连续子序列;
- 对于每个 i i i, S S S中包含 A i A_i Ai或 B i B_i Bi(或同时包含)。
令 f ( k ) = ( f(k)=( f(k)=(长为 k k k的好序列的个数 ) ) )。求 f ( 1 ) , f ( 2 ) , … , f ( M ) f(1),f(2),\dots,f(M) f(1),f(2),…,f(M)。
1
≤
N
≤
2
×
1
0
5
1\le N\le 2\times 10^5
1≤N≤2×105
2
≤
M
≤
2
×
1
0
5
2\le M\le 2\times 10^5
2≤M≤2×105
1
≤
A
i
<
B
i
≤
M
1\le A_i<B_i\le M
1≤Ai<Bi≤M
输入格式
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
⋮
\vdots
⋮
A
N
B
N
A_N~B_N
AN BN
输出格式
输出一行,即 f ( 1 ) , f ( 2 ) , … , f ( M ) f(1),f(2),\dots,f(M) f(1),f(2),…,f(M),用空格分隔。
样例
略,请自行前往AtCoder查看
分析
首先,根据题意,
S
S
S可被表示为一个区间
[
l
,
r
]
[l,r]
[l,r],其中
1
≤
l
≤
r
≤
M
1\le l\le r\le M
1≤l≤r≤M。
当对于每个
i
i
i,
l
≤
A
i
≤
r
l\le A_i\le r
l≤Ai≤r或
l
≤
B
i
≤
r
l\le B_i\le r
l≤Bi≤r时,区间
[
l
,
r
]
[l,r]
[l,r]符合条件。
若按这样直接暴力枚举,时间复杂度为
O
(
N
2
M
)
\mathcal O(N^2M)
O(N2M),明显超时,不可取。
仔细想想会发现,对于两个区间 [ l , r ] [l,r] [l,r]和 [ a , b ] [a,b] [a,b],若 a ≤ l ≤ r ≤ b a\le l\le r\le b a≤l≤r≤b,且 [ l , r ] [l,r] [l,r]符合条件,则 [ a , b ] [a,b] [a,b]也肯定符合条件。
此时,可以考虑使用滑动窗口优化,则时间复杂度降至 O ( M N ) \mathcal O(MN) O(MN)。
继续优化。在窗口滑动的过程中,每次移动左/右端点时考虑一次移动对当前符合条件的 i i i的数量的贡献,需要两个数组 c n t [ N ] \mathrm{cnt}[N] cnt[N](记录每个 A i A_i Ai和 B i B_i Bi符合条件的个数)和 i n v [ M + 1 ] [ … ] \mathrm{inv}[M+1][\dots] inv[M+1][…](预处理每个数值对应的所有元素下标)。
总时间复杂度为 O ( N + M ) \mathcal O(N+M) O(N+M),详见代码。
代码
#include <cstdio>
#include <vector>
#define maxn 200005
using namespace std;
vector<int> inv[maxn];
int cnt[maxn], ans[maxn];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for(int i=0; i<n; i++)
{
int a, b;
scanf("%d%d", &a, &b);
inv[a].push_back(i);
inv[b].push_back(i);
}
int left = n;
for(int i=1, j=1; i<=m; i++)
{
for(; j <= m && left > 0; j++)
for(int x: inv[j])
if(++cnt[x] == 1)
left --;
if(left > 0) break;
ans[j - i] ++, ans[m - i + 2] --;
for(int x: inv[i])
if(--cnt[x] == 0)
left ++;
}
for(int i=1; i<=m; i++)
printf("%d ", ans[i] += ans[i - 1]);
return 0;
}
F - Find 4-cycle
题目大意
给定一个二分图
G
G
G,形如下:
其中顶点集
U
U
U中的顶点数为
S
S
S,
V
V
V的顶点数为
T
T
T,总边数为
M
M
M(第
i
i
i条边连接
u
i
u_i
ui和
v
i
v_i
vi)。
请找出此图中任意长为
4
4
4的环。如果没有,输出-1
。
2
≤
S
≤
3
×
1
0
5
2\le S\le 3\times 10^5
2≤S≤3×105
2
≤
T
≤
3000
2\le T\le 3000
2≤T≤3000
4
≤
M
≤
min
(
S
×
T
,
3
×
1
0
5
)
4\le M\le \min(S\times T,3\times 10^5)
4≤M≤min(S×T,3×105)
1
≤
u
i
≤
S
<
v
i
≤
S
+
T
1\le u_i\le S<v_i\le S+T
1≤ui≤S<vi≤S+T
输入格式
S
T
M
S~T~M
S T M
u
1
V
1
u_1~V_1
u1 V1
u
2
V
2
u_2~V_2
u2 V2
⋮
\vdots
⋮
u
M
V
M
u_M~V_M
uM VM
输出格式
如果有长为
4
4
4的环,输出其中四个顶点的编号(顺序随意,用空格分隔)。
如果没有,输出-1
。
样例
略,请自行前往AtCoder查看
分析
注意到样例中
T
T
T只有
3000
3000
3000,
O
(
T
2
)
=
9
×
1
0
6
\mathcal O(T^2)=9\times 10^6
O(T2)=9×106可以接受。
然后因为是二分图,所以长为
4
4
4的环肯定是在两个顶点集中各有两个点。
令
f
(
x
,
y
)
f(x,y)
f(x,y)为目前发现的与点
x
,
y
x,y
x,y都相连的点,初始化为
−
1
-1
−1(表示未发现)。
输入使用邻接表存储,
G
[
v
]
G[v]
G[v]存储连到
v
v
v的所有点,注意只需存顶点集
U
U
U的
G
[
v
]
G[v]
G[v]即可。
再对于每个
v
v
v,依次枚举
G
[
v
]
G[v]
G[v]中的两个点
(
x
,
y
)
(x,y)
(x,y),如果
f
(
x
,
y
)
=
−
1
f(x,y)=-1
f(x,y)=−1,则执行
f
(
x
,
y
)
:
=
v
f(x,y):=v
f(x,y):=v,如果不是
−
1
-1
−1,则输出{x} {y} {v} {f(x,y)}
,结束程序。
时间复杂度约为 O ( T 2 ) \mathcal O(T^2) O(T2)。
本题中的时间复杂度怎么算?
→ f ( x , y ) \to f(x,y) →f(x,y)中不同 ( x , y ) (x,y) (x,y)的组合只有 T ( T − 1 ) = T 2 − T ≈ T 2 T(T-1)=T^2-T\approx T^2 T(T−1)=T2−T≈T2种。
→ \to~ → 根据鸽笼原理(又称抽屉原理),在最坏情况下, T 2 T^2 T2种组合都记录过 f f f之后,下一种组合无论是什么肯定都已经记录过 f f f,因此最坏时间复杂度为 O ( T 2 ) \mathcal O(T^2) O(T2),对于随机数据的平均时间复杂度远远小于这个值。
代码
#include <cstdio>
#include <cstring>
#include <vector>
#define maxs 300005
#define maxt 3005
using namespace std;
vector<int> G[maxs];
int f[maxt][maxt];
int main()
{
int s, t, m;
scanf("%d%d%d", &s, &t, &m);
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
G[--u].push_back(--v - s);
}
memset(f, -1, sizeof(f));
for(int i=0; i<s; i++)
for(int j=0; j+1<G[i].size(); j++)
for(int k=j+1; k<G[i].size(); k++)
{
int u = G[i][j], v = G[i][k];
if(u > v) u ^= v ^= u ^= v;
if(f[u][v] != -1)
{
printf("%d %d %d %d\n", f[u][v] + 1, i + 1, u + s + 1, v + s + 1);
return 0;
}
f[u][v] = i;
}
puts("-1");
return 0;
}