JZOJ 5600. 【NOI2018模拟3.26】Arg

本文探讨了一类关于求解特定序列的问题,通过多种剪枝策略和动态规划方法,提出了解决方案,适用于长度不超过15的序列。文章详细介绍了暴力求解及优化策略,并给出了具体的代码实现。

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

题目

给出一个长度为 m 的序列 A, 请你求出有多少种 1…n 的排列, 满足 A 是它的一个 LIS.
对于 100% 的数据, 1mn151≤m≤n≤15.

解题思路

对于n12n≤12的,其实可以暴力,将能想到的剪枝想到。
①由于n12n≤12,可以通过O(n2)O(n2)的暴力将f[i]f[i]构造出来。很容易得到,在递归中假设现在递归地层数为xx,那么f[x]已知。如果f[x]>mf[x]>m,那么return。
②考虑反数组即o[a[i]]=io[a[i]]=i.相当于新构造出来的序列中AA的元素在新构造出来的序列中出现的顺序是不变的,维护一个指针,如果加入的新数跳过了本来应在它前面加的数,那么return。
③如果当前LIS的长度加上剩下的数的个数还小于m,则return。
如果n15
考虑有什么方法能够构造出LIS。考虑实时得出某个序列的办法。可以考虑用进制数作为状态的DP。
g[i]g[i]表示当前的上升子序列的长度为i的末尾那个数的最小值。
考虑操作一步对序列的影响。
那么新加入进来的数i必定替代刚好比i大的j。
比如1 3 4 5,加入2,则新序列变成1 2 4 5。
那么一个数有3种状态。0:没出现过。1:在序列中。2:被踢了。
显然对于现在的一个三进制状态ss,可以转换到状态s
BFS的注意事项
在BFS中,如果一个状态要更新其他状态,那么必须要保证它已经被更新完了。
在这里,如果i++地枚举,那么可以满足上述要求。
如果将图建出来,不仅要花很大的空间,而且不一定能满足上述要求。
自己试一试就知道了。

心得

对于n比较小的。数的状态有几个,可以考虑几进制的状态数。
碰到这种关于BFS的DP问题,考虑用什么方法不仅将图遍历,且满足DP的合法的更新状态的方法。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 20
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int i,j,k,l,n,m,x,y,z,wz,ans;
int mx,lim;
int a[N],b[N],f[20000000],o[N];
int _3[N],c[N];
bool p;
int main(){
    freopen("arg.in","r",stdin);
    freopen("arg.out","w",stdout);
    _3[1]=1;fo(i,2,17)_3[i]=_3[i-1]*3;
    scanf("%d%d",&n,&m);
    fo(i,1,m)scanf("%d",&a[i]),c[a[i]]=i;
    f[0]=1;
    fo(x,0,_3[n+1]-1)if(f[x]){
        fo(i,1,n)o[i]=(x/_3[i])%3;
        wz=1;b[0]=0;
        fo(i,1,n)if(o[i]==1)b[++b[0]]=i;
        mx=0;
        fo(i,1,n)if(o[i])mx=max(mx,c[i]);
        fo(i,1,n)if(!o[i]){
            while(i>b[wz]&&wz<=b[0])wz++;
            if(c[i]&&c[i]!=mx+1)continue;
            if(i<b[wz]&&wz<=b[0])f[x+_3[b[wz]]+_3[i]]+=f[x];
                else f[x+_3[i]]+=f[x];
        }
    }
    lim=_3[n+1]-1;
    fo(i,1,lim){
        fo(j,1,n)o[j]=(i/_3[j])%3;
        p=0;
        int c1=0,c2=0;
        if(!p)fo(j,1,m)if(!o[a[j]]){p=1;break;}
        if(!p){
            fo(j,1,n)if(o[j]==1)c1++;
                else if(o[j]==2)c2++;
            if(c1==m&&c1+c2==n)ans+=f[i];
        }
    }
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值