20220524 模拟赛订正 | NOI2016 VP

A. 宝石

\(k\) 个不同的人在一棵 \(n\) 个点的树上各选一个点,每个人选的点互不相同,树上每个节点有颜色 \(w_i\),求所有选法方案的点集构成的斯坦纳树的颜色数之和。两种方案不同当且仅当存在一个人在两种方案中选择了不同的点。\(1\le w_i\le m,1\le n,m\le 5\times 10^5\)

这道题我做得不太好,只拿到了暴力的 50 分。事实上应该一拿到就用补集思想把一种颜色对“所有包含这种颜色的斯坦纳树”的贡献看成总方案减去“没有包含这种颜色的斯坦纳树的选法数”;换句话说,就是要求 ban 掉每种颜色的所有点后各连通块的大小选 \(k\) 的组合数之和。但是我想的是怎么去重,尝试将序列上区间查颜色数的套路搬到斯坦纳树数颜色上,发现都行不通。后来决定打个暴力的容斥:钦定一个 \(c\) 色的点对斯坦纳树产生贡献当且仅当它是该斯坦纳树中所有 \(c\) 色点中编号最小的那一个。这样每次把编号小于它且为
\(c\) 色的点 ban 掉,求所有选法数,但这样不一定包含当前点,所以一阶差分容斥减去把这个点也 ban 掉的方案数。这样就有 \(O(n)\) 轮,每轮做一个 \(O(n)\) 的树形 DP。后来我发现这个树形 DP 就是在求 \(\sum_{S\text{ is a connected component}{|S|\choose k}}\),而同色的轮与轮之间的式子可以抵消成 \({n\choose k}-\) ban 掉所有这种颜色的点之后上式的值。到这时我才得到这个最初通过补集思想可以一步得到的结论,而考试临近结束了,我没能想出一个能线性计算出这个东西的算法。

事实上这个问题的解决办法也很巧妙,我不确定有足够时间时我能否想出(我猜我会写一个线段树分治之类的东西暴力搞)。我们在 dfs 的过程中,用 \(m\) 个栈(vector<int>now[M])记录每个颜色的点的“最后一个点”的“下一个”点。所谓“最后一个点”,就是从根到当前节点的路径上深度最大的这种颜色的点;所谓“下一个点”,就是这个节点的也在从跟到当前节点的路径上的那个儿子。当我们到达 \(u\) 时,应该对 g[now[u].back()]-=siz[u],其中 \(g_i\) 表示:ban 掉 \(w_{fa_i}\) 这种颜色后,以 \(i\) 为根的连通块的大小,其初始赋为 \(siz_i\)

细节:由于 \(fa_1=0\),所以需要特殊处理 \(g_1\)

