NOI2018 你的名字

2022-05-09 10:27:11 字數 3944 閱讀 3646

嘟嘟嘟

這題以前寫過棄掉了,後來竟然連自己的68分寫法都看不懂了……

這次回首這道題,心想怎麼說也得把這題切了,哪怕抄題解也行。

但沒想到別人的題解自己怎麼也看不懂,最終還是自己搞出來了(我真nb)。

總用時前一天下午到第二天凌晨0:30+第二天半個上午。

我們先來回顧\(l = 1, r = n\)的情況。

大體思路就是求出本質不同的公共子串數目,然後用\(t\)串的本質不同的子串減去即可。

\(t\)的本質不同的子串個數大家都會求,就不在這嘮叨了。我們重點看前面的怎麼求。

我們在\(t\)的字尾自動機中插入乙個字元\(t[i]\)之後,馬上記錄\(t[i]\)所在結點的父親的長度,即\(len[link[pos[i]]]\)。這個是什麼東西呢?記前面那個值為\(len\),他表示的是左端點為\(1\)~\(len - 1\),右端點為\(i\)的這些子串第一次的出現位置都是\(i\)。因為新建的這個結點的endpos必定只有這個\(i\),那麼屬於這個結點的所有子串的出現位置也就只有\(i\)了。

注意,一定是插入完後馬上統計。如果把字尾自動機建完後再乙個個統計就不對了。因為有的結點的父親已經改變。

我們記上面求出的這個陣列為\(ha[i]\)。

求出了上面這個東西後就好辦了。我們把\(t\)放在\(s\)的字尾自動機上跑,記\(t\)到位置\(i\)時的匹配長度為\(l\),那麼如果\(l >ha[i]\)的話,就表示在在\(t\)的第\(i\)個位置,本質不同的公共子串數目增加了\(l - ha[i]\),把他加到答案裡即可。

#include#include#include#include#include#include#include#include#include#includeusing namespace std;

#define enter puts("")

#define space putchar(' ')

#define mem(a, x) memset(a, x, sizeof(a))

#define in inline

typedef long long ll;

typedef double db;

const int inf = 0x3f3f3f3f;

const db eps = 1e-8;

const int maxn = 5e5 + 5;

inline ll read()

inline void write(ll x)

char s[maxn], t[maxn];

int ha[maxn];

struct sam

in void insert(int c)

}las = now;

} int pos[maxn << 1], buc[maxn << 1];

in ll lcs(char* s)

return ret;

}}s, t;

in void work0()

int main()

}return 0;

}

噹噹噹噹!下面開始講正解!

現在有了$l, r$的限制。乙個直觀的想法就是線段樹合併求出每乙個結點的endpos集合。然後匹配的時候除了滿足這個節點有字元$c$的轉移,還要保證轉移後到達的結點的endpos有在$[l, r]$裡面的。

這樣一直跳,如果他最終在位置$i$時的匹配長度為$l$,我們要考慮從endpos向前$l$長度的字串超沒超出$l$的限制,即$endpos - l$是否小於$l$,如果小於,那麼匹配的長度實際是$endpos - l + 1$。然後看這個長度是否大於$ha[i]$,如果大於,就減去$ha[i]$加到答案裡。

那麼為了讓匹配的長度盡量不超過下界$l$,我們要找的是在$[l, r]$區間內盡量靠右的endpos。這個線段樹維護最大值即可。

表面上看這麼做似乎就萬事大吉了。但實際上68分以後照樣wa。

為啥咧?

因為我們查詢endpos的時候,是在能匹配的最長長度的結點上的線段樹查詢的,長度越長,endpos就越少,也就說明,我們在較長的匹配上查到的endpos減去$l$後會超過$l$的限制,而且可能超出很多,導致剩下的部分比$ha[i]$小。但是如果我們在匹配長度較短的線段樹上查詢的話,反而能找到乙個endpos,在$l$的限制之內匹配的長度比$ha[i]$大,或者所有的匹配長度乾脆都不會超出$l$。這時候就需要把這些長度加到答案裡。

因此,匹配的時候,我們不僅要在這個結點的線段樹上查詢endpos計算貢獻。還要到他的祖先結點上執行同樣的過程,直到匹配長度不受$l$的限制再退出。接著去匹配下一位。

自認為碼風還是十分工整的。

#include#include#include#include#include#include#include#include#include#includeusing namespace std;

#define enter puts("")

#define space putchar(' ')

#define mem(a, x) memset(a, x, sizeof(a))

#define in inline

typedef long long ll;

typedef double db;

const int inf = 0x3f3f3f3f;

const db eps = 1e-8;

const int maxn = 5e5 + 5;

const int maxt = 4e7 + 5;

inline ll read()

inline void write(ll x)

in void myfile()

int n, l, r;

char s[maxn], t[maxn];

int ha[maxn];

struct tree

seg[maxt];

int root[maxn << 1], cur[maxn], tcnt = 0;

#define ls seg[now].ls

#define rs seg[now].rs

in void update(int& now, int l, int r, int id)

int mid = (l + r) >> 1;

if(id <= mid) update(ls, l, mid, id);

else update(rs, mid + 1, r, id);

seg[now].rpos = max(seg[ls].rpos, seg[rs].rpos);

}in int merge(int x, int y, int l, int r)

int mid = (l + r) >> 1, z = ++tcnt;

seg[z].ls = merge(seg[x].ls, seg[y].ls, l, mid);

seg[z].rs = merge(seg[x].rs, seg[y].rs, mid + 1, r);

seg[z].rpos = max(seg[x].rpos, seg[y].rpos);

return z;

}in int query(int now, int l, int r, int l, int r)

struct sam

in void insert(int c, int id)

}las = now;

if(~id) cur[id] = now;

} int pos[maxn << 1], buc[maxn << 1];

in void solve()

}in ll calc(char* s)}}

}return ret;

}}s, t;

int main()

s.solve();

int m = read();

for(int i = 1; i <= m; ++i)

return 0;

}

NOI2018 你的名字

題目描述 小 a 被選為了 ion2018 的出題人,他精心準備了一道質量十分高的題目,且已經把除了題目命名以外的工作都做好了。由於 ion 已經舉辦了很多屆,所以在題目命名上也是有規定的,ion 命題手冊規定 每年由命題委員會規定乙個小寫字母字串,我們稱之為那一年的命名串,要求每道 題的名字必須是...

NOI2018 你的名字

sam寫的太不熟練了 sam上的線段樹合併也不熟練 調了半天樣例 給定乙個s,q次詢問,每次給出t,l,r,求對於s l,r 屬於t的子串卻不屬於s l,r 的子串有多少個 看上去挺簡潔的乙個問題。對於s 1,n 68pts?如果做過 heoi2015 最短不公共子串 就好做多了!可以對a,b分別建...

NOI2018 你的名字

實力強大的小 a 被選為了 ion2018 的出題人,現在他需要解決題目的命名問題。小 a 被選為了 ion2018 的出題人,他精心準備了一道質量十分高的題目,且已經把除了題目命名以外的工作都做好了。由於 ion 已經舉辦了很多屆,所以在題目命名上也是有規定的,ion 命題手冊規定 每年由命題委員...