题目链接:https://acm.ecnu.edu.cn/contest/695/problem/C/
做法
我是状压 DP 做的,不难发现,我们可以把这一行被上一行占用的位置保存下来,然后转移。
显然如果 $x$ 转移到 $y$ ,那么 $x\And y=0$ ,而这是一个经典的 trick ,这样子转移的复杂度是 $O(3^m)$ 的,所以最终时间复杂度就是:$O(n3^m)$ 。
不过看题解说其实有效转移 $<6000$ ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| #include<bits/stdc++.h> #define N 310 #define M 160 using namespace std; struct node{ int a[26],len,fa; }tr[N];int tot,last; char st[M];int n; void add(int c){ int p=last;int np=last=++tot; tr[np].len=tr[p].len+1; for(;p && !tr[p].a[c];p=tr[p].fa)tr[p].a[c]=np; if(!p)tr[np].fa=1; else{ int q=tr[p].a[c]; if(tr[q].len==tr[p].len+1)tr[np].fa=q; else{ int nq=++tot; tr[nq].fa=tr[q].fa;tr[q].fa=nq; tr[nq].len=tr[p].len+1;memcpy(tr[nq].a,tr[q].a,sizeof(tr[q].a)); tr[np].fa=nq; for(;p && tr[p].a[c]==q;p=tr[p].fa)tr[p].a[c]=nq; } } } bool dp[N][M][M]; int m,X;char ans[M];int fuck; int getval(int x,int fucklen){ int cnt=1; for(int i=fucklen;i>=1;i--){ if(tr[x].a[ans[i]-'a'])x=tr[x].a[ans[i]-'a']; else x=tr[1].a[ans[i]-'a'],cnt++; } return cnt; } bool v[40]; bool work(){ if(fuck!=m){ for(int i=2;i<=tot;i++){ int x=getval(i,fuck); if(x<=X && dp[i][m-fuck][X-x+1])return 1; } return 0; } else return getval(tr[1].a[ans[m]-'a'],m-1)==X; } int main(){ int T;scanf("%d",&T); while(T--){ scanf("%d%d%d",&n,&m,&X); scanf("%s",st+1); tot=last=1;memset(tr,0,sizeof(tr));memset(v,0,sizeof(v)); for(int i=1;i+i<=n;i++)swap(st[i],st[n-i+1]); for(int i=1;i<=n;i++)add(st[i]-'a'),v[st[i]-'a']=1; for(int i=0;i<=25;i++){ if(tr[1].a[i])dp[tr[1].a[i]][1][1]=1; } for(int i=1;i<m;i++){ for(int j=2;j<=tot;j++){ for(int t=1;t<=i && t<=X;t++){ if(!dp[j][i][t])continue; for(int k=0;k<=25;k++){ if(tr[j].a[k])dp[tr[j].a[k]][i+1][t]=1; else if(tr[1].a[k] && t<X)dp[tr[1].a[k]][i+1][t+1]=1; } } } }
bool bk=0; for(int i=1;i<=tot;i++)bk|=dp[i][m][X]; if(!bk){ for(int i=1;i<=tot;i++)memset(dp[i],0,sizeof(dp[i])); printf("-1\n");continue; } for(int i=1;i<=m;i++){ fuck=i; for(int j=0;j<=25;j++){ if(!v[j])continue; ans[i]=j+'a'; if(work()==1)break; } } ans[m+1]='\0';printf("%s\n",ans+1); for(int i=1;i<=tot;i++)memset(dp[i],0,sizeof(dp[i])); } return 0; }
|
官方做法:
可以发现 $(i,j)$ 至多从 $(i-1,j-1)$ 转移过来,所以可以直接轮廓线 DP ,保存所有需要位置的状态就行了。
时间复杂度:$O(nm2^m)$ 。