[CEOI2011]Matching
壹、题目描述 ¶
贰、题解 ¶
我他妈居然没有想到使用反序表进行比较,真的是脑抽了......
我们可以通过 \(a_i\) 得到某个区间离散化之后应该长得样子,我们记这个样子为 \(p\),那么,我们现在考察的就是 \(p\) 和 \(b\) 中任意长度为 \(n\) 的区间离散化之后的匹配。
显然这俩都是排列,于是我们进行的是排列的匹配,这可以使用反序表进行。不难发现过程挺像 \(\rm KMP\),那么我们可以先处理出 \(p_i\) 的反序表,并且处理出 \(\rm KMP\) 使用的 \(nxt\) 数组,具体处理该数组,就是考虑在加入 \(p_i\) 时,在目前已经匹配到的长度中,比 \(p_i\) 大的数个数是否与已经匹配长度 \(len\) 的 \(d_{len+1}\) 相同,这是可以使用 \(\rm BIT\) 维护,如果匹配失败,就暴力将 \([i-len, i-nxt[len]-1]\) 的数字撤回,使用均摊分析,该过程仍然是 \(\mathcal O(n)\) 的,由于使用 \(\rm BIT\),复杂度达到了 \(\mathcal O(n\log n)\).
匹配过程和处理 \(nxt\) 基本上是一样的,唯一需要注意的就是在达成一次匹配之后的撤回区间和 \(\rm KMP\) 失配时的撤回区间稍微有点不同,至于为什么可以自行思考一下。
叁、参考代码 ¶
# include <cstdio>
# include <algorithm>
# include <vector>
using namespace std;
# define NDEBUG
# include <cassert>
namespace Elaina {
# define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
# define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
# define fi first
# define se second
# define mp(a, b) make_pair(a, b)
# define Endl putchar('\n')
# define mmset(a, b) memset(a, b, sizeof (a))
# define mmcpy(a, b) memcpy(a, b, sizeof (a))
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair <int, int> pii;
typedef pair <ll, ll> pll;
template <class T> inline T fab(T x) { return x<0? -x: x; }
template <class T> inline void getmin(T& x, const T rhs) { x=min(x, rhs); }
template <class T> inline void getmax(T& x, const T rhs) { x=max(x, rhs); }
template <class T> inline T readin(T x) {
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template <class T> inline void writc(T x, char s='\n') {
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
} using namespace Elaina;
const int maxn=1000000;
int a[maxn+5], b[maxn+5], n, m;
int p[maxn+5], t[maxn+5];
inline void input() {
n=readin(1), m=readin(1);
rep(i, 1, n) {
a[i]=readin(1);
p[a[i]]=i;
}
rep(i, 1, m) t[i]=b[i]=readin(1);
}
inline void getHash() {
sort(t+1, t+m+1);
rep(i, 1, m) b[i]=lower_bound(t+1, t+m+1, b[i])-t;
}
/** @warning the upper limit should be set to @p maxn */
namespace saya {
int c[maxn+5];
# define lowbit(i) ((i)&(-(i)))
inline void modify(int i, int v) {
for(; i; i-=lowbit(i)) c[i]+=v;
}
inline int query(int i, int ret=0) {
for(; i<=maxn; i+=lowbit(i)) ret+=c[i];
return ret;
}
inline void clear() {
rep(i, 1, maxn) c[i]=0;
}
} // using namespace saya;
int nxt[maxn+5], d[maxn+5];
inline void getNxt() {
rep(i, 1, n) {
d[i]=saya::query(p[i]);
saya::modify(p[i], 1);
}
saya::clear(); nxt[1]=0, nxt[0]=-1;
for(int i=2; i<=n; ++i) {
int len=nxt[i-1];
while(~len && d[len+1]!=saya::query(p[i])) {
rep(j, i-len, i-nxt[len]-1) saya::modify(p[j], -1);
len=nxt[len];
}
nxt[i]=len+1, saya::modify(p[i], 1);
}
}
vector <int> ans;
inline void compare() {
saya::clear();
int len=0; // successfully matched length
rep(i, 1, m) {
while(~len && d[len+1]!=saya::query(b[i])) {
rep(j, i-len, i-nxt[len]-1) saya::modify(b[j], -1);
len=nxt[len];
} assert(~len);
++len, saya::modify(b[i], 1);
if(len==n) { // a successful match
/** @warning pay attention to this special fallback interval */
rep(j, i-len+1, i-nxt[len]) saya::modify(b[j], -1);
len=nxt[len], ans.push_back(i-n+1);
}
}
}
inline void print() {
writc(ans.size());
for(auto x: ans) writc(x, ' ');
}
signed main() {
// freopen("match.in", "r", stdin);
// freopen("match.out", "w", stdout);
input();
getHash();
getNxt();
compare();
print();
return 0;
}
肆、关键の地方 ¶
进行排列的比较时,可以考察使用反序表,因为反序表唯一对应每一个排列,至于为什么我记得曾经自己给出过一种构造方法,通过这个构造方法确是可以说明一一映射的关系的。
另外,\(\rm KMP\) 也是可以魔改的,该题便是利用了 \(\rm KMP\) 判等条件的变化,另外对于 \(\rm KMP\) 复杂度分析稍微有点不熟,差点忘记摊还了......