COCI2021-2022 Contest2 题解更好的阅读体验戳此进入原题面链接Luogu题面T1 Kaučuk题意ExamplesSolutionCodeT2 Kutije题意ExamplesSolutionCodeT3 Hiperkocka题意ExamplesSolutionCodeT4 Magneti题意ExamplesSolution单独形成一个连通块连接在某一个连通块的前端或后端在两个连通块之间,将两个连通块连接起来方案的计算CodeT5 Osumnjičeni题意ExamplesSolutionUPD
(建议您从上方链接进入我的个人网站查看此 Blog,在 Luogu 中图片会被墙掉,部分 Markdown 也会失效)
Kaučuk 程序只有下列三种命令:
:创建新的一级标题,序号从 开始标记。 :创建新的二级标题,序号在每个一级标题的基础上从 开始标记。 :创建新的三级标题,序号在每个二级标题的基础上从 开始标记。 给定
组命令及标题名称,输出所有标题序号及其名称。
Input_1
3
section zivotinje
section boje
section voce
Output_1
1 zivotinje
2 boje
3 voce
Input_2
4
section zivotinje
subsection macke
subsection psi
subsubsection mops
Output_2
1 zivotinje
1.1 macke
1.2 psi
1.2.1 mops
Input_3
1 zivotinje
1.1 macke
1.2 psi
1.2.1 mops
Output_3
1 zivotinje
1.1 psi
2 voce
2.1 ananas
没什么营养的模拟题。
xxxxxxxxxx51123
4567
8using namespace std;9
10mt19937 rnd(random_device{}());11int rndd(int l, int r){return rnd() % (r - l + 1) + l;}12
13typedef unsigned int uint;14typedef unsigned long long unll;15typedef long long ll;16
17string cmp1 = "section", cmp2 = "subsection", cmp3 = "subsubsection";18string title[110];19template<typename T = int>20inline T read(void);21
22int cur1(0), cur2(0), cur3(0);23
24int main(){25 int N = read();26 while(N--){27 string cmp, tit;28 cin >> cmp >> tit;29 if(!cmp1.compare(cmp))cur2 = 0, cur3 = 0, cout << ++cur1 << " " << tit << endl;30 else if(!cmp2.compare(cmp))cur3 = 0, cout << cur1 << "." << ++cur2 << " " << tit << endl;31 else cout << cur1 << "." << cur2 << "." << ++cur3 << " " << tit << endl;32 }33 fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);34 return 0;35}36
37template<typename T>38inline T read(void){39 T ret(0);40 short flag(1);41 char c = getchar();42 while(c != '-' && !isdigit(c))c = getchar();43 if(c == '-')flag = -1, c = getchar();44 while(isdigit(c)){45 ret *= 10;46 ret += int(c - '0');47 c = getchar();48 }49 ret *= flag;50 return ret;51}
Matrin 有 n 个箱子的玩具。箱子分别用序号 1,2,3,⋯,n 表示。初始状态下,每个箱子中有一个与箱子编号相同的玩具。
Matrin 邀请了 m 位朋友来家玩玩具。他注意到,每一位朋友在玩完玩具之后,都会将原先位于 i 号箱子内的玩具放入
号箱内。 给定 q 组询问,每次可随意邀请朋友并自由选择顺序,同时每位朋友可以邀请任意多次。问是否存在一种方案,使得 a 号玩具最终被放入 b 号箱子中。
保证
为一个排列。
, 。
Input_1
4 1 3 1 2 4 3 1 1 1 2 3 4
Output_1
DA NE DA
Input_2
4 2 4 2 1 3 4 1 2 4 3 2 1 3 4 1 4 2 3
Output_2
DA DA NE NE
Input_3
6 2 2 2 1 4 5 3 6 3 2 4 1 5 6 1 5 6 3
Output_3
DA NE
依然是一个没什么营养的水题,考虑因为
搜索,最短路之类的也能过,可以但没必要。
xxxxxxxxxx61123
4567
8using namespace std;9
10mt19937 rnd(random_device{}());11int rndd(int l, int r){return rnd() % (r - l + 1) + l;}12
13typedef unsigned int uint;14typedef unsigned long long unll;15typedef long long ll;16
17class UnionFind{18 private:19 int fa[1100];20 public:21 UnionFind(void){for(int i = 0; i <= 1000; ++i)fa[i] = i;}22 void Union(int x, int y){fa[y] = x;}23 int Find(int x){if(fa[x] == x)return x; return fa[x] = Find(fa[x]);}24}UF;25
26template<typename T = int>27inline T read(void);28
29int N, M, Q;30
31int main(){32 N = read(), M = read(), Q = read();33 for(int m = 1; m <= M; ++m){34 for(int i = 1; i <= N; ++i){35 int tmp = read();36 if(UF.Find(i) != UF.Find(tmp))UF.Union(i, tmp);37 }38 }39 while(Q--){40 printf("%s\n", UF.Find(read()) == UF.Find(read()) ? "DA" : "NE");41 }42
43 fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);44 return 0;45}46
47template<typename T>48inline T read(void){49 T ret(0);50 short flag(1);51 char c = getchar();52 while(c != '-' && !isdigit(c))c = getchar();53 if(c == '-')flag = -1, c = getchar();54 while(isdigit(c)){55 ret *= 10;56 ret += int(c - '0');57 c = getchar();58 }59 ret *= flag;60 return ret;61}(这道题定义了一个 n-dimensional hipercube,但是实际上和这东西没什么关系)
存在一个
给定若干棵有
需要你给定一种放置方案,使放置的树尽可能多。
采用 SPJ,可以证明最多能放
若放置不合法则获得
Input_1
1 0 1
Output_1
1 0 1
Input_2
2 0 1 1 2
Output_2
2 0 1 3 0 2 3
Input_3
3 0 1 0 2 0 3
Output_3
4 0 1 2 4 3 1 2 7 5 1 4 7 6 2 4 7
本题的难度基本都在思维和找规律上,代码实现很简单,也并不需要用到任何高级算法和数据结构。
首先我们观察题目里一个很奇怪的条件,
通过这个性质我们可以进行一些大胆猜想:
假设我们只需要放置一棵树,那么可以考虑采用如下方案:
任意选择一个树上的节点作为根,并对应到图上的任意节点,这里我们考虑令树上的
对于树上剩余的点,我们考虑对其进行 DFS,按照其 DFS 序为其分配映射到图上的节点。
为了保证我们映射的节点不会重复,这里我们按照如下方案选点,对于 DFS 序等于 dfs(son, cur ^ (1 << (cur++)))。
现在我们就需要扩展到更多的树,注意题目有个要求即每条边只能用一次,那么就需要注意扩展的时候不能使用两次相同图上的父子关系(包括父子之间调换),这里我们可以尝试从一些较小的数找规律。
首先考虑若父亲为
下面我们先找一种特殊情况,如
Tips:这里我们只考虑枚举以树上根节点为
然后我们寻找不合法的解,如果我们贪心地优先保留
观察剩余的合法的解:
不难发现其符合一个规律,即二进制中
经过验证,显然当树的形态不为一条链的时候该结论仍然成立。
下面我们需要思考如何计算这些扩展出来的节点,显然我们可以每次都进行一次
观察刚才得到的合法解中的每一列:
不难发现第一个可能的规律,将
于是我们继续尝试构造规律,可以发现若对于每一个符合要求的
至此我们便可以考虑用 dp 思想,
同时建议对于这种规律题如果有时间,打个对拍验证一下规律的正确性。
xxxxxxxxxx77123
4567
8using namespace std;9
10mt19937 rnd(random_device{}());11int rndd(int l, int r){return rnd() % (r - l + 1) + l;}12
13typedef unsigned int uint;14typedef unsigned long long unll;15typedef long long ll;16
17template<typename T = int>18inline T read(void);19
20vector < int > vert[110000];21int N;22int base[110000];23bool vis[110000];24int cur(0);25void dfs(int p, int mapp){26 base[p] = mapp;27 vis[p] = true;28 for(auto i : vert[p]){29 if(!vis[i]){30 vis[i] = true;31 dfs(i, mapp ^ (1 << (cur++)));32 }33 }34}35int dp[110000];36vector < int > legal;37void Init(int N){38 int lim = 1 << N;39 dp[0] = 0;40 for(int i = 1; i <= lim; ++i){41 dp[i] = dp[i >> 1] + (i & 1);42 if(!(dp[i] & 1))legal.push_back(i);43 }44}45int main(){46 N = read();47 for(int i = 1; i <= N; ++i){48 int f = read(), t = read();49 vert[f].push_back(t);50 vert[t].push_back(f);51 }52 dfs(0, 0);53 Init(N);54 printf("%d\n", (int)legal.size() + 1);55 for(int i = 0; i <= N; ++i)printf("%d%c", base[i], i == N ? '\n' : ' ');56 for(auto i : legal)57 for(int j = 0; j <= N; ++j)58 printf("%d%c", base[j] ^ i, j == N ? '\n' : ' ');59 fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);60 return 0;61}62
63template<typename T>64inline T read(void){65 T ret(0);66 short flag(1);67 char c = getchar();68 while(c != '-' && !isdigit(c))c = getchar();69 if(c == '-')flag = -1, c = getchar();70 while(isdigit(c)){71 ret *= 10;72 ret += int(c - '0');73 c = getchar();74 }75 ret *= flag;76 return ret;77}给你
对于
Input_1
1 10 10
Output_1
10
Input_2
4 4 1 1 1 1
Output_2
24
Input_3
3 4 1 2 1
Output_3
4
首先我们可以考虑对于有特殊性质的磁铁,当
然后我们可以考虑通过这个性质来思考这道题的思路。
首先我们思考之前的性质,我们先是确定了一部分长度中磁铁如何排列,然后用隔板法计算方案数。所以对于这道题我们的思路也可以去考虑所有的磁铁的合法排列,然后统计总方案数。
我们会发现如果按照每个磁铁输入顺序考虑,并没有什么好的性质,而我们又考虑,对于两个磁铁互相吸引时,显然会优先因为吸引半径更大的磁铁而不合法,所以可以考虑按照吸引半径升序排序,并从每次插入新的磁铁的角度来考虑。
这道题的
考虑定义一个连通块为一段一定不可能被插入磁铁的区间,且以磁铁开头并以磁铁结尾,显然当两个磁铁,或者更进一步地,两个连通块的吸引区域有重合,或紧挨着,则两个连通块变为同一连通块。

