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;
}