动态规划:线性dp、背包问题、区间4

三个串的最长公共子序列

给3个串,求其最长公共子序列,输出方案。|S|<=300。

令f[i][j][k]为第1个串的前i个字符与第2个串的前j个字符与第3个串的前k个字符的最长公共子序列长度。若a[i]==b[i]==c[i],则f[i][j][k]=f[i-1][j-1][k-1]+1,否则三个字符中一定有一个是没用的,即 f[i-1][j][k]、f[i][j-1][k]、f[i][j][k-1]三个值中取一个最大的。

输出方案怎么办?可以直接将最长公共子序列存下来。若a[i]==b[i]==c[i],则f[i][j][k]=f[i-1][j-1][k-1]+a[i],否则比较字符串之间的长度大小把长度最长的给f[i][j][k]。

最大子矩形

在一个矩形中找到一个子矩阵,该子矩阵和最大,输出最大和。

若是从左上角依次枚举至右下角,则时间复杂度O(n^4),n=100勉强还行。那n=300怎么办?

可以通过枚举当前矩形的最上面一行和最下面一行枚举完后,可以发现两行之间的元素可以按列打包成一行(就是按列加成一行,可以通过求列方向上的前缀和实现),然后问题就转化为在一行之中求最大子串和,则时间复杂度为O(n^3)。

[NOIP1999 普及组] 导弹拦截 - 洛谷

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是\le 50000≤50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

最多能拦截多少导弹:求最长不上升子序列

要配备多少套这种导弹拦截系统:最直观的方法就是求一次不上升子序列后将刚刚要打的导弹去掉,然后重复操作直到去掉了所有导弹。

但反例:6 1 7 3 2         错解:6 3 2/ 1/ 7        正解:6 1/ 7 3 2

可以发现如果某导弹后方还有比它更高的导弹,那就不能打到这个更高的导弹了,即最小需要打到最长上升子序列才可以打完。怎样证明是最长上升子序列长度即为答案呢?假设答案比最长上升子序列长度长,则必然要给数组引入一个新的元素。具体证明详见讲解。

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

[NOIP2004]合唱队形

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK,  则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

法一:枚举中间同学,从左向中间求最长上升子序列,从右向中间求最长上升子序列。

法二:f[i][0/1]表示第i名同学一定选,1代表已经走下坡路了,0表示还在上坡。f[i][0]=f[k][0]+1(k<i, ak<ai),f[i][1]=f[k][0]+1(第k名同学是中间的那名)或=f[k][1]+1(第k名同学不是中间的那名), k<i且ak>ai。

骨牌

令f[i][j]为前i个牌中使上-下的差值=j最小的翻转次数,则

f[i][j]=min(f[i-1][j-(a上-a下)], f[i-1][j-(a下-a上)]+1)。

登录—专业IT笔试面试备考平台_牛客网

Bitset优化背包

一共有n个数,第i个数为xi,xi可以取[li, ri]中任意一个值,设S=\sumxi^2,求S的种类数。

n,li,ri<=400

f[i][j]代表前i个数能否恰好等于j,f[i][j]=(f[i-1][j-xi^2]==1),时间复杂度10^10,普通循环被卡死。

f[i][j]是一个bool类型的数组,每一位要么是0要么是1,不如假设f[i-1]为一长串01串,f[i][j]=(f[i-1][j-xi^2]==1)中的j-xi^2就是将数组向左移动xi^2位啊!相当于将01串移动xi^2位。所以可以将原数组简化为bitset类型f[i]=f[i] | (  f[i-1]<<(x[i]^2)  )。另外,若bitset有1000位,则时间复杂度为1000/32,因为其内部为非常多个int拼接而成。

总结:当看到数据可以用01串表示,且上一位的影响总能够通过整体同样方式变化对应到这一位的值,那么可以考虑用bitset。

细节问题:bitset用法——头文件bitset,定义bitset<length> name;

#include<iostream>
#include<cstdio>
#include<bitset>
using namespace std;
typedef long long ll;
//scanf("",&);
const int inf = 0x3f3f3f, maxn=105;
int n,l,r;
bitset<1000100> f[maxn];
int main(){
	cin>>n;
	f[0][0]=1;
	for(int i=1;i<=n;++i){
		scanf("%d%d",&l,&r);
		for(int k=l;k<=r;++k){
			f[i]=f[i] | (f[i-1]<<(k*k));
		}
	}
	cout<<f[n].count();
	return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

[NOIP2005]过河

在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度)。坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数(包括S,T)。当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。

题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。1<=S<=T<=10,1<=M<=100,L<=10^9。

令f[i]表示青蛙在i时最少踩的石子数,则f[i]=min(f[i-s], f[i-1-s], ..., f[i-t])+1/0(加1还是加0取决于i-s等地方是否有石子),但桥太长了,数组开不下。之前背包中说,如果数组开不下可以用map存,前提是存在许多不存在的状态。但之后考虑到随着桥的长度不断增长,后面的点青蛙大概率都可以跳上去,所以map也不行。

