2021“MINIEYE杯”中国大学生算法设计超级联赛(1)部分题解

题目链接:点这里

1001.Mod, Or and Everything

  • 题意
    给你一个整数 n n n,求出 n m o d    1 ∣ n m o d    2 ∣ . . . ∣ n m o d    n n \mod 1 | n \mod 2 |...| n\mod n nmod1nmod2...nmodn的值。

  • 解题思路
    我们知道 a m o d    b a \mod b amodb的值 c c c可以表示为 a − k b a - kb akb,其中 k b ≤ a kb\leq a kba。所以我们不难发现 n m o d    i ≤ ( n − 1 ) / 2 n \mod i\leq (n - 1) / 2 nmodi(n1)/2,而当 i ≤ ( n − 1 ) / 2 i\leq (n-1)/2 i(n1)/2时, n m o d    ( n − i ) = i n \mod (n-i) = i nmod(ni)=i,则我们可以发现实际上它可以取到 [ 0 , m ] [0,m] [0,m]的所有整数,则答案会是 2 k − 1 2^k-1 2k1 k k k我们可以通过 ( n − 1 ) / 2 (n - 1)/2 (n1)/2来得到。
    当然,我们也可以打表找规律,按题意模拟求出答案的规律性,恰好是常见的数列模型。

  • AC代码

