arc058 F - Iroha Loves Strings & Z函数学习笔记

F - Iroha Loves Strings

dp(i, j)表示考虑前i个串,拼成长度为j的字典序最小串。

显然这样状态数n * m,每个状态是一个长度为m的string,不可行。

发现一个性质,如果当前有字典序 dp(i, j) < dp(i, k)【此处的小于定义为严格存在第一个字符不同位置的小于,不包括dp(i, j)是dp(i, k)前缀或相反,之后的比较也是这样】且(i + 1 ~ n)能够拼出一个长度m - j的串(这一步可从后向前做个可行性dp即可),则dp(i, k)显然不可能成为最优答案的前缀。

然后会发现,当前可能的dp值,是某一个串s以及它的一些前缀,这样状态数就可以表示了。

再考虑转移,当前尝试往后拼接串t,等价于能转移到 s的一些前缀(不拼)或者 s的一些前缀 + t, 然后我们要求出字典序最小且之后有解且最长的s',以及它的可能前缀状态。

显然此时我们要支持快速比较串s的前缀、s的前缀+t的字典序大小,有两种做法,一种是哈希之后二分lcp然后比较下一位置,还有一种是Z函数快速查lcp、为O(1)。这里学了一下Z函数:

Z函数也称为扩展kmp,假设现在有一个串s和t,通过一个对t的O(n)时间预处理,之后可以线性时间对串s的每一个下标i,求以s[i]为开头能和串t匹配的最大长度。

具体做法是对t的每一个下标i求其能与t开头匹配的最大长度,求的时候利用一下之前已经求出的信息(类似manacher),匹配的时候同理。
Z函数模板题:

#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define sc second
#define pb push_back
#define ll long long
#define trav(v,x) for(auto v:x)
#define all(x) (x).begin(), (x).end()
#define VI vector<int>
#define VLL vector<ll>
#define pll pair<ll, ll>
#define double long double
//#define int long long
using namespace std;
const int N = 1e6 + 100;
const int inf = 1e9;
//const ll inf = 1e18;
const ll mod = 998244353;//1e9 + 7;

#ifdef LOCAL
void debug_out(){cerr << endl;}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T)
{
	cerr << " " << to_string(H);
	debug_out(T...);
}
#define debug(...) cerr << "[" << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif

void sol()
{
	string s, t;
	cin >> t >> s;
	int n, m;
	n = s.length();
	m = t.length();
	VI Z(n, 0);
	int lp, rp;
	lp = rp = 0;
	for(int i = 1; i < n; i++)
	{
		if(i <= rp && i + Z[i - lp] - 1 < rp)
			Z[i] = Z[i - lp];
		else
		{
			int k = max(0, rp - i + 1);
			while(i + k < n && s[k] == s[i + k])
				++k;
			Z[i] = k;
		}
		if(i + Z[i] - 1 > rp)
			lp = i, rp = i + Z[i] - 1;
	}
	Z[0] = n;
	ll res = 0;
	for(int i = 0; i < n; i++)
		res ^= 1LL * (i + 1) * (Z[i] + 1);
	cout << res << '\n';
	res = 0;
	lp = rp = -1;
	VI ans(m, 0);
	for(int i = 0; i < m; i++)
	{
		if(i <= rp && i + Z[i - lp] - 1 < rp)
			ans[i] = Z[i - lp];
		else
		{
			int k = max(0, rp - i + 1);
			while(i + k < m && k < n && s[k] == t[i + k])
				++k;
			ans[i] = k;
		}
		if(i + ans[i] - 1 > rp)
			lp = i, rp = i + ans[i] - 1;
		res ^= 1LL * (i + 1) * (ans[i] + 1);
	}
	cout << res << '\n';
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
//	int tt;
//	cin >> tt;
//	while(tt--)
		sol();
}

回到原题,分几类比较情况,需要用到Z函数的地方:比较s(1 ~ a) + t 和 s(1 ~ b) 字典序(b > a),则就是比较s(a + 1 ~ b) 与t,因此对t预处理Z数组即可。

转移过程可以枚举下一轮长度,维护一个字典序相同,长度逐渐增长的栈,然后应用上述比较即可。

#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define sc second
#define pb push_back
#define ll long long
#define trav(v,x) for(auto v:x)
#define all(x) (x).begin(), (x).end()
#define VI vector<int>
#define VLL vector<ll>
#define pll pair<ll, ll>
#define double long double
//#define int long long
using namespace std;
const int N = 1e6 + 100;
const int inf = 1e9;
//const ll inf = 1e18;
const ll mod = 998244353;//1e9 + 7;

