强化学习概述

牛客刷题-Java专项练习-难点整理

  返回  

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

2021/7/21 17:34:36 浏览:

文章目录

    • HDU6950 Mod, Or and Everything
    • HDU6954 Minimum spanning tree
    • HDU6958 KD-Graph
    • HDU6957 Maximal submatrix
    • HDU6955 Xor sum

中超联赛

CSL中超可没巨佬们训练强度大

“什么是BSGS、莫队、KD tree”

我看我是完全不懂噢

HDU6950 Mod, Or and Everything

题意

( n   m o d   1 ) ∣ ( n   m o d   2 ) ∣ . . . ∣ ( n   m o d   ( n − 1 ) ) (n\ mod\ 1)|(n\ mod \ 2)|...|(n\ mod\ (n-1)) (n mod 1)(n mod 2)...(n mod (n1))的值

分析

打比赛时,

队友:经过化简可以得到答案为 2 k − 1 2^k-1 2k1

我:观察样例可以得到答案为 2 k − 1 2^k-1 2k1

QAQ

而实际上std的思路: n   m o d   i ≤ ⌈ n 2 ⌉ − 1 n\ mod\ i \leq \lceil \frac{n}{2}\rceil-1 n mod i2n1,所以 n   m o d   ( n − i ) = i n\ mod\ (n-i)=i n mod (ni)=i

所以这个和值就是 0 0 0 ⌈ n 2 ⌉ − 1 \lceil \frac{n}{2}\rceil-1 2n1的所有整数的和. 求出来是 2 k − 1 2^k-1 2k1,这里的 k k k是某个带 n n n的式子,懒得认真推的话可以根据样例猜出,是令 n = 2 x n=2^x n=2x,这个 x x x向下取整再减1得到 k k k. 但如果 x x x本来就是整数,那么 x x x还要再减去1.(总之看代码吧,或者去看std代码)

代码

#include <bits/stdc++.h>
#define fors(i, a, b) for(int i = (a); i <= (b); ++i)
#define lson k<<1
#define rson k<<1|1
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mem(a) memset(a, 0, sizeof(a))
#define DDLC_ESCAPE_PLAN_FAILED ios::sync_with_stdio(false), cin.tie(0)
#define int long long
const int inf = 0x3f3f3f3f;
const double dinf = 1e100;
typedef long long ll;
//const ll linf = 9223372036854775807LL;
// const ll linf = 1e18;
using namespace std;

signed main()
{
    DDLC_ESCAPE_PLAN_FAILED;
    int t;
    cin >> t;
    while(t--)
    {
        int n;
        cin >> n;
        int x = 0, res = n;
        while(res){
            res >>= 1LL;
            x++;
        }
        if(n == 1LL){
            cout << 0 << endl;
            continue; 
        }
        if(n == (1LL << (x - 1LL))) x--;
        cout << (1LL << (x - 1LL)) - 1LL << endl;
    }
    return 0;
}

HDU6954 Minimum spanning tree

题意

有一个 n − 1 n-1 n1个节点的带权完全图,节点标记为 2 , 3 , . . . , n 2,3,...,n 2,3,...,n. 完全图中从点 i i i到点 j j j的边的权值大小为 l c m ( i , j ) lcm(i,j) lcm(i,j),请你求出这个图的最小生成树。 n ≤ 10000000 n\leq 10000000 n10000000.

分析

容易知道,对任何一个边 ( i , j ) (i,j) (i,j),这个边的权值不会小于 m a x ( i , j ) max(i,j) max(i,j).

那么对于一个合数,它连向自己的因子即可,这时候边的权值就是这个合数。由于因子一定比自己小,所以在 2 , 3 , . . i 2,3,..i 2,3,..i中一定存在。

而对于一个质数 x x x,和任意一点 i i i相连,都有 l c m ( x , i ) = x ⋅ i lcm(x,i)=x·i lcm(x,i)=xi. 故令 i i i最小,取2即可保证边权最小。

分析完毕。找出质数,然后分类处理即可。线性筛处理之

其他

