洛谷2019秋令营 网课第一节

首先讲了数据的预处理,包括离散化和前缀和的东西。

前缀和:

洛谷P3406

关键差分公式为了使区间[l,r]区间加上1 我们需要:

                                                                       \\sum[l]+=1\\ sum[r+1]-=1

前缀和公式:

                                                                      sum[l]+=sum[l-1]+arr[l]

其中arr表示数组,若我们只想用差分数组出来前缀和,这个arr可以不用。

为了让[x1,y1]到[x2,y2]的区间加1

二维差分公式:

                                                                        \\sum[x1][y1]+=1\\ sum[x2+1][y2+1]+=1\\ sum[x1][y2+1]-=1\\ sum[x2+1][y1]-=1\\

二维前缀和:

                                                                       sum[x][y]+=sum[x-1][y]+sum[x][y-1]-sum[x-1][y-1]

二维差分需要二位前缀和来恢复。

洛谷P3406

#include <bits/stdc++.h>
#define int long long
using namespace std;
int32_t main(){
	int n,m;cin>>n>>m;
	vector<int> mv;
	for(int i=0;i<m;i++){
		int t;cin>>t;
		mv.push_back(t);
	}
	vector<vector<int> > arrmv(n,vector<int>(3,0));
	for(int i=0;i<n-1;i++){
		cin>>arrmv[i][0]>>arrmv[i][1]>>arrmv[i][2];
	}
	int dif[n+1];
	memset(dif,0,sizeof(dif));
	for(int i=0;i<m-1;i++){
		int a=mv[i];
		int b=mv[i+1];
		if(b<a)swap(a,b);
		dif[a]+=1;
		dif[b]-=1;
	}
	for(int i=1;i<n;i++){
	dif[i]+=dif[i-1];}
	// for(int i=1;i<=n-1;i++)cerr<<dif[i]<<endl;

	int ans=0;
	for(int i=1;i<=n-1;i++){
		if(dif[i]*arrmv[i-1][0]>(dif[i]*arrmv[i-1][1]+arrmv[i-1][2])){
		ans+=dif[i]*arrmv[i-1][1]+arrmv[i-1][2];}
		else ans+=dif[i]*arrmv[i-1][0];
	}
		cout<<ans<<endl;
	
	return 0;
}

洛谷1115

#include <bits/stdc++.h>
using namespace std;
const int inf=1e9;
int main(){
	int n;cin>>n;
	vector<int> arrmv(n,0);
	for(int i=0;i<n;i++)
		cin>>arrmv[i];
	vector<int> sumarr(n+1,0);
	for(int i=0;i<n;i++){
		sumarr[i+1]+=sumarr[i]+arrmv[i];
	}
	int t=sumarr[0];
	int ans=-inf;
	for(int i=1;i<=n;i++){
		
		ans=max(ans,sumarr[i]-t);
		t=min(sumarr[i],t);
	}
	cout<<ans<<endl;
	return 0;
}

洛谷3397

#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e3+10;
int arr[MAXN][MAXN];
int main(){
	memset(arr,0,sizeof(arr));
	int n,m;cin>>n>>m;
	for(int i=0;i<m;i++){
		int x1,y1,x2,y2;
		cin>>x1>>y1>>x2>>y2;
		x1-=1;y1-=1;x2-=1;y2-=1;
		arr[x1][y1]+=1;
		arr[x2+1][y2+1]+=1;
		arr[x2+1][y1]-=1;
		arr[x1][y2+1]-=1;
	}
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			if(i)arr[i][j]+=arr[i-1][j];
			if(j)arr[i][j]+=arr[i][j-1];
			if(i &&j)arr[i][j]-=arr[i-1][j-1];
		}
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			if(j)cout<<" ";
			
			cout<<arr[i][j];
		}
	cout<<endl;
	}
	
	return 0;
}

树上对点或者边差分

当我们需要频繁地对树上任意路径的边权值或者点权值加w时候,可以使用这一个方法。复杂度为o(nlogn).主要复杂度在计算lca上面。

首先我们写出树上对点的差分公式:

                                                         \\diff[u]+=w\\ diff[v]+=w\\ diff[lca(u,v)]-=w\\ diff[parent[lca(u,v)]]-=w

其中diff为差分数组,记录的是每个点的权值变化。后面需要使用树上前缀和恢复。lca为最近邻公共祖先,一般使用倍增的方法实现:

