2 条题解
-
0
题目链接
解题思路 & 参考代码
考虑朴素 dp, 表示目前共匹配了 个字符串,最后一个匹配的为第 种字符串,显然转移的时候需要用到第 种字符串的后缀和第 种字符串的前缀可以匹配到的最大长度,容易 kmp 做到 的复杂度。
考虑到这个东西可以有 矩阵来加速,直接矩阵乘法优化 dp 即可,时间复杂度为 ,可以通过此题。
:::info[参考代码]
ll n,m; string s[210]; ll len[210]; ll dis[210][210]; ll nxt[210][100010]; struct matrix{ ll n,m,a[210][210]; void _init(ll _n,ll _m) { n=_n,m=_m; forl(i,0,n+1) forl(j,0,m+1) a[i][j]=0; } friend matrix operator * (matrix x,matrix y) { matrix z; z._init(x.n,y.m); forl(i,0,x.n) forl(j,0,x.m) { z.a[i][j]=LM; forl(k,0,y.m) Min(z.a[i][j],x.a[i][k]+y.a[k][j]); } return z; } }Pw,ans; void solve() { cin>>n>>m; forl(i,1,n) cin>>s[i], len[i]=s[i].size(), s[i]=' '+s[i]; dis[0][0]=LM; forl(i,1,n) { dis[0][i]=len[i],dis[i][0]=LM; ll R=0; forl(j,2,len[i]) { while(R && s[i][j]!=s[i][R+1]) R=nxt[i][R]; if(s[i][j]==s[i][R+1]) R++; nxt[i][j]=R; } } forl(i,1,n) forl(j,1,n) { if(i==j) { dis[i][j]=len[i]-nxt[i][len[i]]; continue; } ll R=0; forl(k,1,len[i]) { while(R && s[i][k]!=s[j][R+1]) R=nxt[j][R]; if(s[i][k]==s[j][R+1]) R++; dis[i][j]=len[j]-R; } } Pw._init(n,n); forl(i,0,n) forl(j,0,n) Pw.a[i][j]=dis[i][j]; ans=Pw; m--; while(m) { if(m&1) ans=ans*Pw; Pw=Pw*Pw; m>>=1; } ll minn=LM; forl(i,0,n) Min(minn,ans.a[0][i]); cout<<minn<<endl; }:::
-
0
讲一下自己的大致思路。
先说一下暴力分:
这是我看到此题的第一反应:
dp,复杂度为。我们先定义两字符串间的距离值,表示号串拼接在号串之后,需要在号串末尾添加的字符数。
此时我们再用 表示这个串里应经有了个字符串,并且最后出现在该串里的子串是号串的答案,显然可以按下转移:
,表示取1个字符串,并且是第个字符串结尾,那么它的代价显然是
,我们在号字符串 后面拼接上号字符串,添加字符量即为。
注意转移时的顺序,应该先枚举,再枚举和。读者可以想想,为什么?
贴一下
dp的核心部分:void DP() { memset(dp, 0x7f, sizeof(dp)); for (int i = 1; i <= n; i++) dp[1][i] = len[i]; for (int i = 2; i <= m; i++) for (int j = 1; j <= n; j++) for (int k = 1; k <= n; k++) dp[i][j] = min(dp[i][j], dp[i - 1][k] + dis[k][j]); }所以,我们分析想到了转移公式,那么答案即为,其中(因为一共有种字符串)。
看到有个字符串,并且是一个个字符串首尾拼接而成的,我们很自然的想到
AC自动机。在本人经过长达1小时的思考后,终于想出了正解。。。
本人比较擅长
KMP算法,看到这题,果断想到是KMP,粗略计算一下,KMP()函数部分的复杂度为$O(\sum^{n}_{i=1}\sum^{n}_{j=1}(len(i)+len(j)))= O(\sum^{n}_{i=1}(n\times len[i]+\sum |S|))≈ O(n|S|)$,稍微带一点小常数。下面是
KMP部分的代码:void KMP() { // kmp1:单个字符串内匹配 for (int p = 1; p <= n; p++) { int j = 0; for (int i = 2; i <= len[p]; i++) { while (j > 0 && str[p][j + 1] != str[p][i]) j = nxt[p][j]; if (str[p][j + 1] == str[p][i]) j++; nxt[p][i] = j; } } // kmp2:两两字符串间匹配 for (int p1 = 1; p1 <= n; p1++) { for (int p2 = 1; p2 <= n; p2++) { int j = 0; for (int i = 2; i <= len[p1]; i++) { while (j > 0 && str[p2][j + 1] != str[p1][i]) j = nxt[p2][j]; if (str[p2][j + 1] == str[p1][i]) j++; if (i == len[p1]) { a.v[p1][p2] = len[p2] - j; } } } } }好,既然我们已经预处理
KMP得到了两两字符串间的关系(末尾添加字符量),那么我们接下来考虑如何优化dp。读者不妨再回头去看看
dp的转移部分,有没有发现,这个转移方式似曾相识?没错,这似乎与
floyd的转移代码有异曲同工之妙。它的转移方式,跟floyd是一模一样的!那么这个转移方式等价于一个的floyd,我们从另一种角度去考虑如何优化这个伪的floyd。在优化复杂度之前,请读者先考虑一些细节问题:
首先,将这个转换成
floyd,是基于点操作的,我们可以将每一个字符串的每一个字符都视作是一个点,这下次就是彻彻底底的floyd了233。其次,我们不知道该从第几号字符串作为整一个串的起点,所以我们考虑加一个
超级源,设它为号串,将号串连接到第号串。从到的权值为,从到显然做不到,所以设置为无穷大,即:。好了,接下来我们考虑如何去优化复杂度。
很显然,数据范围中的范围特别大,已经到了的地步,此时我们就数据范围倒推,显然不可能是线性扫,所以极大可能是或者。
如果是,那么一定有关于开根号的东西,但
floyd显然不可能与根号发生关系,所以暂时排除。那么我们发现只可能是的复杂度内运行
floyd,此时我们果断想到:与有关的,极大可能是快速幂,但这不是的形式,而是一个二维的幂形式。相信读者都已经猜到了,这就是
矩阵快速幂!综上,我们发现,这道题可以在的复杂度内跑出结果,常数较小,值得一试。
同时,作为前人,也提醒一下大家:这道题可能要开才能,
数据#1稍微有点卡常数!献上代码,完美撒花:
#include <bits/stdc++.h> using namespace std; #define int long long typedef long long ll; const int N = 202; const int M = 100002; string str[N]; int len[N], t; int nxt[N][M]; //int dis[201][201]; int n, m; struct MAT { ll v[N][N]; void print() { for (int i = 0; i <= n; i++, puts("")) for (int j = 0; j <= n; j++) printf("%lld ", v[i][j]); putchar('\n'); } } ans, a; MAT operator * (MAT a, MAT b) { MAT c; for (int i = 0; i <= n; i++) for (int j = 0; j <= n; j++) c.v[i][j] = 1e15; for (int i = 0; i <= n; i++) for (int j = 0; j <= n; j++) for (int k = 0; k <= n; k++) c.v[i][k] = min(c.v[i][k], a.v[i][j] + b.v[j][k]); return c; } void KMP() { // init a.v[0][0] = 1e15; for (int p = 1; p <= n; p++) a.v[0][p] = len[p], a.v[p][0] = 1e15; // kmp1 for (int p = 1; p <= n; p++) { int j = 0; for (int i = 2; i <= len[p]; i++) { while (j > 0 && str[p][j + 1] != str[p][i]) j = nxt[p][j]; if (str[p][j + 1] == str[p][i]) j++; nxt[p][i] = j; } } // kmp2 for (int p1 = 1; p1 <= n; p1++) { for (int p2 = 1; p2 <= n; p2++) { int j = 0; for (int i = 2; i <= len[p1]; i++) { while (j > 0 && str[p2][j + 1] != str[p1][i]) j = nxt[p2][j]; if (str[p2][j + 1] == str[p1][i]) j++; if (i == len[p1]) { a.v[p1][p2] = len[p2] - j; } } } } /* for (int i = 1; i <= n; i++) for (int j = i; j <= n; j++) printf("dis[%d][%d] = %d\n", i, j, dis[i][j]); */ } void ksm() { //a.print(); for (int i = 0; i <= n; i++) for (int j = 0; j <= n; j++) ans.v[i][j] = a.v[i][j]; // woc, m--! m--; while (m > 0) { //printf("in ksm %d\n", m); //ans.print(); a.print(); //printf("----------\n"); if (m & 1) ans = ans * a; a = a * a; m >>= 1; } } bool check() { for (int i = 1; i <= n; i++) { if (len[i] == 1) return 1; } return 0; } signed main() { scanf("%lld%lld", &n, &m); if (m == 0) { puts("0"); return 0; } for (int i = 1; i <= n; i++) { cin >> str[i]; len[i] = str[i].size(); str[i] = ' ' + str[i]; } if (check()) { printf("%lld\n", m); return 0; } KMP(); ksm(); ll anss = 2e15; for (int i = 1; i <= n; i++) anss = min(anss, ans.v[0][i]); printf("%lld\n", anss); return 0; }附:如果有的网友,我在这里提供几组数据,您可以不妨一测。
5 100 a b c d e1001 0 a04 5 ab bc cd de7
- 1
信息
- ID
- 3750
- 时间
- 1000ms
- 内存
- 64MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者