第六次总结

这一周我想写几个老师之前讲过的但是对于我来说理解起来有困难的题目,希望在之后如果多回来看看是否能够有不同的收获。
1.摆渡车
有n个人分别在t1∼tn 的时间到达,一辆摆渡车要把这些人送到另外一个地方,摆渡车来回一次要m的时间单位。求把这些人都送到的最短时间。

思路:
f[i][j]表示第i个人等待时间为j的时候前i个人的最小等待时间.。
由题可知,若车停了m分钟,那么不如先送走一批人(即便当时没人),所以停车时间T1<m。设一个人等到车回来的时间为T2。因为车往返一次只需m分钟,所以T2<m。综上,一个人等车的时间j=T1+T2<2m

若下一个人能赶上当次车(即t[i]+j>=t[i+1]), 则将f[i][j]+t[i]+j-t[i+1]转移至f[i+1][t[i]+j-t[i+1]];
若下一个人在当次车回来之前开始等车(即t[i]+j+m>=t[i+1]), 则枚举车回来后停的时间k, 用f[i][j]+t[i]+j+m+k-t[i+1]转移f[i+1][t[i]+j+m+k-t[i+1]];
若下一个人在当次车回来之后才到(即t[i]+j+m<t[i+1]),则枚举下 一个人等待的时间k,并用f[i][j]+k转移f[i+1][k];

#include<bits/stdc++.h>
using namespace std;
int inf=0x3f3f3f3f;
int n,m,ans=inf;
int t[550],f[550][550];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		scanf("%d",&t[i]);
	sort(t+1,t+1+n);
	for(int j=0;j<2*m;j++)
		f[1][j]=j;
	for(int i=2;i<=n;i++)
		for(int j=0;j<2*m;j++)
			f[i][j]=inf;

    for(int i=1;i<n;i++)
		for(int j=0;j<2*m;j++)  //等车的时间
			if(f[i][j]<inf)
			{
				int lt=t[i]+j;
				int bt=lt+m;
				if(lt>=t[i+1])   //第i+1个人跟i同一辆车
	            f[i+1][lt-t[i+1]]=min(f[i+1][lt-t[i+1]],f[i][j]+lt-t[i+1]);
				if(bt>=t[i+1])
					for(int k=0;bt+k-t[i+1]<2*m;k++)
	            f[i+1][bt+k-t[i+1]]=min(f[i+1][bt+k-t[i+1]],f[i][j]+bt+k-t[i+1]);

				if(bt<t[i+1])
					for(int k=0;k<2*m;k++)	
					f[i+1][k]=min(f[i+1][k],f[i][j]+k);
			}
	   for(int j=0;j<2*m;j++)
		ans=min(ans,f[n][j]);
	cout<<ans;
	return 0;
}


2.复制书稿
假设有M本书(编号为1,2,…M),想将每本复制一份,M本书的页数可能不同(分别是P1,P2,…PM)。
任务:将这M本书分给K个抄写员(K<=M) 每本书只能分配给一个抄写员进行抄写,而每个抄写员所分配到的书必须是连续顺序的。
复制工作是同时开始进行的,并且每个抄写员复制一页书的速度都是一样的。所以,复制完所有书稿所需时间取决于分配得到最多工作的那个抄写员的复制时间。
试找一个最优分配方案,使分配给每一个抄写员的页数的最大值尽可能小。

思路:
设dp[i][j]表示前i本书由j个人复制所需要的最少时间,有状态转移方程 dp[i][j]=min(dp[i][j],max(dp[v][j-1],sum[v+1][i]))其中1<=i<=m,1<=j<=k,j-1<=v<=i-1,sum[v+1][j]表示第v+1本书到第i本书的页数之和。

#include<iostream>
#include<cstring>
using namespace std;
const int MAXN=510;
int sum[MAXN],path[MAXN],dp[MAXN][MAXN];
int main()
{
    int m,k,i,j,v,ca,p,t;
    scanf("%d",&ca);
    while(ca--)
        {
            scanf("%d%d",&m,&k);
            for(sum[0]=0,i=1;i<=m;i++)
            {
                scanf("%d",&p);
                sum[i]=sum[i-1]+p;
            }
              memset(dp,-1,sizeof(dp));
             for(dp[0][0]=0,i=1;i<=m;i++)
              for(j=1;j<=i&&j<=k;j++)
                {
                    if(j==1)
                       dp[i][j]=sum[i];
                    else
                        for(v=j-1;v<=i-1;v++)
                    {
                        t=max(dp[v][j-1],sum[i]-sum[v]);
                        if(dp[i][j]==-1 || t<=dp[i][j])
                        dp[i][j]=t;
                    }
                 }
        }
}