https://www.geeksforgeeks.org/lca-for-general-or-n-ary-trees-sparse-matrix-dp-approach-onlogn-ologn/

parent代表父亲数组,记录的是当前节点的父亲。在计算lca时候,在用倍增计算父亲时,我们可以计算当前节点的第2^i个父亲,所以这个parent数组应该是在计算lca时候得到的。

注意,u和v下标都从1开始,这样方便计算。主要是因为里面用了parent数组,假如从1做下标,我们就免去了处理0点的parent的特判。然后是树上对边差分,公式如下:

                                                              \\diff[u]+=w\\ diff[v]+=w\\ diff[lca(u,v)]-=2w

注意这里的diff数组指的是当前节点指向父亲节点的边。为了从差分恢复,我们需要计算树上前缀和。代码如下:

int dfs(int u){
	flag[u]=1;    //走过打个标记
	for(int i=0;i<(int)tree[u].size();i++){
		int nx=tree[u][i];
		if(flag[nx])continue;
		diff[u]+=dfs(nx);
	}
	return diff[u];
}

洛谷3258

这题是很好的对边差分的题目。这题看似是对点进行差分,但是我们发现假如使用对点进行差分的话,需要特判而且情况特别多。这里我们可以转换为对边差分。对于除了终止节点的其它节点有如下公式:

                                                                       (1+\sum_i cnt_i)/2

表示当前点的权值为相邻边走过的次数+1再除以2.对于终止节点,分子的+1需要去掉。

#include <bits/stdc++.h>
#define MAXN 300010 
#define level 19 
using namespace std;
vector <int> tree[MAXN]; 
int depth[MAXN]; 
int parent[MAXN][level]; 
// pre-compute the depth for each node and their 
// first parent(2^0th parent) 
// time complexity : O(n) 
void dfs(int cur, int prev) 
{ 
	depth[cur] = depth[prev] + 1; 
	parent[cur][0] = prev; 
	for (int i=0; i<tree[cur].size(); i++) 
	{ 
		if (tree[cur][i] != prev) 
			dfs(tree[cur][i], cur); 
	} 
} 

// Dynamic Programming Sparse Matrix Approach 
// populating 2^i parent for each node 
// Time complexity : O(nlogn) 
void precomputeSparseMatrix(int n) 
{ 
	for (int i=1; i<level; i++) 
	{ 
		for (int node = 1; node <= n; node++) 
		{ 
			if (parent[node][i-1] != -1) 
				parent[node][i] = 
					parent[parent[node][i-1]][i-1]; 
		} 
	} 
} 

// Returning the LCA of u and v 
// Time complexity : O(log n) 
int lca(int u, int v) 
{ 
	if (depth[v] < depth[u]) 
		swap(u, v); 

	int diff = depth[v] - depth[u]; 

	// Step 1 of the pseudocode 
	for (int i=0; i<level; i++) 
		if ((diff>>i)&1) 
			v = parent[v][i]; 

	// now depth[u] == depth[v] 
	if (u == v) 
		return u; 

	// Step 2 of the pseudocode 
	for (int i=level-1; i>=0; i--) 
		if (parent[u][i] != parent[v][i]) 
		{ 
			u = parent[u][i]; 
			v = parent[v][i]; 
		} 

	return parent[u][0]; 
} 

void addEdge(int u,int v) 
{ 
	tree[u].push_back(v); 
	tree[v].push_back(u); 
} 
vector<int> diff(MAXN,0);
vector<int> flag(MAXN,0);
int dfs2(int u){
	flag[u]=1;
	for(int i=0;i<(int)tree[u].size();i++){
		int nx=tree[u][i];
		if(flag[nx])continue;
		diff[u]+=dfs2(nx);
	}
	return diff[u];
}
		
		
		
int main(){
	memset(parent,-1,sizeof(parent));
	int n;cin>>n;
	
	vector<int> arrmv(n);
	for(int i=0;i<n;i++)cin>>arrmv[i];
	for(int i=0;i<n-1;i++){
		int a,b;cin>>a>>b;
		addEdge(a,b);
	}
	depth[0]=0;
	dfs(1,0); 
	
	precomputeSparseMatrix(n); 
	for(int i=0;i<n-1;i++){
		int a,b;
		a=arrmv[i];
		b=arrmv[i+1];
		int lcaa=lca(a,b);
		diff[a]+=1;
		diff[b]+=1;
		diff[lcaa]-=2;
			
	}
	dfs2(1);
	int ans[n+1];
	for(int i=1;i<=n;i++){
		ans[i]=diff[i];
	for(int ii=0;ii<(int)tree[i].size();ii++){
		int nx=tree[i][ii];
		if(nx==parent[i][0])continue;
		ans[i]+=diff[nx];
	}
	}
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<endl;
	}
	
	return 0;
}

