CSP 202109-2 非零段划分

CSP 202109-2 非零段划分

题目

  • 思路一(水面升降法)
    类比岛屿问题,假设一开始p非常大,水面淹没了所有岛屿,随着p的减小,即水面的下降,岛屿数量出现变化。每当一个凸峰出现,岛屿数就会多一个;每当一个凹谷出现,原本相邻的两个岛屿就被这个凹谷连在一起了,岛屿数减少一个。当然也可以从水面上升的角度来解答。

    // 202109-2
    // 1)水面下降解法
    #include 
    using namespace std;
    const int N = 500010;
    const int M = 10010;
    int a[N], d[M];
    int n;
    
    int main()
    {
    	cin>>n;
    	for(int i=1;i<=n;++i) cin>>a[i];
    	a[0]=a[n+1]=0;
    
    	// unique 返回的是去重后的右边界(闭区间)
    	n=unique(a,a+n+2)-a-1;
    	// 此时[1,n)上即为去重后的元素,且a[0]=a[n]=0
    
    	for(int i=1;i<n;++i){
    		if(a[i-1]<a[i] && a[i]>a[i+1])  // 凸峰
    			d[a[i]]++;
    		else if(a[i-1]>a[i] && a[i]<a[i+1]) // 凹峰
    			d[a[i]]--;
    	}
    
    	int res=0,sum=0;
    	for(int i=M-1;i>=1;--i)     // 水平面下降
    		sum+=d[i],res=max(res,sum);
    	cout<<res<<endl;
    
    	return 0;
    }
    // 2)水面上升
    #include 
    using namespace std;
    const int N = 500010;
    const int M = 10010;
    int a[N], d[M];
    int n;
    
    int main()
    {
    	cin>>n;
    	for(int i=1;i<=n;++i) cin>>a[i];
    	a[0]=a[n+1]=0;
    
    	// uniqur 返回的是去重后的右边界(闭区间)
    	n=unique(a,a+n+2)-a-1;
    	// 此时[1,n)上即为去重后的元素,且a[0]=a[n]=0
    	
    
    	for(int i=1;i<n;++i){
    		if(a[i-1]<a[i] && a[i]>a[i+1])  // 凸峰
    			d[a[i]]--;
    		else if(a[i-1]>a[i] && a[i]<a[i+1]) // 凹峰
    			d[a[i]]++;
    	}
    
    	int res=0,sum=1;	// 一开始水面在最低点,只有一个岛屿
    	for(int i=0;i<M;++i)     // 水平面下降,这里i要从0开始
    		sum+=d[i],res=max(res,sum);
    	cout<<res<<endl;
    
    	return 0;
    }
    
    
  • 思路二(射线+差分)
    想象一个跌宕起伏的山群,遍历a[i]时就像是水平扫描高低不同的山峰,对于一个山峰,我们只看左侧的迎风面即可,当前山峰的高度范围在a[i-1]~a[i],即当p属于(a[i-1],a[i]]时,这个山峰就是一个独立的山峰,贡献+1。

    #include 
    using namespace std;
    const int N = 500010;
    const int M = 10010;
    int a[N], diff[M];      // diff 差分数组
    int n;
    
    int main()
    {
    	cin>>n;
    	for(int i=1;i<=n;++i) cin>>a[i];
    	
    	for(int i=1;i<=n;++i)
    		if(a[i-1]<a[i]){
    			// 当p在迎风坡 [ a[i-1]+1,a[i] ]上水平穿过,这个山坡的贡献+1
    			diff[a[i-1]+1]++,diff[a[i]+1]--;
    		}
    	
    	int res=0,sum=0;
    	for(int i=1;i<M;++i){
    		sum+=diff[i];
    		res=max(res,sum);
    	}
    	
    	cout<<res<<endl;
    	
    	return 0;
    }
    
    
  • 思路三(索引法+水面升降)
    该思路与思路一水面上升法类似,不同的是不再两侧判断是凹峰和凸峰,而是判断是在水面之下还是在水面之上,为此需要依据高度对山峰进行“排序”,不过需要两侧山峰的状态,就需要记录原始的次序,为此使用索引法解决。

    #include 
    using namespace std;
    
    const int N=500010;
    const int M=10010;
    
    int n;
    int book[N];	// 记录a[i]是否露出水面,初始时全为0,全都露出水面
    vector<int> v[M];	// v[i][j] 是高度为i的山峰在原始数组中的位置
    
    int main()
    {
    	cin>>n;
    	int a;
    	for(int i=1;i<=n;++i){
    		cin>>a;
    		v[a].push_back(i);
    	}
    	
    	int res=0;
    	if(v[0].size()!=n){
    		book[0]=book[n+1]=1;	// 人为设置边界
    		int last=1;	// 上一个p(水平面)时的岛屿数,开始时,水平面最低,整个为一个山峰
    		
    		for(int i=0;i<M;++i){	// 水平面上升到i
    			if(!v[i].empty()){
    				int t=last;
    				for(int j=0;j<v[i].size();++j){
    					book[v[i][j]]=1;	// 水面之下
                        // 若两则山峰均在水面之上,则山峰+1
                        // 若两侧山峰均在水面之下,则山峰-1
    					if(book[v[i][j]-1] && book[v[i][j]+1]) --t;
    					else if(book[v[i][j]-1]==0 && book[v[i][j]+1]==0) ++t;
    				}
    				res=max(res,t);
    				last=t;
    			}
    		}
    	}
    	cout<<res<<endl;
    	return 0;
    }
    

综上,不管是那种思路,大的方向是转化为水淹岛屿问题,以水平面分割为核心进行处理,思路一和思路三是很类似的,都是整个水平面移动进行计算,而思路二则是逐个对单个山峰进行水平面范围的约束。思路二最简洁。

你可能感兴趣的:(算法与数据结构,算法,c++,csp)