【日常小测】Circle of Stones

题目大意

给定$(n \le 10^6)$个石头,它们排成一圈
每个石头上有一个小写英文字母
定义优雅为任意两个相邻的石头上的字母不同
请你输出$n$个数(0/1),第$i$个数表示删除$i-1$个连续的石头能否使原来的圈成为优雅的圈
每个测试点包含$T(1 \le T \le 6)$组数据

解题报告

我们先来解决一个子问题:如果给定一个优雅的长度为$n$的圈$A$,问保留能否选择连续的$1 \sim n$个石头,使之优雅
考虑长度为$l$的询问,那么左端点和右端点的可行解都是一段长度为$n-l+1$的前缀/后缀
如果这两段区间不完全相等(不是原串的循环节),那么肯定可以把不等的那一位拿出来作为可行解
于是我们用$KMP$求一个循环节就可以了

现在我们再来看原问题,考虑原串中相邻的两位$i,i+1$和右边一次相邻的两位$j,j+1$
显然我们至多保留$[i+1,j]$这一串
那么这一串此时已经是优雅的了,于是就成了刚刚我们讨论的子问题
于是找出每一个极大的优雅子串,跑一边$KMP$更新答案,最后输出即可
时间复杂度:$O(Tn)$

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
const int N = 2000009;
 
int n,nxt[N],vis[N];
char pat[N],ans[N];
 
inline int read() {
    char c=getchar(); int ret=0,f=1;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret*f;
}
 
inline bool SPJ() {
    if (pat[n] == pat[1]) return 0;
    for (int i=2;i<=n;i++) if (pat[i] == pat[i-1]) return 0;
    return 1;
}
 
inline int cyc(int l, int r) {
    nxt[1] = 0; 
    for (int w=0,i=l+1;i<=r;i++) {
        while (w && pat[w+l] != pat[i]) w = nxt[w];
        if (pat[w+l] == pat[i]) w++;
        nxt[i-l+1] = w;
    } int len = r - l + 1;
    if (nxt[len] &&len % (len - nxt[len]) == 0) return len - nxt[len];
    else return 0;
}
 
inline void update(int l, int r) {
    static int id = 1; id++;
    int lop = cyc(l, r), len = r - l + 1;
    for (int w=nxt[len];w;w=nxt[w]) vis[w] = id;
    for (int i=1,lim=min(len,n);i<=lim;i++) 
        if (vis[len-i+1]!=id) ans[n-i] = '1';
}
 
int main() {
    for (int t=1,lop;~scanf("%s",pat+1);++t) {
        n = strlen(pat+1); printf("Case %d: ",t);
        for (int i=0;i<n;i++) ans[i] = '0';
        ans[n] = 0; ans[n - 1] = '1';  
        memcpy(pat+n+1, pat+1, sizeof(char)*n);
        if (SPJ()) update(1, n<<1);
        else {
            int pre = 0;
            for (int i=n<<1;i;i--) {
                if (pat[i] == pat[i+1]) {
                    if (i < n) update(i+1, pre);
                    pre = i;
                }
            }
            if (pre && pat[1] == pat[n] && n > 1) update(1, pre);
        }
        puts(ans);
    }
    return 0;
}

【CodeChef TREEP】Period on tree

相关链接

题目传送门:https://www.codechef.com/NOV15/problems/TREEP
中文题面:http://www.codechef.com/download/translated/NOV15/mandarin/TREEP.pdf
官方题解:https://discuss.codechef.com/questions/76897/treep-editorial

题目大意

给定一个$n(n \le 200000)$个结点的树,每条边上有一个小写字母
定义$Str_{u,v}$为从$u$走到$v$将路径上的小写字母按顺序拼起来组成的字符串
给定$m(m \le 200000)$个操作,操作分两类:

  1. 输入$u,v$,询问$Str_{u,v}$的循环节最短为多长
  2. 输入$u,v,c$,表示将$u \to v$这条边上的字符改成$c$

解题报告

这么奇怪的题目,一看就只能是$Hash$来做
我们不难发现,若循环节为$l$,串长$len$,那么$\forall x \in (l,len]$若$x \equiv 0(\bmod l)$则$x$也是循环节
于是如果我们可以$O(\log n)$判定$l$是不是原串的循环节,我们就可以在$O(n \log^2 n)$的时间复杂度内解决这个问题了

我们发现,若是循环节,那么开头那一段的$Hash$值不仅可以整除整个串的$Hash$值,而且等于一个等比数列
于是我们就可以用BIT维护一个到根的前缀和,然后将判定优化到$O(\log n)$

Code

这是考试的代码,似乎没写多组数据,CC上交不了QwQ

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
const int N = 200009;
const int M = N << 1;
const int SED = 37;
const int MOD = 1000000009;
 
int n,m,head[N],to[M],nxt[M],cost[M],dep[N],beg[N],END[N]; 
int POW[M],tot,pri[N],vis[N],sur[N],fa[N][20],val[N];
char pat[5];
 
inline int read() {
    char c=getchar(); int ret=0,f=1;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret*f;
}
 
inline void AddEdge(int u, int v, int c) {
    static int E = 1; cost[E+1] = cost[E+2] = c;
    to[++E] = v; nxt[E] = head[u]; head[u] = E;
    to[++E] = u; nxt[E] = head[v]; head[v] = E;
}
 
class FenwickTree{
    #define lowbit(x) ((x)&(-(x)))
    int sum[N];
    public:
        inline void modify(int l, int r, int v) {
            for (int i=l-1;i;i-=lowbit(i)) sum[i] = (sum[i] - v) % MOD;
            for (int i=r;i;i-=lowbit(i)) sum[i] = (sum[i] + v) % MOD;
        }   
        inline int query(int p) {
            int ret = 0; p = beg[p];
            for (int i=p;i<=n;i+=lowbit(i)) 
                ret = (ret + sum[i]) % MOD;
            return (ret + MOD) % MOD;
        }
}up,dw;
 