关于贪心:

序列贪心。序列贪心指的是,在一串序列中,我们需要通过不断安排元素的位置使得某一个量最大或者最小。做这种题假若从贪心入手,我们可以这样考虑。我们就假设有两个元素a和b分别将它们排列为a,b和b,a,然后比较量的关系。比如最大值之间的最小值。我们选出max(order(ab)),max(order(ba))做比较,得到最优的一个量后,我们得到一个关键字用作sort函数里面的cmp.

洛谷1842:

#include <bits/stdc++.h>
#define int long long
using namespace std;
bool cmp(pair<int,int> &fir ,pair<int,int> &las){
	if(fir.first+fir.second>las.first+las.second){
		return true;
	}else return false;
}
const int inf=1e18;
int32_t main(){
	int n;cin>>n;
	vector<pair<int,int>> arrmv(n);
	for(int i=0;i<n;i++){
		int a,b;cin>>a>>b;
		arrmv[i]=make_pair(a,b);
	}
	sort(arrmv.begin(),arrmv.end(),cmp);
	reverse(arrmv.begin(),arrmv.end());
	vector<int> presum(n+2,0);
	for(int i=1;i<=n;i++){
		presum[i]+=presum[i-1]+arrmv[i-1].first;
	}
	reverse(arrmv.begin(),arrmv.end());
	reverse(presum.begin(),presum.end());
	int ans=-inf;
	for(int i=0;i<n;i++){
		ans=max(ans,presum[i+2]-arrmv[i].second);
	}
	cout<<ans<<endl;
	return 0;
}

带反悔的贪心

所谓带反悔的贪心就是,我们一直选择贪心策略,但是,我们遇到一个有可能更优的策略时,我们去掉前面做的一个相对更劣的策略,加入这个新的策略,最后我们会全局最优。一般使用c++的优先队列来记录以前做过的策略,每次需要做新策率时候我们就比较即可。

洛谷P4053

#include <bits/stdc++.h>
#define int long long
using namespace std;
int32_t main(){
	int n;cin>>n;
	vector<pair<int,int>> mv;
	for(int i=0;i<n;i++){
		int a,b;cin>>a>>b;
		mv.push_back(make_pair(b,a));
	}
	sort(mv.begin(),mv.end(),less<pair<int,int>>());
	priority_queue<int,vector<int>,less<int>> pq;
	int lstime=0;
	for(int i=0;i<n;i++){
		if(lstime+mv[i].second<=mv[i].first){
			lstime=lstime+mv[i].second;
			pq.push(mv[i].second);
		}else{
			if(pq.size()){
				int no=pq.top();
				if(mv[i].second <no &&lstime-no+mv[i].second<=mv[i].first){
					pq.pop();
					pq.push(mv[i].second);
					lstime=lstime-no;
					lstime+=mv[i].second;
				}
			}
		}
	}
	cout<<pq.size()<<endl;
	return 0;
}

洛谷1230

#include <bits/stdc++.h>
using namespace std;
int main(){
    int m,n;cin>>m>>n;
    vector<pair<int,int>> arrmv(n);
    for(int i=0;i<n;i++){
        cin>>arrmv[i].first;
    }
    int sum=0;
    for(int i=0;i<n;i++){
        cin>>arrmv[i].second;
        sum+=arrmv[i].second;
    }
    sort(arrmv.begin(),arrmv.end(),less<pair<int,int>>());
    priority_queue<int,vector<int>,greater<int>> pq;
    int take=0;
    for(int i=0;i<n;i++){
        if(arrmv[i].first>take){
            take++;
            pq.push(arrmv[i].second);
        }else{
            int no=pq.top();if(arrmv[i].second>no){
                pq.pop();pq.push(arrmv[i].second);
            }
        }
    }
    while(!pq.empty()){
        int no=pq.top();
        pq.pop();
        sum-=no;
    }
    int ans=m-sum;
    cout<<ans<<endl;

        
    return 0;
}
发布了171 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/FrostMonarch/article/details/103244961