如图,三个正方形为磁铁,线段为磁铁的吸引半径,大长方形即为连通块。
为什么要这样设计连通块?因为我们在每次插入一个新的磁铁的时候都要考虑如何放置,而因为升序的原因,我们只需要考虑新插入的磁铁的吸引半径,所以对于每个连通块左右的吸引半径可以忽略。
可以考虑令
然后我们考虑转移,对于新插进来的一个磁铁,显然有如下三种可能性,即我们要将
upd - Tips: 注意因为我们的目的是求出来排列方案数然后按照隔板法求,所以我们插入一个新的连通块的时候应该去贪心地让新的磁铁尽可能地挨着原来的连通块。
显然会使连通块多一个,剩余空位少一个,即:
因为升序排列,所以只考虑我们插入的这个磁铁会占用
而一共有
这里需要注意一个点,我们考虑的是磁铁之间的组合而非排列,所以任意两个块之间都可以被连结,且两者之间不同的顺序也是不同的方案。
与第二种情况类似,因为升序排列,只需要考虑插入的这个磁铁,为了连结,会在左右两侧各自占用
xxxxxxxxxx86123
4567//(int)(1e9 + 7)8
9/******************************10abbr11
12******************************/13
14using namespace std;15
16mt19937 rnd(random_device{}());17int rndd(int l, int r){return rnd() % (r - l + 1) + l;}18
19typedef unsigned int uint;20typedef unsigned long long unll;21typedef long long ll;22
23ll qpow(ll a, ll b){24 ll ret(1), mul(a);25 while(b){26 if(b & 1)ret = ret * mul % MOD;27 b >>= 1;28 mul = mul * mul % MOD;29 }30 return ret;31}32ll frac[11000], inv[11000];33void Init(int N){34 frac[0] = 1;35 for(int i = 1; i <= N; ++i)frac[i] = frac[i - 1] * i % MOD;36 inv[N] = qpow(frac[N], MOD - 2);37 for(int i = N - 1; i >= 0; --i)inv[i] = inv[i + 1] * (i + 1) % MOD;38}39ll C(int n, int m){40 if(m > n)return 0;41 return frac[n] * inv[m] % MOD * inv[n - m] % MOD;42}43template<typename T = int>44inline T read(void);45
46int N, L;47int r[110];48ll dp[55][55][11000];49int main(){50 N = read(), L = read();51 Init(10100);52 for(int i = 1; i <= N; ++i)r[i] = read();53 sort(r + 1, r + N + 1);54 dp[0][0][0] = 1;55 for(int i = 1; i <= N; ++i)56 for(int j = 1; j <= i; ++j)57 for(int k = 1; k <= L; ++k){58 dp[i][j][k] = dp[i - 1][j - 1][k - 1];59 if(k >= r[i])dp[i][j][k] += dp[i - 1][j][k - r[i]] * j % MOD * 2 % MOD;60 if(k >= r[i] * 2 - 1)dp[i][j][k] += dp[i - 1][j + 1][k - r[i] * 2 + 1] * (j + 1) % MOD * (j + 1 - 1) % MOD;61 dp[i][j][k] %= MOD;62 }63 ll ans(0);64 for(int i = 1; i <= L; ++i)ans += dp[N][1][i] * C(L - i + 1 + N - 1, N + 1 - 1) % MOD;65 printf("%lld\n", ans % MOD);66 fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);67 return 0;68}69
70
71
72template<typename T>73inline T read(void){74 T ret(0);75 short flag(1);76 char c = getchar();77 while(c != '-' && !isdigit(c))c = getchar();78 if(c == '-')flag = -1, c = getchar();79 while(isdigit(c)){80 ret *= 10;81 ret += int(c - '0');82 c = getchar();83 }84 ret *= flag;85 return ret;86}有
Input_1
2 1 1 1 1 3 1 1 2 2 1 2
Output_1
1 1 2
Input_2
3 1 1 2 2 3 3 3 1 1 2 3 1 3
Output_2
1 1 1
Input_3
5 1 3 3 3 4 6 2 3 1 1 3 1 4 3 5 1 5
Output_3
3 1 3
首先有一个很显然的贪心,即当我们某一次标记的区间
权值线段树 + 单调队列。
不难想到,用单调队列的思想存身高区间,用权值线段树维护,在当前单调队列中所有身高区间值域中,如果插入新的身高区间,会有哪些身高区间因有区间相交而不合法。以此即可
并且此时我们发现值域过大,并且值域具体的数并不重要,只需要考虑大小关系,所以考虑进行离散化。
于是此时我们便可以发现问题转化为了区间覆盖问题,即CF1175E Minimal Segment Cover。
也就是我们现在有
这里有一个细节需要注意,在我们当前的算法中可能
对于区间覆盖显然就是一个
xxxxxxxxxx123123
45678
9/******************************10abbr11st => Segment Tree12lt => LazyTag13gl/gr => global left/right14ms => Max Section15sec => Section16******************************/17
18using namespace std;19
20mt19937 rnd(random_device{}());21int rndd(int l, int r){return rnd() % (r - l + 1) + l;}22
23typedef unsigned int uint;24typedef unsigned long long unll;25typedef long long ll;26
27int ms;28int N, Q;29
30class SegTree{31 private:32 33 34 35 int st[MAXNQ << 3], lt[MAXNQ << 3];36 public:37 void Pushdown(int p, int gl, int gr){38 if(gl == gr)return (void)(lt[p] = 0);39 st[LS] += lt[p], st[RS] += lt[p];40 lt[LS] += lt[p], lt[RS] += lt[p];41 lt[p] = 0;42 }43 void Modify(int l, int r, int val, int p = 1, int gl = 1, int gr = ms){44 // printf("modifying l=%d, r=%d, v=%d, p=%d, gl=%d, gr=%d\n", l, r, val, p, gl, gr);45 if(l <= gl && gr <= r){st[p] += val, lt[p] += val; return;}46 if(lt[p])Pushdown(p, gl, gr);47 if(l <= MID)Modify(l, r, val, LS, gl, MID);48 if(MID + 1 <= r)Modify(l, r, val, RS, MID + 1, gr);49 st[p] = st[LS] + st[RS];50 }51 bool Query(int l, int r, int p = 1, int gl = 1, int gr = ms){52 // printf("querying l=%d, r=%d p=%d, gl=%d, gr=%d\n", l, r, p, gl, gr);53 if(l <= gl && gr <= r)return st[p];54 if(lt[p])Pushdown(p, gl, gr);55 return ((l <= MID) ? Query(l, r, LS, gl, MID) : false ) | ((MID + 1 <= r) ? Query(l, r, RS, MID + 1, gr) : false); 56 }57}st;58
59template<typename T = int>60inline T read(void);61
62pair < int, int > sec[MAXNQ];63vector < int > values;64//1e5 < 2^1765int dp[MAXNQ][30];66
67int main(){68 N = read();69 for(int i = 1; i <= N; ++i){70 int l = read(), r = read();71 sec[i] = make_pair(l, r);72 values.push_back(l), values.push_back(r);73 }74 sort(values.begin(), values.end());75 ms = distance(values.begin(), unique(values.begin(), values.end()));76 for(int i = 1; i <= N; ++i){77 sec[i].first = distance(values.begin(), lower_bound(values.begin(), values.begin() + ms, sec[i].first) + 1);78 sec[i].second = distance(values.begin(), lower_bound(values.begin(), values.begin() + ms, sec[i].second) + 1);79 }80 int cur(1);81 for(int i = 1; i <= N; ++i){82 while(st.Query(sec[i].first, sec[i].second))83 st.Modify(sec[cur].first, sec[cur].second, -1),84 dp[cur - 1][0] = i - 1,85 ++cur;86 st.Modify(sec[i].first, sec[i].second, 1);87 }88 while(cur <= N + 1)dp[cur - 1][0] = N, ++cur;89 for(int j = 1; j <= 17; ++j)90 for(int i = 0; i <= N; ++i)91 dp[i][j] = dp[dp[i][j - 1]][j - 1];92 Q = read();93 while(Q--){94 int l = read() - 1, r = read();95 int ret(0);96 for(int dis = 17; dis >= 0; --dis){97 if(dp[l][dis] < r){98 ret += 1 << dis;99 l = dp[l][dis];100 }101 }102 printf("%d\n", ret + 1);103 }104
105 fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);106 return 0;107}108
109template<typename T>110inline T read(void){111 T ret(0);112 short flag(1);113 char c = getchar();114 while(c != '-' && !isdigit(c))c = getchar();115 if(c == '-')flag = -1, c = getchar();116 while(isdigit(c)){117 ret *= 10;118 ret += int(c - '0');119 c = getchar();120 }121 ret *= flag;122 return ret;123}2022_09_05 完成 T1 - T3 及 T4 一部分
2022_09_06 初稿
2022_09_07 修改了 T3 T4 的一些存在的错误