😊 | Powered By HeartFireY | MINIEYE Contest 1 Solution

A.Mod, Or and Everything

Problem Description

You are given an integer 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_数据结构.

You are required to calculate 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_c++_02.

The “or” operation means “bitwise OR”.

Input

The first line contains an integer 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_#define_03representing the number of test cases.

For each test case, there is an integer 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_04in one line.

Output

For each test case, print the answer in one line.

Solution

😀 算法标签:打表找规律

读完题还是没有思路,遂打表2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_c++_05​​~2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_打表_06​​,找规律,打表程序和数据:

2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_数据结构_07

打到2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_打表_06​,规律特别明显,遂打到2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_09验证,发现规律成立:
2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_10
然后写了一发AC。但是这跟题目描述究竟有何联系呢?可以对任意值2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_11而言:对其从2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_12取模,那么除去他二进制最高非0位,其模数取遍各位为1的情况(可以打表验证),那么通过题目的按位取或方式得出的结果所有位全为2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_c++_05,正好解释了这个规律。

(其实没必要用快速幂 \doge)

#include <bits/stdc++.h>
#define
#define
using namespace std;

ll binpow(ll a, ll b) {
ll res = 1;
while (b > 0) {
if (b & 1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}

signed main(){
int t = 0; cin >> t;
while(t--){
int n = 0, cnt = 0, ans = 0; cin >> n;
n -= 1;
while(n){
n >>= 1;
cnt++;
}
for(int i = 0; i < cnt - 1; i++) ans += binpow(2, i);
cout << ans << endl;
}
return 0;
}

E.Minimum spanning tree

Problem Description

Given 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_14 points, numbered from 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_15 to 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_数据结构, the edge weight between the two points 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_c++_17 and 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_c++_18 is 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_数据结构_19. Please find the minimum spanning tree formed by them.

A minimum spanning tree is a subset of the edges of a connected, edge-weighted undirected graph that connects all the vertices together, without any cycles and with the minimum possible total edge weight. That is, it is a spanning tree whose sum of edge weights is as small as possible.

2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_数据结构_19 is the smallest positive integer that is divisible by both 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_c++_17 and 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_c++_18.

Input

The first line contains a single integer 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_数据结构_23

The only line of each test case contains one integers 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_24

Output

For each test case, print one integer in one line, which is the minimum spanning tree edge weight sum.

Solution

😀 算法标签:前缀和、素数筛、思维/规律

题目给定2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_#define_25个点,从2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_26​编号,任意两点间的均有一条边,其边权为两点编号的最小公倍数,求该图的MST。

首先明确数据范围2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_数据结构_27​,显然不是一道跑最小生成树算法的题。那么考虑规律。

根据题目条件:边权为两点的最小公倍数,那么我们可以对点进行分类:①.质数点 ②.合数点

对于任意两个质数点,其最小公倍数必然为两质数点乘积,那么为了使边权最小,可以让所有的质数点(2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_c++_28)全部向2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_15​​连线,可保证所有质数点间边权最小。

而对于合数点,则向其约数点连边。此时可保证边权最小。

如此可得总边权和为 质数的和*2+合数的和。

注意开long long!

#include <bits/stdc++.h>
#define
using namespace std;

const int N = 10000010;
int isp[N], prime[N], a[N], cnt = 0;

void init(){
for(int i = 2; i < N; i++){
if(!isp[i]) prime[++cnt] = i;
for(int j = 1; j <= cnt && i * prime[j] < N; j++){
isp[prime[j] * i] = 1;
if(i % prime[j] == 0) break;
}
}
}

signed main(){
init();
for(int i = 3; i <= N - 10; i++){
if(isp[i]) a[i] = a[i - 1] + i;
else a[i] = a[i - 1] + 2 * i;
}
int t = 0; cin >> t;
while(t--){
int n = 0; cin >> n;
cout << a[n] << endl;
}
return 0;
}

F.Xor sum

Problem Description

Given a sequence of integers of length n, find the shortest consecutive subsequence witch XOR sum not less than k.

If there are multiple consecutive subsequences of the same length, print the consecutive subsequence with the smallest left end point.

If there are no consecutive subsequence witch XOR sum not less than k, just print “-1”.

Input

The first line contains a single integer t (t<=100) representing the number of test cases in the input. Then t test cases follow.

The first line of each test case contains two integers n (1<=n<=100000) and k (0<=k<2^30), representing the length of sequence.

The second line of each test contains n integers ai (0<=ai<2^30), representing the integers in sequence.

The number of test witch n>1000 does not exceed 5.

Output

For each test case, print two integers in one line, representing the left end point and right end point of the consecutive subsequence.

If there are no consecutive subsequence witch XOR sum not less than k, print “-1” in one line.

Solution

😀 算法标签:0-1 Trie

给定一个长度为2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_数据结构的序列,求一段最短的连续子序列满足异或和大于等于2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_#define_31

序列长度范围 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_打表_32,考虑使用2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_#define_33复杂度及以下的算法解决。

首先我们需要知道:对于前缀异或和,如果要求一段区间的前缀异或,有2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_数据结构_34​​​

那么对于本题,我们对整个序列求前缀异或,则问题转为求序列内两个距离最近的数,使得他们的异或值不小于2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_#define_31

对于转化后的问题可以使用​​0-1字典树​​解决。

接下来对如何使用​​0-1字典树​​解决这个问题进行分析:

首先,我们采用类似在线处理的方式,边求异或前缀边更新最优解,这样相当于固定第二个数(也就是原题区间右端点),来求最优的第一个数(也就是原题区间左端点)。

  1. 首先读入当前值,求到当前值位置的异或前缀;
  2. 判断:如果当前的异或前缀2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_#define_36​,且​当前区间长度(即为2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_数据结构_37)记录的最短区间长度2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_打表_38,则更新一次最优解;
  3. 求位于2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_打表_39​~当前范围内满足异或前缀2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_#define_36​的最大数(即为原题区间左端点),再次判断是否需要更新最优解;
  4. 将当前异或前缀插入到字典树中。

先对字典树的插入过程进行分析:

和普通字典树一样,我们使用一个数组2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_打表_41,由于2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_打表_42仅有两个字符,因此数组第二维度只需要两个空间,用于表示连接当前节点和子节点两条边,那么2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_43数组的使用方法可以表示为:
2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_打表_44
那么构造2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_43数组的过程就可以得出了:

  1. 首先判断对于待插入位,当前节点是否已经被插入过,如果被插入过,则向子节点继续走
  2. 如果没有插入过,那么新建节点,同时初始化两条边指向的节点为空,记录新子节点位置,向新子节点走

此外,由于我们要求最大异或值的下标,因此还需要一个额外的数组存一下下标:设2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_c++_46表示编号为2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_#define_47的节点对应的序列下标。由于我们要找的是尽可能大的"左端点",因此我们对于下标的存储策略是"新覆盖旧"。这里需要读者重点理解一下。

我们继续对查找区间左端点的过程进行分析:字典树上自根节点到叶节点的每条路径均表示一个二进制数,我们从根节点开始(也就是最高位开始),将待异或值对应位取出与之进行异或,加和后(此处由于是数位异或,因此是累加),继续递归向子节点移动。这个过程实际就是个DFS。

对于递归的终止条件我们需要注意:我们没有必要累加至叶节点,而只需要在遍历过程中进行判断:如果截至当前数位,先前数位相异或后累加的值已经大于等于2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_#define_31,再向下累加一定还是大于等于2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_#define_31,那么我们直接返回当前节点的编号即可。

此外,还要再对递归过程进行剪枝,如果截至当前数位,假设当前位到最低为全部为2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_c++_05,将截至当前的高位异或累加值加上到低位全为2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_c++_05仍不满足大于等于2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_#define_31,那么无论怎么向低位求异或累加也不可能再出现再高的情形,此时直接返回2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_打表_53表示不满足条件即可。

搜索的过程是比较简单的,只需要从当前节点开始,向左右两条边走,分别求出两个数当前位的异或值,累加至和中,向低位移动即可。

AC CODE

#include <bits/stdc++.h>
using namespace std;

const int NN = 31 * (1e5 + 10);
int trie[NN][2], ser[NN], tmp[31], tot = 0;

inline void insert(int x, int pos){
for(int i = 30; i>= 1; i--) tmp[i] = x & 1, x >>= 1;
int now = 0;
for(int i = 1; i <= 30; i++){
ser[now] = pos;
if(trie[now][tmp[i]]) now = trie[now][tmp[i]];
else{
trie[now][tmp[i]] = ++tot;
trie[tot][0] = trie[tot][1] = 0;
now = tot;
}
}
ser[now] = pos;
}

int query(int r, int k, int now, int sum, int bit){
if(sum >= k) return ser[now];
if(sum + (1 << (bit + 1)) <= k) return 0;
int ans = 0, temp = 0;
if(trie[now][0]){
temp = r & (1 << bit);
ans = max(ans, query(r, k, trie[now][0], sum + temp, bit - 1));
}
if(trie[now][1]){
temp = r & (1 << bit) ^ (1 << bit);
ans = max(ans, query(r, k, trie[now][1], sum + temp, bit - 1));
}
return ans;
}

signed main(){
ios_base::sync_with_stdio(false);
int t = 0; cin >> t;
while(t--){
int n, k; cin >> n >> k;
trie[0][0] = trie[0][1] = 0;
for (int i = 0; i <= tot; i ++) ser[i] = 0;
tot = 0;
//memset(trie, 0, sizeof(trie));
//memset(ser, 0, sizeof(ser));
//memset(fa, 0, sizeof(fa));
int ans = n + 1, l = -1, r = -1, sum_all = 0;
for(int i = 1; i <= n; i++){
int x; cin >> x;
sum_all ^= x;
if(sum_all >= k && ans > i) ans = i, l = 1, r = i;
int d = query(sum_all, k, 0, 0, 29);
if(d > 0 && ans > i - d) ans = i - d, l = d + 1, r = i;
insert(sum_all, i);
}
if(ans == n + 1) cout << -1 << endl;
else cout << l << ' ' << r << endl;
}
return 0;
}

H.Maximal submatrix

Problem Description

Given a matrix of n rows and m columns,find the largest area submatrix which is non decreasing on each column

Input

The first line contains an integer T(1≤T≤10)T(1≤T≤10)representing the number of test cases.
For each test case, the first line contains two integers n,m(1≤n,m≤2∗103)n,m(1≤n,m≤2∗103)representing the size of the matrix
the next n line followed. the i-th line contains mm integers vijvij(1≤vij≤5∗103)(1≤vij≤5∗103)representing the value of matrix
It is guaranteed that there are no more than 2 testcases with n∗m>10000

Output

For each test case, print a integer representing the Maximal submatrix

Solution

😀 算法标签:悬线法

听队友说是悬线的板子题…

首先分析一下思路:大小关系只针对纵向而言,那么想到转化为0-1矩阵,但后跑悬线法板子即可。这里放个参考标程的代码,自己写的不堪入目。。。

#include <bits/stdc++.h>
using namespace std;
const int N = 5e3;
int a[N][N], b[N][N];
int h[N];
int que[N];

signed main(){
int t; cin >> t;
while (t--){
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
cin >> a[i][j];
b[i][j] = 0;
if (i > 1) b[i][j] = (a[i][j] >= a[i - 1][j]);
}
}
for (int i = 1; i <= m; i++) h[i] = 0;
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++){
if (b[i][j] == 0) h[j] = 1;
else h[j]++;
}
int tot = 0;
h[m + 1] = 0;
for (int j = 1; j <= m + 1; j++){
while (tot && h[que[tot]] > h[j]){
ans = max(ans, (j - que[tot - 1] - 1) * h[que[tot]]);
tot--;
}
que[++tot] = j;
}
}
cout << ans << endl;
}
}

