1.30学习总结

1.Cow Lineup G(单调队列):问题转化能力很重要
2.发射站(单调队列)map的时间复杂度比较高,能用数组就用数组
3.Closing the Farm S(并查集的撤回操作)
4.求细胞数量(很简单的连通性问题)
 

用线段树的方法尝试两道题,但是还没有涉及lazy标记

树状数组 1https://www.luogu.com.cn/problem/P3374

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 �x

  • 求出某区间每一个数的和

输入格式

第一行包含两个正整数 �,�n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 �n 个用空格分隔的整数,其中第 �i 个数字表示数列第 �i 项的初始值。

接下来 �m 行每行包含 33 个整数,表示一个操作,具体如下:

  • 1 x k 含义:将第 �x 个数加上 �k

  • 2 x y 含义:输出区间 [�,�][x,y] 内每个数的和

输出格式

输出包含若干行整数,即为所有操作 22 的结果。

输入输出样例

输入 #1复制

5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4

输出 #1复制

14
16

说明/提示

【数据范围】

对于 30%30% 的数据,1≤�≤81≤n≤8,1≤�≤101≤m≤10;
对于 70%70% 的数据,1≤�,�≤1041≤n,m≤104;
对于 100%100% 的数据,1≤�,�≤5×1051≤n,m≤5×105。

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define lowbit(x) (x& - (x))
const int N=5e5+5;
int a[N];
struct node{
	int val;
	int l;
	int r;
}tree[4*N];
inline void build(int id,int l,int r){
	//在单纯把数组变成树的时候,就直接把数组的值放到树的节点上 
	tree[id].l=l,tree[id].r=r;
	if (l==r){
		tree[id].val=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(2*id,l,mid);
	build(2*id+1,mid+1,r);
	tree[id].val=tree[2*id].val+tree[2*id+1].val;//这种是把区间的和放到数组的节点上 
}
/*
区间查询 
如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
如果这个区间的左儿子和目标区间有交集,那么搜索左儿子
如果这个区间的右儿子和目标区间有交集,那么搜索右儿子
*/
inline int search(int id,int l,int r){
	if (tree[id].l>=l && tree[id].r<=r){
		return tree[id].val;
	}
	if (tree[id].l>r || tree[id].r<l) return 0;
	int s=0;
	if (tree[2*id].r>=l)  s+=search(2*id,l,r);
	if (tree[2*id+1].l<=r) s+=search(2*id+1,l,r);
	return s;
}
//单点修改,修改x的位置 
inline void add(int id,int x,int k){
	if (tree[id].l==tree[id].r){
		tree[id].val+=k;
		return ;
	}
	if (x<=tree[id*2].r) add(id*2,x,k);//小于左子树的右区间边界,就去左子树找
	else add(id*2+1,x,k); 
	tree[id].val=tree[id*2].val+tree[id*2+1].val;
	return;
}
signed main(){
	int n,m;
	cin>>n>>m;
	for (int i=1;i<=n;++i)cin>>a[i];
	build(1,1,m);
	for (int i=0;i<m;++i){
		int op,b,c;
		cin>>op>>b>>c;
		if (op==1){
			add(1,b,c);
		}else if (op==2){
			int s=search(1,b,c);
			cout<<s<<endl;
		}
	}
} 

树状数组 2https://www.luogu.com.cn/problem/P3368

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 �x;

  2. 求出某一个数的值。

输入格式

第一行包含两个整数 �N、�M,分别表示该数列数字的个数和操作的总个数。

第二行包含 �N 个用空格分隔的整数,其中第 �i 个数字表示数列第 �i 项的初始值。

接下来 �M 行每行包含 22 或 44个整数,表示一个操作,具体如下:

操作 11: 格式:1 x y k 含义:将区间 [�,�][x,y] 内每个数加上 �k;

操作 22: 格式:2 x 含义:输出第 �x 个数的值。

输出格式

输出包含若干行整数,即为所有操作 22 的结果。

输入输出样例

输入 #1复制

5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4

输出 #1复制

6
10
#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define lowbit(x) (x& - (x))
const int N=5e5+5;
int a[N];
struct node{
	int val;
	int l;
	int r;
}tree[4*N];
inline void build(int id,int l,int r){
	//在单纯把数组变成树的时候,就直接把数组的值放到树的节点上 
	tree[id].l=l,tree[id].r=r;
	if (l==r){
		tree[id].val=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(2*id,l,mid);
	build(2*id+1,mid+1,r);
	//tree[id].val=tree[2*id].val+tree[2*id+1].val;//这种是把区间的和放到数组的节点上 
}
/*
区间查询 
如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
如果这个区间的左儿子和目标区间有交集,那么搜索左儿子
如果这个区间的右儿子和目标区间有交集,那么搜索右儿子
*/
inline int search(int id,int l,int r){
	if (tree[id].l>=l && tree[id].r<=r){
		return tree[id].val;
	}
	if (tree[id].l>r || tree[id].r<l) return 0;
	int s=0;
	if (tree[2*id].r>=l)  s+=search(2*id,l,r);
	if (tree[2*id+1].l<=r) s+=search(2*id+1,l,r);
	return s;
}
//单点修改,修改x的位置 
inline void add(int id,int x,int k){
	if (tree[id].l==tree[id].r){
		tree[id].val+=k;
		return ;
	}
	if (x<=tree[id*2].r) add(id*2,x,k);//小于左子树的右区间边界,就去左子树找
	else add(id*2+1,x,k); 
	tree[id].val=tree[id*2].val+tree[id*2+1].val;
	return;
}
//后面是区间修改和单点查询 
void add1(int id,int l,int r,int k){     //与单点修改相比,多了一个元素构成了区间
	if (tree[id].l>=l && tree[id].r<=r){
		tree[id].val+=k;
		return ;
	}
	int mid=(tree[id].l+tree[id].r)>>1;
	if (l<=mid)	add1(id<<1,l,r,k); //进入左子树找
	if (r>mid)	add1(id<<1|1,l,r,k);	//进入右子树找
}
int ans=0;
void search1(int id,int x){	//单点查询 
	ans+=tree[id].val;
	if (tree[id].l==tree[id].r)	return ;
	int mid=(tree[id].l+tree[id].r)>>1;
	if (x<=mid) search1(id<<1,x);
	else search1(id<<1|1,x);
} 
signed main(){
	int n,m;
	cin>>n>>m;
	for (int i=1;i<=n;++i)cin>>a[i];
	build(1,1,n);
	for (int i=0;i<m;++i){
		int op;
		cin>>op;
		if (op==1){
			int a,b,c;
			cin>>a>>b>>c;
			add1(1,a,b,c);
		}else if (op==2){
			ans=0;
			int a;
			cin>>a;
			search1(1,a);
			cout<<ans<<endl;
		}
	}
} 
Cow Lineup Ghttps://www.luogu.com.cn/problem/P3069

题目描述

Farmer John's N cows (1 <= N <= 100,000) are lined up in a row. Each cow is identified by an integer "breed ID" in the range 0...1,000,000,000; the breed ID of the ith cow in the lineup is B(i). Multiple cows can share the same breed ID.

FJ thinks that his line of cows will look much more impressive if there is a large contiguous block of cows that all have the same breed ID. In order to create such a block, FJ chooses up to K breed IDs and removes from his lineup all the cows having those IDs. Please help FJ figure out the length of the largest consecutive block of cows with the same breed ID that he can create by doing this.

农夫约翰的N(1 <= N <= 100,000)只奶牛排成了一队,每只牛都用编上了一个“血统编号”,该编号为范围0...1,000,000,000的整数。血统相同的奶牛有相同的编号,也就是可能有多头奶牛是相同的"血统编号"。

约翰觉得如果连续排列的一段奶牛有相同的血统编号的话,奶牛们看起来会更具有威猛。为了创造这样的连续段,约翰最多能选出k种血统的奶牛,并把他们全部从队列中赶走。

请帮助约翰计算这样做能得到的由相同血统编号的牛构成的连续段的长度最大是多少?

输入格式

* Line 1: Two space-separated integers: N and K.

* Lines 2..1+N: Line i+1 contains the breed ID B(i).

输出格式

* Line 1: The largest size of a contiguous block of cows with

identical breed IDs that FJ can create.

输入输出样例

输入 #1复制

9 1 
2 
7 
3 
7 
7 
3 
7 
5 
7 

输出 #1复制

4 

说明/提示

There are 9 cows in the lineup, with breed IDs 2, 7, 3, 7, 7, 3, 7, 5, 7. FJ would like to remove up to 1 breed ID from this lineup.

By removing all cows with breed ID 3, the lineup reduces to 2, 7, 7, 7, 7, 5, 7. In this new lineup, there is a contiguous block of 4 cows with the same breed ID (7).

思路:这道题也是单调队列的模版,就是需要问题转化。由于会删掉k种,所以就保持k+1的状态就可以了,当到达k+2种的时候,就需要出队,所以找到了出队条件这题就很简单了,在此之前,需要保留已经入队的每个种类的长度

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define lowbit(x) (x& - (x))
const int N=1e5+5;
deque<int>q;
map<int,int>mp;
signed main(){
	int n,k,kind=0,maxn=0,p;
	cin>>n>>k;
	for (int i=0;i<n;++i){
		cin>>p;
		if (mp[p]==0)mp[p]=1;
		else mp[p]++;
		if (mp[p]==1)kind++;
		q.push_back(p);
		while (kind>k+1 && !q.empty())
		{
			mp[q.front()]--;
			if (mp[q.front()]==0)kind--;
			q.pop_front();
		}
		maxn=max(maxn,mp[p]);
	}
	cout<<maxn;
}
发射站https://www.luogu.com.cn/problem/P1901

题目描述

某地有 �N 个能量发射站排成一行,每个发射站 �i 都有不相同的高度 ��Hi​,并能向两边(两端的发射站只能向一边)同时发射能量值为 ��Vi​ 的能量,发出的能量只被两边最近的且比它高的发射站接收。显然,每个发射站发来的能量有可能被 00 或 11 或 22 个其他发射站所接受。

请计算出接收最多能量的发射站接收的能量是多少。

输入格式

第 11 行一个整数 �N。

第 22 到 �+1N+1 行,第 �+1i+1 行有两个整数 ��Hi​ 和 ��Vi​,表示第 �i 个发射站的高度和发射的能量值。

输出格式

输出仅一行,表示接收最多能量的发射站接收到的能量值。答案不超过 32 位带符号整数的表示范围。

输入输出样例

输入 #1复制

3
4 2 
3 5 
6 10

输出 #1复制

7

说明/提示

对于 40%40% 的数据,1≤�≤5000,1≤��≤105,1≤��≤1041≤N≤5000,1≤Hi​≤105,1≤Vi​≤104。

对于 70%70% 的数据,1≤�≤105,1≤��≤2×109,1≤��≤1041≤N≤105,1≤Hi​≤2×109,1≤Vi​≤104。

对于 100%100% 的数据,1≤�≤106,1≤��≤2×109,1≤��≤1041≤N≤106,1≤Hi​≤2×109,1≤Vi​≤104。

思路:单调增的队列。如果入队元素比队尾元素大,那么就需要把队尾元素的能力给入队的元素,然后队尾出队,直到找到对内元素比入队元素高,或者队空,如果入队元素比队尾元素小的话,那就把能量给队尾元素,然后自己入队,这题不能图方便就用map,因为map复杂度高,会TLE

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define lowbit(x) (x& - (x))
const int N=1e6+10;;
deque<int>q;
int v[N],h[N],r[N];
signed main(){
	int n;
	cin>>n;
	int maxn=0;
	for (int i=1;i<=n;++i){
		cin>>h[i]>>v[i];
		while (!q.empty() && h[q.back()]<h[i]){
			r[i]+=v[q.back()];
			q.pop_back();
			maxn=max(maxn,r[i]);
		}
		if (!q.empty()){
			r[q.back()]+=v[i];
			maxn=max(maxn,r[q.back()]);
		}
		q.push_back(i);
	}
	cout<<maxn;
}

Closing the Farm Shttps://www.luogu.com.cn/problem/P3144

题目描述

FJ 和他的奶牛们正在计划离开小镇做一次长的旅行,同时 FJ 想临时地关掉他的农场以节省一些金钱。

这个农场一共有被用 �M 条双向道路连接的 �N 个谷仓(1≤�,�≤30001≤N,M≤3000)。为了关闭整个农场,FJ 计划每一次关闭掉一个谷仓。当一个谷仓被关闭了,所有的连接到这个谷仓的道路都会被关闭,而且再也不能够被使用。

FJ 现在正感兴趣于知道在每一个时间(这里的“时间”指在每一次关闭谷仓之前的时间)时他的农场是否是“全连通的”——也就是说从任意的一个开着的谷仓开始,能够到达另外的一个谷仓。注意自从某一个时间之后,可能整个农场都开始不会是“全连通的”。

输入格式

输入第一行两个整数 �,�N,M。

接下来 �M 行,每行两个整数 �,�u,v(1≤�,�≤�1≤u,v≤N),描述一条连接 �,�u,v 两个农场的路。

最后 �N 行每行一个整数,表示第 �i 个被关闭的农场编号。

输出格式

输出 �N 行,每行包含 YES 或 NO,表示某个时刻农场是否是全连通的。

第一行输出最初的状态,第 �i 行(2≤�≤�2≤i≤N)输出第 �−1i−1 个农场被关闭后的状态。

输入输出样例

输入 #1复制

4 3
1 2
2 3
3 4
3
4
1
2

输出 #1复制

YES
NO
YES
YES

思路:并查集,但是这个如果要用正向思维的话,就需要并查集的撤销,显然没有这种方法,所以可以从后往前找。其中ans是用来判断是否连通的,如果大于1,说明至少有一个以上的独立点,所有是不连通的

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define lowbit(x) (x& - (x))
const int N=1e6+10;
int f[N],m,n,a[N],b[N],ans[N],ss[N],c[N];
int find(int x){
	if (f[x]==x)return x;
	else {
		f[x]=find(f[x]);
		return f[x];
	}
}
void unionn(int i,int j){
	f[find(i)]=find(j);
}
signed main(){
	int n,m;
	cin>>n>>m;	
	memset(ss,1,sizeof(ss));
	for (int i=1;i<=m;++i){
		cin>>a[i]>>b[i];
	}
	for (int i=1;i<=n;++i){
		f[i]=i;
		cin>>c[i];
	}
	for (int i=n;i>=1;--i){
		ss[c[i]]=0;//农场开启 
		for (int j=1;j<=m;++j)
		{
			if (ss[a[j]]==0 && ss[b[j]]==0){//如果两个农场都开启的情况下,并且这两个农场有路就连接 
				unionn(a[j],b[j]);
			}
		}
		for (int j=1;j<=n;++j){
			if (find(j)==j && ss[j]==0){
				ans[i]++;
			}
		}
	}
	for (int i=1;i<=n-1;++i){
		if (ans[i]==1)cout<<"YES"<<endl;
		else cout<<"NO"<<endl;
	}
	cout<<"YES"<<endl;
}
求细胞数量https://www.luogu.com.cn/problem/P1451

题目描述

一矩形阵列由数字 00 到 99 组成,数字 11 到 99 代表细胞,细胞的定义为沿细胞数字上下左右若还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。

输入格式

第一行两个整数代表矩阵大小 �n 和 �m。

接下来 �n 行,每行一个长度为 �m 的只含字符 0 到 9 的字符串,代表这个 �×�n×m 的矩阵。

输出格式

一行一个整数代表细胞个数。

输入输出样例

输入 #1复制

4 10
0234500067
1034560500
2045600671
0000000089

输出 #1复制

4

说明/提示

数据规模与约定

对于 100%100% 的数据,保证 1≤�,�≤1001≤n,m≤100。

思路:很简单的连通性判断

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x& - (x))
const int N=1e6+10;
int vis[1000][1000];
int ss[1000][1000];
int cnt=0,n,m;;
void dfs(int x,int y){
	if (ss[x][y]==0)return;
	ss[x][y]=0;
	int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
	for (int i=0;i<4;++i){
		int  tx=x+dir[i][0],ty=y+dir[i][1];
		if (tx<0 ||ty<0 || tx>=n ||ty>=m)continue;	
		if (vis[tx][ty]==0){
			vis[tx][ty]=1;
			dfs(tx,ty);
		}
	}
	return ;
}
int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++) 
			scanf("%1d",&ss[i][j]);
	for (int i=0 ;i<n;++i)
	{
		for (int j=0;j<m;++j)
		{
			if (ss[i][j]!=0){
				cnt++;
				vis[i][j]=1;
				dfs(i,j);
			}
		}
	}
	cout<<cnt;
}

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值