DP好题#1.1

原题链接

首先根据数据范围会发现这道题相当的状压dp,而且显然2^M应当作为一维的状态,然而2^20已经够大了,很有可能状态仅有这一维。

下面开始找性质:

会发现在最终形成的末状态中是m个“块”的一种排列,显然排列方式有M!种,每种排列方式可以用N的复杂度判断其“贡献”,所以总复杂度O(N×M!),只能过前面四个。考虑优化,优化方向有两个,一个是优化dfs,一个是优化判断方式。前者可以通过链表优化,后者可以通过一种特殊的前缀和优化(之所以不用正常的前缀和是因为,正常的前缀和复杂度略高),这样的话可以得到70pts

#include<bits/stdc++.h>
using namespace std;
#define re register
#define fo1(l,r) for(re int i=l;i<=r;++i)
#define fo2(l,r) for(re int j=l;j<=r;++j)
#define fo3(l,r) for(re int k=l;k<=r;++k)
#define fo4(l,r) for(re int tt=l;tt<=r;++tt)
#define fo(l) for(re int i=h[l],go;i;i=x[i].last)
#define inf 0x3f3f3f3f
#define INF 0x7fffffffffffffff
#define LL long long
#define itn int
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<3)+(x<<1)+ch-48;
        ch=getchar();
    }
    return x*f;
}
const itn M=1e5+10;
int n,m;
itn cnt[21],a[M];
itn sum[21][M],st[21];
struct node
{
    node *nxt,*pre;
    int data;
};
node *h;
itn ans,ANS,num=1;
inline int max2(int xx,int yy)
{
    return xx>yy?xx:yy;
}
inline void dfs(int now)
{//ANS为保留的偶像总数,应该最多 
    if(now==m+1)
    {
        ANS=max2(ANS,ans);
        return;
    }
    node *p;
    p=h->nxt;
    while(p->data!=-1)
    {
//        printf("目前处理第%d个队,处在位置%d,选择%d队,有%d需要保留\n",now,num,p->data,sum[p->data][num]);
        ans+=sum[p->data][num];
        num+=cnt[p->data];
        p->pre->nxt=p->nxt;
        p->nxt->pre=p->pre;
        dfs(now+1);
        num-=cnt[p->data];
        ans-=sum[p->data][num];
        p->pre->nxt=p;
        p->nxt->pre=p;
        p=p->nxt;
    }
    return;
}
int main()
{
    n=read();m=read();
    fo1(1,n)
    {
        a[i]=read();++cnt[a[i]];
        if(a[cnt[a[i]]]==a[i])
            ++st[a[i]];
    }
    fo1(1,m)
    {
        itn now=st[i];
        fo2(1,n-cnt[i]+1)
        {
            sum[i][j]=now;
            if(a[j]==i)
                --now;
            if(a[j+cnt[i]]==i)
                ++now;
        }
    }
    node *r,*ls;
    h=new node;
    r=new node;
    r->data=1;
    h->nxt=r;
    r->pre=h;
    fo1(2,m)
    {
        ls=new node;
        ls->data=i;
        r->nxt=ls;
        ls->pre=r;
        r=ls;
    }
    ls=new node;
    ls->data=-1;
    ls->pre=r;
    ls->nxt=h;
    r->nxt=ls;
    h->pre=ls;
    
    dfs(1);
    ANS=n-ANS;
    printf("%d",ANS);
    return 0;
}

开始思考状压dp的正解,这道题带给我的收获就在这里,此题的状态设计方式不是那么直观。

我们思考一下普遍的状压dp的状态设计含义(做题太少了)。

用二或三进制表现一行或一个矩阵上的点的某个状态(一般是“有/无”对应了1/0,大部分是二进制)

但状压dp的状态显然不是那么局限,它可以存储许多局部具有有限状态的整体信息(大部分是两个)

本题状态设计为:第i位表示第i个乐队是否已经被处理过了。

显然我们状态转移是每次多加个1,也就是说状态中1的个数划分了dp的阶段。很显然此时我们的一个状态存储的不再是某种特殊排列方式的解,而是很多种可能的方式中的最优解。

如f[110]实际上就包含了“先2后3”与“先3后2”两种排列方式的较优解,从这里我们可以看出,状态转移多加的“1”可以理解为在末尾添加1

由于以1的个数划分了dp的阶段,这就给状态的遍历带来了问题,因为寻常的状压dp,我们不在乎状态的遍历方式,但这里我们需要保证当前状态任何一种删除1可以形成的新状态一定在该状态前处理了,所以我们只能从0递增枚举状态

显然一种状态所要转移的状态最多有m种,因此总复杂度为O(M×(2^M))

#include<bits/stdc++.h>
using namespace std;
#define re register
#define fo1(l,r) for(re int i=l;i<=r;++i)
#define fo2(l,r) for(re int j=l;j<=r;++j)
#define fo3(l,r) for(re int k=l;k<=r;++k)
#define fo4(l,r) for(re int tt=l;tt<=r;++tt)
#define fo(l) for(re int i=h[l],go;i;i=x[i].last)
#define inf 0x3f3f3f3f
#define INF 0x7fffffffffffffff
#define LL long long
#define itn int
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<3)+(x<<1)+ch-48;
        ch=getchar();
    }
    return x*f;
}
const itn M=1e5+10;
int n,m;
itn cnt[21],a[M],SUM;
itn sum[21][M],st[21];
itn f[1100000];
inline int max2(itn xx,int yy)
{
    return xx>yy?xx:yy;
}
int main()
{
    n=read();m=read();SUM=(1<<m)-1;
    fo1(1,n)
    {
        a[i]=read();++cnt[a[i]];
        if(a[cnt[a[i]]]==a[i])
            ++st[a[i]];
    }
    fo1(1,m)
    {
        itn now=st[i];
        fo2(1,n-cnt[i]+1)
        {
            sum[i][j]=now;
            if(a[j]==i)
                --now;
            if(a[j+cnt[i]]==i)
                ++now;
        }
    }
    fo1(1,SUM)
    {
        itn ss=1;
        fo2(0,m-1)
        {
            if((i>>j)&1!=0)
                ss+=cnt[j+1];
        }
        fo2(0,m-1)
        {
            if((i>>j)&1!=0)
            {
                itn la=i-(1<<j);
                int now=ss-cnt[j+1];
                f[i]=max2(f[i],f[la]+sum[j+1][now]);
            }
        }
    }
    printf("%d",n-f[SUM]);
    return 0;
}

 

posted @ 2023-06-24 08:37  小鱼儿吼吼  阅读(17)  评论(0)    收藏  举报