#ifdef LOCAL
void debug_out(){cerr << endl;}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T)
{
	cerr << " " << to_string(H);
	debug_out(T...);
}
#define debug(...) cerr << "[" << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif

void sol()
{
	int n, m;
	cin >> n >> m;
	vector<string> a(n);
	for(int i = 0; i < n; i++)
		cin >> a[i];
	vector<vector<bool>> can(n, vector<bool>(m + 1, 0));
	for(int i = n - 1; i >= 0; i--)
	{
		if(i == n - 1)
		{
			can[i][m] = 1;
			continue;
		}
		for(int j = 0; j <= m; j++)
		{
			if(can[i + 1][j] || (j + (int)a[i + 1].length() <= m && can[i + 1][j + a[i + 1].length()]))
				can[i][j] = 1;
		}
	}
	string s = "", t = "", ans = "";
	VI Z, mat;
	vector<pii> stk;
	vector<bool> ed(m + 1, 0);
	ed[0] = 1;
	for(int i = 0; i < n; i++)
	{
		t = a[i];
		stk.clear();
		int ls, lt;
		ls = s.length();
		lt = t.length();
		Z.resize(lt);
		mat.resize(ls);
		int lp, rp;
		lp = rp = -1;
		for(int j = 1; j < lt; j++)
		{
			if(j <= rp && j + Z[j - lp] - 1 < rp)
				Z[j] = Z[j - lp];
			else
			{
				int k = max(rp - j + 1, 0);
				while(j + k < lt && t[k] == t[j + k])
					++k;
				Z[j] = k;
			}
			if(j + Z[j] - 1 > rp)
				lp = j, rp = j + Z[j] - 1;
		}
		lp = rp = -1;
		for(int j = 0; j < ls; j++)
		{
			if(j <= rp && j + Z[j - lp] - 1 < rp)
				mat[j] = Z[j - lp];
			else
			{
				int k = max(0, rp - j + 1);
				while(k < lt && j + k < ls && t[k] == s[j + k])
					++k;
				mat[j] = k;
			}
			if(j + mat[j] - 1 > rp)
				lp = j, rp = j + mat[j] - 1;
		}
		auto small = [&](int x)
		{
			int len = mat[x];
			if(len == lt || x + len == ls)
				return (bool)0;
			return t[len] < s[x + len];
		};
		auto big = [&](int x)
		{
			int len = mat[x];
			if(len == lt || x + len == ls)
				return (bool)0;
			return t[len] > s[x + len];
		};
		auto same = [&](int x)
		{
			int len = mat[x];
			if(len == lt || x + len == ls)
				return 1;
			return 0;
		};
		auto cmp = [&](pii x, pii y)
		{
			if(y.sc == 0)
			{
				if(x.sc == 0 || x.fi >= y.fi)
					return 0;
				int len = mat[x.fi];
				if(x.fi + len >= y.fi)
					return 0;
				if(small(x.fi))
					return -1;
				return 1;
			}
			if(x.sc == 0)
			{
				if(big(y.fi))
					return -1;
				if(same(y.fi))
					return 0;
				return 1;
			}
			int len = mat[y.fi];
			if(y.fi + len > x.fi)
			{
				int ex = y.fi + len - x.fi;
				int le = Z[lt - ex];
				if(le == ex)
					return 0;
				if(t[le] < t[lt - ex + le])
					return -1;
				return 1;
			}
			else
			{
				if(big(y.fi))
					return -1;
				if(same(y.fi))
					return 0;
				return 1;
			}
		};
		for(int j = 1; j <= m; j++)
		{
			if(!can[i][j])
				continue;
			if(ed[j] || (j - lt >= 0 && ed[j - lt]))
			{
				int op = -1;
				if(ed[j] && j - lt >= 0 && ed[j - lt])
				{
					if(small(j - lt))
						op = 1;
					else op = 0;
				}
				else
				{
					if(ed[j])
						op = 0;
					else op = 1;
				}
				pii cur = pii(j - op * lt, op);
				while(!stk.empty() && cmp(cur, stk.back()) < 0)
					stk.pop_back();
				if(stk.empty() || cmp(cur, stk.back()) == 0)
					stk.pb(cur); 
			}
		}
		fill(all(ed), 0);
		ed[0] = 1;
		if(!stk.empty())
		{
			pii cur = stk.back();
			s = s.substr(0, cur.fi);
			if(cur.sc == 1)
				s += t;
			if(s.length() == m)
				ans = s;
			trav(v, stk)
			{
				int len = v.fi + v.sc * lt;
				ed[len] = 1;
			}
		}
		else
			s = "";
	}
	cout << ans << '\n';
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
//	int tt;
//	cin >> tt;
//	while(tt--)
		sol();
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值