KMP和扩展KMP ZOJ 3817 Chinese Knot

Chinese Knot

Time Limit: 2 Seconds       Memory Limit: 65536 KB       Special Judge

Chinese knot is a decorative handicraft that began as a form of Chinese folk artifact in the Tang and Song Dynasty in China. It was later popularized in the Ming.

Alice is learning Chinese knotting. To take the first step, she went to the store and bought a string. To her surprise, she found the string was composed of characters! Anyway, she made a simple Chinese knot. It consists of one knot with 4 loops:

Look at the picture above. There are four loops connected at the center black knot. To make the image clear and neat, the characters on the string are shown as red and yellow points. As a Mid-Autumn Festival gift, Alice give this handcrafted Chinese knot to you. Then she asked you a question:

You are given a target character sequence. Can you get the sequence by walking on the knot? "Walking on the knot" means:

  • You can start at any point you want.
  • You can not start at or move to the black knot in the center.
  • Each step, you can only move to an adjacent point, except for the point you came from immediately.
  • The 8 points closely around the black knot are adjacent to each other. For example, in the picture above, they are the 8 yellow points adjacent to the black knot.
  • Your path should not contain three continuous points which are both one of the 8 points mentioned above.

Input

There are multiple test cases. The first line of input contains an integer T indicating the number of test cases. For each test case:

The first line contains two integers N and M (3 <= NM <= 100000). The total length of string that Alice bought from store is 4 * N.

Then followed by 4 lines, each line contains N lower case letters as a loop of the Chinese knot. These characters are indexed from 1 to 4 * N corresponding to the input order.

The next line contains M lower case letters indicating the target character sequence.

Output

For each test case, output the M indexes of characters you visited. If there are multiple solutions, output any one. If there is no solution, output "No solution!" instead.

Sample Input
2
3 3
abc
abc
abc
abc
baa
3 3
abc
abc
abc
abc
bab
Sample Output
2 1 4
No solution!
Hint

In the first case, we start at the second character of the first loop and stop at the first character of the second loop.


题意:就是有一个如上面图所示的中国结....上面除了中间的黑色的点外,都有一个字符在上面,你能选取任意一个点作为起点然后沿着上面的珠子走,不能马上走到刚刚过来的点,不能走到黑色的节点,与黑色节点相邻的点互相可以直接走过去。然后需要你经过一些珠子,使得这些经过的珠子的字符根据先后顺序组成的字符串要跟目标串一样,需要你输出一种走法,或者判断无解。


思路:首先将问题简化一下吧,假设我们只能从中间出发,那么我们只需要把输入的字符串按照规则连接起来,组成目标就行了。现在有目标串是S,输入的串是T. 假如我们已经匹配到了S[i]时在T的开头,那么我们只需要预先处理出从S[i]开始能否完全匹配T就行了,可以的话就能进行转移,不能的话,代表已经走不下去了。这要怎么预处理呢? 我们可以用KMP来处理,用T来匹配S,看什么时候能完全匹配T,我们就标记一下这个刚好完全匹配好的地方-N+1的地方,表示这里从这里开始能完全走完这条字符串。

但是,题目的起点是任意的,麻烦来了,怎么样判断一个起点是否是可行的呢?这时候就要用到了扩展KMP了,扩展KMP能求出S串从某个位置开始和T串的最长公共前缀。所以我们要用每个输入进来的字符串S,跟目标串T进行匹配,看从S的某个点开始跟T的最长公共前缀的长度是多少,能否走到端点,能就标记一下。 同样的,结束的时候可能也是在任意一个点,这时候跟刚才相反,看从T的某个位置开始和S的最长公共前缀,看能不能走到T的结尾,可以就标记为这里能成功结束了。


最后直接记忆化搜索一下就行了。


基本思路是这样的,可能还会有一些技巧性的东西,就是将串翻转之类的啊,转移的方法啊之类的,具体可以看代码。


代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <algorithm>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<(int)(b);++i)
#define rrep(i,b,a) for(int i=(b);i>=(int)(a);--i)
#define eps 1e-9
#define clr(a,x) memset(a,x,sizeof(a))
#define LL long long
const int maxn = 100000+5;
int N , M;
char S[maxn];
char T[8][maxn];
bool as_start[8][maxn];
bool go_through[8][maxn];
int id[8][maxn];