我们观察到m<=100,这意味着石子的个数相对于桥的点数很少,说明平均来说石子与石子之间隔得很开。当桥的距离很长,说明青蛙在后面某一段可以到达桥上的任何位置。不考虑只能跳固定长度特殊情况(那样可以直接判断踩了多少石子),当某个点距离起点足够远(数学上证明为s*t-s-t),至少s*t绝对可以。例如s=9,t=10时,则到达90可以跳9次10或10次9;若要到91,只需要9次9,1次10。后面以此类推。所以说当两个石子的距离为1000,与到石子距离100没有区别。所以可以将石子从小到大排序,然后间隔大于100的全部改为100.

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const long long inf = 1e14;
const int N = 1e5 + 5;
const double eps = 1e-10;
const ll mod = 1e9 + 7;
int a[N];
int dp[5000005];
int vis[5000005];
int dist[5000005];
int main() {
  ios::sync_with_stdio(false), cin.tie(nullptr);
  int L;
  int S, T, n;
  cin >> L >> S >> T >> n;
  for(int i = 1; i <= n; i++) cin >> a[i];
  sort(a + 1, a + 1 + n);
  a[n + 1] = L;
  if(S == T) { // 特判
    int cnt = 0;
    for(int i = 1; i <= n; i++) {
      if(a[i] % T == 0) {
        cnt++;
      }
    }
    cout << cnt << "\n";
    return 0;
  }
  for(int i = 1; i <= n; i++) {
    dist[i] = (a[i] - a[i - 1]) % 2520; // 缩点
  }
  for(int i = 1; i <= n; i++) { // 缩点
    a[i] = a[i - 1] + dist[i];
    vis[a[i]] = 1; // 此处有石头
  }
  L = a[n];
  //cout << L << "\n";
  memset(dp, 0x3f, sizeof(dp));
  dp[0] = 0;
  for(int i = 1; i <= L + T; i++) {
    for(int j = S; j <= T; j++) {
      if(i - j >= 0)
        dp[i] = min(dp[i], dp[i - j] + vis[i]);
    }
  }
  int ans = 1e9;
  for(int i = L; i <= L + T - 1; i++) {
    ans = min(ans, dp[i]);
  }
  cout << ans << "\n";
  return 0;
}

Problem - 505C - Codeforces

周石群岛是由 30001 个小岛组成的群岛,位于 Yutampo 海中。这些岛屿沿一条线均匀分布,从西到东编号从 0 到 30000。众所周知,这些岛屿包含许多宝藏。Shuseki群岛共有n颗宝石,第i颗宝石位于pi岛。从0往下标大的地方跳,第一步跳d,如果上一步跳l,这一步可以跳l-1或l+1(距离必须>0),问能拿到多少宝藏。

令f[i][j]为到i时跳了j这个距离最多能拿到的宝藏数,则f[i][j]=max( f[i-j][j-1], f[i-j][j+1] )+a[i]。

此解法的问题在于,如果跳的距离一开始就为2w,数组开不下(但若从1开始最多走3w步,3w*3w,时间复杂度可以)。若步数不断增大,则(1+2+...+n)<=30000,即n(n-1)/2<=30000,估算n<200,即d的变化不会超过200,所以j最多在d的基础上 上下浮动250。所以用j表示与d的差值,如果为正就是d+j, 为负就是d-j,这样数组第二维开到500就够了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
int a[30005],dp[30005][605];
int main(){
    int n,d,i,j,v,ans,num;
    while(scanf("%d%d",&n,&d)!=EOF){            //直接就会想到dp[i][j]表示在第i个点当前这步为j米
        memset(a,0,sizeof(a));                  //但是时间和空间都不允许,但是按照每次加1走,最多
        memset(dp,-1,sizeof(dp));               //也就走了二百多步,所以第二维范围是[d-250,d+250]
        for(i=1;i<=n;i++){
            scanf("%d",&num);
            a[num]++;
        }
        dp[d][300]=a[d];                        //进行偏移,以300为d
        for(i=d;i<=30000;i++){
            for(j=1;j<=600;j++){
                if(dp[i][j]==-1)
                continue;
                v=j-300+d;
                if(i+v<=30000&&v>=1)            //按照三种情况转移
                dp[i+v][j]=max(dp[i+v][j],dp[i][j]+a[i+v]);
                if(i+v+1<=30000&&v+1>=1)
                dp[i+v+1][j+1]=max(dp[i+v+1][j+1],dp[i][j]+a[i+v+1]);
                if(i+v-1<=30000&&v-1>=1)
                dp[i+v-1][j-1]=max(dp[i+v-1][j-1],dp[i][j]+a[i+v-1]);
            }
        }
        ans=0;
        for(i=d;i<=30000;i++)
        for(j=1;j<=600;j++)
        ans=max(ans,dp[i][j]);
        printf("%d\n",ans);
    }
    return 0;
}

Problem - 510D - Codeforces

给出n个不同的数,取第i个数的代价为ci,求取出若干个数使得其最大公约数为1的最小代价。1<=n<=300,li<=10^9

令f[i][j]表示前i个数gcd=j的最小代价,则f[i][j]=min( f[i-1][j], f[i-1][k]+c[i](当k与a[i]的gcd=j时) ),由于其中的很多值不存在,所以可以使用map对f中的数据进行存储。

#include<bits/stdc++.h>
using namespace std;
const int maxn=303;
int val[maxn],cost[maxn];
map<int,int>mp;
queue<int>P;
int gcd(int a,int b) {
	if(b==0)return a;
	return gcd(b,a%b);
}
int main() {
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++)scanf("%d",&val[i]);
	for(int i=1; i<=n; i++)scanf("%d",&cost[i]);
	for(int i=1; i<=n; i++) {
		if(mp.find(val[i])!=mp.end()) {
			mp[val[i]]=min(mp[val[i]],cost[i]);
			continue;
		}
		mp[val[i]]=cost[i];
		P.push(val[i]);
	}
	while(!P.empty()) {
		int v=P.front();
		P.pop();
		for(int i=1; i<=n; i++) {
			int t=gcd(v,val[i]);
			if(mp.find(t)==mp.end()||mp[v]+cost[i]<mp[t]) {
				mp[t]=mp[v]+cost[i];
				P.push(t);
			}
		}
	}
	if(mp.find(1)==mp.end())printf("-1\n");
	else printf("%d\n",mp[1]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值