inline void prework() {
    POW[0] = sur[1] = 1; 
    for (int i=1;i<M;i++) POW[i] = (LL)POW[i-1] * SED % MOD;
    for (int i=2;i<N;i++) {
        if (!vis[i]) pri[++tot] = i, sur[i] = i;
        for (int j=1;j<=tot&&i*pri[j]<N;j++) {
            vis[i*pri[j]] = 1; sur[i*pri[j]] = pri[j];
            if (i % pri[j] == 0) break;
        }
    }
}
 
inline int LCA(int u, int v) {
    if (dep[u] < dep[v]) swap(u, v);
    for (int j=19;~j;j--) if (dep[fa[u][j]]>=dep[v]) u=fa[u][j];
    if (u == v) return u;
    for (int j=19;~j;j--) if (fa[u][j]!=fa[v][j]) u=fa[u][j],v=fa[v][j];
    return fa[u][0];
}
 
void DFS(int w, int f) {
    static int dfs_cnt = 0; 
    beg[w] = ++dfs_cnt;
    fa[w][0] = f; dep[w] = dep[f] + 1;
    for (int i=head[w];i;i=nxt[i]) {
        if (to[i] != f) {
            val[to[i]] = cost[i];
            DFS(to[i], w);
        }
    }
    END[w] = dfs_cnt;
    up.modify(beg[w], END[w], (LL)val[w] * POW[n-dep[w]+1] % MOD);
    dw.modify(beg[w], END[w], (LL)val[w] * POW[dep[w]] % MOD); 
}
 
inline int FA(int u, int k) {
    for (int t=0;k;k>>=1,t++) 
        if (k&1) u = fa[u][t];
    return u;
}
 
inline int query(int u, int v, int lca, int k) {
    if (dep[u] - dep[lca] >= k) {
        int ret = (up.query(u) - up.query(FA(u, k))) % MOD;
        return (((LL)ret * POW[dep[u]-1] % MOD) + MOD) % MOD;
    } else {
        int ret = (up.query(u) - up.query(lca)) % MOD;
        ret = (LL)ret * POW[dep[u] - 1] % MOD;
        int res = dep[u] + dep[v] - (dep[lca] << 1) - k;
        res = (dw.query(FA(v, res)) - dw.query(lca)) % MOD;
        res = (LL)res * POW[n + dep[u] - (dep[lca] << 1) - 1] % MOD;
        return ((res + ret) % MOD + MOD) % MOD;
    }
}
 
inline int Pow(int w, int t) {
    int ret = 1;
    for (;t;t>>=1,w=(LL)w*w%MOD)
        if (t&1) ret=(LL)ret*w%MOD;
    return ret;
}   
 
inline int query(int u, int v) {
    int lca = LCA(u, v); tot = 0;
    int len = dep[u] + dep[v] - (dep[lca] << 1);
    int STA = query(u, v, lca, len), ori = len;
    for (int c,w=len;c=sur[w],w>1;) {
        pri[++tot] = c; vis[tot] = 0;
        for (;w%c==0;w/=c) vis[tot]++; 
    }
    for (int i=1,sta,cur,PRI;i<=tot;i++) {
        PRI = pri[i];
        for (int j=1;j<=vis[i];j++) {
            sta = (LL)(Pow(POW[len/PRI], ori/len*PRI) - 1) * Pow(POW[len/PRI]-1, MOD-2) % MOD;
            cur = (LL)STA * Pow(query(u, v, lca, len / PRI), MOD-2) % MOD;
            if (sta==cur) len /= PRI;
            else break;
        }
    }
    return len;
}
 
int main() {
    prework();
    n = read(); 
    for (int i=1,u,v;i<n;i++) {
        u = read(); v = read();
        scanf("%s",pat+1);
        AddEdge(u, v, pat[1]-'a'+1);
    }
    DFS(1, 1);
    for (int j=1;j<20;j++) {
        for (int i=1;i<=n;i++) {
            fa[i][j] = fa[fa[i][j-1]][j-1];
        }
    } 
    m = read(); 
    for (int i=1,u,v,c;i<=m;i++) {
        if (read() == 1) {
            u = read(); v = read();
            printf("%d\n",query(u, v));
        } else {
            u = read(); v = read();
            if (dep[u] > dep[v]) swap(u, v);
            scanf("%s",pat+1); c = pat[1]-'a'+1;
            if (c == val[v]) continue;
            up.modify(beg[v], END[v], (LL)(c - val[v]) * POW[n-dep[v]+1] % MOD);
            dw.modify(beg[v], END[v], (LL)(c - val[v]) * POW[dep[v]] % MOD);
            val[v] = c;
        }
    } 
    return 0;
}

【日常小测】String

题目大意

先给定一个长度为$n(n \le 50000)$的字符串$S$
之后给定$m(m \le 50000)$个操作(强制在线),操作分两种

  1. 在字符串$S$末尾加入一个字符
  2. 将$S$的子串$[l,r]$取出来,设为字符串$T$,询问$T$中出现至少两次的字符串的最长长度

解题报告

我们考虑用一棵线段树来支持插入斜率为$-1$的线段,并询问区间最值
这个是用来维护答案的,至于为什么插入的是线段,待会儿再说
至于具体实现,因为斜率相同,所以我们只需要将直线的纵截距插入即可,换言之我们只需要支持区间取$\max$即可

