暑期集训第五周与题目精讲(8.7-8.13)

Point1  并查集

Point2  合并优化

Point3  路径压缩

Point4  线段树

 

Point5  树状数组

NO.1

题目描述:

题意分析:求任意两条村庄都可以通路的最短时间

解题思路:我们可以将这N个村庄看成N个点,其中M是我们要合并的次数。很明显,修路时间t越早的就要越早合并,所以先进行排序!由于x,y也要跟着动,那就要用到结构体!排序后,按照时间逐个合并,如果合并过程中道路连通,那么输出时间;否则输出-1

复杂度分析:O(n+m)

带注释的代码:

#include<bits/stdc++.h>
using namespace std;
int uset[100005];
int n,m;
struct xiu{//定义一个结构体存放x村和两村庄之间公路修完的时间
	int x,y,t;
}a[100005];
int cmp(xiu x,xiu y){
	return x.t<y.t;//返回时间短的
}
int find(int i){//查找
	if(i==uset[i]){
		return i;
	}
	else{
		return uset[i]=find(uset[i]);
	}
}
void unite(int x,int y){//合并
int u=find(x);
	int v=find(y);
	if(u==v){
		return;
	}
	uset[u]=v;
}
bool check(){
	int t=0;
	for(int i=1;i<=n;i++){
		if(find(i)==i){//找总共要分成几堆,如果只有一堆就表示能相互通车
		    t=t+1;
		}
		if(t==2){/
			return 0;//返回0:防止继续查找,那就不是最早时间了
		}
	}
	return 1;//如果找到最早时间(任意两个村庄能够通车),返回1;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		uset[i]=i;//初始化
	}
	for(int i=1;i<=m;i++){
		int x,y,t;
		cin>>a[i].x>>a[i].y>>a[i].t;
	}
	sort(a+1,a+m+1,cmp);//将修路的时间进行排序,时间短的放在前面
	for(int i=1;i<=m;i++){
		unite(a[i].x,a[i].y);//合并的过程,调用函数unite
		if(check()){
			printf("%d\n",a[i].t);//输出最短时间
			return 0;
		}
	}
	printf("-1\n");//否则输出-1
}
 

NO.2

题目描述:

题意分析:大小为h*w,要贴n张公告,每个公告的长度是k,高度固定为1,公告放的要尽可能靠上并尽可能靠左,每给出一张公告,要求这个公告在满足要求的情况下放在了第几层

解题思路:我们创建一颗长度为h的线段树,线段树叶子节点初始值设置为w,题意很自然的就转化为找到线段树中第一个值大于等于k的下标。 我们可以维护一个最大值,然后写一个queryPos函数去查找

带注释的代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int a[N];
struct Node {
    int l, r; // 节点代表的区间
    long long val, lazy;  // 维护的值
} tr[N * 4];// 我们用u*2代表节点u的左节点[l, mid], (u*2+1)代表右节点[mid+1, r]
void pushup(int u) {
    //TODO:通过两个子节点维护当前节点信息
    // [l, mid]和[mid+1,r]的信息维护[l, r]的信息
    tr[u].val = tr[u * 2].val + tr[u * 2 + 1].val;
}
// 在我要访问下面的数据时,原本存在节点u上的lazy, 我必须先把它更新下去
void pushdown(int u) {
    if(!tr[u].lazy) { // 没有lazy要往下发,根据要维护的信息来判断
    return;
}// 更新子节点的lazy
    tr[u * 2].lazy += tr[u].lazy;
    tr[u * 2 + 1].lazy += tr[u].lazy;
    // 更新子节点的值
    tr[u * 2].val += 1LL * (tr[u * 2].r - tr[u * 2].l + 1) * tr[u].lazy;
    tr[u * 2 + 1].val += 1LL * (tr[u * 2 + 1].r - tr[u * 2 + 1].l + 1) *
    tr[u].lazy;
    // 更新完将u的lazy清空
    tr[u].lazy = 0;
}
void build(int u, int l, int r) {
    if(l == r) { // 退出条件,到达线段树的叶子节点了
        tr[u] = {l, r, a[l]};
        return;
    }
    // 初始化tr[u]的区间范围
    tr[u] = {l, r};
    int mid = (l + r) / 2;
    // 建立左右子树
    build(u * 2, l, mid);
    build(u * 2 + 1, mid + 1, r);
    // 两个子节点维护我当前节点的信息
    pushup(u); 
    }
    // 和pushup差不多
    // 也可以直接写,不写这个函数
