2021杭电多校(MINIEYE) 第一场 补题

1001 Mod, Or and Everything

问题描述

你需要给出一个整数n,需要计算(n mod 1) | (n mod 2) |… | (n mod (n - 1)) | (n mod n).

其中“|”代表位运算或

输入

第一行包含整数T(1≤T≤5000)表示测试案例的数量。
对于每个测试案例,都有一个
整数n(1≤n≤ 1012 )在一行。

输出

对于每个测试案例,将答案打印在一行中。

示例输入

5
1
2
3
4
5

样本输出

0
0
1
1
3

解题思路

签到题。公式不太好打还是直接贴图吧······

在这里插入图片描述

代码

#include<bits/stdc++.h>
#define ll long long
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int _; for (cin >> _; _--;) {
        ll n; cin >> n;
        if (n == 1)cout << "0\n";
        else {
            int k = log(n) / log(2);
            ll sum = pow(2, k);
            if (sum != n)cout << sum - 1 << "\n";
            else cout << sum / 2 - 1 << "\n";
        }
    }
}

1005 Minimun spanning tree(最小生成数)

问题描述

给定 n-1 个点,从 2 到 n 数,A 和 b 之间的边权为 lcm(a, b)。请找到由他们形成的最小跨度树。
最小跨度树是连接的边加权非定向图边缘的子集,该图将所有顶点连接在一起,没有任何周期,并且具有尽可能小的总边缘重量。这就是说,它是一棵跨越的树,其边缘重量的总和尽可能小。

输入

第一行包含一个整数 t (t<=100),表示输入中的测试案例数。然后t测试案例如下。
每个测试案例的唯一行包含上述一个整数 n (2<\n<=100000000)。

输出

对于每个测试案例,将一个整数打印成一行,这是树缘重量的总和。

示例输入

2
2
6

样本输出

0
26

解题思路

考虑编号为 i, j 的两个点,根据 lcm 的性质,lcm(i, j) = i * j / gcd(i, j)

对于编号为3~n的点,将所有编号为合数的点向其约数连边,编号为质数的点向2连边,不难证明这样形成的生成树是最小的那么。

那么,对于质数 i ,它对边权和的贡献仅为 i*2,对于合数 i ,它对边权和的贡献仅为 i。所以,计算最小的边权和,需要计算 2~n 内的质数和 S1 以及合数和 S2,得出最终答案为 S1 * 2 + S2 。用线性筛,求个前缀和,我们就能够在 O(n) 的时间复杂度下解决该问题。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e7 + 7;
int pri[N], cnt;
ll ans[N];
bool ispri[N];
void prim()//线性筛法
{
    for (int i = 2; i < N; i++)
        ispri[i] = 1;
    for (int i = 2; i < N; i++) 
    {
        if (ispri[i])
            pri[++cnt] = i;//先全部标记为素数
        for (int j = 1; j <= cnt; j++)
        {
            if (i * pri[j] >= N)
                break;
            ispri[i * pri[j]] = 0;//标记为合数
            if (i % pri[j] == 0)
                break;
        }
    }
}
int main()
{
    int t,n[100];
    scanf("%d",&t);
    for(int i=0;i<t;i++)
    {
        scanf("%d",&n[i]);
    }
    prim();
    for(int i=0;i<t;i++)
    {
        for (int j = 3; j < n[i]; j++) // 类比前缀和求解答案数组
        {
            if (ispri[j])
                ans[j] = ans[j - 1] + j * 2;
            else
                ans[j] = ans[j - 1] + j;
        }
        printf("%lld",ans[i])
    }
    return 0;
}

补充: 线性筛法

若 x 是素数,它的倍数是合数;而若 x 是合数,那么它的倍数也一定是合数;

因此,如果我们从小到大考虑每个数,然后把当前这个数的所有(比自己大的)倍数记为合数,那么运行时没有被标记的数就是素数了

实际运行中会有合数被标记了多次,如果我们保证每个数只会被标记一次,即每个数我们仅在枚举到它的最小素因子时进行标记,那么我们就可以显著地优化筛法的时间复杂度。

1006 Xor sum (Good ,字典树)

问题描述

给定长度 n 的整数序列,找到最短的连续子序列使得异或和不低于 k。

如果存在多个满足条件的序列,请输出左端点最小的连续子序列的左端点和右端点,

如果不存在连续子序列的异或和 不小于 k 则输出 -1

输入

第一行包含一个整数 t (t<=100),表示输入中的测试案例数。然后t测试案例如下。
每个测试案例的第一行包含两个整数 n(1<=n<=100000) 和 k (0<=k<2^30),表示序列的
长度。
每个测试的第二行包含 n 整数 ai (0<=ai<2^30),按顺序表示整
数。
测试 异或和n >1000 的数量不超过 5。

输出

对于每个测试案例,将两个整数打印在一行中,表示连续子句数的左端点和右端点。
如果没有连续的子序异或和不小于 k,在一行中打印"-1"。

示例输入

2
3 2
1 2 2
9 7
3 1 3 2 4 0 3 5 1

样本输出

2 2
5 7

解题思路

讲题的时候说的要点是

·(a1^a2^...^al-1)^(a1^a2^a3^...ar)=al^al+1^...^ar

·做前缀和转化成 a1,a2,...,an中两两异或大于k 中寻找距离最近的

·考虑到可能直接从1~n要多添加一项a0=0

还没肝到字典树的小白有点晕 (¦3」∠)

