题目链接: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=1;i<=tot;i++){
// printf("%d %d\n",tr[i].fa,tr[i].len);
// }
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;
// if(i==1 && j==5 && t==1)printf("OK\n");
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;
}
}
}
}

// for(int i=1;i<=tot;i++)printf("%d:%d %d\n",i,tr[i].len,tr[i].fa);
// printf("%d %d %d\n",dp[4][2][1],tr[5].a[0],dp[5][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;
}
// ans[1]=ans[2]='a';printf("%d\n",getval(5,2));
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)$ 。