long long opt(long long lhs, long long rhs) {
    // TODO:根据操作实现
    return lhs + rhs;
}
long long query(int u, int l, int r) { // [l, r]的查询值
    if(tr[u].l > r || tr[u].r < l) {
        // TODO:返回一个数代表不存在的情况
        // min  INF
        // max -INF
        // sum  0
        return 0;
    }
// 该节点区间被我的查询区间包含
if(tr[u].l >= l && tr[u].r <= r) {
    return tr[u].val;
}
//我们需要往下走了, 需要把原本放在u节点的lazy值,分发下去
pushdown(u);
    // 根据操作
    return opt(query(u * 2, l, r), query(u * 2 + 1, l, r));
}
//将[l, r]的值都增加x
void modify(int u, int l, int r, int x) {
    if(tr[u].l > r || tr[u].r < l) { // 如果现在这个区间和我要修改的区间没有交集
        return;
    }
    // 如果这个区间是我要修改的一个子区间
    if(tr[u].l >= l && tr[u].r <= r) {
        tr[u].lazy += x; //先不更新子节点,增加lazy值
        // 修改对现在这个区间的影响[tr[u].l, tr[u].r]每一位增加x
        // 总共就是 (tr[u].r - tr[u].l + 1) * x
        // 注意数据范围是否爆int
        tr[u].val += 1LL * (tr[u].r - tr[u].l + 1) * x;
        return;
    }
    int mid = (tr[u].l + tr[u].r) / 2;
        // 在要访问子树数据前先把之前未更新内容分下去
    pushdown(u);
        // 修改
    modify(u * 2, l, r, x);
    modify(u * 2 + 1, l, r, x);
    //修改完向上更新
    pushup(u);
}
int main() {
    int n, q;
    cin >> n >> q;
    for (int i = 1; i <= n; i ++ ) {
        cin >> a[i];
    }
    build(1, 1, n);
    while (q -- ) {
        int op, x, y, v;
        cin >> op >> x >> y;
        if(op == 1) { // [x, y]全加v
            cin >> v;
            modify(1, x, y, v);
        }
        else{ // 查询[x, y]的和
            cout << query(1, x, y) << "\n";
        }
    }
}

NO.3

题目描述:

题意分析:对于每个输入数据集,在放置所有海报后打印可见海报的数量。

解题思路:考虑输入的值域很大,我们将所给的数离散化了,之后直接用区间赋值线段树区间赋值,最后遍历一下每个位置就行了。

