杭电多校总结2021-08-20

1001

** Problem Description**
You are given an integer n.
You are required to calculate (n mod 1) or (n mod 2) or … or (n mod (n - 1)) or (n mod n).
The “or” operation means “bitwise OR”.

Input
The first line contains an integer T(1≤T≤5000)representing the
number of test cases.

For each test case, there is an integer n(1≤n≤1012)in one line.

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

当n为偶数时,设m=n/2-1
当n为奇数时,设m=(n-1)/2
可以发现,n mod i<=m,且当i<=m时,有 n mod (n-i)=i。于是可以得出n mod i取到0~m的所有整数,因此答案会是2^k-1,k的具体值判断一下即可。
换句话说,就是其存在log(n)-1的规律。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL T,n;
int main()
{
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&n);
		if((n&(-n))==n){
			printf("%lld\n",max(0ll,n/2-1));
			continue;
		}
		while((n&(-n))!=n)
			n-=(n&(-n));
		printf("%lld\n",n-1);
	}
}

1005

Problem Description
Given n-1 points, numbered from 2 to n, the edge weight between the
two points a and b is lcm(a, b). 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.

lcm(a, b) is the smallest positive integer that is divisible by both a
and b.

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 only line of each test case contains one integers n
(2<=n<=10000000) as mentioned above.

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

对于编号为3~n的点,将所有编号为合数的点向其约数连边,编号为质数的点向2连边,不难证明这样形成的生成树是最小的。
总的边权和为(质数的和*2+合数的和),用欧拉筛预处理前缀和即可。
效率:O(n)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int inf=1e9;
const int N=1e6+10;
const int M=1e7+20;
const int mo=998244353;
bool v[M];
int tot,p[N],i;
ll a[N];
int n;
int main(){
	int t;
	for(int i=2;i<M;i++)
	{
		if(!v[i]){
			p[++tot]=i;
		}
		for(int j=1;j<=tot;j++)
		{
			if(i*p[j]>=M)
				break;
			v[i*p[j]]=1;
			if(!(i%p[j]))
				break;
		}
	}
	for(int i=2;i<=tot;i++){
		a[i]=p[i]+a[i-1];
	}
	cin>>t;
	while(t--){
		cin>>n;
		printf("%lld\n",ll(3+n)*(n-2)/2+a[upper_bound(p+1,p+tot+1,n)-p-1]);
	}	
}

1006

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.

首先对a求前缀异或,得到新的数组pre,将问题转变成找出 pre i 和 pre j (j > i) 使得 ( pre i 异 或 pre j ) = = k ,也可以是pre i ≥ k 。
枚举 a_R向前搜,看是否满足条件,如果暴力搜肯定会炸,需要维护一个字典树来实现先前搜的复杂度为O(log2 n),算上枚举的复杂度,总的复杂度就是O(nlog2 n)。
下边详细说明一下如何维护字典树
首先,树上存储的字符串为 p pre_i的2进制位。
以下面这组数据为例子,来进行分析。

1
9 7
3 1 3 2 4 0 3 5 1
1
2
3
k=7,二进制位为111。
从第一个点开始枚举, 3 a_1=3,二进制位str=“011”;需要让a_1与某一个数x异或,使得得到的结果不小于k。
考虑是否能大于k,从最高位开始(如果最高位比k大,数值就一定比k大),str[0]与1异或会得到1,但是k的最高位也是1,所以这一位最大也只能相等,所以x二进制位的第一个一定是1,否则就一定小于k。
可以看出来,如果拿到一个数a_R,从二进制位最高位开始遍历,就能确定a_i需要满足的二进制位的前缀,可以通过字典树来维护这个这个前缀。这时候字典树中没有存任何数据,所以无法找到满足条件的a_i。将a_1=011存入字典树。
枚举第二个点, a_2=1,二进制位str=“001”。定义一个cur表示当前位于哪个节点上,初始位置为根节点。
从二进制位的最高位开始判断,str[0]与1异或会得到1,k的最高位也是1,这一位无法大于k,在字典树中找0(左子树),能够找到,将cur移动到这个节点。续判断次高位,这一位需要是0,字典树上不存在这个节点,搜索停止。将a_2=001存入字典树。(这时需要刷新节点上的下标,因为要找离得最近的L和R)后边重复同样的操作,先去字典树中搜索,然后将当前点存入字典树。
一些补充:
当k的某一位为0,a_R的该位与a_L的该位异或为1时,后缀无论是什么,两个值异或的值一定大于k,不需要继续搜,直接更新答案。
需要判断一下等于k的情况,这时候二进制位搜索到了最后,特判一下。
需要判断当pre_i大于k时的答案,可以先在字典树中存一个0,这样就不用特殊判断这种类型了

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