I.KD-Graph

Problem Description

Let’s call a weighted connected undirected graph of nn vertices and m edges KD-Graph, if the
following conditions fulfill:

* nn vertices are strictly divided into KK groups, each group contains at least one vertice

* if vertices pp and qq ( pp ≠ qq ) are in the same group, there must be at least one path between pp and qq meet the max value in this path is less than or equal to DD.

* if vertices pp and qq ( pp ≠ qq ) are in different groups, there can’t be any path between pp and qq meet the max value in this path is less than or equal to DD.

You are given a weighted connected undirected graph GG of nn vertices and mm edges and an integer KK.

Your task is find the minimum non-negative DD which can make there is a way to divide the nn vertices into KK groups makes GG satisfy the definition of KD-Graph.Or −1−1 if there is no such DD exist.

Input

The first line contains an integer TT (1≤ TT ≤5) representing the number of test cases.
For each test case , there are three integers n,m,k(2≤n≤100000,1≤m≤500000,1≤k≤n)n,m,k(2≤n≤100000,1≤m≤500000,1≤k≤n) in the first line.
Each of the next mm lines contains three integers u,vu,v and cc (1≤v,u≤n,v≠u,1≤c≤109)(1≤v,u≤n,v≠u,1≤c≤109) meaning that there is an edge between vertices uu and vv with weight cc.

