bzoj4922 [Lydsy1706月赛]Karp-de-Chant Number 贪心+dp

123 篇文章 0 订阅
33 篇文章 0 订阅

Description


卡常数被称为计算机算法竞赛之中最神奇的一类数字,主要特点集中于令人捉摸不透,有时候会让水平很高的选手迷之超时。
普遍认为卡常数是埃及人Qa’a及后人发现的常数。也可认为是卡普雷卡尔(Kaprekar)常数的别称。主要用于求解括号序列问题。
据考证,卡(Qa’a)是古埃及第一王朝的最后一位法老。他发现并研究了一种常数,后世以他的名字叫做卡常数。卡特兰数的起源也是因为卡的后人与特兰克斯结婚,生下来的孩子就叫卡特兰,而他只是发表了祖传的家书而已。Sereja也是卡的后人,提出括号序列问题,也是从家书里得到的资料。然而Sereja为了不让这个秘密公开,于是隐瞒了这道题的真正做法。可是由于卡的后人不是各个都像卡特兰一样爱慕虚荣,这一算法也无法找到。“欲见贤人而不以其道,犹欲其入而闭之门也”。卡之常数的奥秘,需要以一颗诚心去追寻。
现给定n个括号序列,你需要选择若干序列,将它们按一定的顺序从左往右拼接起来,得到一个合法的括号序列。
显然,这个问题可以用卡常数解决,为了检验你是否会卡常数,请写一个程序,计算可以得到的合法的括号序列的长度的最大值。

Solution


一开始一直在想怎么确保序列的中途前缀和不会小于0,后来发现我们只要先选正的再选负数就可以了。

一个合法的括号序列显然满足每一位的前缀和不小于0。考虑求出每段括号序的和y,以及前缀和的最小值x。
我们把y不小于0的放前面,其余放后面。对于前半部分以x为关键字升序排序,后半部分以x+y为关键字降序排序

考虑这样做的正确性。
由于要使序列最长,那么肯定是先把使前缀和变大的选掉,这一部分能选就选
对于使前缀和变小的那些序列,我们优先选取剩余前缀和最大的那些,这样一定不会更劣

然后做一个背包就可以了。

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define fill(x,t) memset(x,t,sizeof(x))

const int N=305;

struct data {
	int x,y,len;
} a[N],b[N],d[N];

char str[N];

int f[N][N*N];

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

bool cmp1(data a,data b) {
	return a.x<b.x;
}

bool cmp2(data a,data b) {
	return a.x+a.y>b.x+b.y;
}

int main(void) {
	int n=read();
	rep(i,1,n) {
		scanf("%s",str+1); d[i].len=strlen(str+1);
		for (int j=1;j<=d[i].len;++j) {
			d[i].y+=(str[j]=='(')?1:-1;
			d[i].x=std:: max(d[i].x,-d[i].y);
		}
	}
	int cntA=0,cntB=0;
	rep(i,1,n) (d[i].y>=0)?(a[++cntA]=d[i]):(b[++cntB]=d[i]);
	if (cntA) std:: sort(a+1,a+cntA+1,cmp1);
	if (cntB) std:: sort(b+1,b+cntB+1,cmp2);
	rep(i,1,cntA) d[i]=a[i];
	rep(i,1,cntB) d[i+cntA]=b[i];
	fill(f,-31); f[0][0]=0;
	int sum=0;
	rep(i,1,n) {
		rep(j,0,sum) f[i][j]=f[i-1][j];
		rep(j,d[i].x,sum) {
			f[i][j+d[i].y]=std:: max(f[i][j+d[i].y],f[i-1][j]+d[i].len);
		}
		sum+=d[i].len;
	}
	printf("%d\n", f[n][0]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值