const int N = 3e6 + 10;
const int M = 30;
const ll inf = LLONG_MAX;
ll tot, res, res_l, res_r;
ll n, k;
ll a[N], pre[N];

struct node {
	int l, r;// 左边表示0,右边表示1
	int id;
}trie[N];


void update(int id) {
	int x = pre[id];
	int cur = 1;
	int i;
	for (i = M - 1; i >= 0 && cur != -1; i--) {
		if ((k >> i) & 1) {
			if ((x >> i) & 1) {
				cur = trie[cur].l;
			}
			else {
				cur = trie[cur].r;
			}
		}
		else {
			if ((x >> i) & 1) {
				if (trie[cur].l != -1) {
					if (id - trie[trie[cur].l].id < res) {
						res = id - trie[trie[cur].l].id;
						res_l = trie[trie[cur].l].id + 1;
						res_r = id;
					}
				}
				cur = trie[cur].r;
			}
			else {
				if (trie[cur].r != -1) {
					if (id - trie[trie[cur].r].id < res) {
						res = id - trie[trie[cur].r].id;
						res_l = trie[trie[cur].r].id + 1;
						res_r = id;
					}
				}
				cur = trie[cur].l;
			}
		}
		if (i == 0 && cur != -1) {
			if (id - trie[cur].id < res) {
				res = id - trie[cur].id;
				res_l = trie[cur].id + 1;
				res_r = id;
			}
		}

	}
}

void add(int id) {
	int x = pre[id];
	int cur = 1;
	for (int i = M - 1; i >= 0; i--) {
		if ((x >> i) & 1) {
			if (trie[cur].r == -1) {
				// 不存在这条边,新建上
				trie[++tot].l = -1;
				trie[tot].r = -1;
				trie[tot].id = id;
				trie[cur].r = tot;
			}
			else {
				// 存在这条边,刷新id 
				trie[trie[cur].r].id = id;
			}
			cur = trie[cur].r;
		}
		else {
			if (trie[cur].l == -1) {
				// 不存在这条边,新建上
				trie[++tot].l = -1;
				trie[tot].r = -1;
				trie[tot].id = id;
				trie[cur].l = tot;
			}
			else {
				// 存在这条边,刷新id
				trie[trie[cur].l].id = id;
			}
			cur = trie[cur].l;
		}
	}
}

void init() {
	res = inf;
	res_l = -1;
	res_r = -1;
	tot = 1;// 根节点设置为1
	trie[tot].l = -1;
	trie[tot].r = -1;
	add(0);// 加进去一个0
}


int main() {
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin.exceptions(ios::badbit | ios::failbit);
	int t;
	cin>>t;
	while (t--) {
		init();// 初始化字典树
		cin>>n>>k;
		for (int i = 1; i <= n; i++) {
			cin>>a[i];
			pre[i] = a[i] ^ pre[i - 1];// 计算前缀异或
		}
		for (int i = 1; i <= n; i++) {
			update(i);// 更新答案
			add(i);// 更新字典树
		}
		if (res == inf) {
			cout<<-1<<endl;
		}
		else {
			cout<<res_l<<" "<<res_r<<endl;
		}
	}
	return 0;
}