点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace IO {
const int buflen = 1 << 21;
int x;
bool f;
char ch, buf[buflen], *p1 = buf, *p2 = buf;
inline char gc() {
    return p1 == p2 && (p2 = buf + fread(p1 = buf, 1, buflen, stdin), p1 == p2) ? EOF : *p1++;
}
inline int read() {
    x = 0, f = 1, ch = gc();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            f = 0;
        ch = gc();
    }
    while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = gc();
    return f ? x : -x;
}
}  // namespace IO
using IO::read;
const int N = 1e6 + 5, mod = 998244353;
int n, m, K, w[N], ans;
vector<int> G[N];
namespace sub1 {
int box[22], fa[22][6], dep[22];
void init(int x, int p) {
    fa[x][0] = p, dep[x] = dep[p] + 1;
    for (int i = 1; i <= 5; i++) fa[x][i] = fa[fa[x][i - 1]][i - 1];
    for (int y : G[x])
        if (y ^ p) {
            init(y, x);
        }
}
inline int glca(int u, int v) {
    if (u == v)
        return u;
    if (dep[u] > dep[v])
        swap(u, v);
    for (int i = 5; ~i; i--)
        if (dep[fa[v][i]] >= dep[u])
            v = fa[v][i];
    if (u == v)
        return u;
    for (int i = 5; ~i; i--)
        if (fa[u][i] != fa[v][i])
            u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}
void sousuo(int x) {
    if (x == n + 1) {
        int cnt = 0;
        for (int i = 1; i <= n; i++) cnt += box[i];
        if (cnt != K)
            return;
        int lca = 0;
        for (int i = 1; i <= n; i++)
            if (box[i]) {
                if (!lca)
                    lca = i;
                else
                    lca = glca(lca, i);
            }
        vector<int> vec;
        for (int i = 1; i <= n; i++)
            if (box[i]) {
                for (int j = i; j != lca; j = fa[j][0]) vec.emplace_back(w[j]);
            }
        vec.emplace_back(w[lca]);
        sort(vec.begin(), vec.end());
        int tmp = 1;
        for (int i = 1; i < vec.size(); i++)
            if (vec[i - 1] != vec[i])
                tmp++;
        ans += tmp;
        return;
    }
    sousuo(x + 1);
    box[x] = 1;
    sousuo(x + 1);
    box[x] = 0;
}
void solve() {
    init(1, 0);
    sousuo(1);
    for (int i = 1; i <= K; i++) ans = (ll)ans * i % mod;
    cout << ans << '\n';
}
}  // namespace sub1
namespace sub2 {
int dfc, jc[N], ijc[N], g[N], siz[N];
int ans = 0;
vector<int> now[N];
inline void add(int &x, int y) { (x += y) >= mod && (x -= mod); }
inline int C(int n, int m) {
    if (n < m || n < 0 || m < 0)
        return 0;
    return (ll)jc[n] * ijc[m] % mod * ijc[n - m] % mod;
}
inline int qp(int a, int b) {
    int c = 1;
    for (; b; b >>= 1, a = (ll)a * a % mod)
        if (b & 1)
            c = (ll)c * a % mod;
    return c;
}
void init(int x, int p) {
    siz[x] = 1;
    for (int y : G[x])
        if (y ^ p) {
            init(y, x);
            siz[x] += siz[y];
        }
}
void dfs(int x, int p) {
    g[now[w[x]].back()] -= siz[x];
    for (int y : G[x])
        if (y ^ p) {
            now[w[x]].emplace_back(y);
            dfs(y, x);
            now[w[x]].pop_back();
        }  // cerr<<x<<'_'<<g[x]<<':';
}
void solve() {
    jc[0] = ijc[0] = 1;
    for (int i = 1; i <= n; i++) jc[i] = (ll)jc[i - 1] * i % mod;
    ijc[n] = qp(jc[n], mod - 2);
    for (int i = n - 1; i; i--) ijc[i] = (i + 1ll) * ijc[i + 1] % mod;
    set<int> st;
    for (int i = 1; i <= n; i++) st.insert(w[i]);
    ans = (ll)m * C(n, K) % mod;
    init(1, 0);
    for (int i = 1; i <= n; i++) g[i] = siz[i];
    for (int i = n + 1; i <= n + m; i++) g[i] = siz[1];
    for (int i = 1; i <= m; i++) now[i].emplace_back(n + i);
    dfs(1, 0);
    // for(int i=1;i<=n+m;i++)cerr<<g[i]<<':';
    for (int i = 2; i <= n + m; i++) add(ans, mod - C(g[i], K));
    for (int i = 1; i <= K; i++) ans = (ll)ans * i % mod;
    cout << ans << '\n';
}
}  // namespace sub2
int main() {
    freopen("gem.in", "r", stdin);
    freopen("gem.out", "w", stdout);
    n = read(), m = read(), K = read();
    for (int i = 1; i <= n; i++) w[i] = read();
    for (int i = 1, u, v; i < n; i++) {
        u = read(), v = read();
        G[u].emplace_back(v);
        G[v].emplace_back(u);
    }
    /*if(n<=20){
            sub1::solve();
            return 0;
    }*/
    sub2::solve();
    return 0;
}
/*
g++ -o gem.exe gem.cpp -O2 -std=c++14 -lm -Wall -Wextra
./gem.exe<in

*/

B.序列

有一个初始全为 \(0\) 的序列 \(\{a_n\}(n\le 10^9)\)
先进行 \(m(m\le 2\times 10^5)\) 次操作,第 \(i\) 次操作给出 \(p_i,x_i\)\(\forall j\ge 1,j\cdot p_i\le n,a_{j\cdot p_i}\gets a_{j\cdot p_i}+x_i\cdot j\)
再提出 \(q(q\le 2\times 10^5)\) 个询问,第 \(i\) 次询问给出 \(k_i\),输出 \(\sum_{j\ge 1,j\cdot k_i\le n}a_{j\cdot k_i}\times j\)