首先看到题:哇, 1 e 8 1e8 1e8的范围,这怎么做。这算法都给限制到 O ( n ) O(n) O(n)了,难不成是有什么公式?

思考一会之后:(其实已经得到正解)用线性筛试一波,自己调试1.5秒,完蛋

尝试各种卡常之后:优化到1.1秒了,实在顶不住了,要不直接交?

交上去之后:卧槽,MLE,那我不求 M S T MST MST前缀和了吧,直接算,但是这样更容易超时了,纠结

再交:居然700多ms过了,杭电评测机强啊

打完比赛后,才发现,原来数据规模看错了,是 1 e 7 1e7 1e7,根本不需要卡()

代码

#include <bits/stdc++.h>
#define fors(i, a, b) for(int i = (a); i <= (b); ++i)
#define lson k<<1
#define rson k<<1|1
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mem(a) memset(a, 0, sizeof(a))
#define DDLC_ESCAPE_PLAN_FAILED ios::sync_with_stdio(false), cin.tie(0)
#define int long long
const int inf = 0x3f3f3f3f;
const double dinf = 1e100;
typedef long long ll;
//const ll linf = 9223372036854775807LL;
// const ll linf = 1e18;
using namespace std;
const int maxn = 1e7 + 10; // 比赛的时候看成1e8,导致多了一次MLE
bool p[maxn];
int pr[maxn];
int pre[maxn];
void Prime(){
    int cnt = 0;
    mem(p);
    p[0] = p[1] = 1;
    for(int i = 2; i < maxn; ++i){
        if(!p[i]) pr[cnt++] = i;
        for(int j = 0; j < cnt && pr[j] * i < maxn; ++j){
            p[pr[j] * i] = 1;
            if(i % pr[j] == 0) break;
        }
    }
    pre[0] = pre[1] = pre[2] = 0;
    for(int i = 3; i < maxn; ++i){
        if(!p[i]) pre[i] = pre[i - 1] + 2 * i;
        else pre[i] = pre[i - 1] + i;
    }
}
signed main()
{
    DDLC_ESCAPE_PLAN_FAILED;
    int t;
    cin >> t;
    Prime();
    while(t--)
    {
        int n;
        cin >> n;
        cout << pre[n] << endl;
    }
    return 0;
}

HDU6958 KD-Graph

看到过题数不多以及题面比较奇怪就没做了,以后一定不能盲目看榜,以及耐心读懂题

题意

有一个 n n n m m m边的图( n ≤ 100000 n\leq100000 n100000, m ≤ 500000 m\leq500000 m500000),你需要将其分割成恰好 K K K组,满足:

  1. 每组中任取两个点 p , q p,q p,q,他们之间的路径中,最长边不大于 D D D
  2. 不同组中任取两个点 p , q p,q p,q,他们之间的路径中,所有边都大于 D D D

现在给定图和 K K K,问你是否找得到一个满足条件的 D D D,如果找得到,输出最小的 D D D. 否则输出 − 1 -1 1.

分析

容易知道,相同权值的边要么不在任何组内部,要么全都在同一组点里面。假如相同权值的边,有的在组内,有的在组外的话,这个 D D D值肯定只会更大不会更小。

那么贪心思路显而易见了,将所有边按权值升序排序,使用并查集,从左往右,将所有权值相同的边的点合并。然后统计并查集数量,更新答案。也就是在保证“相同权值的边要么不在任何组内部,要么全都在同一组点里面”这个性质的前提下寻找能否分为 K K K组。而要使 D D D尽可能小,应让处于组内的边尽可能小,所以使用升序。

代码

