HDU 4455 Substrings dp

博客内容涉及动态规划算法的应用,具体是一个关于计算序列中长度为w的连续子串中不同数字个数之和的问题。通过递推公式`dp[i] = dp[i-1] - lnc[i-1] + sum`来更新状态,其中`lnc[i]`表示w=i的最后一个子串权值,`add[i]`表示一个数与其前面相同数的最近距离。给出了两组测试数据及对应的结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给定一个序列,个数为n。再给出一系列w;对于每个w,求序列中,所有长度为w的连续子串中的权值和,子串权值为子串中不同数字的个数。

dp[i]表示w=i时所求的答案。dp[1]=n,这个很容易知道,dp[2]中的子串就是删去dp[1]中最后一个子串,再每个子串加上其之后的那个数,以此类推。

对于dp[i-1]推dp[i],加上的那部分:只有当这个数与它前面同值数最短距离大于等于i时才会加权值,否则会重复而不加。

所以可以推出递推式:dp[i]=dp[i-1]-lnc[i-1]+sum。


所以关键点有二:

1.求要删去的最后一个子串的权值,这个for循环一遍就可以,lnc[i]表示w=i的最后一个子串权值。

2.难的就是求加上一个数后所加的权值:令add[i]表示一个数与它前面值相同的数的最近距离,这也能循环一遍存下来。递推求解时,加上sum


如:

3 3 3 4 2 3 3 5

3 3 4 2 3 3 5

3 4 2 3 3 5

4 2 3 3 5

2 3 3 5

3 3 5

3 5

5


给出两组测试数据:

input
8
3 3 3 4 2 3 3 5
8
1 2 3 4 5 6 7 8
output
8 11 13 14 13 10 7 4

input
9
3 2 4 3 2 4 3 2 4
9
1 2 3 4 5 6 7 8 9
output
9 16 21 18 15 12 9 6 3

代码如下:

#include<bits/stdc++.h>
#define CPY(A,B)memcpy(A,B,sizeof(A))
typedef long long LL;
typedef unsigned long long uLL;
const int MOD=1e9+7;
const int INF=0x3f3f3f3f;
const LL INFF=0x3f3f3f3f3f3f3f3fLL;
const double EPS=1e-9;
const double OO=1e20;
const double PI=acos (-1.0);
int dx[]= {0,1,0,-1};
int dy[]= {1,0,-1,0};
int gcd (const LL &a,const LL &b) {return b==0?a:gcd (b,a%b);}
using namespace std;
const int maxn=1e6+50;
int num[maxn],lnc[maxn],cx[maxn],add[maxn];
LL dp[maxn];
int main() {
    int n;
    while (~scanf ("%d",&n) &&n) {
        dp[1]=n;
        for (int i=1; i<=n; ++i) {scanf ("%d",&num[i]);}
        memset (cx,0,sizeof (cx) );
        lnc[1]=1; cx[num[n]]=1;
        //第一组最后一列个数为一,cx[num[n]]=1的意思就是num[n]这个数存在

        for (int i=2; i<=n; i++) {
            if (cx[num[n-i+1]]==0) {
                cx[num[n-i+1]]=1;//第一次遇到该数字
                lnc[i]=lnc[i-1]+1;//数字不重复,所以计数加一
            } else { lnc[i]=lnc[i-1]; }//忽略重复的数字
        }//存下每一回最后一组的不重复数字个数

        memset (cx,0,sizeof (cx) );
        memset (add,0,sizeof (add) );
        for (int i=1; i<=n; i++) {
            add[i-cx[num[i]]]++;
            //数字和在其前面一样的数字的最近距离
            cx[num[i]]=i;
            //记录当前数字的位置
        }

        int sum=n;
        for (int i=2; i<=n; i++) {
            dp[i]=dp[i-1]-lnc[i-1];
            //减掉最后一组
            sum-=add[i-1];
            dp[i]+=sum;
	    //加上新增加的部分
        }
        int Q; scanf ("%d",&Q);
        while (Q--) {
            int w; scanf ("%d",&w);
            printf ("%I64d\n",dp[w]);
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值