void input()
{
    clr(S,0);
    scanf("%d%d",&N,&M);
    int cnt = 1;
    for(int i = 0; i < 8; i += 2) {
        scanf("%s",S);
        strcpy(T[i],S);
        rep(j,0,N) id[i][j] = cnt + j;
        reverse(S,S+N);
        rrep(j,N-1,0) id[i+1][j] = cnt + (N-1-j);
        cnt += N;
        strcpy(T[i+1],S);
    }
    scanf("%s",S);
    S[M] = '$';
    S[M+1] = '\0';
}

int f[maxn];

void prefix(char *S, char *T, int lens, int lent,int*next)
{
	int len, L;
	int i, j, k;
	j = 0;
	while (T[j + 1] == T[j] && j < lent) ++j;
	f[1] = j, k = 1;
	for (i = 2; i < lent; ++i) {
		len = k + f[k], L = f[i - k];
		if (len>L + i) f[i] = L;
		else {
			j = len - i>0 ? len - i : 0;
			while (T[i + j] == T[j] && i + j < lent) ++j;
			f[i] = j, k = i;
		}
	}
	j = 0;
	while (S[j] == T[j] && j < lens&&j < lent) ++j;
	next[0] = j, k = 0;
	for (i = 1; i < lens; ++i) {
		len = k + next[k], L = f[i - k];
		if (len>L + i) next[i] = L;
		else {
			j = len - i>0 ? len - i : 0;
			while (S[i + j] == T[j] && i + j < lens&&j < lent)++j;
			next[i] = j, k = i;
		}
	}
}

void getFail(char * P,int * f)
{
    int m = strlen(P);
    f[0] = 0; f[1] = 0;
    rep(i,1,m) {
        int j = f[i];
        while (j && P[i]!=P[j]) j = f[j];
        f[i+1] = P[i] == P[j] ? j + 1 : 0;
    }
}

int next[maxn];
bool success[maxn][8];
void pre_init()
{
    clr(as_start,0);
    clr(success,0);
    rep(i,0,8) {
        prefix(T[i],S,N,M,next);
        rep(j,0,N)
        as_start[i][j] = (next[j] == N-j);
        prefix(S,T[i],M,N,next);
        rep(j,0,M)
        success[j][i] = (j + next[j]) == M;
    }
    clr(go_through,0);
    rep(z,0,8) {
        T[z][N] = '#';
        T[z][N+1] = 0;
        getFail(T[z],f);
        int j = 0;
        rep(i,0,M) {
            while (j && T[z][j] != S[i]) j = f[j];
            if (T[z][j] == S[i]) ++j;
            if (j == N) go_through[z][i-N+1] = true;
        }
        T[z][N] = 0;
    }
}

int ans[maxn],c;
bool fail[maxn][8];
int dfs(int z,int s,int cur)
{
    if (success[cur][z]) return 1;
    if (s == 0 && !go_through[z][cur]) return 0;
    if (s == 0 && fail[cur][z]) return 0;
    rep(i,0,8) {
        if (i == (z ^ 1)) continue;
        ans[c++] = i;
        if (dfs(i,0,cur+N-s)) return 1;
        --c;
    }
    if (s == 0) fail[cur][z] = true;
    return 0;
}

void output(int start)
{
    int len = M;
    rep(i,start,N) {
        if (i != start) printf(" ");
        printf("%d",id[ans[0]][i]);
        --len;
    }
    rep(i,1,c-1) rep(j,0,N) {
        printf(" %d",id[ans[i]][j]);
        --len;
    }
    rep(j,0,len) printf(" %d",id[ans[c-1]][j]);
    puts("");
}

void solve()
{
    pre_init();
    rep(i,0,8) {
        rep(j,0,N) {
            c = 0;
            ans[c++] = i;
            if (as_start[i][j] && dfs(i,j,0) == 1) {
                output(j);
                return;
            }
        }
    }
    puts("No solution!");
}

int main()
{
    int T; cin >> T;
    while (T--) {
        input();
        solve();
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值