3.最大子段和问题
描述:给定 N (1 <= N <= 1000000) 个绝对值不超过 32768 的整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[N],求从该序列中取出 M 个连续不相交子段,使这 M 个子段的和最大。如果子段全都是负数则最大子段和也为负数。
输入:有一个正整数 M 和 一个正整数 N,后面紧跟 N 个绝对值不大于 32768 的整数
输出:最大 M 子段和
样例输入:
2 6 -1 4 -2 3 -2 3
样例输出:
8

思路:
令 dp[i][j] 表示在前i个数中选取j段且第i个数在最后一组中的最大子段和那么现在对于第i个数有两种决策
第i个数和第i-1个数连接成一段
dp[i][j] = dp[i-1][j] + a[i]
第i个数自己单独做一段.那么前面就需要有 j-1段
dp[i][j] = max{dp[k][j-1]|j-1<=k<i}+a[i]
就有了状态转移方程
dp[i][j]=max(dp[i-1][j],max{dp[k][j-1]|j - 1<= k < i})+a[i];

#include <iostream>
using namespace std;
int dp[1000005],num[1000005],n,m;
int max(int a,int b)
{
	return a>b?a:b;
}
int solve()
{
	for(int i=1;i<=m;i++)
	{
		int step=0;
		for(int k=1;k<=i;k++)
			step+=num[k];
		dp[n]=step;
		for(int j=i+1;j<=n;j++)
		{
			step=max(step,dp[j-1])+num[j];
			dp[j-1]=dp[n];
			dp[n]=max(step,dp[n]);
		}
	}
	return dp[n];
}
int main()
{
	while(scanf("%d%d",&m,&n)!=EOF)
	{
		for(int i=1;i<=n;i++)
		{
			dp[i]=0;
			scanf("%d",&num[i]);
		}
		printf("%d\n",solve());
	}
	return 0;
}




4.整数划分
将N分为若干个不同整数的和,有多少种不同的划分方式,例如:n = 6,{6} {1,5} {2,4} {1,2,3},共4种。由于数据较大,输出Mod 10^9 + 7的结果即可。
Input
输入1个数N(1 <= N <= 50000)。
Output
输出划分的数量Mod 10^9 + 7。
Sample Input
6
Sample Output
4

思路
把dp[i][j]看作整数i的划分为j个不同的数字的方案数。
dp[i][j] = dp[i-j][j] + dp[i-j][j-1]
前者理解为从i-j的数字划分为j个不同的数字,那么如果想到达i只需每一个数字加1即可;
后者是数字i-j划分为j-1个数字,我们把每一个加1之后再加上最后的1就是dp[i][j]。

#include<iostream>
using namespace std;
const int maxn=50005;
const int mod=1e9+7;
int n;
int dp[maxn][400];
int main()
{
   
    scanf("%d",&n);
    dp[0][0] = 1;
    for(int i=1;i<=n;i++)
       {
        for(int j=1;j*j<=i*2;j++)
           {
            dp[i][j]=(dp[i-j][j]+dp[i-j][j-1])%mod;
        
           }
       }
    int ans=0;
    for(int i=1;i<=n;i++)
       {
        ans+=dp[n][i];
        ans%=mod;
       }
    printf("%d\n",ans);
    return 0;
}


小zc现在有三个字符串,他想知道前两个字符串能不能生成第三个字符串,生成规则如下:第一个串的每个字符都可以往第二个串的任意位置插入(包括首尾位置),但必须保证来源于第一个串中的字符在生成后的串中的相对顺序不可以改变。
举个例子:
String A: cu
String B: mt
那么,A和B可以生成的所有字符串是{cumt,cmut,cmtu,mcut,mctu,mtcu},而不能生成ucmt,因为uc来源于A串,但改变了A中字符原来的相对顺序。
但小zc觉得这个问题太简单,于是他想让你计算对于给定的A, B, C串,共有多少种方案能够生成C串。方便起见,你只需要输出最后答案对1000000007取模的值。

#include<iostream>
#include<cstring>
typedef long long ll;
using namespace std;
const int mod = 1e9+7;
const int N = 3000;
int l1,l2,l3,i,dp[N][N];
char s1[N],s2[N],s3[N*2];

