[Codeforces div1] Round 739C. Alyona and towers 线段树+差分数组

这题应当有很多做法

拿到题目以后有很多想法,光是不同的线段树就有两三种,而且我觉得分块卡一卡也是能卡过去的。

最后写了最简单的方法,代码比起其它的解法都短很多。


考虑差分后的数组,原问题变可以转化成求最长的一段正数后紧接一段负数的长度。

若用折线将这串数字连接起来,那么每一段“合唱队形”数便可以表示成两个波谷之间的距离。

线段树维护所有的波谷,维护最大的两个波谷之间的距离。

修改直接在差分数组上单点修改,对点x修改的时候可能会产生/消失波谷的位置是x-1,x,x+1,对这三个点进行单点修改即可。

Notice:

1、差分数组可能会加爆int,需要开long long

2、波峰波谷在临界值和最左最右端的取值方法细节需要注意


#include 
#include 

#define mid ((l+r)>>1)
#define ls l,mid,2*t
#define rs mid+1,r,2*t+1

#define N 300050
using namespace std;
typedef long long LL;

inline int rd() { int t; scanf("%d",&t); return t; }
inline void ut(int &x,int y) { x = max(x,y); }
struct Tree{ int lb,rb,ans; }tr[4*N];
int tmp[N],n,bg[N];
LL a[N];

int dis(int x,int y) {
	if (!x || !y) return 0;
	int r = x - y;
	if (x == y+1 && a[x] == 0) return 1;
	return r+1;
	
	if (a[y+1] > 0 && a[x] < 0) r++;
	return r;
}

Tree merge(Tree p1,Tree p2) {
	Tree tmp;
	tmp.lb = p1.lb ? p1.lb : p2.lb;
	tmp.rb = p2.rb ? p2.rb : p1.rb;
	tmp.ans = max(p1.ans , p2.ans);
	ut(tmp.ans , dis( p2.lb , p1.rb ));
	return tmp;
}

void build(int l,int r,int t) {
	if (l == r) {
		tr[t].lb = tr[t].rb = bg[l] > 0 ? l : 0; 
		tr[t].ans = 1; 
		return ; }
	build(ls); build(rs);
	tr[t] = merge(tr[2*t] , tr[2*t+1]);
}

int x;
void update(int l,int r,int t) {
	if (l > x || r < x) return ;
	if (l == r) {
		tr[t].lb = tr[t].rb = bg[l] > 0 ? l : 0; 
		tr[t].ans = 1; 
		return ; }
	update(ls); update(rs);
	tr[t] = merge(tr[2*t] , tr[2*t+1]);
}

void init() {
	n=rd();
	tmp[0] = -1;
	for (int i=1;i<=n;i++) tmp[i] = rd();
	for (int i=2;i<=n;i++) a[i] = tmp[i] - tmp[i-1]; 
	
	for (int i=1;i<=n;i++) if ((a[i]<0 && a[i+1]>0) || (!a[i]) || (!a[i+1])) bg[i] = 1;
	build(1,n,1);
}

inline void c(int t) {
	if (t<1 || t>n) return ;
	bg[x=t] = (a[t]<0 && a[t+1]>0) || (!a[t]) || (!a[t+1]);
	update(1,n,1);
}

void solve() {
	int m = rd();
	while (m--) {
		int ql=rd() , qr=rd() , d=rd();
		a[ql] += 1LL * d; if (qr+1 <= n) a[qr+1] -= 1LL * d; a[1] = 0;
		c(ql-1); 
		c(ql); 
		c(ql+1);
		c(qr-1); 
		c(qr); 
		c(qr+1);
		int ans = tr[1].ans;
		if (tr[1].lb) ut(ans,tr[1].lb);
		if (tr[1].rb) ut(ans,n-tr[1].rb+1);
		if (!tr[1].lb && !tr[1].rb) ut(ans,n);
		printf("%d\n",ans);
	}
}

int main() {
	#ifndef ONLINE_JUDGE
		freopen("2.in","r",stdin);
		freopen("2.out","w",stdout);
	#endif
	init();
	solve();
	return 0;
}



对拍暴力程序:

直接暴力处理一个数向左/向右单调下降序列的长度,两个叠加-1更新答案即可。n^2的暴力可以跑很大的数据。

#include 
#include 
#define N 100050
#define INF (1<<30)
using namespace std;
typedef long long LL;
inline void ut(int &x,int y) { x = max(x,y); }
LL a[N];
int l[N],r[N],n,m;
int main() {
	freopen("2.in","r",stdin);
	freopen("2_cmp.out","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%I64d",&a[i]);
	a[0] = a[n+1] = -INF;
	scanf("%d",&m);
	while (m--) {
		int ll,rr,d; scanf("%d%d%d",&ll,&rr,&d);
		for (int i=ll;i<=rr;i++) a[i] += d;
		for (int i=1;i<=n;i++) l[i] = (a[i]>a[i-1]) ? l[i-1]+1 : 1;
		for (int i=n;i>=1;i--) r[i] = (a[i]>a[i+1]) ? r[i+1]+1 : 1;
		int ans = -INF;
		for (int i=1;i<=n;i++) ut(ans,l[i]+r[i]-1);
		printf("%d\n",ans);
	}
	return 0;
}


造数据程序:

随机数组随机l,r,d,很简单的造数据程序。可以构造n=1和n=2两组天坑数据来卡你的程序。

#include 
#include 
#include 
#include 

using namespace std;

const int maxn = 10000;
const int top = 10000;
const int maxm = 10000;

int main() {
	freopen("2.in","w",stdout);
	srand(time(0));
	int n = rand() % maxn + 1; printf("%d\n",n);
	for (int i=1;i<=n;i++) printf("%d%c",rand() % top + 1,i==n?'\n':' ');
	int m = rand() % maxm + 1; printf("%d\n",m);
	for (int i=1;i<=m;i++) {
		int l = rand() % n + 1;
		int r = rand() % (n-l+1) + l;
		int d = rand() % top + 1;
		printf("%d %d %d\n",l,r,d);
	}	
	return 0;
}


你可能感兴趣的:(线段树,中等难度练习题2)