集训第一周:DP入门和01背包模板(萌新代码:杭电2041,杭电2084)

南昌理工acm

动态规划入门

刷了一些题,举俩例子:

(1) 超级楼梯(杭电 2041)
杭电 2041
*Problem Description
有一楼梯共M级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第M级,共有多少种走法?

Input
输入数据首先包含一个整数N,表示测试实例的个数,然后是N行数据,每行包含一个整数M(1<=M<=40),表示楼梯的级数。

Output
对于每个测试实例,请输出不同走法的数量

Sample Input
2
2
3

Sample Output
1
2*

这类问题可以用斐波那契(Fibonacci)数列进行

//dp数组,用以保存已经计算过的结果
//dp[n]记录F(n)的结果,dp[n]= -1表示没有计算过
int fib(int n){
  if ( n == 1 || n==2 ) return 1;
  if ( dp[n]  != -1 ) return dp[n];
  else  {
      dp[n] =  fib(n-1) + fib(n-2);  
      return  dp[n];
      }
}

本题AC代码:

#include<stdio.h>
int a[41];
int fun(int i);
int main()
{
    int n;
    scanf("%d",&n);
    while(n--)
    {
        int m,f;
        scanf("%d",&m);
        f=fun(m);
        printf("%d\n",f);
    }
 }
 int fun(int i)
{
    a[41]={0};
    if(i==1||i==2) return 1;
    if(a[i]!=0) return a[i];
    else{
        a[i]=fun(i-1)+fun(i-2);
    }
    return a[i];
}

(2)数塔(杭电 2084)
杭电 2084
*在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:
在这里插入图片描述
有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
已经告诉你了,这是个DP的题目,你能AC吗?

Input
输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。

Output
对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。

Sample Input
1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

Sample Output
30*

这是一经典的动态规划问题
做题方法有 递推 递归 记忆化搜索

递推:

void solve ()
{
int i , j;
for(j = 1; j <= n; j ++) d[n][j ] = a[n][j];
for(i = n -1; i >= 1; i - -)
for(j = 1; j <= i; j ++)
d[i][j ] = a[i][j ] + max (d[i +1][ j], d[i +1][ j +1]);
}

时间复杂度为:时间复杂度O(n^2)

递归:

int solve ( int i , int j)
{
if  (i == n)  return a[i][j];  else 
return a[i][j] + max( solve (i+1,j), solve (i+1 , j +1));
}

太多重复运算

记忆化搜索:

// initially , all d[i][j] are -1
int solve ( int i , int j)
{
if( i == n ) return a[i][j];
if(d[ i ][ j ] >= 0) return d[i][j];
d[i][j] = a[i][j]+max(solve ( i+1,  j ), solve ( i+1 , j +1) );
return d[i][j];
}

时间复杂度:时间复杂度O(n^2)
不必事先确定各状态的计算顺序

AC代码(递推)

#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
	int c,n,i,j,k;
	int dp[101][101],a[101][101];
	cin>>c;
	while(c--)
	{
		cin>>n;
		for(i=0;i<=n;i++)
		{
			for(j=1;j<=i;j++)
			{
				cin>>a[i][j];
			}
		}
		for(j=1;j<=n;j++) dp[n][j]=a[n][j];
		for(i=n-1;i>=1;i--)
		{
			for(j=1;j<=i;j++)
			{
				dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
			}
		}
		cout<<dp[1][1]<<endl;
	}
 } 

背包

01背包模板
还没做题,刚搞懂模板

#include<bits/stdc++.h>
using namespace std;
int f[100];//背包容积
int V;//背包容积
int n;//物品数
int w[100],c[100];//物品价值&体积
int work()
{
  for(int i=1;i<=n;++i)
    for(int v=V;v>=c[i];--v)
      f[v]=max(f[v],f[v-c[i]]+w[i]);
  return f[V];  

}
int main()
{





}

百度之星

另外搞懂了一些比赛的题目,以百度之星为例
拿周日(7月26号初赛三)的一题举个例子

1003 Permutation
1003
Problem Description
一开始有 n 个数,他们按 1…n1…n 的顺序排列,要求交换最多 m 对数字(同一个数字可以参与多次交换),使得逆序对数目最大。对于一个序列 A,如果存在正整数 i, j使得 1≤i<j≤n 而且 A[i]>A[j],则 <A[i],A[j]> 这个有序对称为 A的一个逆序对。
Input
第一行一个正整数 )test (1≤test≤100000) 表示数据组数。
对于每组数据,一行两个整数n,m (1≤n≤1000000,0≤m≤1000000) 表示数字个数和最多可以交换的数字对数。
Output
对于每组数据,一行一个整数表示答案。
Sample Input
6
1 1
2 0
2 1
3 1
4 1
4 2
Sample Output
0
0
1
3
5
6

解题方法:
这一题不能直接根据题意设数组去做,需要找公式规则

当 m≥⌊n/2⌋ 时,一定能变成 n…1,这时逆序对数最大。否则我们依次交换 1 和 n,2 和 n-1,…,一共交换 m 对。

(1) 直接设数组做,会超空间,无论数据开多大都会超空间
会出现:
Runtime Error
(ACCESS_VIOLATION)

#include<iostream>

using namespace std;
const long long maxn=1000000000000001;
long long test,n,m;
long long a[maxn];
int main()
{
	long long i,j,k;
	cin>>test;
	while(test--)
	{
		cin>>n>>m;
		for(i=0;i<n;i++)
		{
			a[i]=i+1;
		}
		if(n>1)
		{
			for(i=0,j=n-1;i<=m-1;i++,j--)
		    {
		        k=a[i];
		  	    a[i]=a[j];
		        a[j]=k;
		    }
		}
	long long count = 0;
    for(i=0;i<n-1;i++)
    {
    	for(j=i+1;j<n;j++)
            if(a[i]>a[j]) count+= 1;
	}
    cout<<count<<endl;
	}
}

AC代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

typedef long long ll;

int n, m;

ll cal(ll x) {
    return x * (x + 1) / 2;
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        if (m >= n / 2) printf("%lld\n", cal(n - 1));
        else {
            ll ans = cal(n - 1) - cal(n - 1 - m) + cal(m - 1);
            ans += 1LL * (n - m * 2) * m;
            printf("%lld\n", ans);
        }
    }
    return 0;
}

这一周还刷了不少水题,就不举例了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值