#include <bits/stdc++.h>
#define fors(i, a, b) for(int i = (a); i <= (b); ++i)
#define lson k<<1
#define rson k<<1|1
#define pb push_back
#define pii pair<int, int>
#define lowbit(x) ((x)&(-(x)))
#define mem(a) memset(a, 0, sizeof(a))
#define DDLC_ESCAPE_PLAN_FAILED ios::sync_with_stdio(false), cin.tie(0)
// #define int long long
const int inf = 0x3f3f3f3f;
const double dinf = 1e100;
typedef long long ll;
//const ll linf = 9223372036854775807LL;
// const ll linf = 1e18;
using namespace std;
const int maxn= 100000 + 5;
int fa[maxn];
struct edge{
    int l, r, val;
    bool operator < (const edge& b)const{
        return val < b.val;
    }
};
vector<edge> e;
inline int find(int x)
{
    if(fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}
bool join(int x, int y)
{
    int ans = 0;
    int fx = find(x), fy = find(y);
    if(fx == fy) return 0;
    fa[fy] = fx;
    return 1;
}
signed main()
{
    DDLC_ESCAPE_PLAN_FAILED;
    int t;
    cin >> t;
    while(t--)
    {
        e.clear();
        int n, m, k;
        cin >> n >> m >> k;
        fors(i, 1, n) fa[i] = i;
        int u, v, c;
        fors(i, 1, m){
            cin >> u >> v >> c;
            e.pb({u, v, c});
        }
        sort(e.begin(), e.end());
        int now = n; // 并查集数量,初始为n
        bool flag = 0;
        int ans = 0;
        for(int i = 0; i < m; ++i){
            if(i == 0 || e[i].val != e[i - 1].val){
                if(now == k) break;
            }
            if(!join(e[i].l, e[i].r)) continue;
            now--;
            ans = e[i].val;
        }
        cout << (now == k ? ans : -1) << endl;
    }
    return 0;
}

HDU6957 Maximal submatrix

题意

给出一个矩阵,要你找一个最大的子矩阵,要求这个子矩阵每一列从上往下都是单调不下降序列。输出最大子矩阵的面积

分析

悬线法,将每个不比上面的元素小的元素标记,然后用这些标记过的元素组成子矩阵。接下来使用悬线dp即可。需要注意的是,子矩阵的最上面一行是可以不被标记的。

代码

(比赛时自己凹的二维dp一直超时,应该是复杂度超过 O ( n 2 ) O(n^2) O(n2)了)

第一次接触到悬线法,代码可能相对繁琐

#include <bits/stdc++.h>
#define fors(i, a, b) for(int i = (a); i <= (b); ++i)
#define lson k<<1
#define rson k<<1|1
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mem(a) memset(a, 0, sizeof(a))
#define DDLC_ESCAPE_PLAN_FAILED ios::sync_with_stdio(false), cin.tie(0)
// #define int long long
const int inf = 0x3f3f3f3f;
const double dinf = 1e100;
typedef long long ll;
//const ll linf = 9223372036854775807LL;
// const ll linf = 1e18;
using namespace std;
const int maxn = 2005;
bool v[maxn][maxn];
int l[maxn][maxn];
int r[maxn][maxn];
int u[maxn][maxn];
signed main()
{
	DDLC_ESCAPE_PLAN_FAILED;
	int t;
	cin >> t;
	while(t--)
	{
		mem(v), mem(l), mem(r), mem(u);
		int n, m;
		cin >> n >> m;
		fors(i, 1, n){
			fors(j, 1, m){
				cin >> u[i][j];
				l[i][j] = j;
				r[i][j] = j;
			}
		}
		fors(i, 1, n){
			fors(j, 1, m){
				if(i == 1 || u[i][j] >= u[i - 1][j]){
					v[i][j] = 1;
				}
			}
		}
		fors(i, 1, n){
			fors(j, 2, m){
				if(v[i][j] && v[i][j - 1]) l[i][j] = l[i][j - 1];
			}
			for(int j = m - 1; j >= 1; --j){
				if(v[i][j] && v[i][j + 1]) r[i][j] = r[i][j + 1];
			}
		}
		mem(u);
		fors(i, 1, n){
			fors(j, 1, m) u[i][j] = 1;
		}
		int ans = 0;
		fors(i, 1, n){
			fors(j, 1, m){
				if(v[i][j] && v[i - 1][j]){
					l[i][j] = max(l[i - 1][j], l[i][j]);
				}
				if(v[i][j]) u[i][j] = u[i - 1][j] + 1;
			}
			for(int j = m; j >= 1; --j){
				if(v[i][j] && v[i - 1][j]){
					r[i][j] = min(r[i - 1][j], r[i][j]);
				}
			}
		}
		fors(i, 1, n){
			fors(j, 1, m){
				ans = max(ans, u[i][j] * (r[i][j] - l[i][j] + 1));
			}
		}
		cout << ans << endl;
	}
	return 0;
}

HDU6955 Xor sum

题意

给一个整数数组,你需要找到最短的区间,其按位异或和不小于 k k k. 如果有多个,找出左端点最左的

分析

先转化一下。异或运算里,任意 x x x的逆元是 x x x本身,故对于前缀和 p r e [ i ] pre[i] pre[i], p r e [ j ] pre[j] pre[j] i i i j j j的异或和可以表示为 p r e [ i ] pre[i] pre[i]^ p r e [ j ] pre[j] pre[j].

这之后,直接存异或和,问题转化为:找到两个相距最近的 i , j i,j i,j,其异或和不小于 k k k.

枚举右端点,然后可能的左端点用 T r i e Trie Trie树维护,从左到右找,每次找到一个可能的编号就覆盖一下,这样能保证最后找到的是最右边的。不过也可以直接保存最右边的节点。

以后千万不要看着std调试代码,调着调着就调得跟std一模一样了

不过说白了也是之前从来没用过trie吧orz,根本不熟

代码

/**
  * @file    :vsDebug.cpp
  * @brief   :
  * @date    :2021-07-21
  * @Motto   :Love Sakurai Yamauchi Forever
  */
#include <bits/stdc++.h>
#define fors(i, a, b) for(int i = (a); i <= (b); ++i)
#define lson k<<1
#define rson k<<1|1
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mem(a) memset(a, 0, sizeof(a))
#define DDLC_ESCAPE_PLAN_FAILED ios::sync_with_stdio(false), cin.tie(0)
// #define int long long
const int inf = 0x3f3f3f3f;
const double dinf = 1e100;
typedef long long ll;
//const ll linf = 9223372036854775807LL;
// const ll linf = 1e18;
using namespace std;
const int maxn = 1e5 + 10;
const int mnx = (1LL << 24) + 10;
int p[mnx][2], res[mnx], a[maxn];
signed main()
{
	DDLC_ESCAPE_PLAN_FAILED;
	int t;
	cin >> t;
	while(t--)
	{
		int n, k;
		cin >> n >> k;
		fors(i, 1, n){
			cin >> a[i];
			a[i] ^= a[i - 1]; // 只存前缀和
		}
		int l = -1, r = n, ans = 1;
		res[1] = -1;
		p[1][0] = p[1][1] = 0; // init
		fors(i, 0, n){
			int x = 1;
			int tmp = -1;
			for(int j = 29; j >= 0; --j){
				if(!x) break;
				int u = (a[i] >> j) & 1; // 从上往下第j位
				if(!((k >> j) & 1)){
					if(p[x][u ^ 1]){
						tmp = max(tmp, res[p[x][u ^ 1]]);
					}
					x = p[x][u];
				}
				else x = p[x][u ^ 1];
			}
			if(x) tmp = max(tmp, res[x]);
			if(tmp >= 0 && i - tmp < r - l) l = tmp, r = i;
			x = 1;
			for(int j = 29; j >= 0; --j){
				int u = (a[i] >> j) & 1;
				if(!p[x][u]){
					ans++;
					p[x][u] = ans, res[ans] = -1;
					p[ans][0] = p[ans][1] = 0;
				}
				x = p[x][u];
				res[x] = max(res[x], i);
			}
		}
		if(l >= 0) cout << l + 1 << ' ' << r << endl;
		else cout << -1 << endl;
	}
	return 0;
}

联系我们

如果您对我们的服务有兴趣,请及时和我们联系!

服务热线:18288888888
座机:18288888888
传真:
邮箱:888888@qq.com
地址:郑州市文化路红专路93号