void solve() 
{
    memset(dp,0,sizeof(dp));
    dp[0][0] =1;
    rep(i,1,l1) 
    {
        if(s1[i] == s3[i])
            dp[i][0] = dp[i-1][0];
    }
    rep(i,1,l2)
    {
        if(s2[i] == s3[i])
            dp[0][i] = dp[0][i-1];
    }
rep(i,1,l1)
{
        rep(j,1,l2) 
        {
            if(s1[i] == s3[i+j])
                {
                dp[i][j] = (dp[i][j] + dp[i-1][j]) %mod;
                }
            if(s2[j] == s3[i+j]) 
            {
                dp[i][j] = (dp[i][j] + dp[i][j-1]) %mod;
            }
        }
}
    cout << dp[l1][l2] <<endl;
}

int main(){
    int T; scanf("%d",&T);
    while (T--) {
        scanf("%s %s %s",s1+1,s2+1,s3+1);
        l1 = strlen(s1+1);
        l2 = strlen(s2+1);
        l3 = strlen(s3+1);
        solve();
    }
    return 0;
}



6.线段覆盖
数轴上有n条线段,线段的两端都是整数坐标,坐标范围在0~1000000,每条线段有一个价值,请从n条线段中挑出若干条线段,使得这些线段两两不覆盖(端点可以重合)且线段价值之和最大。n<=1000
输入描述 Input Description第一行一个整数n,表示有多少条线段。
接下来n行每行三个整数, ai bi ci,分别代表第i条线段的左端点ai,右端点bi(保证左端点<右端点)和价值ci。
输出描述 Output Description输出能够获得的最大价值
样例输入 Sample Input3
1 2 1
2 3 2
1 3 4
样例输出 Sample Output4

思路:
按照右区间坐标排个序再来DP,DP[i]=max{DP[j],1<=j<i}+第i条线段的价值

#include<iostream>
#include<cstring>
#include<algorithm>
#define get(x) scanf("%d", &x)
#define put(x) printf("%d", x)
#define cls(x) memset(x, 0, sizeof(x))
using namespace std;

int n, res;
int dp[1010];
struct L
{
    int l,r,c;
}a[1010];

bool cmp(L a,L b)
{
    return a.r < b.r;
} 
int main()
{
    get(n);
    for (int i=1;i<=n;i++)
    {
        get(a[i].l),get(a[i].r),get(a[i].c);
    }
    sort(a+1,a+n+1,cmp);
    cls(dp);
    for (int i=1;i<=n;i++)
    {
        int maxx=0;
        for (int j=1;j<i;j++)
            if (a[i].l>=a[j].r)
                maxx=max(maxx,dp[j]);
        dp[i]=maxx+a[i].c;
        res=max(res,dp[i]);
    }
    put(res);
    return 0;
}


7
现在有F束不同品种的花束,同时有至少同样数量的花瓶被按顺序摆成一行,其位置固定于架子上,并从1至V按从左到右顺序编号,V是花瓶的数目(F≤V)。花束可以移动,并且每束花用1至F的整数唯一标识。标识花束的整数决定了花束在花瓶中排列的顺序,如果i<j,花束i必须放在花束j左边的花瓶中。每个花瓶只能放一束花。如果花瓶的数目大于花束的数目,则多余的花瓶空置。 每一个花瓶都具有各自的特点。因此,当各个花瓶中放入不同的花束时,会产生不同的美学效果,并以一美学值(一个整数)来表示,空置花瓶的美学值为零。为取得最佳美学效果,必须在保持花束顺序的前提下,使花束的摆放取得最大的美学值。请求出具有最大美学值的一种摆放方式。
每一个花瓶都具有各自的特点。因此,当各个花瓶中放入不同的花束时,会产生不同的美学效果,并以一美学值(一个整数)来表示,空置花瓶的美学值为零。为取得最佳美学效果,必须在保持花束顺序的前提下,使花束的摆放取得最大的美学值。请求出具有最大美学值的一种摆放方式。

#include <iostream>
using namespace std;

int dp[105][105];
int a[105][105];

int max(int a,int b)
{
	return a>b?a:b;
}
int main()
{
	int n,m;
	cin>>n>>m;
int i,j;
	for(i=1;i<=n;i++)
		for (j=1;j<=m;j++)
			cin>>a[i][j];
		for(i=1;i<=n;i++)
			for (j=i;j<=m;j++)
			{
				dp[i][j]=dp[i-1][j-1]+dp[i][j];
				if(j>i)
					dp[i][j]=max(dp[i][j],dp[i][j-1]);
			}
			cout<<dp[n][m]<<endl;
			return 0;
}

这里面的题都对于我来说需要加深理解,所以我以后会经常多回来看看这些题目,以便加深理解并且将它更好的利用起来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值