现在假设我们已经在维护了$[1,r-1]$的答案,并且我们在维护$[1,r]$回答所有右端点为$r$的询问
考虑插入第$r$个字符会影响哪些部分的答案:显然只会影响$fail$链上的答案
我们记录$last_x$表示$SAM$上的结点x代表的串在$S$中出现的最后一次的时间
我们注意到一条$fail$链上$last_x$一定是相等的,所以我们只需要考虑最长长度$l$
于是我们需要在线段树的$[last_x-l+1,last_x]$区间插入$x+y-(last_x+1)=0$的直线
之后我们更新$fail$链上的$last_x$,至于询问我们只需要到线段树上查询区间最值
当然维护$fail$树的话,我们需要使用$LCT$。

注意到$LCT$的一个$splay$里的$last_x$相等,所以单次$access$只会贡献$\log n$次区间取$\max$操作
所以我们已经能在$O(n \log ^2 n)$的时间复杂度内解决离线版本了
现在考虑在线版本,我们将线段树可持久化即可

【日常小测】相似子串

题目大意

给定一棵$n(n \le 10^5)$的无根树,每条边上有一个字符
定义$str_{u,v}$为从$u$走到$v$,将边上的字符按顺序写下来得到的字符串
给定$m(m \le 10^5)$个询问,每一次询问会定义$k_i$种形如$a,b$这样的等价关系,意义为将所有的$a,b$字符视为同一个字符(关系具有传递性)
对于每一个询问还会给定$u_1,v_1,u_2,v_2$四个点,请你回答$str_{u_1,v_1}$与$str_{u_2,v_2}$是否至多有一个位置不匹配

解题报告

这种奇怪的匹配题目除了$FFT$之外,似乎只能$Hash$了吧?

我们先来考虑没有等价类,且是一条链的情况

定义$Hash$函数$f_{i,u,v}$为对于第$i$种字符来讲,$str_{u,v}$的$Hash$值
更进一步,$f_{i,u,v}=\sum\limits_{j=u}^{v}{[char_j=i] \cdot G^{j-u+1}}$
那么这两个字符串的$Hash$值只能在两种字符下不同,且这两种字符差的$G$的次幂相等

考虑快速求得$Hash$值,这个很简单,做一个前缀和就好
考虑快速求的差值对应的$G$的次幂,这个我们先预处理一下,然后扔到一个$map$里去就可以了
至此我们已经能在$O(|\sum|+\log n)$的时间复杂度内回答单个询问了

现在考虑加入等价类,这个我们可以将整个等价类的$Hash$值加起来一起比较就可以了
至于不是链的情况,那我们求个到根前缀和就能够解决问题啦!
至此我们已经在$O(n \log n + m (|\sum| + \log n))$的时间复杂度内解决了这个问题

【BZOJ 4199】[NOI2015] 品酒大会

相关链接

题目传送门Ⅰ:http://uoj.ac/problem/131
题目传送门Ⅱ:http://www.lydsy.com/JudgeOnline/problem.php?id=4199
神犇题解:https://blog.sengxian.com/solutions/bzoj-4199

解题报告

其实sengxian都写了题解了,我还有什么写的意义 _(:з」∠)_
不过还是简单说两句吧!

我们考虑把SA建出来,height数组搞出来
然后把height数组排个序
从小到大依次合并上去就可以辣!

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 600009; 
const LL INF = 1e18;

int n,val[N],sz[N],mx[N],mn[N]; 
int height[N],bot[N],sa[N],arr[N];
int *rak,*que,arr1[N],arr2[N],fa[N];
char pat[N]; LL ans1,ans2;
stack<pair<LL,LL> > ans;

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

inline bool cmp(const int &a, const int &b) {
	return height[a] > height[b];
}

class Suffix_Array{
	public:
		inline void build() {
			rak = arr1; que = arr2;
			for (int i=1;i<=n;i++) bot[pat[i]-'a'+1]++;
			for (int i=1;i<=26;i++) bot[i] += bot[i-1];
			for (int i=1;i<=n;i++) sa[bot[pat[i]-'a'+1]--] = i;
			for (int ty=0,i=1;i<=n;i++) rak[sa[i]] = pat[sa[i]]==pat[sa[i-1]]? ty: ++ty;
			for (int l=1,tot=0,ty;l<n;l<<=1,tot=0) {
				memset(bot,0,sizeof(bot));
				for (int i=n-l+1;i<=n;i++) que[++tot] = i;
				for (int i=1;i<=n;i++) if (sa[i]>l) que[++tot] = sa[i] - l;
				for (int i=1;i<=n;i++) bot[rak[i]]++;
				for (int i=1;i<=n;i++) bot[i] += bot[i-1];
				for (int i=n;i;i--) sa[bot[rak[que[i]]]--] = que[i];
				swap(rak, que); rak[sa[1]] = ty = 1; bot[1] = 1;
				for (int i=2;i<=n;i++) 
					if (que[sa[i]] == que[sa[i-1]] && que[sa[i]+l] == que[sa[i-1]+l]) rak[sa[i]] = ty;
					else bot[rak[sa[i]] = ++ty] = 0;
				if (ty >= n) break;
			}
			Get_Height();
		}
		inline void solve() { 
			for (int i=1;i<=n;i++) {
				arr[i] = fa[i] = i; sz[i] = 1; 
				mn[i] = mx[i] = val[sa[i]];
			}
			sort(arr+1, arr+1+n, cmp);
			for (int i=n-1,j=1;~i;i--) {
				for (;height[arr[j]]>=i;++j) update(arr[j]);
				ans.push(make_pair(ans1, ans2));
			} 
			for (;!ans.empty();ans.pop()) 
				printf("%lld %lld\n",ans.top().first, ans.top().second);
		}	
	private:
		inline void Get_Height() {
			for (int i=1,len,p1,p2;i<=n;i++) {
				len = max(height[rak[i-1]] - 1, 0);
				p1 = i + len; p2 = sa[rak[i]-1] + len;
				while (pat[p1] == pat[p2]) ++p1, ++p2, ++len;
				height[rak[i]] = len;
			} height[1] = -1;
		}
		int find(int x) {return fa[x]==x?x:find(fa[x]);}
		inline void update(int i) { 
			static int E = 1; if (E) E = 0, ans2 = -INF;
			int f1 = find(i), f2 = find(i-1);
			ans1 += (LL)sz[f1] * sz[f2];
			ans2 = max(ans2, (LL)mx[f2] * mx[f1]);
			ans2 = max(ans2, (LL)mn[f2] * mn[f1]);
			sz[f1] += sz[f2]; fa[f2] = f1;
			mx[f1] = max(mx[f1], mx[f2]);
			mn[f1] = min(mn[f1], mn[f2]);
		}
}SA;

