洛谷P1564 膜拜【DP】【黄】

Date:2022.02.20
题目描述
神牛有很多…当然…每个同学都有自己衷心膜拜的神牛.
某学校有两位神牛,神牛甲和神牛乙。新入学的 n 位同学们早已耳闻他们的神话。
所以,已经衷心地膜拜其中一位了。现在,老师要给他们分机房。但是,要么保证整个机房都是同一位神牛的膜拜者,或者两个神牛的膜拜者人数差不超过 m。另外,现在 n 位同学排成一排,老师只会把连续一段的同学分进一个机房。老师想知道,至少需要多少个机房。
输入格式
输入文件第一行包含两个整数 n 和 m。
第 2 到第 (n + 1) 行,每行一个非 1 即 2 的整数,第 (i + 1) 行的整数表示第 i 个同学崇拜的对象,1 表示甲,2 表示乙。
输出格式
输出一个整数,表示最小需要机房的数量。
输入输出样例
输入 #1复制
5 1
2
2
1
2
2
输出 #1复制
2
说明/提示
数据规模与约定
对于 30% 的数据,保证 1≤n,m≤50。
对于 100% 的数据,保证 1≤n,m≤2500。

思路①:
f [ i ] f[i] f[i]:前 i i i个同学至少分到几个机房。
我们规定 s u m [ i ] [ k ] : sum[i][k]: sum[i][k]: i i i个元素中有多少个 k k k。其中 k ∈ [ 1 , 2 ] k\in[1,2] k[1,2]。那么有以下三种状态时可能面临存在分段与否的抉择:
a b s ( ( s u m [ i ] [ 1 ] − s u m [ i − 1 ] [ 1 ] ) − ( s u m [ j − 1 ] [ 2 ] − s u m [ j − 1 ] [ 2 ] ) ) < = m : [ j , i ] abs((sum[i][1]-sum[i-1][1])-(sum[j-1][2]-sum[j-1][2]))<=m:[j,i] abs((sum[i][1]sum[i1][1])(sum[j1][2]sum[j1][2]))<=m[j,i]这一段元素 1 1 1 2 2 2的数量差绝对值 < = m <=m <=m
s u m [ i ] [ 1 ] − s u m [ j − 1 ] [ 2 ] = = 0 : [ j , i ] sum[i][1]-sum[j-1][2]==0:[j,i] sum[i][1]sum[j1][2]==0[j,i]这一段只含元素 1 1 1
s u m [ i ] [ 2 ] − s u m [ j − 1 ] [ 2 ] = = 0 : [ j , i ] sum[i][2]-sum[j-1][2]==0:[j,i] sum[i][2]sum[j1][2]==0[j,i]这一段只含元素 2 2 2
而面临是否分段时,状态转移方程为: f [ i ] = m i n ( f [ i ] , f [ j − 1 ] + 1 ) ; f[i]=min(f[i],f[j-1]+1); f[i]=min(f[i],f[j1]+1);
代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 3010;
typedef long long LL;
LL n,m,k,f[N],sum[N][3];
int main()
{
    cin>>n>>m;memset(f,0x3f,sizeof f);f[0]=0;f[1]=1;//第一个元素必为一段
    for(int i=1;i<=n;i++)
    {
        LL x;cin>>x;
        sum[i][x]=sum[i-1][x]+1;
        sum[i][!(x-1)+1]=sum[i-1][!(x-1)+1];
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
            if(abs((sum[i][1]-sum[j-1][1])-(sum[i][2]-sum[j-1][2]))<=m || (sum[i][2]-sum[j-1][2]==0) || (sum[i][1]-sum[j-1][1]==0))//这里第一个表达式很妙。
                f[i]=min(f[i],f[j-1]+1);
    cout<<f[n];
    return 0;
}

思路②:一个很妙的简化思路,将所有 2 2 2改写为 − 1 -1 1,题目则转变为找每段的和 < = m <=m <=m的情况下最少分为多少段。和上面一样的思路,令 s u m [ i ] sum[i] sum[i]表示前 i i i个元素的和,只有当以下两种情况时面临分段与否的抉择:
a b s ( s u m [ i ] − s u m [ j − 1 ] ) < = m : [ j , i ] abs(sum[i]-sum[j-1])<=m:[j,i] abs(sum[i]sum[j1])<=m[j,i]这一段 1 1 1 − 1 -1 1的数量差 < = m <=m <=m
a b s ( s u m [ i ] − s u m [ j − 1 ] ) = = i − j + 1 : [ j , i ] abs(sum[i]-sum[j-1])==i-j+1:[j,i] abs(sum[i]sum[j1])==ij+1[j,i]这一段只含 1 1 1 或 只含 − 1 -1 1
代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 3010;
typedef long long LL;
LL n,m,k,f[N],sum[N];
int main()
{
    cin>>n>>m;memset(f,0x3f,sizeof f);f[0]=0;f[1]=1;//第一个元素必为一段
    for(int i=1;i<=n;i++)
    {
        LL x;cin>>x;
        if(x==1) sum[i]=sum[i-1]+1;
        else sum[i]=sum[i-1]-1;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
            if(abs(sum[i]-sum[j-1])<=m || abs(sum[i]-sum[j-1])==i-j+1) 
                f[i]=min(f[i],f[j-1]+1);
    cout<<f[n];
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值