AtCoder Beginner Contest 247 Solution
目录更好的阅读体验戳此进入
题面链接
题面 Luogu 链接
A - Move Right
题面
给定一个 $ 4 $ 位的二进制串,求其右移一位之后的值的二进制。
Solution
语法题没营养。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
int main(){
printf("0");
char c = getchar();
for(int i = 1; i <= 4; ++i){
while(c != '1' && c != '0')c = getchar();
if(i != 4)printf("%c", c);
c = getchar();
}printf("\n");
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
B - Unique Nicknames
题面
给定 $ n $ 个人的姓和名,定义一个人的昵称为他的姓或名,且一个人的昵称不能与其他人的姓或名重复,判断给定的数据是否能为每个人钦定一个昵称。可行输出 Yes
,反之输出 No
。
Solution
(语法题没一遍过,身败名裂了)
开个 map
维护一下每个字符串的出现次数,如果一个人的姓和名出现次数都大于等于 $ 2 $ 那么他的昵称无论选择姓还是名都会重复一定不合法。需要注意特判一下如果一个人的姓名相同那么就是出现次数大于等于 $ 3 $ 才会不合法。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
unordered_map < string, int > name;
string fn[110], ln[110];
int main(){
int N = read();
for(int i = 1; i <= N; ++i){
cin >> fn[i] >> ln[i];
name[fn[i]]++, name[ln[i]]++;
}
for(int i = 1; i <= N; ++i)
if((fn[i] == ln[i] && name[fn[i]] >= 3) || (fn[i] != ln[i] && name[fn[i]] >= 2 && name[ln[i]] >= 2))
printf("No\n"), exit(0);
printf("Yes\n");
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
C - 1 2 1 3 1 2 1
题面
我们按如下方式定义序列 $ S_n $:
$ S_1 $ 只包含一个整数 $ 1 $。
$ S_n $ 为 $ S_{n - 1}, n, S_{n - 1} $ 构成的序列。
给定 $ n $,输出序列 $ S_n $。
Solution
也算是个语法题吧。。
从定义就能看出来这是递归定义的,于是我们也写个递归,$ n = 1 $ 的时候输出 $ 1 $,否则按照要求递归并输出即可。
(如果把 $ n $ 开大一点变成求第 $ k $ 位应该能略微增加一点难度)
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
void PrintAns(int n){
if(n == 1)return printf("1 "), void();
PrintAns(n - 1);
printf("%d ", n);
PrintAns(n - 1);
}
int main(){
int N = read();
PrintAns(N);
printf("\n");
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
D - Cylinder
题面
存在一个队列,给定 $ q $ 个操作或询问,按以下格式输入:
1 x c
,表示向队尾插入 $ c $ 个 $ x $。
2 c
,表示从队头取出 $ c $ 个元素并输出它们的和。
Solution
大概也算是个语法题?
维护一个队列,里面元素是个 pair < int, int >
记录元素值和数量,$ 1 $ 操作就是队尾插入一个 {x, c}
,$ 2 $ 操作判断一下,如果队头能取就取出来然后求和,否则对队头做一个类似 ODT 的 Split
的操作,然后对应维护一下 $ ans $ 即可。
对于 $ 2 $ 操作详细解释一下,不难想到维护询问剩余的 $ c $,令队头元素的 $ c $ 为 $ c' $,值为 $ x $,显然若 $ c' \le c $,那么直接 $ c \leftarrow c - c' $ 然后答案加上 $ c' \times x $ 后把队头弹出,反之答案加上 $ c \times x $ 并 $ c' \leftarrow c' - c $,注意判断队列非空即可。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
queue < pair < int, int >/*value, cnt*/ > balls;
int main(){
int N = read();
while(N--){
int opt = read();
if(opt == 1){
int x = read(), c = read();
balls.push({x, c});
}else{
ll ans(0);
int c = read();
while(c > 0 && !balls.empty()){
auto tp = balls.front();
if(c >= tp.second)balls.pop(), c -= tp.second, ans += (ll)tp.first * tp.second;
else ans += (ll)c * tp.first, balls.front().second -= c, c = 0;
}printf("%lld\n", ans);
}
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
E - Max Min
题面
给定数列 $ A_n $,给定 $ X, Y $,我们定义数对 $ (L, R) $ 满足 $ 1 \le L \le R \le n $,且数列 $ A_L, A_{L + 1}, \cdots, A_R $ 满足最大值为 $ X $,最小值为 $ Y $,求有多少种满足条件的数对。
Solution
关于本题有一些较为显然的 $ O(n \log ^2 n) $ 或 $ O(n \log n) $ 或 $ O(n) $ 的做法,前两个大概对应着树套树和线段树,然后后者大概是维护前一个等于 $ X $ 等于 $ Y $,在 $ (X, Y) $ 之间的啊等等一堆东西然后跑一遍,这里就不多赘述了,网上相关做法很多。这里主要提供一个机房巨佬 @Zpair 想出来的人类智慧容斥的做法与推导。
首先我们考虑这段区间里一定不能包含在 $ (-\infty, Y) \cup (X, +\infty) $ 内的数,所以我们考虑让所有在这个区间内的数作为分割点,这样最终会让我们的序列被分成很多个子序列,这里我们不难发现,合法的区间一定不会有跨越两个子区间的,一定都是在子区间内的子子区间。
所以这时候我们只需要对于每个划分出来的子区间考虑其中的合法区间即可,不难想到我们要找的区间合法当且仅当里面包含了至少一个 $ X $ 和至少一个 $ Y $,为了方便记录这里我们令等于 $ X $ 的点为 $ 1 $,等于 $ Y $ 的点为 $ -1 $,在 $ (X, Y) $ 之间的点为 $ 0 $,也就是我们要找的就是每个子区间里包含 $ -1 $ 和 $ 1 $ 的区间由多少个。这里我们令包含 $ 1 $ 的区间的集合为 $ A $,包含 $ -1 $ 的为 $ B $,令子区间内的所有区间的集合为 $ \Omega $,不难想到我们比较好求的是,不包含 $ 1 $ 和 $ -1 $ 的区间,不包含 $ 1 $ 的区间,不包含 $ -1 $ 的区间,以及全集,分别对应着 $ \overline{A} \cap \overline{B} \(,\) \overline{A} \(,\) \overline{B} \(,\) \Omega $。我们的目标是求出 $ A \cap B $,所以我们希望通过前面的四个求出答案,不难想到如下推导:
\[A \cap B = \Omega - \overline{A} - \overline{B} + \overline{A} \cap \overline{B} \]此时考虑如何维护这四个值,显然我们可以考虑把子区间再次进行分割,遍历三次,分别由 $ 1 $ 分割,由 $ -1 $ 分割,由 $ -1 $ 或 $ 1 $ 分割,也就分别对应着式子中的三个区间的大小,然后对于每个子区间维护一遍即可,最后就是一个大常数的线性求解。
然后还有个点就是如果 $ X = Y $ 的话那么其实际上是既是 $ 1 $ 又是 $ -1 $,需要特判一下。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template< typename T = int >
inline T read(void);
int N, X, Y;
int a[210000];
int cnt0(0), cnt1(0), cnt_1(0), l(1), r(-1);
ll ans(0);
ll GetC(int n, int m){
if(n == 1)return 1;
if(!n || m > n)return 0;
ll ret(1);
for(int i = n; i >= n - m + 1; --i)ret *= i;
for(int i = m; i >= 1; --i)ret /= i;
return ret + n;
}
int main(){
N = read(), X = read(), Y = read();
for(int i = 1; i <= N + 1; ++i){
a[i] = i <= N ? read() : INT_MAX;
a[i] = a[i] == X && a[i] == Y ? -114514 : (a[i] == X ? 1 : (a[i] == Y ? -1 : (Y <= a[i] && a[i] <= X ? 0 : 114514)));
if(a[i] != 114514){r = i; continue;}
if(!~r){l = i + 1; continue;}
ll cur(0);
cur += GetC(r - l + 1, 2);
int ll(l), rr(-1);
for(int j = l; j <= r; ++j){
if(a[j] != -1 && a[j] != -114514){rr = j; continue;}
if(!~rr){ll = j + 1; continue;}
cur -= GetC(rr - ll + 1, 2);
ll = j + 1;
}if(ll <= rr)cur -= GetC(rr - ll + 1, 2);
ll = l, rr = -1;
for(int j = l; j <= r; ++j){
if(a[j] != 1 && a[j] != -114514){rr = j; continue;}
if(!~rr){ll = j + 1; continue;}
cur -= GetC(rr - ll + 1, 2);
ll = j + 1;
}if(ll <= rr)cur -= GetC(rr - ll + 1, 2);
ll = l, rr = -1;
for(int j = l; j <= r; ++j){
if(!a[j]){rr = j; continue;}
if(!~rr){ll = j + 1; continue;}
cur += GetC(rr - ll + 1, 2);
ll = j + 1;
}if(ll <= rr)cur += GetC(rr - ll + 1, 2);
ans += cur;
l = i + 1;
}
printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
然后因为我是完全按照这个思路实现的所以上面的可能非常丑,所以这里也贴一个 @Zpair 的优美实现。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int l1,l2,l3,l4,n,x,y;ll ans;
ll get(int x){return (ll)x*(x+1)/2;}
int main(){
cin>>n>>x>>y;int v;
for(int i=1;i<=n;++i){
scanf("%d",&v);
if(v>x||v<y){ans+=get(l1)-get(l2)-get(l3)+get(l4),l1=l2=l3=l4=0;continue;}
if(v!=x&&v!=y)l1++,l2++,l3++,l4++;
if(v==x&&v==y)l1++,ans+=-get(l2)-get(l3)+get(l4),l2=l3=l4=0;
if(v==x&&v!=y)l1++,l3++,ans+=-get(l2)+get(l4),l2=l4=0;
if(v!=x&&v==y)l1++,l2++,ans+=-get(l3)+get(l4),l3=l4=0;
}
ans+=get(l1)-get(l2)-get(l3)+get(l4);
cout<<ans;
}
F - Cards
题面
给定 $ n $ 张卡片,每张卡片正反面各有一个数,给定每张卡片正面和反面的数,保证正面的数构成的序列,和反面的数构成的,分别均为 $ 1 $ 到 $ n $ 的排列,可以选择任意张卡片并获得其正反面的数,要求最终所有获得的数至少包含 $ 1 $ 到 $ n $ 每个数至少一次。求有多少种取法,对 $ 998244353 $ 取模。
Solution
考虑卡片之间的联系,显然若一个数在同一张卡牌的正反面那么这张卡牌必须选择,否则无法凑齐一个排列,反之这个数一定会在两张卡牌中出现,且这两张卡牌一定至少有一张被选择,否则依然无法凑齐一个排列,这个很好理解。
于是我们考虑一些对于这种题的一般的套路,可以尝试建图,对于本题不难想到,我们将一张卡牌作为一个点,将这个点向卡牌上存在的数的另一次出现所在的卡牌连一条边。抽象地说,对于一个数所在的两张(或一张)卡牌位置 $ pos_{i, 0} $ 和 $ pos_{i, 1} $,其中一定有一个刚好为 $ i $,我们就是要从 $ i $ 向不等于 $ i $ 的那个 $ pos_i $ 连一条边,这里便不难想到从 $ i $ 向 $ pos_{i, 0} \oplus pos_{i, 1} \oplus i $ 连边即可。
不难想到这样会形成多个环,每一个环代表着其中几张卡牌,同时这些卡牌上的数在环内一定每个数都出现了两遍,如排列 $ { 1, 2, 3 } $ 和排列 $ { 2, 1, 3 } $,显然 $ i = 1 $ 时向数字 $ 1 $ 所在的另一张卡牌 $ 2 $ 和数字 $ 2 $ 所在的另一张卡牌 $ 2 $ 连边(不拿发现很有可能存在重边与自环,所以需要注意判重并忽略),对于 $ i = 2 $ 同理,最终一定会形成 $ 1 \rightarrow 2 \rightarrow 1 $ 和 $ 3 \rightarrow 3 $ 两个环,第一个环可以求出带有 $ 1, 2 $ 数字的卡牌的让 $ 1, 2 $ 都至少存在一次的卡牌选择方案数,第二个环则为 $ 3 $ 的方案数,将两者乘起来即为最终答案。并且环上每个边相连的两个点,或者说两张卡牌,至少要被选择一张,因为一条边连结的两个卡牌一定存在着相同的数,为了保证构成排列至少需要选择一个。
同时发现不同环的方案数,只跟环的长度有关,于是我们考虑令 $ dp(i) $ 表示 $ i $ 个数构成的环,则有边界 $ dp(1) = 1 $ 和 $ dp(2) = 3 $,和转移 $ dp(i) = dp(i - 1) + dp(i - 2) $,大概就是我们考虑向原环中插入第 $ i $ 个数,如果选择第 $ i $ 个数,那么任意的前 $ i - 1 $ 个数的合法方案都可以接上新的这个数,反之如果不选第 $ i $ 个,那么第 $ i $ 个的两侧都应该是选择的,这个时候我们直观地可以想到,固定其左右的两个然后由 $ dp(i - 3) $ 转移而来,但是这个是有遗漏的。观察发现对于 $ i $ 不选择的,是存在类似 $ \cdots \rightarrow 0 \rightarrow 1 \rightarrow i(0) \rightarrow 1 \rightarrow 0 \rightarrow \cdots $,也就是在固定三个之后左右各自接一个不选的,这个东西显然是不在 $ dp(i - 3) $ 里的,因为两个都不选接在一起形成环是不合法的,考虑如何正确地转移:我们可以考虑在 $ dp(i - 2) $ 的方案中找到任意一个选择的点,然后在其旁边接上 $ i $ 和另一个固定的选择的点,这样也是合法的且不重不漏,最终就是 $ dp(i) = dp(i - 1) + dp(i - 2) $。
于是考虑并查集维护好每个环的大小,$ \prod dp(siz_i) $ 即为最终的答案。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD 998244353
template< typename T = int >
inline T read(void);
int N;
int P[210000], Q[210000];
int cnt[210000];
int pos[210000][2];
int f[210000];
bool vis[210000];
int ans(1);
class UnionFind{
private:
public:
int fa[210000];
int siz[210000];
UnionFind(void){for(int i = 1; i <= 201000; ++i)fa[i] = i, siz[i] = 1;}
int Find(int x){return x == fa[x] ? x : fa[x] = Find(fa[x]);}
void Union(int origin, int son){
if(Find(origin) == Find(son))return;
siz[Find(origin)] += siz[Find(son)], fa[Find(son)] = Find(origin);
}
}uf;
int main(){
N = read();
f[1] = 1, f[2] = 3;
for(int i = 3; i <= N; ++i)f[i] = (ll)(f[i - 1] + f[i - 2]) % MOD;
for(int i = 1; i <= N; ++i)P[i] = read(), pos[P[i]][cnt[P[i]]++] = i;
for(int i = 1; i <= N; ++i)Q[i] = read(), pos[Q[i]][cnt[Q[i]]++] = i;
for(int i = 1; i <= N; ++i)
uf.Union(i, pos[P[i]][0] ^ pos[P[i]][1] ^ i),
uf.Union(i, pos[Q[i]][0] ^ pos[Q[i]][1] ^ i);
for(int i = 1; i <= N; ++i)if(uf.Find(i) == i)ans = (ll)ans * f[uf.siz[uf.Find(i)]] % MOD;
printf("%d\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
G - Dream Team
题面
咕咕咕
Solution
似乎是个费用流,先暂时咕掉了,以后再做。
Code
//TODO
Ex - Rearranging Problem
题面
分治NTT,跳!
Solution
等联赛考完如果考过了再来做吧。。
Code
//TODO
UPD
update-2022_10_25 初稿
标签:AtCoder,return,int,题解,void,ret,247,getchar,define From: https://www.cnblogs.com/tsawke/p/16945513.html