保证 \(lcm(p_1,...,p_m,k_1,...,k_q)\) 的不同质因子个数只有 \(\le 10\) 个。

这类倍数之间做贡献的题目有一种专门的办法解决——狄利克雷卷积。
步骤为:对于 \(u\Rightarrow X(u|X)\Rightarrow v(v|X)\) 的贡献关系,我们将贡献的式子写成 \(f(u)\times g(X)\times h(v)\) 的形式(在本题中 \(f(p_i)\gets {x_i\over p_i},g(X)=X^2,h(v)={1\over v}\))。我们先处理出 \(f\),然后做一遍狄利克雷前缀和,在每个 \(i\) 处乘上 \(g(i)\),然后再卷回去,最后在每个 \(v\) 处乘上 \(h_v\) 得到答案。
但是值域是 \(10^9\) 的,过不了,怎么办呢?我们要利用上质因子数很少的这个条件——可以发现,仅由这 10 个质因子构成的数的数量只有不超过 \(2\times 10^5\) 个。能否通过只考虑这些“关键数”做狄利克雷卷积来做到同样的事情呢?
那么我们就必须让包含其他质因子的数在某个“关键数”处统计贡献了。尝试钦定其在其除去所有其它质因子后剩下的数处统计贡献。不难发现如果原数为 \(X\),除掉其他质因子后为 \(X'\),那么贡献就是 \(X^2({X\over X'})^2\)(废话)。那么就需要枚举所有“关键数”,求 \(X'=\) 这个数的所有 \(X\)\(X\) 除以当前数的商的平方和。注意到最核心的限制是 \(X\) 不能包含更多的“关键质因子”(更多种或者更多个),【套路】用容斥消除掉这个碍事的限制,\(O(2^10)\) 钦定一些“关键质因子”被 \(X\over X'\) 包含,计算相应的 \(X\over X'\) 之和即可。这里涉及到 \(1^2+2^2+3^2+...=x(x+1)(2x+1)/6\) 的公式,一开始居然写了个拉格朗日插值,也真是服了。

实际上,我在今天 VP NOI2016 时打《循环之美》的暴力时也活学活用了这个套路,拿到了 40 分。

点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace IO {
const int buflen=1<<21;
int x;
bool f;
char ch,buf[buflen],*p1=buf,*p2=buf;
inline char gc(){return p1==p2&&(p2=buf+fread(p1=buf,1,buflen,stdin),p1==p2)?EOF:*p1++;}
inline int read(){
	x=0,f=1,ch=gc();
	while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=gc();}
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=gc();
	return f?x:-x;
}
}
using IO::read;
void print(int x){
	if(x/10)print(x/10);
	putchar(x%10+48);
}
/*
const int N=1e7+5,mod=998244353;
int n,m,q;
int X[N];
__int128 ans[N],a[N];
inline int norm(int x){return x>=mod?x-mod:x;}
inline void add(int &x,int y){
	(x+=y)>=mod&&(x-=mod);
}
namespace sub2{
const int kN=2e5+5;
int p[kN],x[kN],k[kN],ans[kN],inv[5],y[5];
int kk[kN];
int gcd(int a,int b){
	return b?gcd(b,a%b):a;
}
int lag(int x){
	int ret=0;
	for(int i=1;i<=4;i++){
		int tmp=1;
		for(int j=1;j<=4;j++)if(i!=j){
			tmp=(ll)tmp*(x-j+mod)%mod*(i>j?inv[i-j]:mod-inv[j-i])%mod;
		}
		add(ret,(ll)tmp*y[i]%mod);
	}
	return ret;
}
inline int qp(int a,int b){
	int c=1;
	for(;b;b>>=1,a=(ll)a*a%mod)if(b&1)c=(ll)c*a%mod;
	return c;
}
unordered_map<int,int>mp;
void solve(){
	mp.reserve(2048),mp.max_load_factor(0.38);
	for(int i=1;i<=m;i++)p[i]=read(),x[i]=read(),add(mp[p[i]],x[i]);
	for(int i=1;i<=q;i++)k[i]=read(),kk[i]=k[i];
	sort(kk+1,kk+q+1);
	int qq=unique(kk+1,kk+q+1)-kk-1;//cerr<<qq<<';';cerr<<mp.size()<<';';
	y[1]=1,y[2]=5,y[3]=14,y[4]=30;
	for(int i=1;i<=4;i++)inv[i]=qp(i,mod-2);
	for(auto [P,X]:mp){
		for(int j=1;j<=qq;j++){
			ll lcm=(ll)P/gcd(P,kk[j])*kk[j];
			if(lcm<=n)add(ans[j],(ll)X*lag(n/lcm)%mod*(lcm/P)%mod*(lcm/kk[j])%mod);
		}
	}
	for(int i=1;i<=q;i++){
		int pos=lower_bound(kk+1,kk+qq+1,k[i])-kk;
		print(ans[pos]),putchar('\n');
	}
}
}
int main(){
	//freopen("sequence.in","r",stdin);freopen("sequence.out","w",stdout);
	n=read(),m=read(),q=read();
	if(n<=1e6){
		for(int i=1,p,x;i<=m;i++){
			p=read(),x=norm(read());
			add(X[p],x);
		}
		for(int i=1;i<=n;i++)if(X[i]){
			for(int j=1;j*i<=n;j++)a[j*i]+=(ll)X[i]*j;
		}
		for(int i=1;i<=n;i++)ans[i]=-1;
		for(int i=1,k;i<=q;i++){
			k=read();
			if(ans[k]!=-1)print(ans[k]),putchar('\n');
			else {
				ans[k]=0;
				for(int j=k,o=1;j<=n;j+=k,o++)ans[k]+=o*a[j];
				print(ans[k]%=mod),putchar('\n');
			}
		}
	}
	else {
		sub2::solve();
		return 0;
	}
	return 0;
}*/
const int N=2e5+5,M=2e5+5,mod=998244353,I6=(mod+1)/6;
int n,m,q,p[N],x[N],k[N],ppc[1<<10];
int tot,arr[M];
vector<int>all;
set<int>st;
unordered_map<int,int>X;
inline int norm(int x){return x>=mod?x-mod:x;}
inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);}
inline int qp(int a,int b){
	int c=1;
	for(;b;b>>=1,a=(ll)a*a%mod)if(b&1)c=(ll)c*a%mod;
	return c;
}
inline void FJ(int x){
	for(int i=2;i*i<=x;i++)if(x%i==0){
		st.insert(i);
		while(x%i==0)x/=i;
	}
	if(x>1)st.insert(x);
}
void sousuo(int x,int prod){
	if(x==all.size()){
		arr[tot++]=prod;
		return;
	}
	for(ll i=1;i<=n/prod;i*=all[x])sousuo(x+1,prod*i);
}
inline int s2(int x){return (ll)x*(x+1)%mod*(2*x+1)%mod*I6%mod;}
map<int,int>tab;
int rongchi(int lim){
	if(tab.count(lim))return tab[lim];
	int ret=0;
	for(int msk=0;msk<(1<<all.size());msk++){
		int prod=1;
		bool fl=0;
		for(int i=0;i<all.size();i++)if(msk>>i&1){
			if(prod<=lim/all[i])prod*=all[i];
			else {fl=1;break;}
		}
		if(fl)continue;
		add(ret,(ppc[msk]&1?mod-1ll:1ll)*s2(lim/prod)%mod*prod%mod*prod%mod);
	}
	return tab[lim]=ret;
}
int main(){
	freopen("sequence.in","r",stdin);freopen("sequence.out","w",stdout);
	n=read(),m=read(),q=read();
	for(int i=1;i<=m;i++){
		p[i]=read(),x[i]=norm(read());
		FJ(p[i]);
	}
	for(int i=1;i<=q;i++){
		k[i]=read();
		FJ(k[i]);
	}
	for(int i:st)all.emplace_back(i);
	sousuo(0,1);
	sort(arr,arr+tot);
	for(int i=1;i<(1<<10);i++)ppc[i]=ppc[i^(i&-i)]+1;
	X.reserve(2048),X.max_load_factor(0.38);
	for(int i=1;i<=m;i++)add(X[p[i]],(ll)x[i]*qp(p[i],mod-2)%mod);
	for(int pp:all)for(int i=0;i<tot;i++)if(arr[i]%pp==0)add(X[arr[i]],X[arr[i]/pp]);
	for(int i=0;i<tot;i++)X[arr[i]]=(ll)X[arr[i]]*arr[i]%mod*arr[i]%mod*rongchi(n/arr[i])%mod;
	for(int pp:all)for(int i=tot-1;i>=0;i--)if(arr[i]<=n/pp)add(X[arr[i]],X[arr[i]*pp]);
	for(int i=1;i<=q;i++)cout<<(ll)qp(k[i],mod-2)*X[k[i]]%mod<<'\n';
	return 0;
}

