10.27

    一、

题目大意:给定长度为n的序列a,b,求一个最长区间l,r,满足∑ai和∑bi都大于等于0,i∈【l,r】。

这道题算是一个比较裸的树状数组二位数点。最初学树状数组的时候,书上是给有类似这种的题目的。例题求得是关于当前这个点,在他之前的x,y都小于他的点的个数。我们可以按照x的大小排序,然后依次插入每一个y,求一下比y小的点有多少个,就是所求答案。这道题只不过让你求所有满足条件的点中最小下标的点而已,你只需要把原来维护区间和的树状数组改成维护区间最小值就行了。只不过我当时感到疑惑的是,按照x排完序之后,顺序不就改变了?万一有一个点在序列中的第一个,但是他的x很大怎么办啊,其实你发现,这个并不影响知道题。恰恰因为你要求得是满足条件的点中的坐标最小值,所以就算有的点在当前点的后面,他减出来的结果是负的,不影响结果。如果担心到当前点时,在他前面的所有点并没有全部加入树状数组的话,其实你仔细想一下,我们按照a排序,因为找的点他的x必须小于当前点的x,所以当前点i虽然前面的所有点并没有全部加进去,但是所有可能会更新答案的点,肯定加进去了。当时就是困惑在这里,没有敢排序。哎。哦对了,一定加快读qaq

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e6+10;
int n,siz1,siz2;ll a[maxn],b[maxn],c[maxn],d[maxn];
int h[maxn],ans,temp;
struct node{
	int x,y;int xu;
}e[maxn];
void discrete(){
	sort(c+1,c+n+1);
	sort(d+1,d+n+1);
	siz1=unique(c+1,c+n+1)-c-1;
	siz2=unique(d+1,d+n+1)-d-1;
	for(int i=1;i<=n;i++) a[i]=lower_bound(c+1,c+siz1+1,a[i])-c;
	for(int i=1;i<=n;i++) b[i]=lower_bound(d+1,d+siz2+1,b[i])-d;
}
bool cmp(node a,node b){
	return a.x<b.x;
}
int lowbit(int x) { return x&(-x); }
void update(int x,int val){
	for(;x<=maxn;x+=lowbit(x)){
		h[x]=min(h[x],val);
	}
}
void query(int x){
	for(;x>0;x-=lowbit(x)){
		temp=min(temp,h[x]);
	}
}
int main(){
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),a[i]+=a[i-1],c[i]=a[i];
	for(int i=1;i<=n;i++) scanf("%lld",&b[i]),b[i]+=b[i-1],d[i]=b[i];
	for(int i=1;i<=n;i++) if(a[i]>=0&&b[i]>=0) ans=max(ans,i); 
	discrete();
	for(int i=1;i<=n;i++) e[i].xu=i,e[i].x=a[i],e[i].y=b[i];
	sort(e+1,e+n+1,cmp);
	memset(h,0x3f,sizeof(h));
	for(int i=1;i<=n;i++){
		update(e[i].y,e[i].xu);
		temp=1e9;query(e[i].y);
		if(e[i].xu!=temp)
		ans=max(ans,e[i].xu-temp);
	}
	printf("%d\n",ans);
	return 0;
}

考场上被这道题搞懵,最后只写了30分的暴力。不过收获还是有的,先讲一下考场上的心路历程吧。刚看到第一题,大致理解了题意,以为是个傻逼题,直接双指针扫一遍就行了。就写了10分钟的双指针,然后发现愚蠢的想错了。然后才仔细揣摩题意,明白这道题无法O(n)扫。于是就去想log的做法。一直在想这道题每到一个新的i,求最长长度应该是找到某一个前缀和a和前缀和b都小于当前的前缀和的最小的一个i,所以一直在想这个玩意儿怎么用一个数据结构维护啊,set?好像不太行。线段树?好像只能维护关于某一个的前缀和吧。树状数组?应该更不行吧,树状数组只能求区间和啊。突然脑子一闪,二分行不行啊。想了想复杂度就开始码,码完之后样例也过了,还傻了吧唧的挺开心,A了一道题。不过还好我还知道自己菜,老老实实拍一下吧。一拍就出问题了,再仔细一想,这道题好像并不单调啊,,于是又挂了。这时候已经十点了,只剩下两个小时。我先去看了一下第二题和第三题,第二题是一个在二叉搜索树上搞事情的题,以前从没写过这种题,不很可做,第三题又是期望dp,而且有环需要高斯消元,就果断弃疗了。然后突发奇想,第一题把两个分开处理然后取最小值行不行啊,貌似没问题,就去写线段树了。码了大概三十分钟啊,出了些bug就debug(下次坚决不能无脑debug了),调了半个小时突然发现这个思路也不对啊。。于是就原地去世了。

至于收获:(1)如果暂时想不出正解就不要太贪,果断一些,放弃他去写其他的题。

(2)树状数组原来还能用来求前缀最值。

(3)写一个思路之前一定要先证明正确性,手玩出来样例再去写。更不要无脑debug。

(4)没事就写快读

二、1~n这n个节点,你需要使用它们建立一颗二叉搜索树,然后每一个节点都有一个必须访问的次数。规定:如果一个节点需要被访问,那么这次访问的代价是从根节点到这个点所经过的点的个数。求满足所有访问条件之后的最小代价。

当时看完这道题之后觉得十分不可做,好像得把所有的二叉搜索树建出来,求最优值,怎么建二叉搜索树啊。。。今天订正的时候,想了一下,对于每一个节点来说,它的左子树上所有点一定比它小,右子树上所有点一定比它大。那么你把它压成一条线,会发现所有的节点所处的相对位置和它的编号顺序是一样的,仍然是1~n。那么其实就是一道区间dp了。我们枚举一个i,j,然后枚举一个根节点来更新最优值。显然复杂度是O(n^3)的,只能过40分。首先第一维一定是枚举阶段,也就是区间长度,然后状态显然也无法再做优化,那么我们考虑决策。现在我们要找到对于i,j的最优值决策,发现如果知道了i,j-1和i+1,j的最优决策,那么当前要求得决策一定在两个决策的中间。比如对于第一个i,j-1的决策k1吧,如果我们此时找一个比k1小的k2来做根,首先在求i,j-1的最优值得时候,我们就已经知道,f【i】【k2-1】+f【k2+1】【j-1】没有f【i】【k1-1】+f【k1+1】【j-1】优秀,那么我们此时在右子树上加了一个j,显然仍然不可能k2的那个决策比当前k1的优秀。然后我们另开一个数组g【i】【j】记录决策就好了。

#include<bits/stdc++.h>
#define ll long long
const int maxn=5100;
ll f[maxn][maxn],a[maxn];
int g[maxn][maxn],n;
using namespace std;
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		g[i][i]=i;f[i][i]=a[i];
		a[i]+=a[i-1];
	}
	int j;
	for(int k=1;k<n;k++)
	for(int i=1;(j=i+k)<=n;i++){
		f[i][j]=1e16;
		for(int l=g[i][j-1];l<=g[i+1][j];l++){
			if(f[i][l-1]+f[l+1][j]+a[j]-a[i-1]<f[i][j])
			f[i][j]=f[i][l-1]+f[l+1][j]+a[j]-a[i-1],g[i][j]=l;
		}
	}
	cout<<f[1][n]<<endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39759315/article/details/83476975