问题 A: 最大K段和

时限: 1 Sec  内存: 128 MB

题目描述

fzszkl有一个长度为N的序列A 。他希望从中选出不超过K个连续子段,满足它们两两不相交,求总和的最大值(可以一段也不选,答案为 0)。

输入格式

第一行输入两个正整数 N,K 。 第二行输入 N个整数Ai 。

输出格式

输出一行一个非负整数表示答案。

输入样例

7 2
-1 9 -2 6 -8 1 -7

输出样例

15

提示

N,M<=10^5

 

 

 

题解

明显WQS二分(虽然不知道是不是凸函数)(应该是凸函数吧)

然后把每个位置都加了一个权。。。调了1.5h,以为是自己的二分或check写错了,各种换二分姿势。。。

后来发现对每个位置加权连单调性都没有(因为增大加权可以减少段数,减小加权也可以减少段数)。。。更别说凸了

于是就。。。。。。人没了

其实应该对每一个要选的段加一个权

然后单调队列DP,又由于它没有决策点的左端的限制,所以直接记一下最大值的位置就好了,注意特判

WQS二分最重要的就是:限制什么的个数,就对什么加权(当然前提是凸函数)

代码:
 

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
#define N 100005
#define LL long long
const LL INF=0x3f3f3f3f3f3f3f3fll;
int a[N],g[N],n;
LL sum[N],f[N];
int check(LL mid)
{
	int mx=0;
	for(int i=1;i<=n;i++){
		if(f[mx]+sum[i]-sum[mx]+mid>f[i-1]||(f[mx]+sum[i]-sum[mx]+mid==f[i-1]&&g[mx]+1<g[i]))
			f[i]=f[mx]+sum[i]-sum[mx]+mid,g[i]=g[mx]+1;
		else
			f[i]=f[i-1],g[i]=g[i-1];
		if(f[i]-sum[i]>f[mx]-sum[mx]||(f[i]-sum[i]==f[mx]-sum[mx]&&g[i]<g[mx]))
			mx=i;
	}
	return g[n];
}
int main()
{
	int k,i,cnt=0;LL tmp=0;
	n=gi();k=gi();
	for(i=1;i<=n;i++){
		a[i]=gi();
		sum[i]=sum[i-1]+1ll*a[i];
		if(a[i]>0)tmp+=1ll*a[i],cnt++;
	}
	if(cnt<k){
		printf("%lld",tmp);
		return 0;
	}
	LL l,r,mid,ans=0;
	l=-INF;r=0;
	while(l<=r){
		mid=(l+r)>>1;
		if(check(mid)<=k){
			ans=mid;
			l=mid+1;
		}
		else
			r=mid-1;
	}
	check(ans);
	printf("%lld",f[n]-1ll*ans*k);
}

其实还有一个加强版本:https://www.luogu.org/problem/CF280D(带修改+区间查询)

这道题有一点模拟费用流的思想(就有点像可以反悔的贪心)

把选走的区间的所有数乘一个-1

然后用线段树大力维护就可以了

 

 

 

问题 B: 双端队列xLIS问题

时限: 1 Sec  内存: 128 MB

题目描述

fzszkl有N个数Ai ,他准备将他们依次插入一个双端队列(每次可以在头或尾插入一个元素),最后将 整个队列从尾到头看成一个序列,求出最长上升子序列的长度L 。他想知道, L的最大值是多少。 序列A 的一个上升子序列的严谨定义:从一个序列中删掉若干(可以为0 )个数,剩下的数满足对于任意i<j,Ai<Aj ,则称剩下的数为原序列的一个上升子序列。

输入格式

第一行输入一个正整数 N。 第二行输入N个正整数Ai 。

输出格式

输出一行一个正整数表示答案。

输入样例

8
1 9 2 6 8 1 7 1926817

输出样例

5

提示

插入时,分别选择头头尾头头尾头头,得到的序列为1,2,1,9,6,8,7,1926817,最长上升子序列为1,2,6,8,1926817 ,长度为5 。可以证明,不存在更优的方案能构造出更长的上升子序列。  
对于所有数据,满足:1<=N<=10^5,1<=Ai<=10^9 

 

 

 

 

题解

首先对于一个数,从它开始的最长上升子序列是必定排在它后面的

然后观察了一下性质,发现1 9 2是永远不能排列为成1 2 9的

但是2 1 3可以排列为1 2 3

于是可以推理出:一个数,如果它后面有数比它小,则这个较小的数可以排到它的前面

但是又发现这些较小的数必须是一个递减子序列

所以答案就是从一个数开始的最长上升子序列+最长下降子序列-1

代码:
 

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
#define N 100005
int a[N],tmp[N],b[N],f[N],g[N];
int main()
{
	int n,i,len=1;
	n=gi();
	for(i=1;i<=n;i++)
		a[i]=gi();
	for(i=1;i<=n;i++)
		tmp[i]=a[n-i+1];
	len=0;
	for(i=1;i<=n;i++){
		int j=lower_bound(b+1,b+len+1,tmp[i])-b;
		b[j]=tmp[i];
		if(j==len+1)len++;
		f[i]=j;
	}
	for(i=1;i<=n;i++)
		tmp[i]=-a[n-i+1];
	memset(b,0,sizeof(b));
	len=0;
	for(i=1;i<=n;i++){
		int j=lower_bound(b+1,b+len+1,tmp[i])-b;
		b[j]=tmp[i];
		if(j==len+1)len++;
		g[i]=j;
	}
	int ans=1;
	for(i=1;i<=n;i++)
		ans=max(ans,f[i]+g[i]-1);
	printf("%d",ans);
}

 

 

 

 

