F. Chainword
题目描述
给出 (n) 个长度不超过 (5) 的原料字符串,两个人分别拿这些字符串任意拼接成长度为 (m) 的新字符串,求有多少种情况两人的新字符串完全相同。
(1leq nleq 8,1leq mleq 10^9)
解法
本题的关键是如何判断两人字符串完全相同,如果我们真的以字符串来拼接的话是不好判断的。用 ( t tire) 树可以将字符串的判断转化成单个字符的判断。
重新考察字符串相同的条件,两个人在 ( t tire) 树上走,如果走到代表原料字符串的节点就可以选择回到起点,只需要保证两个人都走了 (m) 步,并且每一步走的下一个字符相同即可。
然后可以用矩阵加速,设 (dp(i,u,v)) 表示两个人走了 (i) 步,第一个人走到 (u),第二个人走到 (v) 的方案数,咋一看状态数有 (40 imes 40=1600) 个,实际上 (u,v) 必须满足一个是另一个的后缀,所以状态数只有 (5 imes 40=200) 个。
总结
判断相同,以大化小,小处相同,大处自然同。
G. Chips on a Board
题目描述
有 (n) 个数,值域在 ([1,m]) 中,每次询问保留值域 ([l,r]) 的数并把它们都减去 (l),问这些数异或和是不是 (0),询问独立。
(1leq n,m,qleq 2cdot10^5)
解法
强行把异或和减法运算放在一起是很复杂的,这会破坏异或的性质,无法用传统区间方法维护。
而且拆位也不好,因为本题的数位之间不是很独立(这就是官解辣鸡的原因),还有一类解决区间问题的方法是倍增,由于倍增是基于二进制的正好能和本题的异或联系在一起,那么我们从倍增角度考虑该问题。
设 (dp[i][j]) 表示以 (i) 为左端点,值域在 ([i,i+2^j-1]) 里面数的异或和,倍增先把 (dp[i][j-1]) 和 (dp[i+2^{j-1}][j-1]) 异或起来,但是 ([i+2^{j-1},i+2^j-1]) 里面数全部都少了 (2^{j-1}),而且这里面数的权值严格小于 (2^{j-1}),所以我们只需要考察该区间中数字个数就可以决定是否在原基础上异或 (2^{j-1}) 来调整。
算答案也很容易,用类似的方法看是否要异或 (2^i) 来调整,时间复杂度 (O(mlog m))
总结
区别于传统的用数据结构维护区间问题,倍增解决区间问题的特点是:区间信息可合并性,区间询问可拆分性,倍增基于的二进制有时候还会带来意想不到的性质。
#include <cstdio>
const int M = 200005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,q,s[M],dp[M][20];
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
int x=read();
s[x]^=1;
}
for(int i=1;i<=m;i++)
s[i]^=s[i-1];
for(int j=1;(1<<j)<=m;j++)
for(int i=1;i+(1<<j)-1<=m;i++)
{
int to=i+(1<<j-1);
dp[i][j]=dp[i][j-1]^dp[to][j-1];
if(s[i+(1<<j)-1]^s[to-1])
dp[i][j]^=(1<<j-1);
}
q=read();
while(q--)
{
int l=read(),r=read(),ans=0;
for(int i=19;i>=0;i--)
{
if(l+(1<<i)-1>r) continue;
ans^=dp[l][i];
l+=(1<<i);
if(s[r]^s[l-1]) ans^=(1<<i);
}
printf("%c",ans?'A':'B');
}
}