Output

For each test case print a single integer in a new line.

Solution

😀 算法标签:并查集

给定边集,要求划分为2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_54组,求一个2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_55,使得同一组的顶点间的路径最大值小于等于2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_55

首先将给定的边集按照权值大小进行排序,初始化并查集为点集,每个点看作一个连通块,然后遍历边集合,使用并查集查询是否位于同一集合,不位于同一集合则合并点,连通块个数-1,直至下一次合并后连通块个数为2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_54​时,那么此时被遍历的边的权即为答案(按照大小遍历边集,保证所有边小于等于2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_55),否则输出2021“MINIEYE杯”中国大学生算法设计超级联赛(1)题解_算法_59

确实签到题。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 5e6 + 10;
struct edge{
int u, v, c;
const bool operator<(const edge &a){ return c < a.c; }
}edgeset[maxn];

int fa[maxn];

inline void init(int n){
for(int i = 0; i <= n + 10; i++) fa[i] = i;
}

inline int find(int x){
while(x != fa[x]) x = fa[x] = fa[fa[x]];
return x;
}

signed main(){
ios_base::sync_with_stdio(false);
int t = 0; cin >> t;
while(t--){
int n, m, k; cin >> n >> m >> k;
for(int i = 1; i <= m; i++) cin >> edgeset[i].u >> edgeset[i].v >> edgeset[i].c;
init(n);
sort(edgeset + 1, edgeset + 1 + m);
int ans = INT_MAX, flag = 1, cnt = n;
if(k == n) ans = 0;
for(int i = 1; i <= m; i++){
if(!ans) break;
int uu = edgeset[i].u, vv = edgeset[i].v, cc = edgeset[i].c;
int fu = find(uu), fv = find(vv);
if(fu == fv) continue;
else{
if(cnt - 1 == k) ans = cc;
else if(cnt - 1 < k){
if(cc <= ans) flag = 0;
break;
}
}
fa[fu] = fv;
cnt--;
}
if(!flag || ans == INT_MAX) cout << -1 << endl;
else cout << ans << endl;
}
return 0;
}