问题 C: 最大前缀和

时限: 1 Sec  内存: 128 MB

题目描述

输入格式

输入两个正整数N和M,表示1的数量和-1的数量 (N<=2000,M<=2000)

输出格式

输出答案对998244853(是个质数)取模的值。

输入样例

2 2

输出样例

5

提示

本质不同的序列有6种。 
[1,1,-1,-1],最大前缀和为 2; 
[1,-1,1,-1] ,最大前缀和为1 ; 
[1,-1,-1,1] ,最大前缀和为1 ; 
[-1,1,1,-1] ,最大前缀和为1 ; 
[-1,1,-1,1] ,最大前缀和为0; 
[-1,-1,1,1],最大前缀和为0. 

 

 

 

题解

先从数据范围自然想到一个O(n^3)的DP

然后又调了1h,最后发现自己有一个地方没有+1(我*……#@*¥……)!)

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=998244853;
int f[205][105][105];
int n,m,ans;
int cnt1,cnt_1;
void dfs(int i,int mx)
{
	if(i>n+m){
		ans+=mx;
		if(ans>=mod)ans-=mod;
		return;
	}
	if(cnt1<n){
		cnt1++;
		dfs(i+1,max(mx,cnt1-cnt_1));
		cnt1--;
	}
	if(cnt_1<m){
		cnt_1++;
		dfs(i+1,max(mx,cnt1-cnt_1));
		cnt_1--;
	}
}
int main()
{
	int i,j,k;
	scanf("%d%d",&n,&m);
	if(n<=10&&m<=10){
		dfs(1,0);
		printf("%d",ans);
	}
	else if(n<=100&&m<=100){
		f[0][0][0]=1;
		for(i=0;i<n+m;i++)
			for(j=0;j<=i;j++)if(i-j<=m&&j<=n)
				for(k=0;k<=j;k++){
					if(!f[i][j][k])continue;
					if(j+1<=n)(f[i+1][j+1][max(j+1-(i-j),k)]+=f[i][j][k])%=mod;
					if(i+1-j<=m)(f[i+1][j][max(j-(i+1-j),k)]+=f[i][j][k])%=mod;
				}
		int sum=0;
		for(i=max(n-m,0);i<=n;i++){
			sum+=1ll*f[n+m][n][i]*i%mod;
			if(sum>=mod)sum-=mod;
		}
		printf("%d",sum);
	}
}

 

然后有因为是+1 -1,自然想到括号序列——>卡特兰数——>直线翻折

我们把+1 -1分别看成上走一步和右走一步

然后问题就转换为求点(0,0)到点(n,m)的路径,每条路径的加权为该路径上的所有点(i,j)的max(i-j,0)

点(0,0)到点(a,b)(只能右上走和右下走)且不超过直线y=x+b的方案数为:

点(0,0)到点(a,b)的所有方案 - 点(0,0)关于y=x+b的对称点到(a,b)的所有方案

于是就可以O(n)了

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=998244853;
int f[205][105][105];
int n,m,ans;
int cnt1,cnt_1;
void dfs(int i,int mx)
{
	if(i>n+m){
		ans+=mx;
		if(ans>=mod)ans-=mod;
		return;
	}
	if(cnt1<n){
		cnt1++;
		dfs(i+1,max(mx,cnt1-cnt_1));
		cnt1--;
	}
	if(cnt_1<m){
		cnt_1++;
		dfs(i+1,max(mx,cnt1-cnt_1));
		cnt_1--;
	}
}
int jcinv[2005],jc[2005];
int ksm(int x,int y)
{
	int ans=1;
	while(y){
		if(y&1)ans=1ll*ans*x%mod;
		y>>=1;x=1ll*x*x%mod;
	}
	return ans;
}
void shai()
{
	jcinv[0]=jc[1]=jc[0]=1;
	for(int i=2;i<=2000;i++)
		jc[i]=1ll*jc[i-1]*i%mod;
	jcinv[2000]=ksm(jc[2000],mod-2);
	for(int i=1999;i>=1;i--)
		jcinv[i]=1ll*jcinv[i+1]*(i+1)%mod;
}
int C(int x,int y)
{
	return 1ll*jc[x]*jcinv[y]%mod*jcinv[x-y]%mod;
}
int main()
{
	//freopen("maxpsum.in","r",stdin);
	//freopen("maxpsum.out","w",stdout);
	int i,j,k;
	scanf("%d%d",&n,&m);
	if(n<=10&&m<=10){
		dfs(1,0);
		printf("%d",ans);
	}
	else if(n<=100&&m<=100){
		f[0][0][0]=1;
		for(i=0;i<n+m;i++)
			for(j=0;j<=i;j++)if(i-j<=m&&j<=n)
				for(k=0;k<=j;k++){
					if(!f[i][j][k])continue;
					if(j+1<=n)(f[i+1][j+1][max(j+1-(i-j),k)]+=f[i][j][k])%=mod;
					if(i+1-j<=m)(f[i+1][j][max(j-(i+1-j),k)]+=f[i][j][k])%=mod;
				}
		int sum=0;
		for(i=max(n-m,0);i<=n;i++){
			sum+=1ll*f[n+m][n][i]*i%mod;
			if(sum>=mod)sum-=mod;
		}
		printf("%d",sum);
	}
	else{
		shai();
		int ans=0,pre=0;
		for(i=n-m;i<=n;i++){
			int t=((C(n+m,n)-C(n+m,i+m+1))%mod-pre)%mod;
			if(i>0)ans=(1ll*ans+1ll*t*i)%mod;
			pre=(1ll*pre+1ll*t)%mod;
		}
		printf("%d",ans);
	}
}