带注释的代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 5e5 + 10;
int a[N];
struct Node {
    int l, r; // 区间
    long long val, lazy;  // 维护的值
} tr[N * 4];
void pushup(int u) {
    //TODO:两节点维护当前节点信息
    tr[u].val = tr[u * 2].val + tr[u * 2 + 1].val;
}
void pushdown(int u) { // 破坏了 u节点的区间一致性, 将还未往下分发的值向下分发
    if(!tr[u].lazy) {
        return;
    }
    tr[u * 2].lazy = tr[u].lazy;
    tr[u * 2 + 1].lazy = tr[u].lazy;
    tr[u * 2].val = 1LL * (tr[u * 2].r - tr[u * 2].l + 1) * tr[u].lazy;
    tr[u * 2 + 1].val = 1LL * (tr[u * 2 + 1].r - tr[u * 2 + 1].l + 1) *tr[u].lazy;
    tr[u].lazy = 0;
}
void build(int u, int l, int r) {
    if(l == r) {
        tr[u] = {l, r, 1};
        return;
    }
    tr[u] = {l, r};
    int mid = (l + r) / 2;
    build(u * 2, l, mid);
    build(u * 2 + 1, mid + 1, r);
    pushup(u); // 两个子节点维护我当前节点的信息
}
long long opt(long long lhs, long long rhs) {
    // TODO:根据操作实现
    return lhs + rhs;
}
long long query(int u, int l, int r) { // [l, r]的查询值
    if(tr[u].l > r || tr[u].r < l) {
        // TODO:返回一个不存在值
        // min INF 1e9
        // max -INF
        // sum 0
        return 0;
    }
    // 该节点区间被我的查询区间覆盖的时候
    if(tr[u].l >= l && tr[u].r <= r) {
        return tr[u].val;
    }
    //我们需要往下走了, 需要把原本放在u节点的lazy值,分发下去s
    pushdown(u);
    // 根据操作
    return opt(query(u * 2, l, r), query(u * 2 + 1, l, r));
}
//将[l, r]的值都增加x
void modify(int u, int l, int r, int x) {
    if(tr[u].l > r || tr[u].r < l) {
        return;
    }
    if(tr[u].l >= l && tr[u].r <= r) {
        tr[u].lazy = x;
        tr[u].val = 1LL * (tr[u].r - tr[u].l + 1) * x;
        return;
    }
    int mid = (tr[u].l + tr[u].r) / 2;
    pushdown(u);
    modify(u * 2, l, r, x);
    modify(u * 2 + 1, l, r, x);
    //更新
    pushup(u);
}
int main() {
    int t;
    cin >> t;
    while(t -- ) {
        int n;
        cin >> n;
        std::vector<int> b;
        std::vector<int> l(n + 1), r(n + 1);
        for (int i = 1; i <= n; i ++ ) {
            cin >> l[i] >> r[i];
            b.push_back(l[i]);
            b.push_back(r[i]);
       }
       std::sort(b.begin(), b.end());
       b.erase(std::unique(b.begin(), b.end()), b.end());
       //离散化
       build(1, 1, b.size());
       for (int i = 1; i <= n; i ++ ) {
           int now_l = std::lower_bound(b.begin(), b.end(), l[i]) - b.begin() +1;
           int now_r = std::lower_bound(b.begin(), b.end(), r[i]) - b.begin() +1;
           modify(1, now_l, now_r, i);
       }
       std::vector<int> cnt(n + 1);
       for (int i = 1; i <= b.size(); i ++ ) {
           cnt[query(1, i, i)] = 1;
       }
       int res = 0;
       for (int i = 1; i <= n; i ++ ) {
           res += cnt[i];
       }
       std::cout << res << "\n";
   }
}

NO.4

题目描述:

解题思路:区间修改,单点查询

带注释的代码:

#include <bits/stdc++.h>
#define int long long int
using namespace std;
const int N = 1e6 + 10;
int tr[N];
int lowbits(int x) {
    return x & (-x);
}
void updata(int id, int x) {
    for (int i = id; i < N; i += lowbits(i)) {
        tr[i] += x;
    }
}
int query(int x) {
    int ans = 0;
    for (int i = x; i; i -= lowbits(i)) {
        ans += tr[i];
    }
    return ans;
}
signed main(void) {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, q;
    cin >> n >> q;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
    }
    while (q --) {
        int opt;
        cin >> opt;
        if (opt == 1) {
            int l, r, x;
            cin >> l >> r >> x;
            updata(l, x);
            updata(r + 1, -x);
        } else {
            int id;
            cin >> id;
            cout << query(id) + a[id] << endl;
        }
    }
}

NO.5

题目描述:

解题思路:

偶数个3的个数,就是奇数个3的个数,末尾加上一个3+偶数个3的个数,末尾加上非3的数
奇数个3的个数,就是偶数个3的个数,末尾加上非3的数+末尾加上一个3+偶数个 的个数,

对0的询问情况要特判,如果只有1位的话,那么0是可以放在第1位的

带注释的代码:

#include<iostream>
using namespace std;
const int N = 1010;
const int mod = 12345;
int even[N],odd[N];
int main(){
    int n;
    cin>>n;
    if(n==1){
        //n为1时单独处理
        cout<<9;
        return 0;
    }
    //n大于等于2位时,0不能放在第一位上,所以只有八种
    even[1]=8;
    odd[1]=1;
    for(int i=2; i<=n; i++){
        even[i]=(even[i-1]*9+odd[i-1])%mod;
        odd[i]=(odd[i-1]*9+even[i-1])%mod;
    }
    cout<<even[n];
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值