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; }

浙公网安备 33010602011771号