/**
  *@filename:1001Current
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-21 10:42
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 100000 + 5;
const int P = 1e9+7;

int t;
ll quick_pow(ll x,ll b){
    ll ans = 1;
    while(b){
        if(b & 1){
            ans = ans * x;
        }
        x *= x;
        b >>= 1;
    }
    return ans;
}
ll n;
int cal(ll m){
    int temp = -1;
    for(ll j = 50; j >= 0; -- j){
        if(m >> j){
            temp = j;
            break;
        }
    }
    return temp;
}
void solve(){
    if(n <= 2){
        cout << "0" << endl;
        return;
    }
    ll m = (n - 1) / 2;
    int k = cal(m);
    cout << quick_pow(2,k + 1) - 1 << endl;
}
int main(){
    cin >> t;
    while(t -- ){
        cin >> n;
        solve();
    }
    return 0;
}

1005.Minimum spanning tree

  • 题意
    给出 n − 1 n -1 n1个点,编号为 2 2 2~ n n n,两点之间都有一条边,其中边权值为 l c m ( a , b ) lcm(a,b) lcm(a,b)。求出最小生成树的权重。

  • 解题思路
    看到 n n n的范围 1 e 7 1e7 1e7,我们就应该想到了,此题肯定不是无脑建树。我们需要抓住 l c m ( a , b ) lcm(a,b) lcm(a,b)这个线索,我们试想,对于每个点,它加入正在构建的最小生成树顶点集合中都需要与其中的一点建立边,那么如果它有除 1 1 1和它本身的因子,我们直接与因子相连,这样保证了 l c m lcm lcm就是它本身;如果它不是这样的,即是质数,那么 l c m = a × b lcm = a\times b lcm=a×b按照贪心原则,此时构建的都必须和 2 2 2建立,故此题易解,将 2 2 2加入集合,对 3 3 3~ n n n依次处理放入集合中。需要提前处理好 1 e 7 1e7 1e7的质数,应用线性筛。

  • AC代码

/**
  *@filename:E
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-20 12:27
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 10000000 + 5;
const int P = 1e9+7;

int t,n;
bool notPrimer[N];
int primer[N];
void init(){
    int cnt = 0;
    for(int i = 2; i < N; ++ i){
        if(!notPrimer[i]){
            //将素数记录
            primer[cnt ++] = i;
        }
        for(int j = 0; j < cnt && primer[j] * i < N; ++ j){
            notPrimer[primer[j] * i] = true;
            if(i % primer[j] == 0)break;
        }
    }
}
void solve(){
    ll ans = 0;
    for(int i = 3; i <= n; ++ i){
        if(notPrimer[i]){
            ans += i;
        }
        else{
            ans += 2 * i;
        }
    }
    printf("%lld\n", ans);
}
int main(){
    scanf("%d", &t);
    init();
    while(t -- ){
        scanf("%d", &n);
        solve();
    }
    return 0;
}

1006.Xor sum

  • 题意
    给你一个序列 a a a,找出最短的连续子序列使得它们的异或和不小于 k k k

  • 解题思路
    我们知道,假设我们要求 [ l , r ] [l,r] [l,r]的异或和,根据异或的性质,即是求 [ 1 , r ] [1,r] [1,r]^ [ 1 , l ] [1,l] [1,l]所以我们可以对整个数列作异或前缀和,那么求一段区间的异或和我们可以通过对两个数异或得到。 根据题意,我们可以枚举右端点,然后对于前面的 k − 1 k - 1 k1个数进行查询选择,为了提高效率,我们可以利用 t r i e trie trie树来维护,将异或值用二进制形式表现出来,那么我们还需要保存节点的信息,我们可以保存靠右的位置,这样实现我们的区间最短。这样我们就可以通过 k k k来构建我们所需要的异或值从而选择了。具体看 A C AC AC代码。

  • AC代码

/**
  *@filename:1006
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-21 09:57
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 3000000 + 5;
const int P = 1e9+7;

int t,n,k,tot,trie[N][2],a[N],cnt[N],l,r;
int query(int i){
	int res = -1, rt = 1;
	for(int j = 30; j >= 0; -- j){
		int u = (a[i] >> j) & 1;//获取a[i]的第j位上的值。
		int v = (k >> j) & 1;//获取k上的第j位上的值。
		if(!v){
			//如果为0,我们这个时候只要异或形成1就保证大于k了。
			if(trie[rt][u ^ 1]){
				res = max(res, cnt[trie[rt][u ^ 1]]);//获取其位置。
			}
			rt = trie[rt][u];//当然,我们也可以选择和u相同的,此时继续往下。
		}
		else{
			//如果为1,我们要保证不小于k,这个时候必须要为1.
			rt = trie[rt][u ^ 1];
		}
		if(!rt)break;//说明不存在子树了。
	}
	if(rt){
		res = max(res, cnt[rt]);
	}
	return res;
}
void insert(int i){
	int rt = 1;
	for(int j = 30; j >= 0; -- j){
		int u = (a[i] >> j) & 1;
		if(!trie[rt][u]){
			//如果该节点不存在,就创建。
			trie[rt][u] = ++ tot;
			cnt[tot] = -1;
			trie[tot][0] = trie[tot][1] = 0;
		}
		rt = trie[rt][u];
		//更新左端的节点。
		cnt[rt] = max(cnt[rt],i);
	}
}
void solve(){
	//这里默认1为根节点。即管理整个字典树的节点。
	tot = 1, l = -1, r = n;
	cnt[1] = -1;//最初的时候左端点默认都为-1.
	trie[1][0] = trie[1][1] = 0;
	for(int i = 1; i <= n; ++ i){
		int res = query(i);
		//判断获取的最大节点是否有效。
		if(res >= 0 && i - res < r - l){
			r = i, l = res;
		}
		insert(i);
	}
	//注意这里的l需要+1.因为取到的区间是(l + 1,r);
	//这里因为区间异或和决定的。
	if(l >= 0)printf("%d %d\n", l + 1, r);
	else printf("-1\n");
}
int main(){
	scanf("%d", &t);
	while(t -- ){
		scanf("%d%d", &n, &k);
		for(int i = 1; i <= n; ++ i){
			scanf("%d", &a[i]);
			a[i] ^= a[i - 1];//求异或前缀和
		}
		solve();
	}
	return 0;
}

1008.Maximal submatrix

  • 题意
    给定一个 n n n m m m列的矩阵,求每列不递减的最大面积子矩阵。

  • 解题思路
    按照题意,我们可以先处理得到以 ( i , j ) (i,j) (i,j)位置延申往上得到的非递减的最大长度。那么对于每一行,其上的每一列都有一个高度,这个问题我们实际上就转换成了求直方图的中的最大矩形面积。 具体可以看这一题:点这里,这种就相当于是模板题了,我们暴力做法肯定是不行的,我们可以用单调栈来维护,即维护一个单调递增的栈,而当不符合单调栈的时候,说明对于栈顶是出现了缺口,不能形成以栈顶为高的连续矩形,这个时候我们就要尽快计算栈顶形成的最大面积。这样我们不断维护即可得出。

  • AC代码

/**
  *@filename:H
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-20 13:02
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 2e3 + 5;
const int P = 1e9+7;

int t,n,m;
int g[N][N],h[N][N],q[N],w[N];
ll cal(int n){
    //第n行。
    int top = 0;
    ll ans = 0;
    h[n][m + 1] = 0;
    for(int j = 1; j <= m + 1; ++ j){
        if(h[n][j] > q[top]){
            q[++ top] = h[n][j],w[top] = 1;
        }
        else{
            int length = 0;
            while(q[top] > h[n][j]){
                length += w[top];
                ans = max(ans, 1LL * length * q[top]);
                top --;
            }
            q[++ top] = h[n][j];
            w[top] = length + 1;
        }
    }
    return ans;
}
void solve(){
    ll ans = 0;
    for(int i = 1; i <= n; ++ i){
        ans = max(ans, cal(i));
    }
    printf("%lld\n", ans);
}
int main(){
    scanf("%d", &t);
    while(t -- ){
        memset(h,0,sizeof(h));
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++ i){
            for(int j = 1; j <= m; ++ j){
                scanf("%d", &g[i][j]);
                //记录其延伸到第i行的高度。
                if(g[i][j] >= g[i - 1][j]){
                    h[i][j] = h[i - 1][j] + 1;
                }
                else{
                    h[i][j] = 1;
                }
            }
        }
        solve();
    }
    return 0;
}

1009.KD-Graph

  • 题意
    给出 K D − G r a p h KD-Graph KDGraph的定义:一个 n n n个顶点 m m m条边的有权无向图,其中 n n n个顶点被严格分成了 k k k组,其中在同一组的任意两点之间的最大路径小于等于 D D D,不在同一组的任意两点之间的最大路径大于 D D D。现给出这个无向图,需要你判断是否有这样的 D D D使得满足 K D − G r a p h KD-Graph KDGraph

  • 解题思路
    这道题其实想到了应该就很简单,由于在同一组的任意两点之间的最大路径要小于等于 D D D,所以我们可以按边权值从小到大排序,这样我们每次可以取出相同权值的边,将这些边的端点两两合并形成一个组,这样当某一个阶段全部的边合并完后组数量为 k k k,则当前边权值则为正确答案,否则不能形成。

  • AC代码

/**
  *@filename:1009
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-21 11:06
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 500000 + 5;
const int P = 1e9+7;

struct node{
    int u,v,w;
    bool operator<(const node &A){
        return w < A.w;
    }
}edges[N];
int t,n,m,k,u,v,w;
int father[N];
int find(int x){
    int r = x;
    while(r != father[r]){
        r = father[r];
    }
    int i = x,j;
    while(father[i] != r){
        j = father[i];
        father[i] = r;
        i = j;
    }
    return r;
}
void solve(){
    sort(edges + 1, edges + 1 + m);
    for(int i = 1; i <= n; ++ i){
        father[i] = i;
    }
    int cnt = n, ans = 0;
    for(int i = 1; i <= m; ++ i){
        if(edges[i].w != edges[i - 1].w && cnt == k){
            printf("%d\n",ans);
            return;
        }
        int u = find(edges[i].u), v = find(edges[i].v);
        if(u == v)continue;
        father[u] = v;
        cnt --;
        ans = edges[i].w;
    }
    printf("%d\n", cnt == k ? ans : -1);
}
int main(){
    scanf("%d", &t);
    while(t -- ){
        scanf("%d%d%d", &n, &m, &k);
        for(int i = 1; i <= m; ++ i){
            scanf("%d%d%d", &edges[i].u, &edges[i].v, &edges[i].w);
        }
        solve();
    }
    return 0;
}
posted @   unique_pursuit  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示