/*
g++ -o sequence.exe sequence.cpp -O2 -lm -std=c++14 -Wall -Wextra
./sequence.exe<sequence3.in>out
diff -Z sequence3.out out

note must include: the application of Dirichlet Prefix-sum.
Contribution relation like this: ans[j]=\sum _{j|k}\sum_{i|k}val[i]u[k]e[j]
val[i]---->p*i
    	   ||
		   (do a few things at p*i=q*j=X)
		   ||
ans[j]<----q*j
*/

C. 制胡窜

给定一个字符串 \(s(|s|\le 2\times 10^5)\)\(q\) 次询问一个子段 \(s[l\sim r]\),要求将其划分为任意个非空段,使得至少有两个段本质相同,且本质不同的段数最少,输出本质不同段数的最小值,或者判断无解。

这个题目我在考场上连最基本的思路都没有,打了一个指数级的大暴力。我只想到了如果有解那么答案不超过 4,却不会分类讨论。

事实上,经过一些简单的分析就能发现,每一种答案所对应的串都可以表示成一种或多种简单的形式。
如果答案是 1,那么就是说它有完整循环节。
如果答案是 2,那么只会有 ABA,AAB,ABB 三种情况。(但我考试时却只是认为情况数不胜数而放弃思考。)思考过程可以是:先随便写出一种表示形式 XXYYXYXYYX...Y(X和Y各代表一子串),看看能否简化它。发现如果首尾同为 X 或 Y,那么可以简化成 ABA,否则不妨设首 X 尾 Y,那么如果第二个为 X 就可以简化成 AAB,如果第二个是 Y,那么如果倒数第二个是 X 就可以简化成 ABA,如果倒数第二个是 Y 就可以简化成 ABB。
如果答案是 3,那么只会有 ABCB,ACAB,ACCB 三种情况。仍然考虑简化。由于答案不是 2 所以 \(s_l,s_r\) 必然不同。那么如果 \(s_l\) 在中间出现过,就直接用 ACAB 形式,如果 \(s_r\) 在中间出现过,就直接用 ABCB 形式,否则中间全是 Z,就可以选两个相邻的作为 CC。
而如果无解/1/2/3都不是,就是 4 了。
无解的情况就是所有字符都只出现过一次。

