这题也很有意思,不只是单纯的要构造出含输入字符串最多的字符串,单个字符还有数量上限。题目的说法是对原字符串进行重新安排,但具体怎么安排,以及操作次数这些是没说的,所以我们可以理解成直接重新构造字符串,并且每个字符出现的次数不能超过原字符串中它出现的次数。
这道题中的字符只含ACTG,简化了问题。最终构造出的字符串长度小于40,但如果对ACTG每个字符都开40的大小,那需要404040*40的空间,显然不可能,但我们注意到这四个字符加起来最多也只能出现40次,所以考虑状压,用一维保存这四个字符出现的次数。
考虑ACTG出现多少次,才能有最多的状态?不难发现,各取10个是状态最多的情况。因此状态数取114,然后ACTG出现的次数我们可以用类似进位的方式从一个数中提取出来。这道题一共50个串,单个长度不超过十,所以字典树节点可以开500多点,然后dp数组第二维存ACTG出现的次数的状态,大小开114,下面就可以进行dp了。
dp前我们还需要先把进位做一下,我们让A当第一维,那么一个A指代1,A出现的次数就是表示状态的数的最后一位的进制;C当第二维,一个C指代的值等于A出现的次数,倒数第二位的进制就是A出现的次数乘以C出现的次数,依此推出T与G。然后状态就可以表示成当前各个字符出现的次数,分别乘以他们指代的值,终态就是原串中各个字符出现的次数,也就是出现次数的最大值,与他们指代的值的乘积,我们在这个状态上找最大值即可。
#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <queue>
#include <map>
#include <cstring>
#define fi first
#define se second
#define FIN freopen("in.txt","r",stdin)
#define FIO freopen("out.txt","w",stdout)
#define INF 0x3f3f3f3f
#define per(i,a,n) for(int i = a;i < n;i++)
#define rep(i,a,n) for(int i = n;i > a;i--)
#define pern(i,a,n) for(int i = a;i <= n;i++)
#define repn(i,a,n) for(int i = n;i >= a;i--)
#define fastio std::ios::sync_with_stdio(false)
#define all(a) a.begin(), a.end()
#define ll long long
#define pb push_back
#define endl "\n"
#define pii pair<int,int>
#define sc(n) scanf("%d", &n)
#define CASET int ___T; scanf("%d", &___T); for(int cs=1;cs<=___T;cs++)
template<typename T> inline void _max(T &a,const T b){if(a<b) a = b;}
template<typename T> inline void _min(T &a,const T b){if(a>b) a = b;}
using namespace std;
//inline ll read(){
// ll a=0;int f=0;char p=getchar();
// while(!isdigit(p)){f|=p=='-';p=getchar();}
// while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=getchar();}
// return f?-a:a;
//}
const int maxn = 550;
const int maxnode = 4;
int ch[maxn][maxnode]; //字典树
int cnt[maxn]; //单词出现次数
int sz;
int fail[maxn];
map<char,int> ma;
char s[50];
int n;
int num[4];
void init()
{
num[0] = num[1] = num[2] = num[3] = 0;
ma['A'] = 0;
ma['C'] = 1;
ma['G'] = 2;
ma['T'] = 3;
sz = 1;
memset(ch[0], 0, sizeof(ch[0]));
memset(cnt,0,sizeof(cnt));
cnt[0] = 0;
}
void insert(char str[], int len) //插入字符串
{
int u = 0;
per(i, 0, len)
{
int v = ma[str[i]];
if (!ch[u][v])
{
memset(ch[sz], 0, sizeof(ch[sz]));
cnt[sz] = 0;
ch[u][v] = sz++;
}
u = ch[u][v];
}
cnt[u]++;
//在这里我们可以建立一个int-string的映射,以通过节点序号得知这个点是哪个单词的结尾
}
void getfail()
{
//所有模式串已插入完成
queue<int> q;
per(i, 0,maxnode)
{
if (ch[0][i])
{
fail[ch[0][i]] = 0;
q.push(ch[0][i]);
}
}
while (!q.empty())
{
int now = q.front();
q.pop();
per(i, 0, maxnode)
{
if (ch[now][i])
{
fail[ch[now][i]] = ch[fail[now]][i];
q.push(ch[now][i]);
}
else
ch[now][i] = ch[fail[now]][i];
}
cnt[now] += cnt[fail[now]];
}
}
int b[4];
int dp[550][11*11*11*11+100];
int main()
{
#ifndef ONLINE_JUDGE
int startTime = clock();
FIN;
#endif
//fastio;
//忘记初始化是小狗
//freopen("out.txt","w",stdout);
//ios::sync_with_stdio(false);
int _T = 0;
while(~sc(n))
{
if(n == 0)return 0;
init();
memset(dp,-1,sizeof(dp));
per(i,0,n)
{
scanf("%s",s);
insert(s,strlen(s));
}
getfail();
scanf("%s",s);
int len = strlen(s);
per(i,0,len)
{
num[ma[s[i]]]++;
}
//per(i,0,4)cout << num[i] << ' ';
b[0] = 1;
b[1] = num[0]+1;
b[2] = b[1]*(num[1]+1);
b[3] = b[2]*(num[2]+1);
dp[0][0] = 0;
pern(A,0,num[0])
{
pern(C,0,num[1])
{
pern(G,0,num[2])
{
pern(T,0,num[3])
{
int st = A+C*b[1]+G*b[2]+T*b[3];
per(j,0,sz)
{
if(dp[j][st] == -1)continue;
per(k,0,4)
{
if(k == 0 && A == num[0])continue;
if(k == 1 && C == num[1])continue;
if(k == 2 && G == num[2])continue;
if(k == 3 && T == num[3])continue;
int v = ch[j][k];
dp[v][st+b[k]] = max(dp[v][st+b[k]],dp[j][st]+cnt[v]);
}
}
}
}
}
}
int ans = 0;
int st = num[0]+num[1]*b[1]+num[2]*b[2]+num[3]*b[3];
per(i,0,sz)ans = max(ans,dp[i][st]);
printf("Case %d: %d\n",++_T,ans);
}
#ifndef ONLINE_JUDGE
printf("\nTime = %dms\n", clock() - startTime);
#endif
return 0;
}