int main() {
	n = read();
	scanf("%s",pat+1);
	for (int i=1;i<=n;i++) val[i] = read();
	SA.build();
	SA.solve();
	return 0;
}

—————————— UPD 2017.7.10 ——————————
显然这货用SAM也是可做的

【日常小测】最长公共前缀

题目大意

给定一棵以$1$号点为根、大小为$n$的有根树($n \le 10^5$),每一条边上有一个小写英文字母
给定$m$个询问,第$i$个询问包含一个长度为$t_i$的点集(元素可重,$\sum{t_i} \le 2 \cdot 10^5$),询问$\sum\limits_{a=1}^{t_i}{\sum\limits_{b=a+1}^{t_i}{lcp(a,b)}}$

定义$s_a$为从a出发走向根,将边上的字符按顺序写下来构成的字符串
定义$lcp(a,b)$为$s_a$与$s_b$的最长公共前缀

下面举一个栗子
若树长成这样:

那么$s_5=cb,s_4=cbb$
更进一步,若某次询问的点集为$\{4,5\}$那么答案为$2$

解题报告

看到这种树上的字符串,我能想到AC自动机,或者广义后缀自动机
想了想AC自动机做不了,那就广义后缀自动机来做咯!

考虑后缀自动机上的Fail树
一个点的祖先实际上是这个点代表的字符串的后缀
那么题目中的$lcp(a,b)$就变成了$lca(a,b)$
于是对于每一次询问,我们建一个虚树出来
然后在虚树上DFS一次,统计一下答案就好啦!

另外,这题用后缀数组也可以做!
听武爷讲一讲,大概就是先把Trie树建出来
同时记录下每一次倍增的$Rank$数组(波兰表……)
然后就正常倍增,写法已经和罗穗骞的后缀数组的实现很不同了

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;
 
const int N = 200009;
const int M = N << 1;
 
int n,m,E,head[N],nxt[M],to[M],col[M],pos[N],id[N];
 
inline int read() {
    char c=getchar(); int ret=0,f=1;
    while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
    while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret * f;
}
 
inline void Add_Edge(int u, int v, int c = 0) {
    to[++E] = v; nxt[E] = head[u]; head[u] = E; col[E] = c;
    to[++E] = u; nxt[E] = head[v]; head[v] = E; col[E] = c;
}   
 
class Suffix_Automaton{
	int cnt=1,ch[N][26],fail[N],vis[N],dep[N],sum[N];
	int tot,arr[N],que[N],len[N],fa[N][20],_hash[N];
	vector<int> G[N]; LL ans_tmp;
    public:
        inline int insert(int t, int cc) {
            if (ch[t][cc] && len[ch[t][cc]] == len[t] + 1) return ch[t][cc];
            int tail = ++cnt; len[tail] = len[t] + 1;
            while (!ch[t][cc] && t) ch[t][cc] = tail, t=fail[t];
            if (!t) fail[tail] = 1;
            else {
                if (len[ch[t][cc]] == len[t] + 1) fail[tail] = ch[t][cc];
                else {
                    int nw = ++cnt, w = ch[t][cc]; len[nw] = len[t]+1;
                    memcpy(ch[nw], ch[w], sizeof(ch[nw]));
                    fail[nw] = fail[w]; fail[w] = fail[tail] = nw;
                    for (;ch[t][cc]==w;t=fail[t]) ch[t][cc] = nw;
                }
            } return tail;
        }
        inline void Build_Fail_Tree() {
            memset(head,0,sizeof(head)); E = 0;
            for (int i=2;i<=cnt;i++) Add_Edge(fail[i], i);
            dfs(1, 1);
            for (int j=1;j<20;j++) {
                for (int i=1;i<=cnt;i++) {
                    fa[i][j] = fa[fa[i][j-1]][j-1];
                }
            }   
        }
        inline LL solve(int nn) {
            for (int i=1;i<=nn;i++) arr[i] = pos[read()];
            sort(arr+1, arr+1+nn, [](const int &a, const int &b) {return id[a] < id[b];});
            for (int i=1;i<=nn;i++) _hash[i] = arr[i];
            int mm = nn; nn = unique(arr+1, arr+1+nn) - arr - 1;
            for (int i=1;i<=nn;i++) sum[i] = 0;
            for (int i=1,j=1;i<=mm;i++) {
                while (arr[j] != _hash[i]) j++;
                sum[j]++;
            }
            que[tot=1] = 1; int MX = 1;
            for (int i=1,lca;i<=nn;i++) {
                vis[arr[i]] = sum[i];
                lca = LCA(que[tot], arr[i]);
                while (dep[que[tot]] > dep[lca]) {
                    if (dep[que[tot-1]] >= dep[lca]) G[que[tot-1]].push_back(que[tot]);
                    else G[lca].push_back(que[tot]);
                    --tot;
                }
                if (que[tot] != lca) que[++tot] = lca;
                if (arr[i] != que[tot]) que[++tot] = arr[i];
                MX = max(MX, tot);
            }
            while (tot > 1) G[que[tot-1]].push_back(que[tot]), --tot;
            ans_tmp = 0;
            Get_Ans(1);
            return ans_tmp;
        }
    private:
        void dfs(int w, int f) {
            static int id_count = 0;
            id[w] = ++id_count;
            fa[w][0] = f; dep[w] = dep[f] + 1;
            for (int i=head[w];i;i=nxt[i]) {
                if (to[i] != f) {
                    dfs(to[i], w);
                }
            }
        } 
        int Get_Ans(int w) {
            int ret = vis[w]; 
            if (w > 1) ans_tmp += vis[w] * (vis[w] - 1ll) / 2 * len[w]; 
            for (int i=G[w].size()-1,tmp;~i;i--) { 
                tmp = Get_Ans(G[w][i]);
                ans_tmp += (LL)tmp * ret * len[w];
                ret += tmp;
            }
            vis[w] = 0; 
            G[w].clear();
            return ret;
        }
        inline int LCA(int a, int b) {
            if (dep[a] < dep[b]) swap(a, b);
            for (int i=19;~i;i--) if (dep[fa[a][i]] >= dep[b]) a = fa[a][i];
            if (a == b) return a;
            for (int i=19;~i;i--) if (fa[a][i] != fa[b][i]) a = fa[a][i], b = fa[b][i];
            return fa[a][0];
        }   
}SAM;
 