再来考虑实现的方法。
如果答案是 1,那么枚举 \(len\) 的因子 \(i\) 判断 \(s[l:r-i]\) 是否等于 \(s[l+i:r]\) 即可。
如果答案是 2,那么如果是 AAB,可以用《NOI2016 优秀的拆分》的套路,求出以 \(i\) 为左端点的所有 AA 串的右端点的最小值,判断其是否不超过 \(r\) 即可;ABB 同理。
ABA 的情况比较难以理解。需要考虑根号分治,如果 \(|A|\le \sqrt n\),那么暴力哈希即可。
如果 \(|A|\ge \sqrt n\),那么 A 作为 \(s[l:r]\) 的一个 border,我们可以钦定它是满足 ABA 形式的最短的 border A。最短 border 有什么性质呢?就是它在 \(s\) 中的出现互不重叠。这是因为一旦重叠:\(s[l_1:r_1],s[l_2:r_2](l_2\le r_1)\),那么 \(s[l_2:r_1]\) 一定可以取代 A 成为最短 border。于是我们可以发现 A 在 \(s\) 中出现了不超过 \(\sqrt n\) 次。而 A 又是后缀 \(l\) 的一个前缀,因此所有跟 \(l\) 的 LCP \(\ge |A|\) 的后缀 \(i\) 的个数是 \(\le sqrt n\) 的,我们只需要枚举 sa 数组上 \(rk_l\) 半径为 \(\sqrt n\) 的邻域一一判断 \(i\)\(l\) 的 LCP 是否 \(\ge r-i+1\) 即可用 \(r-i+1\) 更新答案。
而对于答案为 3 的情况,只需按照上一段的方法判断 ACAB 和 ABCB 的形式,然后用 \(\min_{i\in [l+1,r-1]}(i+L_i-1)\)(\(L_i\) 为优秀的拆分算出的左端点为 \(i\) 的 AA 串最短长度)是否 \(\le r\) 判断。