先存个档,具体思路可以参考一下这位大神,小白讲不清楚了…2021杭电多校第一场 (1) 个人补题记录 - RioTian (cnblogs.com)

代码

参考上面的

1008 Maxial Submatrix

问题描述

给出n行和m列的矩阵,找到每个列上未减少的最大区域子矩阵

输入

第一行包含整数T(1≤T≤10)表示测试案例的数量。
对于每个测试案例,第一行包含两个整数n,m(1≤n,m≤2∗ 103)表示下一行所遵循
的下一行矩阵的大小。i-th行包含m整数vij(1≤vij≤5∗103)表示矩阵
的价值保证不超过2个试剂箱n∗米>10000

输出

对于每个测试案例,打印一个代表最大子矩阵的整数

示例输入

1
2 3
1 2 4
2 3 3

样本输出

4

解题思路

首先预处理出自该位置向上、满足性质的数的个数,记为 h[i][j]
对于每行 (h[i]) 维护一个单调递增栈,所有元素进栈和出栈一次,每个元素出栈时更新最大的矩形面积。
为了能够更新最大的矩形面积,我们需要在栈内记录两个数据——元素的高度 h[i][j] 和它到栈中上一个元素的宽度差。
元素出栈时,因为高度逐渐递减,故记width 为当前已经弹出的元素的宽度和

代码

const int N = 2010;
int c[N][N];
int n, m, h[N][N];

LL work(int h[N], int n) {
    int l[N], r[N], q[N];
    h[0] = h[n + 1] = -1;
    int tt = 0;
    q[0] = 0;
    for (int i = 1; i <= n; i++) {
        while (h[i] <= h[q[tt]]) tt--;
        l[i] = q[tt];
        q[++tt] = i;
    }
    tt = 0;
    q[0] = n + 1;
    for (int i = n; i >= 1; i--) {
        while (h[i] <= h[q[tt]]) tt--;
        r[i] = q[tt];
        q[++tt] = i;
    }
    LL ans = 0;
    for (int i = 1; i <= n; i++) {
        ans = max(ans, (LL)h[i] * (r[i] - l[i] - 1));
    }
    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--) {
        memset(c, 0, sizeof(c));
        memset(h, 0, sizeof(h));
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                cin >> c[i][j];
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                if (c[i][j] >= c[i - 1][j]) h[i][j] = h[i - 1][j] + 1;
                else h[i][j] = 1;
        LL ans = 0;
        for (int i = 1; i <= n; i++) {
            ans = max(ans, work(h[i], m));
        }
        cout << ans << endl;
    }
    return 0;
}

补充:单调栈

什么是单调栈?
就是维护一个栈内元素自底向上都保持单调(递增/递减)的栈。
怎么维护呢?

这个要分两种情况,以自底向上单调递减的单调栈为例:
如果待入栈元素比栈顶元素小,则直接入栈
如果待入栈元素比栈顶元素大,则将栈中所有比其小的元素全部弹出,然后使待入栈元素入栈

常见应用:求柱状图中的最大面积/离线RMQ问题/优化DP

[1009 KD-Graph](Problem - 1009 (hdu.edu.cn))

问题描述

让我们调用加权连接的非定向图形n如果满足
以下条件,则边缘和m边缘KD-图形:

* n眩晕被严格分为K组,每个组包含至少一个顶点

*如果顶点p和问 ( p≠问)在同一组,必须至少有一个路径之间p和问在此路径中满足最大值小于或等于D.

p和问 ( p≠问)在不同的组,不能有任何路径之间p和问在此路径中满足最大值小于或等于D.
您将获得一个加权连接的非定
向图形G之n紫杉和m边缘和整数K.
您的任务是找到最小非
负数D这可以使有一种方法来划分n眩晕成K组使G满足KD图的定义。−1如果没有这样的D存在。

输入

第一行包含整数T(1≤T≤5)代表测试案例的数量。
对于每个测试案例,有三个整数n,m,k(2≤n≤100000,1≤米≤500000,1≤k≤n )在第一行。
下一个m行包含三个整数u,v和c (1≤v,u≤n,v≠u,1≤c≤109)这意味着在紫杉之间有一个边缘美国和v重量c.

输出

对于每个测试案例,在新行中打印一个整数。

示例输入

2
3 2 2
1 2 3
2 3 5
3 2 2
1 2 3
2 3 3

样本输出

3
-1

解题思路

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=1e6+10;
int T,n,m,k,fa[maxn],now,ans;
struct da{int u,v,w;}q[maxn];
bool cmp(da aa,da bb){return aa.w<bb.w;}
int getf(int x)
{
	if (fa[x]==x) return x;
	fa[x]=getf(fa[x]);
return fa[x];
}
void solve()
{
	scanf("%d%d%d",&n,&m,&k); now=n; ans=0;
	for (int i=1;i<=n;i++) fa[i]=i;
	for (int i=1;i<=m;i++) scanf("%d%d%d",&q[i].u,&q[i].v,&q[i].w);
	sort(q+1,q+m+1,cmp);
	for (int i=1;i<=m;i++)
	{
		if (q[i].w!=q[i-1].w){if (now==k) {printf("%d\n",ans); return;}}
		if (getf(q[i].u)==getf(q[i].v)) continue;
		now--; fa[getf(q[i].u)]=getf(q[i].v); ans=q[i].w;
	}
	printf("%d\n",now==k?ans:-1);
}
int main()
{
	scanf("%d",&T);
	while (T--) solve();
return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值