void DFS(int w, int f, int t) {
    pos[w] = t;
    for (int i=head[w];i;i=nxt[i]) {
        if (to[i] != f) {
            int nt = SAM.insert(t, col[i]);
            DFS(to[i], w, nt);
        }
    }
}
 
int main() {
    n = read(); char pat[10];
    for (int i=1,u,v;i<n;i++) {
        u = read(); v = read();
        scanf("%s",pat+1);
        Add_Edge(u, v, pat[1] - 'a');
    }   
    DFS(1, 1, 1);
    SAM.Build_Fail_Tree();
    for (int i=read();i;i--) 
        printf("%lld\n",SAM.solve(read()));
    return 0;
}

【BZOJ 4373】算术天才⑨与等差数列

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4373
神犇题解:http://blog.csdn.net/TA201314/article/details/51177011

解题报告

首先,假如一个区间满足以下三个条件那么该区间一定合法:

$\%k$后相等
每一个数互不相同
区间和等于某一特定值

后两个条件都可以用线段树维护
问题就是第一个条件非常麻烦

先来考虑一种简单的Hash方式:记录区间的平方和
这样的话,我们可以$O(1)$计算基准值,$O(\log n)$判断是否等于基准值

但众所周知,Hash是不优雅的
于是我们可以先做一个差分序列,然后维护差分序列相邻两项的$\gcd$
考虑如果所有数的差都是$k$的整数倍,那么这些数$ \bmod \ k$之后肯定就相等啦!

另外的话,区间GCD配合差分的题,也不是第一次见了
之前NOI还考过一道:魔幻棋盘
以后和GCD相关的题目要想一想差分行不行啊!

【BZOJ 4641】基因改造

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4641
神犇题解:http://blog.csdn.net/neither_nor/article/details/52218134

解题报告

据说可以KMP,具体可以看这里:传送门
不过,考虑一个更优美的做法:

这题显然可以转化为带通配符的字符串匹配问题
于是我们再撸一发FFT就可以了!

什么?复杂度过不去?
那我也没办法 _(:з」∠)_

【BZOJ 3926】[ZJOI2015] 诸神眷顾的幻想乡

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3926
神犇题解:http://wjmzbmr.com/archives/zjoi-2015-day-1题解/

解题报告

陈老师的语文真刺激!
把叶节点最多只有20个这个条件埋得那么深 QwQ
一开始以为那个条件意思是度数不超过20,于是想边分治,后来都想到”万径人踪灭”去了……

考虑树上每一条路径,至少存在一个叶子结点,使得将该叶子结点作为根后,这条路径上的结点深度递减
于是对于每一个叶节点都DFS一遍,然后建广义后缀自动机
广义自动机的话,直接看这份代码就好吧!
或者想全面学习的话,可以看这里:传送门

另外,这题据陈老师说,后缀数组也可以做
但我不会啊,跪求神犇带 _(:з」∠)_

Code

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 200000+9;
const int M = 2000000;
const int C = 10;

int n,c,col[N],head[N],to[N],nxt[N],in[N];

class Suffix_Automaton{
	int ch[M][C],pre[M],len[M],cnt;
	public:
		inline int insert(int last, int t) {
			int w = ++cnt; len[w] = len[last] + 1; pre[0] = -1;
			for (;~last&&!ch[last][t];last=pre[last]) ch[last][t] = w;
			if (~last) {
				if (len[ch[last][t]] == len[last] + 1) {
					pre[w] = ch[last][t];
				} else {
					int nw = ++cnt, tmp = ch[last][t];
					len[nw] = len[last] + 1; 
					pre[nw] = pre[tmp]; pre[tmp] = pre[w] = nw; 
					memcpy(ch[nw],ch[tmp],sizeof(ch[nw]));
					for (;~last&&ch[last][t]==tmp;last=pre[last]) ch[last][t] = nw;
				}
			} return w;
		} 
		inline LL solve() {
			LL ret = 0;
			for (int i=1;i<=cnt;i++)
				ret += len[i] - len[pre[i]];
			return ret; 
		}
}SAM;