1008

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)representing the number of
test cases. For each test case, the first line contains two integers
n,m(1≤n,m≤2∗103)representing the size of the matrix the next n line
followed. the i-th line contains m integers
vij(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

先预处理出每个点的高度,对于每一行而言就是一个直方图。然后利用单调栈对于每一行求出面积最大的矩阵,其实就和最大面积全 1 11 矩阵问题是一样的。原理都是,对于一个直方图而言,其面积最大的矩阵的高度一定会卡在某个横坐标的上限,不然的话一定会有面积更大的矩阵,也就是不然高度一定可以往上涨。基于这个原理,我们就去遍历横坐标,假设高度上限在这,然后计算出向左向右最多能扩张到哪里,这里就要利用单调栈了。然后最后对于每个横坐标我们都求出了以此为上限的最大可能面积,答案就肯定是这其中的 M a x MaxMax 了。

#include <bits/stdc++.h>

using namespace std;

int n, m, Max;
int a[2003][2003];
int h[2003][2003];
int l[2003], r[2003];
stack<int> s;

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    int T;
    scanf("%d", &T);
    while(T--) {
        Max = 0;
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                scanf("%d", &a[i][j]);
            }
        }
        for (int j = 1; j <= m; ++j) h[1][j] = 1;
        for (int i = 2; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                h[i][j] = a[i][j] >= a[i - 1][j] ? h[i - 1][j] + 1 : 1;
            }
        }
        for (int i = 1; i <= n; ++i) {
            while(!s.empty()) s.pop();
            for (int j = 1; j <= m; ++j) {
                while(!s.empty() && h[i][j] <= h[i][s.top()]) s.pop();
                if (s.empty()) l[j] = 1;
                else l[j] = s.top() + 1; 
                s.push(j);
            }
            while(!s.empty()) s.pop();
            for (int j = m; j >= 1; --j) {
                while(!s.empty() && h[i][j] <= h[i][s.top()]) s.pop();
                if (s.empty()) r[j] = m;
                else r[j] = s.top() - 1;
                s.push(j);
                Max = max(Max, h[i][j] * (r[j] - l[j] + 1));
            }
        }
        printf("%d\n", Max);
    }
}

1009

Problem Description

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

  • n vertices are strictly divided into K groups, each group contains at least one vertice

  • if vertices p and q ( p ≠ q ) are in the same group, there must be at least one path between p and q meet the max value in this path is
    less than or equal to D.

  • if vertices p and q ( p ≠ q ) are in different groups, there can’t be any path between p and q meet the max value in this path is less
    than or equal to D.

You are given a weighted connected undirected graph G of n vertices
and m edges and an integer K.

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

Input

The first line contains an integer T (1≤ T ≤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) in the first line. Each of the next
m lines contains three integers u,v and c (1≤v,u≤n,v≠u,1≤c≤109)
meaning that there is an edge between vertices u and v with weight c.

Output

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

大概题意,当图以d为分界点,大于d的视为断路,小于d的视为通路,能将图分为k个子图。求d

思路,将每条边按权值排序,通过并查集分为k个子图时,当前边的权值即为所求。

#include <iostream>
#include <algorithm>
#include <cstdio>

using namespace std;
const int N = 1000110;
int p[N];

class edge {
public:
    int s, e;
    int v;

    bool operator<(edge &x) {
        return this->v < x.v;
    }
};

edge a[N];

int find(int x) {
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}

void play() {
    int n, k, m;
    scanf("%d%d%d", &n, &m, &k);
    int cur = n;
    for (int i = 0; i <= n; ++i)
        p[i] = i;
    for (int i = 1; i <= m; ++i)
        scanf("%d%d%d", &a[i].s, &a[i].e, &a[i].v);
    sort(a + 1, a + m + 1);
    int d = 0;
    for (int i = 1; i <= m; ++i) {
        if (a[i].v != a[i - 1].v)
            if (cur == k)
                break;
        if (find(a[i].s) == find(a[i].e))
            continue;
        p[find(a[i].s)] = find(a[i].e);
        cur--;
        d = a[i].v;
    }
    printf("%d\n", cur == k ? d : -1);
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--)
        play();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值