点击查看代码
// ubsan: undefined
// accoders
//start coding at 19:58
#include <bits/stdc++.h>
using namespace std;
bool Mbe;
typedef long long ll;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=getchar();}
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return f?x:-x;
}
typedef unsigned long long ull;
const int N=2e5+5;
int n,m,T,b[19][N],Log[N];
char s[N];
ull ha[N],mi[N];
int cnt[N],id[N],rkid[N],oldrk[N<<1];
vector<int>occ[26];
struct Suffix_Array {
	char s[N];
	int sa[N],rk[N<<1],ht[19][N];
	bool cmp(int x,int y,int w){return oldrk[x]==oldrk[y]&&oldrk[x+w]==oldrk[y+w];}
	void SA(){
		for(int i=1;i<=n;i++)sa[i]=i,rk[i]=s[i];
		int M=max(125,n);
		for(int w=1;w<n;w<<=1){
			memset(cnt,0,sizeof cnt);
			for(int i=1;i<=n;i++)id[i]=sa[i];
			for(int i=1;i<=n;i++)cnt[rk[id[i]+w]]++;
			for(int i=1;i<=M;i++)cnt[i]+=cnt[i-1];
			for(int i=n;i>=1;i--)sa[cnt[rk[id[i]+w]]--]=id[i];
			memset(cnt,0,sizeof cnt);
			for(int i=1;i<=n;i++)id[i]=sa[i],rkid[i]=rk[id[i]];
			for(int i=1;i<=n;i++)cnt[rkid[i]]++;
			for(int i=1;i<=M;i++)cnt[i]+=cnt[i-1];
			for(int i=n;i>=1;i--)sa[cnt[rkid[i]]--]=id[i];
			memcpy(oldrk,rk,sizeof rk);
			int p=0;
			for(int i=1;i<=n;i++)if(cmp(sa[i],sa[i-1],w))rk[sa[i]]=p;else rk[sa[i]]=++p;
			if(p==n){for(int i=1;i<=n;i++)sa[rk[i]]=i;break;}
		}
		for(int i=1,j=0;i<=n;i++){
			if(j)j--;
			while(s[i+j]==s[sa[rk[i]-1]+j])j++;
			ht[0][rk[i]]=j;
		}
		for(int j=1;j<=18;j++)
			for(int i=1;i+(1<<j)-1<=n;i++)
				ht[j][i]=min(ht[j-1][i],ht[j-1][i+(1<<j-1)]);
	}
	int lcp(int x,int y){
		if(x==y)return n-x+1;
		x=rk[x],y=rk[y];
		if(x>y)swap(x,y);
		x++;
		return min(ht[Log[y-x+1]][x],ht[Log[y-x+1]][y-(1<<Log[y-x+1])+1]);
	}
}S1,S2;
void init_HASH(){
	mi[0]=1;
	for(int i=1;i<=n;i++)ha[i]=ha[i-1]*37+s[i]-'a'+1,mi[i]=mi[i-1]*37;
}
inline ull gha(int l,int r){
	if(l>r)return 0;
	return ha[r]-ha[l-1]*mi[r-l+1];
}
struct DSU {
	int fa[N],val[N];
	void init(){
		for(int i=1;i<=n+1;i++)fa[i]=i,val[i]=1e9;
	}
	int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
	void unite(int u,int v){fa[find(v)]=find(u);}
	void sET(int l,int r,int v){
		l=max(l,1),r=min(r,n);
		if(l>r)return;
		for(int i=find(l);i<=r;i=find(i+1)){
			val[i]=v;
			fa[i]=i+1;
		}
	}
}mnl,mnr;
void NOI2016(){
	for(int len=1,len1,len2;len<=n;len++){
		int las=0;
		for(int i=1;i<=n;i+=len){
			if(las){
				len1=S2.lcp(n-las+1,n-i+1);
				len2=S1.lcp(las+1,i+1);
				//cerr<<las<<' '<<i<<": "<<len1<<' '<<len2<<'\n';
				if(len1+len2>=len)mnl.sET(las-len1+1,i+len2-2*len+1,len),mnr.sET(las-len1+2*len,i+len2,len);//cerr<<i<<'_';
			}
			las=i;
		}
	}
}
inline int b_rmq(int l,int r){
	if(l>r)return 1e9;
	return min(b[Log[r-l+1]][l],b[Log[r-l+1]][r-(1<<Log[r-l+1])+1]);
}
bool check1(int l,int r){
	int len=r-l+1;
	for(int i=1;i*i<=len;i++)if(len%i==0){
		if(gha(l,r-i)==gha(l+i,r))return 1;
		if(i*i!=len&&i!=1&&gha(l,r-len/i)==gha(l+len/i,r))return 1;
	}
	return 0;
}
bool check2(int l,int r){
	//aab
	if(l+mnl.val[l]*2-1<=r)return 1;
	//abb
	if(r-mnr.val[r]*2+1>=l)return 1;
	//aba
	for(int i=1;i<=min(r-l,T);i++)if(gha(l,l+i-1)==gha(r-i+1,r))return 1;
	for(int i=max(1,S1.rk[l]-T+1);i<=min(n,S1.rk[l]+T-1);i++)if(S1.sa[i]>l&&S1.sa[i]<=r&&S1.lcp(S1.sa[i],l)>=r-S1.sa[i]+1)return 1;
	return 0;
}
bool check3(int l,int r){
	//acab
	auto it=upper_bound(occ[s[l]-'a'].begin(),occ[s[l]-'a'].end(),l);
	if(it!=occ[s[l]-'a'].end()&&r>=*it)return 1;
	//abcb
	it=lower_bound(occ[s[r]-'a'].begin(),occ[s[r]-'a'].end(),r);
	if(it!=occ[s[r]-'a'].begin()&&l<=*prev(it))return 1;
	//accb
	if(b_rmq(l+1,r-1)<=r)return 1;
	return 0;
}
bool no_solution(int l,int r){
	auto it1=occ[0].begin(),it2=it1;
	for(int i=0;i<26;i++){
		it1=lower_bound(occ[i].begin(),occ[i].end(),l);
		it2=upper_bound(occ[i].begin(),occ[i].end(),r);
		if(it1==it2)continue;
		it2--;
		if(it1==it2)continue;
		return 0;
	}
	return 1;
}
bool Med;
int main(){
	freopen("string.in","r",stdin);freopen("string.out","w",stdout);
	fprintf(stderr,"%.3f\n",(&Mbe-&Med)/(double)1024/1024);
	scanf("%d%s%d",&n,s+1,&m);
	T=sqrt(n);
	for(int i=1;i<=n;i++)Log[i]=log2(i);
	for(int i=1;i<=n;i++)S1.s[i]=s[i],S2.s[n-i+1]=s[i];
	S1.SA(),S2.SA();
	// for(int i=1;i<=n;i++)cout<<S1.sa[i]<<' ';puts(":S1.sa");
	// for(int i=1;i<=n;i++)cout<<S1.ht[0][i]<<' ';puts(":S1.ht");
	init_HASH();
	mnl.init(),mnr.init();
	NOI2016();cerr<<'A';
	//for(int i=1;i<=n;i++)cout<<mnr.val[i]<<' ';puts(":mnl.val");
	for(int i=1;i<=n;i++)occ[s[i]-'a'].emplace_back(i);
	for(int i=1;i<=n;i++)b[0][i]=i+2*mnl.val[i]-1;
	for(int j=1;j<=18;j++)for(int i=1;i+(1<<j)-1<=n;i++)b[j][i]=min(b[j-1][i],b[j-1][i+(1<<j-1)]);
	for(int qq=1;qq<=m;qq++){
		int l=read(),r=read();
		if(no_solution(l,r))puts("-1");
		else if(check1(l,r))puts("1");
		else if(check2(l,r))puts("2");
		else if(check3(l,r))puts("3");
		else puts("4");
	}
	return 0;
}
/*
g++ -o string.exe string.cpp -O2 -std=c++14
./string.exe<in
*/