inline int read() {
	char c=getchar(); int f=1,ret=0;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret * f;
}

inline void Add_Edge(int u, int v) {
	static int E = 1; in[u]++; in[v]++;
	to[++E] = v; nxt[E] = head[u]; head[u] = E;
	to[++E] = u; nxt[E] = head[v]; head[v] = E;
}

void DFS(int w, int f, int s) {
	int rt = SAM.insert(s, col[w]);
	for (int i=head[w];i;i=nxt[i]) 
		if (to[i] != f) DFS(to[i], w, rt);
}

int main() {
	n = read(); c = read();
	for (int i=1;i<=n;i++) col[i] = read();
	for (int i=1;i<n;i++) Add_Edge(read(),read());
	for (int i=1;i<=n;i++) if (in[i] == 1) DFS(i, i, 0);
	printf("%lld\n",SAM.solve());
	return 0;
}

【BZOJ 4556】[TJOI2016&HEOI2016] 字符串

相关链接

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4556
神犇题解:http://www.cnblogs.com/Var123/p/5575518.html

解题报告

这个题目实际上是询问 $s(c,d)$ 的 $LCP$
于是我们考虑二分这个长度 $L$
那么这个 $L$ 就相当于在 $SA$ 的数组上划了一个区间
不难发现如果这个区间中存在一个后缀的起点属于 $s(a,b)$ ,这个长度就是合法的
于是我们二分一下,然后搞一个后缀数组和函数式线段树就可以啦!

【日常小测】生物进阶

相关链接

题目传送门:http://oi.cyo.ng/wp-content/uploads/2016/12/26312367.pdf
官方题解:http://oi.cyo.ng/wp-content/uploads/2016/12/324324132.pdf

解题报告

之前鏼爷讲课的时候讲过了
于是这一次直接开始写了

就是每种字母都分开做一遍
符合条件/是该字母就置为1
否则置为0,之后FFT就好
似乎NTT就可以做了?然而不会 QwQ

Code

#include<bits/stdc++.h>
#define E complex<double>
using namespace std;

const int N = 1100000+9;
const double EPS = 1e-3;
const double PI = acos(-1);

int n,m,K,stp,len,a1[N],a2[N],sum[N];
char pat[N]; int vout[N],pos[N];
complex<double> s1[N],s2[N];

