week12作业

C - 必做题 - 3

东东每个学期都会去寝室接受扫楼的任务,并清点每个寝室的人数。
每个寝室里面有ai个人(1<=i<=n)。从第i到第j个宿舍一共有sum(i,j)=a[i]+…+a[j]个人
这让宿管阿姨非常开心,并且让东东扫楼m次,每一次数第i到第j个宿舍sum(i,j)
问题是要找到sum(i1, j1) + … + sum(im,jm)的最大值。且ix <= iy <=jx和ix <= jy <=jx的情况是不被允许的。也就是说m段都不能相交。
注:1 ≤ i ≤ n ≤ 1e6 , -32768 ≤ ai ≤ 32767 人数可以为负数。。。。(1<=n<=1000000)

Input

输入m,输入n。后面跟着输入n个ai
处理到 EOF

Output

输出最大和
Sample Input
1 3 1 2 3
2 6 -1 4 -2 3 -2 3
Sample Output
6
8

Hint

数据量很大,需要scanf读入和dp处理。

解题思路:

对于这道题目,自己感觉难度比较大,短时间内没有想到有效的方法,尤其是对于m段这个条件感到无从下手,而从下面代码中可以看到对于m段的处理,我们是采用了一个循环进行的,这个外层循环潜在的表明了分了多少段。另外利用了滚动数组以及一个fun变量进行了处理,这个确实有点难想,自己的思维水平有待提高。func数组的作用在于在分为i段时以第j个元素结尾时的最大和,而func2数组的作用在于记录在分为i段时前j个元素的最大和,该值利用fun变量进行更新,从自身的感受来讲,这种处理方法真的少见,func与func1数组相当与同时进行了两种意义上的动态规划,并且这两种意义上的动态规划在更新时利用了滚动数组互相套用进行更新,是真的巧妙。
我们接下来系统的分析以下这样做的原因,前面说到m段难以处理,这是在定状态时产生的绊脚石,前面文章提到过增维的概念,尤其是对于动态规划的题目来讲。在此,我们利用func[i][j]表示前j个元素分为i段的最大和(并且第j个元素包含在其中,保留信息更全面一点,这样便于我们将来的更新),这在意义上相当于坐标的动态规划,很浅显应当将元素数放在第二维。这样就到了状态转移方程的构造,我们比较容易能够得func[i][j]=max(func[i][j-1]+a[j],func1[i-1][k]+a[j])(k的范围是i-1 ~ j-1)这样一个方程因为func[i][j]只能由这两者更新而来,在这里,func1有着其自身的意义,其表示前j个元素分为i段得到的最大和。所以这要求我们需要另外保存信息,即保留一个func1数组,此数组利用一个特定变量进行更新,由于func[i][j]只用到了func1[i-1][k]以及func[i][j-1]的值,i的需求在大方向上为我们消去维度提供了支持,而j的需求在小方向上为我们滚动提供了支持。不过讲真的,这道题我感觉确实有点难,即使做完这道题,仍感觉没能够完全把握住。

#include<iostream>
#include<stdio.h>
#include<cmath>
#include<string.h>
#include<algorithm>
using namespace std;
int m,n;
long long a[1000005];
long long func[1000005]={0};
long long func2[1000005]={0};
long long fun;
int main()
{
	while(scanf("%d%d",&m,&n)!=EOF)
	{
		memset(func,0,sizeof(func));
		memset(func2,0,sizeof(func2));
		for(int i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);
		}
		for(int i=1;i<=m;i++)
		{
			fun=-1000000000000;
			for(int j=i;j<=n;j++)
			{
				func[j]=max(func[j-1]+a[j],func2[j-1]+a[j]);
				func2[j-1]=fun;
				fun=max(func[j],fun);
			}
		}
		cout<<fun<<endl;
	}	
}在这里插入代码片

D - 选做题 - 1

