[雅礼集训]arg(三进制状压dp)

6 篇文章 0 订阅
0 篇文章 0 订阅

题目描述:给出一个长度为 m的序列A, 请你求出有多少种1……n的排列, 满足A是它的一个LIS. (1<=m<=n<=15)

题面异常的简洁,身为蒟蒻我也是第一次接触这种三进制状压dp(考场上当然只打了暴力

稍微转化一下问题,就是求序列A在1……n的排列中并且n的lis等于m;

看到15的数据范围再加上计数问题很容易就能想到状压dp,而我考试时只往二进制方面去想了,甚至想去设一个n^3*2^n的状态。。。

首先考虑平常怎么做最长上升子序列问题,一般的nlog算法是有一个f数组(因为之后还有一个dp数组所以先起名叫f,这好像也算是dp套dp?),f[i]表示长度为i的子序列里末尾最小能是多少,然后就能二分nlog出解了;

然后很容易就能想到一个性质,这个dp数组里在任何时刻都是单调不降的,而且如果是求排列中的,那也一定是递增的;

所以考虑利用这种性质,设dp[sta]为当前的状态下的方案数,在三进制下,如果第j位为0,那么j还没有被选,如果第j位为1,那么j是在求lis过程中处于f数组中,如果第j位为2那么就不在f数组中;
然后就类似lis的做法每次找f数组中第一个比当前要插入的数小的进行插入;
!!注意,这里处于f数组中必须状态设为1,因为有可能当前插入的数会取代其中一个f数组中的数,这时候要考虑dp无后效性的问题,只能进行sta的加法操作,所以不能由2减回1,所以只能由1加到2.。。

这样,如果当前三进制状态下有cnt个1,那么当前状态的lis长度就为cnt;
当前状态都不为0时,就可以计入答案;
而当前如果cnt等于m,则不能再继续使其lis增加;

这样看起来似乎复杂度是3^n*n^2的,但是由于很多状态不满,实际能跑过;

具体转移细节见代码,感觉只要理解这个状态的设置转移就很好写;

#include<cstdio>
#include<algorithm>
#include<cstring>
#define LL long long
LL dp[14348907];
int n,m;
int id[16];
int v[16];
int zt[16];
int st[16];
LL ans=0;
void write(int ta)//输出当前状态在3进制下的表示,调试用的qwq
{
    int a[16];
    for(int i=1;i<=n;i++){
        a[i]=ta%3;
        printf("%d ",a[i]);
        ta/=3;
    }
        printf("\n");
}
int main()
{
    freopen("arg.in","r",stdin);
    freopen("arg.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d",&v[i]);
        id[v[i]]=i;
    }
    st[0]=1;
    dp[0]=1;
    for(int i=1;i<=15;i++)st[i]=st[i-1]*3;
    for(int sta=0;sta<st[n];sta++){
        if(!dp[sta])continue;
        int ta=sta;
        int cnt=0,num=0;// cnt:当前状态被选了的数目 num:当前状态中属于最长上升子序列f数组中的数 
        for(int i=1;i<=n;i++){
            zt[i]=ta%3;
            ta/=3;
            if(zt[i])cnt++;
            if(zt[i]==1)num++;
        }
        if(cnt==n){
            ans+=dp[sta];
            continue;
        }
        for(int i=1;i<=n;i++)if(zt[i])continue;
        else{
            if(id[i]>1&&!zt[v[id[i]-1]])continue;
            int tot=0;//tot:当前数插进去后f数组中之前有多少比它小的 
            for(int j=1;j<i;j++){
                if(!zt[j])continue;
                else if(zt[j]==1)tot++;
            }
            if(tot==num){
                if(tot==m)continue;//lis长度不能超过m
                int ns=sta+st[i-1];
                dp[ns]+=dp[sta];
            }else{
                int ns=sta+st[i-1];
                for(int j=i+1;j<=n;j++)if(zt[j]==1){
                    ns+=st[j-1];//替换掉f数组中一个不优的数
                    break;
                }       
                dp[ns]+=dp[sta];
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值