inline int read() {
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

inline void prework() {
	for (int tmp=1;tmp<=len;tmp<<=1,stp++);
	len = (1<<stp);
	for (int i=1,hig=1<<stp-1;i<=len;i++) {
		pos[i] = pos[i>>1] >> 1;
		if (i & 1) pos[i] |= hig;
	}
}

inline void FFT(complex<double> *arr, int t) {
	for (int i=0;i<len;i++) 
		if (pos[i] < i) swap(arr[pos[i]], arr[i]);
	for (int i=1,num=2;i<=stp;i++,num<<=1) {
		complex<double> wn(cos(2*PI/num),sin(t*2.0*PI/num));
		for (int j=0;j<len;j+=num) {
			complex<double> w(1,0),buf;
			for (int i=j,lim=num/2+j;i<lim;i++,w*=wn) {
				buf = w * arr[i+num/2];
				arr[i+num/2] = arr[i] - buf;
				arr[i] += buf;
			}
		}
	}
	if (!~t) for (int i=0;i<len;i++)
		s1[i].real() /= len;
}

int main() {
	freopen("biology.in","r",stdin);
	freopen("biology.out","w",stdout);
	n = read(); m = read(); 
	K = read(); len = n + m;
	scanf("%s",pat);
	for (int i=0;i<n;i++) {
		if (pat[i] == 'A') a1[i] = 1;
		else if (pat[i] == 'C') a1[i] = 2;
		else if (pat[i] == 'G') a1[i] = 3;
		else if (pat[i] == 'T') a1[i] = 4;
	}
	scanf("%s",pat);
	for (int i=0;i<m;i++) {
		if (pat[i] == 'A') a2[i] = 1;
		else if (pat[i] == 'C') a2[i] = 2;
		else if (pat[i] == 'G') a2[i] = 3;
		else if (pat[i] == 'T') a2[i] = 4;
	}
	
	prework();
	for (int j=1;j<=4;j++) {
		memset(s1,0,sizeof(s1));
		memset(s2,0,sizeof(s2));
		for (int i=0;i<n;i++) 
			sum[i] = (i>0?sum[i-1]:0) + (a1[i] == j);
		for (int i=0;i<n;i++) 
			s1[i].real() = (sum[min(n-1,i+K)]!=((i-K-1)>=0?sum[i-K-1]:0));
		for (int i=0;i<m;i++) 
			s2[i].real() = (a2[i] == j);
		for (int l=0,r=m-1;l<r;l++,r--) 
			swap(s2[l], s2[r]);
		
		FFT(s1,1); FFT(s2,1);
		for (int i=0;i<len;i++)
			s1[i] = s1[i] * s2[i];
		FFT(s1,-1);
		for (int i=1;i<=n;i++)
			vout[i] += s1[i+m-2].real() + EPS;
	}
	int ret = 0;
	for (int i=1;i<=n;i++)
		ret += (vout[i] == m);
	printf("%d\n",ret);
	return 0;
}

【CodeChef FAVNUM】Favourite Numbers

题目传送门:https://www.codechef.com/problems/FAVNUM
中文题面:number_dp_by_zky

这个题目还是挺好玩的!
数位DP配上AC自动机!别有一番风味!

然而这个傻Ⅹ的AC自动机写炸了
调试了3个小时! (╯‵□′)╯︵┻━┻

#include<bits/stdc++.h>
#define LL long long
using namespace std;

char pat[20];

namespace AC_Automaton{
	#define AC AC_Automaton
	LL f[20][2000][2];
	int ch[2000][10],tag[2000],cnt,fail[2000],sta[20];
	queue<int> que;
	
	inline void insert(char *s){
		int n = strlen(s+1), w = 0;
		for (int i=1,c;i<=n;i++) {
			c = s[i] - '0';
			if (!ch[w]) {
				ch[w] = ++cnt;
			}
			w = ch[w];
		}
		tag[w] = 1;
	}
	
	inline void Build(){
		for (int i=0;i<=9;i++) if (ch[0][i]) {
			que.push(ch[0][i]);
		}
		
		while (!que.empty()) {
			int w = que.front(); que.pop();
			for (int c=0;c<=9;c++) if (ch[w]) {
				int nv = ch[w], pv = fail[w];
				while (pv && !ch[pv]) pv = fail[pv];
				fail[nv] = ch[pv];
				tag[nv] |= tag[fail[nv]];
				que.push(nv);
			} else {
				ch[w] = ch[fail[w]];
			}
		}
	}
	
	LL DFS(int t, int w, bool lim, bool leg) {
		if (!t) {
			return leg;
		} else if (!lim && ~f[t][w][leg]) {
			return f[t][w][leg];
		} else {
			LL ret = 0;
			for (int i=0,ty=lim?sta[t]:9;i<=ty;i++) {
				ret += DFS(t-1, ch[w][i], lim&&i==sta[t], leg|tag[ch[w][i]]);
			}
			return lim? ret: f[t][w][leg] = ret;
		}
	}
	
	inline LL query(LL lim) {
		int cnt = 0;
		while (lim) {
			sta[++cnt] = lim % 10;
			lim /= 10;
		}
		memset(f,-1,sizeof(f));
		return DFS(cnt,0,1,0);
	}
};

int main(){	
	LL L,R,k,n,bas,w,stp;
	cin>>L>>R>>k>>n;
	for (int i=1;i<=n;i++) {
		scanf("%s",pat+1);
		AC::insert(pat);
	}
	
	AC::Build();
	k += AC::query(L - 1);
	if (AC::query(R) < k) {
		puts("no such number");
		exit(0);
	}
	
	stp = 1; w = L - 1; 
	while (stp < (R - L + 1)) stp <<= 1;
	while (stp) {
		if (AC::query(w + stp) < k) {
			w += stp;
		}
		stp >>= 1;
	}
	printf("%lld\n",w+1);
	return 0;
}

【Codeforces 696D】Legen…

题目传送门:http://codeforces.com/contest/696/problem/D
官方题解:http://codeforces.com/blog/entry/46031

这个题目,看一眼数据范围就知道是AC自动机+矩阵快速幂
所以剩下的唯一问题就是,我们重定义矩阵运算之后为何其仍然满足结合律?

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 200+9;
const int SGZ = 27;
const int MX = 201;

inline int read(){
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

struct Matrix{
	LL a[N][N];
	inline Matrix() {memset(a,-1,sizeof(a));}
	inline Matrix(Matrix &v) {memcpy(this,&v,sizeof(v));}
	inline Matrix(int bas, int v) {memset(a,bas,sizeof(a));for (int i=1;i<=MX;i++) a[i][i]=v;}
	inline Matrix operator * (const Matrix &B) {
		Matrix ret; 
		for (int i=1;i<=MX;i++) for (int j=1;j<=MX;j++) for (int k=1;k<=MX;k++) 
			if (~a[k][j] && ~B.a[i][k]) ret.a[i][j] = max(ret.a[i][j], a[k][j] + B.a[i][k]);
		return ret;
	}
	inline Matrix operator ^ (LL t) {
		Matrix ret(-1,0), tmp(*this); while (t) {
			if (t & 1) ret = ret * tmp;
			tmp = tmp * tmp; t >>= 1;
		} return ret; 
	} 
}tran,ans;

namespace AC_Automaton{
	#define ATM AC_Automaton
	int ch[N][SGZ],fail[N],val[N],cnt=1,vis[N];
	queue<int> que;
	
	inline void Add(char *s, int v){
		int w = 1, n = strlen(s+1);
		for (int i=1;i<=n;i++) {
			int c = s[i] - 'a' + 1;
			if (!ch[w]) ch[w] = ++cnt;
			w = ch[w];
		} val[w] += v;	
	}
	
	inline void Get_Fail(){
		que.push(1); fail[0] = fail[1] = 1; 
		for (int i=1;i<SGZ;i++) ch[1][i] = ch[1][i]?ch[1][i]:1;
		while (!que.empty()) {
			int w = que.front(); que.pop();
			val[w] += val[fail[w]]; vis[w] = 1;
			for (int c=1;c<SGZ;c++) if (ch[w]) {
				int nw = fail[w];
				while (nw > 1 && !ch[nw]) nw = fail[nw];
				fail[ch[w]] = nw!=w?ch[nw]:1; 
				if (!vis[ch[w]]) que.push(ch[w]);
			} else ch[w] = ch[fail[w]];
		}
	}
	
	inline void Build_Matrix(){
		ans.a[1][1] = 0;
		for (int i=1;i<=cnt;i++)
			for (int c=1;c<SGZ;c++) 
				tran.a[ch[i]][i] = val[ch[i]];	
	}
}; 

int main(){
	int n,val[N]; LL T; char pat[N]; cin>>n>>T;
	for (int i=1;i<=n;i++) cin>>val[i];
	for (int i=1;i<=n;i++) scanf("%s",pat+1), ATM::Add(pat,val[i]);
	ATM::Get_Fail(); ATM::Build_Matrix(); tran = tran^T; 
	ans = ans * tran; LL vout = 0; 
	for (int i=2;i<=MX;i++) vout = max(vout, ans.a[i][1]);
	printf("%I64d\n",vout);
 	return 0;
}

—– UPD 2016.10.2 —–
今天问了一下鏼爷,鏼爷给了一个非常简洁的证明:
最短路问题可以写成矩阵形式(比如倍增Floyd)
然后最短路显然满足结合律
然后这题对于矩乘的定义岂不是和最短路一样?

—————————— UPD 2017.2.1 ——————————
这题后来问了问YYY,结果YYY暴力展开矩乘
似乎这样就可以知道是否满足结合律了
不过如果在考场上,直接拍一拍吧
如果不出错,那就假装他满足结合律吧?

【BZOJ 4516】[SDOI2016] 生成魔咒

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4516

考试的时候,一看到要动态维护就把SA扔一边凉快去了QAQ
想一想,不能这么草率啊!
来说一说SA和SAM两种做法

1) SAM:
很明显就是求一个字符串本质不同的字串个数
SAM本身就是增量构造,于是就成裸题了

2) SA:
考虑离线之后,先逆序建一个完整的出来
那么加入一个字符,实际上就是加入了一个后缀
那么其对于答案的贡献,就是原来的height数组搞一搞
实现的话,搞一个RMQ什么的就好
代码的话,我们来召唤Menci:https://oi.men.ci/sdoi2016-incantation/