NOI2016 VP 记

从 7:30 开始 VP 了 NOI2016。由于体验不错,我决定接下来有空把 NOI2017,2018,2020,2021 都 VP 一下。

开 D1T1,由于昨天晚上已经看了这个题并很快想到了 95 分做法,因此先去看 T2。
D1T2 是个 vfk 题,看完后以为可以拿到 \(nm\le 10^6\) 的 44 分(一开始没看到是 56 分),然后又看到了 \(c\le 300\) 的部分分,由于忽略了要先判断跳蚤是否连通,所以以为不难做。这时感觉84分都很好拿。
好在看完 T3 也没有记起之前做这道题时的思路,并且感觉根本不会判断纯循环。后来尝试推了一下式子,两边同时乘以 \(y\),就得到了一个同余式,然后记起了之前的结论是要 \(k,y\) 互质。但是不记得怎么莫反了,遂打暴力,看到部分分中 \(m\)\(n\) 大很多而且很多无限制,于是考虑枚举 \(x\) 怎么做,发现可以用上昨天 T2 的套路求出所有 \(1\le y\le n,gcd(x,y)=1\)\(y\) 个数。这样 40 分就大功告成了。
先写完了 T1 的 95 分,用了双哈希保险(昨天 T1 在 CF 上交时 ull 哈希被卡了)。
回头看T2时,觉得只通过蛐蛐来判断跳蚤是否连通在 \(10^9\times 10^9\) 的网格上是一件非常繁琐且不好办到的事情。而且 \(nm\le 10^6\) 的细节也看起来不是很能描述得出来。于是先把 T3 写了。有趣的是,题目只给了两个几乎没有强度的小样例和一个暴力需要跑 5 分钟的大样例,所以为了测试暴力对不对等了 5 分钟,期间继续思考 T2 的细节。跑出来了,是对的,遂用暴力拍了两组容斥,也拍上了,于是开始努力思考 T2 \(nm\le 10^6\),毕竟至少得上 200 吧。
11:38时终于想到判断是否存在割点是一个几乎没有细节的做法了,于是在12:07 写完调完。交上去发现没挂分。
吃完饭回来到了 12:30,等到了 12:40 开了Day2,目标是打上200。
T1之前翻的时候看到了洛谷上“线段树”标签,然后看到交集不为空,我居然想了一个 \(log^3\) 的线段树分治。发现实在优化不了,就看后面的题。T2国王饮水记这么经典的题我居然还没读过题意,看完之后先是被高精度库震撼到了,然后感觉比较显然的 DP 都有大几十分,加一个决策单调性就有 85 了。这样加上 T1 的 75 就有 160 了。耐心读完了 T3 的造计算机,觉得非常有意思,决定留到最后做。
回头码 T1 后发现连预想的 75 分都拿不到,与暴力老哥要同分了,觉得这样不行啊,这还是 NOI2016,到 NOI2023 怎么办,还是要想正解啊!在草稿纸上写来写去发现好像把所有区间捆在一起处理有点太傻了,不如枚举一个作为最小值,再来二分那个极差,钦定这个作为最小值,于是就是排序之后的一段区间,那怎么判断是否能从中选 m 个有交集的呢?这不就是线段树区间加全局查max吗?这不是跟前年省选卡牌游戏一模一样吗(不知道是谁抄谁)!快速写完并不停在脑子里重复“我真傻”后赶紧码 T2,此时只剩 1h 40 min。
先按照long double打了一遍,发现过了大样例、正确性没问题(赛后发现挂了15分?怎么回事啊?)后开始学习高精度库,看懂后随便改改就丢一边了。还剩 40 分钟,那 T3 就把前30分拿了吧!
nodes1一次就试出了6次操作的方案,nodes2想了想发现用上左移就能减少到6了,nodes3想了好一会,最后用上了S操作的S(0)=0.5,就过了(实在不明白为什么exp能不昂贵啊),nodes4是思考许久未果,最后写了个6分就算了,nodes5比nodes4简单不少,但是输入量大了些,写写就过了。5个小时马上到了。回头检查了一下各题代码。
正要交时,居然还真检查出来了!!T1没判无解!!(一开始的写法判了,后来就忘了,还专门在注释里提醒了的)交完发现没挂分。

进队了?!

才发现,国王饮水机从85挂成了70诶!怎么回事呢?

posted @ 2023-05-25 22:19  pengyule  阅读(9)  评论(0编辑  收藏  举报