We give the following inductive definition of a “regular brackets” sequence:
the empty sequence is a regular brackets sequence,
if s is a regular brackets sequence, then (s) and [s] are regular brackets sequences, and
if a and b are regular brackets sequences, then ab is a regular brackets sequence.
no other sequence is a regular brackets sequence
For instance, all of the following character sequences are regular brackets sequences:
(), [], (()), ()[], ()[()]
while the following character sequences are not:
(, ], )(, ([)], ([(]
Given a brackets sequence of characters a1a2 … an, your goal is to find the length of the longest regular brackets sequence that is a subsequence of s. That is, you wish to find the largest m such that for indices i1, i2, …, im where 1 ≤ i1 < i2 < … < im ≤ n, ai1ai2 … aim is a regular brackets sequence.
Given the initial sequence ([([]])], the longest regular brackets subsequence is [([])].

Input

The input test file will contain multiple test cases. Each input test case consists of a single line containing only the characters (, ), [, and ]; each input test will have length between 1 and 100, inclusive. The end-of-file is marked by a line containing the word “end” and should not be processed.

Output

For each input case, the program should print the length of the longest possible regular brackets subsequence on a single line.

Sample Input

((()))
()()()
([]])
)[)(
([][][)
end

Sample Output

6
6
4
0
6

解题思路:

其实看到这个题目,脑子中首先感觉题目描述的像是一个栈的功能,不过仔细分析会发现,题目的描述并没有栈中的连续性,所以,用栈是解决不了的。这道题目是一道经典的dp问题。那这道题目归类来讲,其属于一道区间dp,不过从感觉很上来讲区间dp与线性dp有点像,这个题目可以与LIS一题做个比较,从自身的感觉来讲,对于单个数组的动态规划问题,大多应该是这两种解决方案,至于选择哪一种,要根据题目的性质进行分析。例如LIS问题中,其潜在的存在一个单调的序列,这时,单个元素之间的关系比较明确,我们选择线性dp比较好,而对于这个问题,其更像是坐标dp,其扩充的时候不仅仅需要单方向上考虑,更多的体现出一种两端扩充,或是继承的原则,这一点需要有一定的印象,我们在做题目时便可以从这几个角度去分析题目的类型。另外,我们需要格外注意这道区间dp的扩充方式,思维比较新,这种完备的思考方式正是自己所欠缺的,这种区间内分段考虑的方式我们也应当有一定的印象。

#include<iostream>
#include<stdio.h>
#include<string>
#include<string.h>
using namespace std;
string s;
int ans[101][101]={0};
int main()
{
	cin>>s;
	while(s!="end")
	{
		memset(ans,0,sizeof(ans));
		for(int i=2;i<=s.size();i++)
		{
			for(int j=0;j<=s.size()-i;j++)
			{
				if((s[j]=='('&&s[i+j-1]==')')||(s[j]=='['&&s[i+j-1]==']'))
				ans[i][j]=ans[i-2][j+1]+2;
				for(int k=1;k<=i;k++)
			    ans[i][j]=max(ans[i][j],ans[k][j]+ans[i-k][j+k]);	
			}
		}
		cout<<ans[s.size()][0]<<endl;
		cin>>s;
	}
 } 在这里插入代码片

E - 选做题 - 2

Description

马上假期就要结束了,zjm还有 n 个作业,完成某个作业需要一定的时间,而且每个作业有一个截止时间,若超过截止时间,一天就要扣一分。
zjm想知道如何安排做作业,使得扣的分数最少。
Tips: 如果开始做某个作业,就必须把这个作业做完了,才能做下一个作业。

Input

有多组测试数据。第一行一个整数表示测试数据的组数
第一行一个整数 n(1<=n<=15)
接下来n行,每行一个字符串(长度不超过100) S 表示任务的名称和两个整数 D 和 C,分别表示任务的截止时间和完成任务需要的天数。
这 n 个任务是按照字符串的字典序从小到大给出。

Output

每组测试数据,输出最少扣的分数,并输出完成作业的方案,如果有多个方案,输出字典序最小的一个。
Sample Input
2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3
Sample Output
2
Computer
Math
English
3
Computer
English
Math

Hint

在第二个样例中,按照 Computer->English->Math 和 Computer->Math->English 的顺序完成作业,所扣的分数都是 3,由于 English 的字典序比 Math 小,故输出前一种方案。

解题思路:

整体来讲,这个题目也算比较难,其属于状态压缩dp,不过我们还是关注其中的思维。首先理一下二进制进行状压,二进制的功能我们在之前也提到过,多重背包问题中其相当于一个状压作用。其实总结起来,二进制的作用主要体现在其自身表示的连续性,以及在这道题目中所体现出的表示数的唯一性,即一个数就能表示唯一一个状态。对于本题而言,其是作业调度的升级版本,因为其中的作业耗时不再相同,这也就说明每一份作业不再具有绝对的优略性(等同于01背包问题),这在之前我们曾提到过,所以这种理不清且又比较繁琐的问题我们就要甩锅,故要动态规划。
我们比较容易的能够感觉到,在线性的基础上利用一种迭代的规划是不怎么现实的,因为保留信息太少,状态更新起来几乎不可能。通常情况下,我们会增维。但我们应当为每一个维度赋予怎样的实际意义呢?我们可以感受到,在作业调度问题中,每一份作业是处于平等位置的(在我们所要求解的问题的层次上),不同于以往,我们不应再从考虑选择前多少个。。,然后进行更新,我们应当考虑选择那几个去进行更新。走到这一步,我们就迫切需要一种能唯一表示”那几个作业“的一种机制,于是,二进制便挺身而出,这种优秀的唯一性表示性质是二进制的一大优势。这样状态的更新方法就比较容易想出来了。另一方面,我们思考这个题目的解法,体会其中的思想,能够体会到spfa与floyd的意味,这种动态的更新并不是一步到位的,而是基于一种事实存在的,结果必然能得到的逻辑进行的。
这个题目还有一个要点,便是字典序的处理,这里很容易引起思维上的错误,详见代码中重载小于号的方式并加以思考,我们针对其不再展开论述(因为这种注意点是这种做法下才存在的,我们只需要了解有这么个思维过程即可)。

#include<iostream>
#include<stdio.h>
#include<string> 
#include<string.h>
#include<algorithm>
#include<cmath> 
using namespace std;
struct node{
	string s;
	int ddl,cost;
	node(){
	}
	node(string _s,int _ddl,int _cost)
	{
		s=_s;
		ddl=_ddl;
		cost=_cost;
	}
	bool operator<(const node& p)const
	{
		return s>p.s;
	}
}no[16];
int m,n;
int ans[32768];
int pre[32768]={0};
void output(int number)
{
	if(number==0)
	return;
	output(number-(int)pow(2,pre[number]-1));
	cout<<no[pre[number]].s<<endl;	
} 
void funcc()
{
	for(int i=1;i<(int)pow(2,n);i++)
	{
		for(int j=1;j<=n;j++)
		{
			int cc=1;
			if((i&(cc<<(j-1)))!=0)
			{
				int sum=0;
				int dd=1;
				for(int k=1;k<=n;k++)
				{
					if((i&(dd<<(k-1)))!=0)
					sum+=no[k].cost;
				}
				int temp=sum-no[j].ddl;
				if(temp<=0)
				{
					if(ans[i]!=-1)
					{
						if(ans[i-(int)pow(2,j-1)]<ans[i]&&ans[i]!=0)
						pre[i]=j;
						ans[i]=max(0,min(ans[i],ans[i-(int)pow(2,j-1)]));
					}
					else
					{
						ans[i]=ans[i-(int)pow(2,j-1)];
						pre[i]=j;
					}
				}
				else
				{
					if(ans[i]!=-1)
					{
						if(temp+ans[i-(int)pow(2,j-1)]<ans[i]&&ans[i]!=0)
						pre[i]=j;
						ans[i]=max(0,min(ans[i],temp+ans[i-(int)pow(2,j-1)]));	
					}
					else
					{
						ans[i]=temp+ans[i-(int)pow(2,j-1)];
						pre[i]=j;
					}
				}
			}
		}
	}
	cout<<ans[(int)pow(2,n)-1]<<endl; 
	int tt=(int)pow(2,n)-1;
	output(tt);
}
int main()
{
	scanf("%d",&m);
	for(int i=0;i<m;i++)
	{
		scanf("%d",&n);
		memset(ans,-1,sizeof(ans));
		ans[0]=0;
		for(int j=1;j<=n;j++)
		{
			cin>>no[j].s>>no[j].ddl>>no[j].cost;
		}
		sort(no+1,no+n+1);
		funcc();	
	}
}在这里插入代码片
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值