自己太懒,于是就只写了SAM的代码:

#include<bits/stdc++.h>
#include<tr1/unordered_map>
#define LL long long
using namespace std;
using namespace tr1;

const int N = 200000+9; 

inline int read(){
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

namespace Suffix_Automaton{
	#define SAM Suffix_Automaton
	int len[N],fail[N],cnt,tail;
	unordered_map<int,int> ch[N]; 
	
	inline int Extend(int c) {
		int w = tail; tail = ++cnt; len[tail] = len[w] + 1;
		while (w&&!ch[w]) ch[w] = cnt, w = fail[w];
		if (!w) fail[tail] = 1;
		else {
			if (len[ch[w]] == len[w]+1) fail[tail] = ch[w];
			else {
				int to = ch[w]; ch[++cnt] = ch[to];
				fail[cnt] = fail[to]; fail[to] = cnt; fail[tail] = cnt; 
				len[cnt] = len[w] + 1; while (ch[w] == to) ch[w] = cnt, w = fail[w];
			}
		} return len[tail] - len[fail[tail]];
	}
	
	inline void solve(int n) {
		cnt = tail = 1; 
		for (LL i=1,vout=0;i<=n;i++) 
			printf("%lld\n",vout+=Extend(read()));
	}
};

int main(){
	SAM::solve(read());
	return 0;
}

—– UPD 2016.9.29 —–
这™map比unordered_map快这么多有几个意思QAQ
475864785645
这种感觉……
真·生无可恋
V5[YANBJ`4%A2`%PIH$BH_F

【BZOJ 4571】[SCOI2016] 美味

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4571

考试的时候,找了一个小时的规律,总觉得加法在二进制下有奇特的规律
考完试后知道真相的我,哭晕在厕所…

仍然考虑按位贪心
于是发现高位确定,低位不确定在十进制下是一个区间,于是搞一搞函数式线段树
仔细想一想,一般的Xor问题也可以这么做,trie树只是一个特殊的优化

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int N = 200000+9;
const int M = N * 18;

int arr[N],n,m,MX; 

inline int read(){
	char c=getchar(); int ret=0,f=1;
	while (c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
	while (c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
	return ret*f;
}

namespace Chair_Tree{
	#define CT Chair_Tree
	int ch[M][2],sum[M],cnt,root[N];
	int ans_tmp,L,R;
	
	void build(int p, int &w, int l, int r, int cur) {
		w = ++cnt; memcpy(ch[w],ch[p],sizeof(ch[w]));
		sum[w] = sum[p] + 1; if (l < r) {
			int mid = l + r + 1 >> 1;
			if (cur < mid) build(ch[p][0],ch[w][0],l,mid-1,cur);
			else build(ch[p][1],ch[w][1],mid,r,cur);
		}
	}
	
	inline void Build_Tree() {
		for (int i=1;i<=n;i++) 
			build(root[i-1],root[i],0,MX,arr[i]);
	}
	
	void ask(int w, int l, int r, int f) {
		if (L <= l && r <= R) ans_tmp += sum[w]*f;
		else if (w) {
			int mid = l + r + 1 >> 1;
			if (L < mid) ask(ch[w][0],l,mid-1,f);
			if (R >= mid) ask(ch[w][1],mid,r,f);
		}
	}
	
	inline int Query(int r1, int r2, int l, int r) {
		l = max(0,l); r = min(MX,r); 
		if (l > r) return 0;
		
		ans_tmp = 0; L = l; R = r;
		ask(r1,0,MX,-1); ask(r2,0,MX,1);
		return ans_tmp;
	}
	
	inline int query(int b, int x, int l, int r){
		int ret = 0, r1 = root[l-1], r2 = root[r];
		for (int i=17;~i;i--) {
			int c = (b & (1<<i)) ^ (1<<i); 
			if (Query(r1,r2,ret+c-x,ret+c+(1<<i)-1-x)) ret += c;
			else ret += c ^ (1<<i);
		} return ret^b;
	}
};

int main(){
	n = read(); m = read();
	for (int i=1;i<=n;i++) MX = max(MX, arr[i]=read());
	CT::Build_Tree();
	for (int i=1,b,x,l,r;i<=m;i++) {
		b = read(); x = read(); 
		l = read(); r = read();
		printf("%d\n",CT::query(b,x,l,r));
	}
	return 0;
}