LOJ#2720 你的名字
题意:给定母串s和若干个询问。每个询问是一个串t和两个数l,r,表示求t中有多少个本质不同的子串没有在s[l,r]中出现过。
解:我写的并不是正解......是个毒瘤做法。只在loj上面卡时过了就写loj的题号好了...
首先有个68分部分分是l = 1, r = |s|,这个怎么做呢?
回忆起之前写的广义SAM的套路,我们建出广义SAM之后把s的所有子串标记。
然后对于每个t跑一遍SAM,跳fail的时候如果该节点被标记了就停止。这样走到的节点所代表的子串总数就是该串的答案。
68分还是比较友善的...
然后顺着这个思路思考正解。
多了个母串范围限制,又听说这个题是线段树合并,这就很自然了。
对于每个节点用值域线段树维护right集合,然后对于每一个询问,查看该节点是否有个right存在于[l,r]之间。存在就GG了,同时也不能往上传递。否则可以加上这个节点的贡献。有一种居中的情况就是一个节点所表示的串中,有些可选,有些不行。这种情况下不用向上传递(然而我当时没想到,传了...无伤大雅)
细节上就是找到那一段暧昧区域的最右边一个right,用线段树上找第k个实现。
那么我们要一个一个询问的处理吗?我很天真的以为线段树合并之后下面的线段树就不存在了....于是只能把询问一次性处理。于是我又SB的对询问开了个线段树,继续线段树合并......
具体来说,对于每个询问串,都在对应节点加上该串的询问。然后DFSfail树,在每一个节点遍历询问线段树,处理询问,然后向上合并。如果不会有贡献就删去这个节点,减少复杂度。
还有个小问题,right线段树合并的时候sum是两棵树的sum和,但是询问的那棵线段树合并要去重,所以不能把sum累加...然后发现询问线段树不需要查sum......这样就可以了。
因为加了很多常数优化所以代码不是很能看......复杂度分析也不会...反正估计也是不对的。uoj洛谷都T了。bzoj空间限制512M根本开不下。只有loj过了(loj牛逼!!!)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <queue> template <class T> inline void read(T &x) {
x = ;
char c = getchar();
while(c < '' || c > '') {
c = getchar();
}
while(c >= '' && c <= '') {
x = (x << ) + (x << ) + c - ;
c = getchar();
}
return;
} typedef long long LL;
const int N = , M = ;
using std::string; struct SGT { // 两个线段树合并
int tot, sum[M * ], ls[M * ], rs[M * ], rt[M * ], p, k;
std::queue<int> Q;
inline int np() {
if(Q.empty()) {
return ++tot;
}
int t = Q.front();
Q.pop();
sum[t] = ls[t] = rs[t] = ;
return t;
}
void add(int l, int r, int &o) {
if(!o) {
o = np();
}
if(l == r) {
sum[o]++;
return;
}
int mid = (l + r) >> ;
if(p <= mid) {
add(l, mid, ls[o]);
}
else {
add(mid + , r, rs[o]);
}
sum[o] = sum[ls[o]] + sum[rs[o]];
return;
}
int merge(int x, int y) {
if(!x || !y) {
return x | y;
}
int z = np();
sum[z] = sum[x] + sum[y];
ls[z] = merge(ls[x], ls[y]);
rs[z] = merge(rs[x], rs[y]);
Q.push(x);
Q.push(y);
return z;
}
int ask(int L, int R, int l, int r, int o) {
if(!o) {
return ;
}
if(L <= l && r <= R) {
return sum[o];
}
int mid = (l + r) >> , ans = ;
if(L <= mid) {
ans += ask(L, R, l, mid, ls[o]);
}
if(mid < R) {
ans += ask(L, R, mid + , r, rs[o]);
}
return ans;
}
inline void exmerge(int x, int y) {
rt[x] = merge(rt[x], rt[y]);
return;
}
int getK(int l, int r, int o) {
if(l == r) {
return r;
}
int mid = (l + r) >> ;
if(k <= sum[ls[o]]) {
return getK(l, mid, ls[o]);
}
else {
k -= sum[ls[o]];
return getK(mid + , r, rs[o]);
}
}
}rt, st; string str[N];
char ss[N], s[N];
int tot = , fail[M], len[M], e[M], tr[M][], top, n, m, nodel[N], noder[N], edgenex[M], edgev[M];
LL nodea[N]; inline void add(int x, int y) {
top++;
edgev[top] = y;
edgenex[top] = e[x];
e[x] = top;
return;
} inline int split(int p, int f) {
int Q = tr[p][f];
int nQ = ++tot;
len[nQ] = len[p] + ;
fail[nQ] = fail[Q];
fail[Q] = nQ;
memcpy(tr[nQ], tr[Q], sizeof(tr[Q]));
while(tr[p][f] == Q) {
tr[p][f] = nQ;
p = fail[p];
}
return nQ;
} inline int insert(int p, char c) {
int f = c - 'a';
if(tr[p][f]) {
int Q = tr[p][f];
if(len[Q] == len[p] + ) {
return Q;
}
return split(p, f);
}
int np = ++tot;
len[np] = len[p] + ;
while(p && !tr[p][f]) {
tr[p][f] = np;
p = fail[p];
}
if(!p) {
fail[np] = ;
}
else {
int Q = tr[p][f];
if(len[Q] == len[p] + ) {
fail[np] = Q;
}
else {
fail[np] = split(p, f);
}
}
return np;
} void work(int l, int r, int &o, int x) {
if(!o || !st.sum[o]) {
if(o) {
st.Q.push(o);
}
o = ;
return;
}
if(l == r) {
// node[r]
int sum = ;
if(nodel[r] + len[fail[x]] <= noder[r]) {
sum = rt.ask(nodel[r] + len[fail[x]], noder[r], , n, rt.rt[x]);
}
if(!sum) {
nodea[r] += len[x] - len[fail[x]];
}
else {
int temp = ;
if(nodel[r] + len[x] - <= noder[r]) {
temp = rt.ask(nodel[r] + len[x] - , noder[r], , n, rt.rt[x]);
}
if(temp) {
//GG
st.sum[o] = ;
st.Q.push(o);
o = ;
return;
}
else {
// add some...
// find ->| (the right pos)
rt.k = rt.ask(, nodel[r] + len[x] - , , n, rt.rt[x]);
int ed = rt.getK(, n, rt.rt[x]);
nodea[r] += len[x] - (ed - nodel[r] + );
}
}
return;
}
int mid = (l + r) >> ;
if(st.ls[o]) {
work(l, mid, st.ls[o], x);
}
if(st.rs[o]) {
work(mid + , r, st.rs[o], x);
}
st.sum[o] = st.sum[st.ls[o]] + st.sum[st.rs[o]];
if(!st.sum[o]) {
st.Q.push(o);
o = ;
}
return;
} void solve(int x) {
for(int i = e[x]; i; i = edgenex[i]) {
int y = edgev[i];
solve(y);
if(x > ) {
rt.exmerge(x, y);
}
}
if(x > ) {
work(, m, st.rt[x], x);
if(fail[x] > ) {
st.exmerge(fail[x], x);
}
}
return;
} int main() { //freopen("name.in", "r", stdin);
//freopen("name.out", "w", stdout); scanf("%s", ss);
n = strlen(ss);
int last = ;
for(int i = ; i < n; i++) {
last = insert(last, ss[i]);
}
read(m);
for(int i = ; i <= m; i++) {
scanf("%s", s);
str[i] = (string)(s);
int t = strlen(s);
last = ;
for(int j = ; j < t; j++) {
last = insert(last, s[j]);
}
read(nodel[i]);
read(noder[i]);
}
//
int p = ;
for(int i = ; i < n; i++) {
p = tr[p][ss[i] - 'a'];
rt.p = i + ;
rt.add(, n, rt.rt[p]);
}
for(int i = ; i <= tot; i++) {
add(fail[i], i);
} for(int i = ; i <= m; i++) {
int t = str[i].size(), p = ;
for(int j = ; j < t; j++) {
// str[i][j]
p = tr[p][str[i][j] - 'a'];
st.p = i;
st.add(, m, st.rt[p]);
}
} solve(); for(int i = ; i <= m; i++) {
printf("%lld\n", nodea[i]);
}
return ;
}
AC代码
正解不是广义SAM,是普通SAM,还不用离线......还是我太菜了>_<
时限4s,你们感受一下...尤其是跟下面那个AC代码的对比......
正解:
68分:对S和T分别建sam然后同时跑T。跑到一个位置的时候会有一个匹配长度lenth。这时给T的节点打上长度为lenth的标记。
最后拓扑序跑一遍T的sam,统计答案。总不同子串数 - 匹配子串数。
#include <cstdio>
#include <cstring>
#include <algorithm> typedef long long LL;
const int N = , M = ; struct Edge {
int nex, v;
}edge[N << ]; int tp; int tr[N << ][], fail[N << ], len[N << ], last, tot;
int e[N << ], n, vis[N << ], Time, f[N], vis2[N << ], use[N << ], vis3[N << ];
int rt[N << ], ls[M], rs[M], cnt;
char str[N], ss[N]; inline void init() {
tot = last = ;
return;
} inline void add(int x, int y) {
tp++;
edge[tp].v = y;
edge[tp].nex = e[x];
e[x] = tp;
return;
} void insert(int p, int l, int r, int &o) {
if(!o) o = ++cnt;
use[o] = ;
if(l == r) {
return;
}
int mid = (l + r) >> ;
if(p <= mid) insert(p, l, mid, ls[o]);
else insert(p, mid + , r, rs[o]);
return;
} int merge(int x, int y) {
if(!x || !y) return x | y;
int o = ++cnt;
use[o] = use[x] | use[y];
ls[o] = merge(ls[x], ls[y]);
rs[o] = merge(rs[x], rs[y]);
return o;
} inline void insert(char c, int id) {
int f = c - 'a', p = last, np = ++tot;
last = np;
len[np] = len[p] + ;
///insert(id, 1, n, rt[np]);
while(p && !tr[p][f]) {
tr[p][f] = np;
p = fail[p];
}
if(!p) {
fail[np] = ;
}
else {
int Q = tr[p][f];
if(len[Q] == len[p] + ) {
fail[np] = Q;
}
else {
int nQ = ++tot;
len[nQ] = len[p] + ;
fail[nQ] = fail[Q];
fail[Q] = fail[np] = nQ;
memcpy(tr[nQ], tr[Q], sizeof(tr[Q]));
while(tr[p][f] == Q) {
tr[p][f] = nQ;
p = fail[p];
}
}
}
return;
} void DFS_1(int x) {
for(int i = e[x]; i; i = edge[i].nex) {
int y = edge[i].v;
DFS_1(y);
rt[x] = merge(rt[x], rt[y]);
}
return;
} namespace sam {
int tot, len[N << ], fail[N << ], tr[N << ][], last, large[N << ];
int bin[N << ], topo[N << ];
inline void clear() {
for(int i = ; i <= tot; i++) {
memset(tr[i], , sizeof(tr[i]));
large[i] = bin[i] = fail[i] = len[i] = ;
}
tot = last = ;
return;
}
inline void insert(char c) {
//printf("insert : "); putchar(c); printf("\n");
int f = c - 'a', p = last, np = ++tot;
last = np;
len[np] = len[p] + ;
while(p && !tr[p][f]) {
tr[p][f] = np;
p = fail[p];
}
if(!p) {
fail[np] = ;
}
else {
int Q = tr[p][f];
if(len[Q] == len[p] + ) {
fail[np] = Q;
}
else {
int nQ = ++tot;
len[nQ] = len[p] + ;
fail[nQ] = fail[Q];
fail[Q] = fail[np] = nQ;
memcpy(tr[nQ], tr[Q], sizeof(tr[Q]));
while(tr[p][f] == Q) {
tr[p][f] = nQ;
p = fail[p];
}
}
}
return;
}
inline LL sort() {
for(int i = ; i <= tot; i++) {
bin[len[i]]++;
}
for(int i = ; i <= tot; i++) {
bin[i] += bin[i - ];
}
for(int i = ; i <= tot; i++) {
topo[bin[len[i]]--] = i;
}
LL ans = ;
for(int i = tot; i >= ; i--) {
int x = topo[i];
if(large[x] > len[fail[x]]) {
ans -= std::min(len[x], large[x]) - len[fail[x]];
}
ans += len[x] - len[fail[x]];
large[fail[x]] = std::max(large[fail[x]], large[x]);
}
return ans;
}
} int main() { freopen("in.in", "r", stdin);
freopen("my.out", "w", stdout); init();
scanf("%s", str);
n = strlen(str);
for(int i = ; i < n; i++) {
insert(str[i], i + );
}
for(int i = ; i <= tot; i++) add(fail[i], i);
//DFS_1(1);
/// build over
int q, x, y;
scanf("%d", &q);
for(Time = ; Time <= q; Time++) {
//printf("i = %d \n", Time);
scanf("%s%d%d", ss, &x, &y);
int m = strlen(ss);
sam::clear();
for(int i = ; i < m; i++) {
sam::insert(ss[i]);
}
/// match
int p1 = , p2 = , lenth = ;
for(int i = ; i < m; i++) {
int ff = ss[i] - 'a';
p2 = sam::tr[p2][ff];
while(p1 && !tr[p1][ff]) {
p1 = fail[p1];
lenth = len[p1];
}
if(!p1) {
p1 = ;
}
if(tr[p1][ff]) {
p1 = tr[p1][ff];
lenth++;
}
sam::large[p2] = std::max(sam::large[p2], lenth);
}
LL ans = sam::sort();
printf("%lld\n", ans);
}
return ;
}
68分代码
100分:写个匹配函数来判断能不能匹配。失配的话先不跳fail,而是lenth--。
#include <cstdio>
#include <cstring>
#include <algorithm> typedef long long LL;
const int N = , M = ; struct Edge {
int nex, v;
}edge[N << ]; int tp; int tr[N << ][], fail[N << ], len[N << ], last, tot;
int e[N << ], n, Time, use[M];
int rt[N << ], ls[M], rs[M], cnt, X, Y;
char str[N], ss[N]; inline void init() {
tot = last = ;
return;
} inline void add(int x, int y) {
tp++;
edge[tp].v = y;
edge[tp].nex = e[x];
e[x] = tp;
return;
} void insert(int p, int l, int r, int &o) {
if(!o) o = ++cnt;
use[o] = ;
if(l == r) {
return;
}
int mid = (l + r) >> ;
if(p <= mid) insert(p, l, mid, ls[o]);
else insert(p, mid + , r, rs[o]);
return;
} int merge(int x, int y) {
if(!x || !y) return x | y;
int o = ++cnt;
use[o] = use[x] | use[y];
ls[o] = merge(ls[x], ls[y]);
rs[o] = merge(rs[x], rs[y]);
return o;
} inline void insert(char c, int id) {
int f = c - 'a', p = last, np = ++tot;
last = np;
len[np] = len[p] + ;
insert(id, , n, rt[np]);
while(p && !tr[p][f]) {
tr[p][f] = np;
p = fail[p];
}
if(!p) {
fail[np] = ;
}
else {
int Q = tr[p][f];
if(len[Q] == len[p] + ) {
fail[np] = Q;
}
else {
int nQ = ++tot;
len[nQ] = len[p] + ;
fail[nQ] = fail[Q];
fail[Q] = fail[np] = nQ;
memcpy(tr[nQ], tr[Q], sizeof(tr[Q]));
while(tr[p][f] == Q) {
tr[p][f] = nQ;
p = fail[p];
}
}
}
return;
} void DFS_1(int x) {
for(int i = e[x]; i; i = edge[i].nex) {
int y = edge[i].v;
DFS_1(y);
rt[x] = merge(rt[x], rt[y]);
}
return;
} inline bool ask(int L, int R, int l, int r, int o) {
//printf("ask [%d %d] [%d %d] o = %d sum = %d \n", L, R, l, r, o, use[o]);
if(!o) return ;
if(L <= l && r <= R) return use[o];
int mid = (l + r) >> ; bool ans = ;
if(L <= mid) ans |= ask(L, R, l, mid, ls[o]);
if(mid < R) ans |= ask(L, R, mid + , r, rs[o]);
return ans;
} inline bool match(int p, int lenth, int f) {
if(!tr[p][f]) return ;
return ask(X + lenth, Y, , n, rt[tr[p][f]]);
} namespace sam {
int tot, len[N << ], fail[N << ], tr[N << ][], last, large[N << ];
int bin[N << ], topo[N << ];
inline void clear() {
for(int i = ; i <= tot; i++) {
memset(tr[i], , sizeof(tr[i]));
large[i] = bin[i] = fail[i] = len[i] = ;
}
tot = last = ;
return;
}
inline void insert(char c) {
//printf("insert : "); putchar(c); printf("\n");
int f = c - 'a', p = last, np = ++tot;
last = np;
len[np] = len[p] + ;
while(p && !tr[p][f]) {
tr[p][f] = np;
p = fail[p];
}
if(!p) {
fail[np] = ;
}
else {
int Q = tr[p][f];
if(len[Q] == len[p] + ) {
fail[np] = Q;
}
else {
int nQ = ++tot;
len[nQ] = len[p] + ;
fail[nQ] = fail[Q];
fail[Q] = fail[np] = nQ;
memcpy(tr[nQ], tr[Q], sizeof(tr[Q]));
while(tr[p][f] == Q) {
tr[p][f] = nQ;
p = fail[p];
}
}
}
return;
}
inline LL sort() {
for(int i = ; i <= tot; i++) {
bin[len[i]]++;
}
for(int i = ; i <= tot; i++) {
bin[i] += bin[i - ];
}
for(int i = ; i <= tot; i++) {
topo[bin[len[i]]--] = i;
}
LL ans = ;
for(int i = tot; i >= ; i--) {
int x = topo[i];
if(large[x] > len[fail[x]]) {
ans -= std::min(len[x], large[x]) - len[fail[x]];
}
ans += len[x] - len[fail[x]];
large[fail[x]] = std::max(large[fail[x]], large[x]);
}
return ans;
}
} int main() { //printf("%d \n", (sizeof(ls) * 3) / 1048576); freopen("name.in", "r", stdin);
freopen("name.out", "w", stdout); init();
scanf("%s", str);
n = strlen(str);
for(int i = ; i < n; i++) {
insert(str[i], i + );
}
for(int i = ; i <= tot; i++) add(fail[i], i);
DFS_1();
/// build over
int q;
scanf("%d", &q);
for(Time = ; Time <= q; Time++) {
//printf("i = %d \n", Time);
scanf("%s%d%d", ss, &X, &Y);
int m = strlen(ss);
sam::clear();
for(int i = ; i < m; i++) {
sam::insert(ss[i]);
}
/// match
int p1 = , p2 = , lenth = ;
for(int i = ; i < m; i++) {
int ff = ss[i] - 'a';
p2 = sam::tr[p2][ff];
while(p1 && !match(p1, lenth, ff)) {
if(lenth) lenth--;
if(lenth == len[fail[p1]]) p1 = fail[p1];
}
if(!p1) {
p1 = ;
}
else {
p1 = tr[p1][ff];
lenth++;
}
sam::large[p2] = std::max(sam::large[p2], lenth);
//printf("lneth = %d \n", lenth);
}
LL ans = sam::sort();
printf("%lld\n", ans);
}
return ;
}
AC代码
LOJ#2720 你的名字的更多相关文章
- [LOJ 2720][BZOJ 5417][UOJ 395][NOI 2018]你的名字
[LOJ 2720][BZOJ 5417][UOJ 395][NOI 2018]你的名字 题意 给定一个大串 \(S\) 以及 \(q\) 次询问, 每次询问给定一个串 \(T\) 和区间 \([l, ...
- LOJ 2720 「NOI2018」你的名字——后缀自动机
题目:https://loj.ac/problem/2720 自己总是分不清 “SAM上一个点的 len[ ] ” 和 “一个串的前缀在 SAM 上匹配的 len ”. 于是原本想的 68 分做法是, ...
- loj#2720. 「NOI2018」你的名字
链接大合集: loj uoj luogu bzoj 单纯地纪念一下写的第一份5K代码.../躺尸 因为ZJOI都不会所以只好写NOI的题了... 总之字符串题肯定一上来就拼个大字符串跑后缀数组啦! ( ...
- SA / SAM 题目集
上一次做 SA / SAM 相关的题还要数到某场毒瘤 NOIP 模拟赛--这么久没做了都快忘光了--写点东西记录一些最近做到的水好题. LOJ2059 「TJOI / HEOI2016」字符串 题意 ...
- 【LOJ】#2720. 「NOI2018」你的名字
题解 把S串建一个后缀自动机 用一个可持久化权值线段树维护每个节点的right集合是哪些节点 求本质不同的子串我们就是要求T串中以每个点为结束点的串有多少在\(S[l..r]\)中出现过 首先我们需要 ...
- LOJ_#2720. 「NOI2018」你的名字 _后缀数组+主席树+倍增
题面: https://loj.ac/problem/2720 考虑枚举T串的每个后缀i,我们要做两件事. 一.统计有多少子串[i,j]在S中要求位置出现. 二.去重. 第二步好做,相当于在后缀数组上 ...
- 「NOI2018」你的名字
「NOI2018」你的名字 题目描述 小A 被选为了\(ION2018\) 的出题人,他精心准备了一道质量十分高的题目,且已经 把除了题目命名以外的工作都做好了. 由于\(ION\) 已经举办了很多届 ...
- Material Design Reveal effect(揭示效果) 你可能见过但是叫不出名字的小效果
Material Design Reveal effect(揭示效果) 你可能见过但是叫不出名字的小效果 前言: 每次写之前都会来一段(废)话.{心塞...} Google Play首页两个tab背景 ...
- idea怎么设置自己的名字和时间
1.直接修改idea64.exe.vmoptions 里面添加上 -Duser.name=yourname 重启即可生效 1.file - settings-Editor- File and Code ...
随机推荐
- 如何使用 Yum Repository 安装指定版本的 MySQL
自从从使用 debian 系的 apt-get 转到使用 yum 工具之后一直不是很习惯,也没有去看过很多工具包安装的时候到底影响到了哪些文件等.这次借这次社区版 MySQL 安装来一并梳理一下. 首 ...
- git format-patch制作内核补丁
git init git add ./ git commit 之后修改代码 修改代码后执行 git add ./ git commit 执行完成后执行git log查询commit 的id 执行git ...
- GitHub & OAuth 2.0 & JWT
GitHub & OAuth 2.0 & JWT https://www.rfcreader.com/#rfc6749 GitHub & OAuth https://www.b ...
- Yii2控制台命令
Yii2控制台表格输出: 例如: $in_sheet_number_queue = []; $wms_material_in_sheet_list = \core\models\WmsMaterial ...
- dbExpress操作中用TDBGrid显示数据
由于一些数据感知组件如TDBGrid等是需要用到数据缓存的,这和dbExpress组件的存取机制是矛盾的.所以当打开数据集时会出现如下内容的警告框:“Operation not allowed on ...
- Log4j2配置与使用
依赖包: <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api --> <depend ...
- 二、两条Linux删除数据跑路命令
一.rm rm -rf / 无提示循环删除根目录,,删除存在被恢复的可能 二.dd dd if=/dev/urandom of=/dev/hda1 随机填写数据到相应分区,直到填满为止.重写后的分区无 ...
- LODOP打印安装到win的特殊字体
LODOP能够打印的字体,来源于安装到本机windows里字体库的字体,如果需要打印特别的字体,需要在该操作系统安装.由于web网站的用户千差万别,字体库也有不同,但是一般常见的字体都是有的,因此做模 ...
- window.onpopstate
概述 window.onpopstate是popstate事件在window对象上的事件句柄. 每当处于激活状态的历史记录条目发生变化时,popstate事件就会在对应window对象上触发. 如果当 ...
- codeforces/gym/101291/B
题意:给你n个杠铃的杆子,在给你m个杠铃片,问你能组成多少个重量不同的完整杠铃(杠铃杆子也算一个完整的的杠铃) 解题思路:dfs直接搜,数据很小,每个杠铃片有三种状态(放杆子左边,放杆子右边,两边都不 ...