异或粽子
Luogu
LOJ
BZOJ
先做个前缀异或和求出(s_i),那么要做的就是找在序列中任选两个数异或的最大值。
然后对每个(s_i)求出(maxlimits_{j}(s_ioperatorname{xor}s_j)),全部丢到一个堆里面。
每次把堆里面的最大值取出来加入答案,假如这是(koperatorname{thmax}limits_j(s_ioperatorname{xor}s_j)),就把((k+1)operatorname{thmax}limits_j(s_ioperatorname{xor}s_j))丢进堆。
具体可以用Trie树实现,时间复杂度为(O((n+k)(log a+log n)))。
另外这样求的是无序对,所以最开始要(kleftarrow 2k),最后(ansleftarrow frac{ans}2)。
#include<queue>
#include<cctype>
#include<cstdio>
#include<utility>
#include<algorithm>
using u32=unsigned int;
using i64=long long;
using pi=std::pair<u32,u32>;
const int N=500007;
int tot,num[N];u32 s[N];i64 ans;char ibuf[1<<23|1],*iS=ibuf;
struct node{int ch[2],size;}t[N<<4];std::priority_queue<pi>q;
u32 read(){u32 x=0;while(isspace(*iS))++iS;while(isdigit(*iS))(x*=10)+=*iS++&15;return x;}
void insert(u32 x)
{
for(int p=0,i=31,d;~i;++t[p=t[p].ch[d]].size,--i) if(!t[p].ch[d=x>>i&1]) t[p].ch[d]=++tot;
}
u32 query(u32 x,int k)
{
u32 ans=0;
for(int p=0,i=31,d;~i;--i) if(t[t[p].ch[!(d=x>>i&1)]].size<k) k-=t[t[p].ch[!d]].size,p=t[p].ch[d]; else ans|=((u32)1)<<i,p=t[p].ch[!d];
return ans;
}
int main()
{
fread(ibuf,1,1<<23,stdin);
int n=read(),k=read()*2;i64 ans=0;insert(0);
for(int i=1;i<=n;++i) insert(s[i]=s[i-1]^read());
for(int i=0;i<=n;++i) q.emplace(query(s[i],num[i]=1),i);
for(int x;k;--k) ans+=q.top().first,x=q.top().second,q.pop(),q.emplace(query(s[x],++num[x]),x);
printf("%lld",ans/2);
}
字符串问题
Luogu
LOJ
(u)支配(v)则连边(A_u
ightarrow B_v)。
若(B_u)是(A_v)的前缀则连边(B_u
ightarrow A_v)。
那么我们要求的就是这张图的点权最长路,拓扑排序+dp即可。
考虑如何优化建图。
先建出反串的SAM。
然后对于给定的子串(s_{l,r}),parent树上倍增找到它在SAM中的出现位置,然后把这个子串挂在SAM的这个点上。
注意到SAM上一个节点中的字符串在反串中互为后缀,因此它们在原串中互为前缀。
那么我们以结束位置为第一关键字,是否为(A)类子串(是为(1),不是为(0))为第二关键字对每个节点上的子串降序排序。
这样每一个(B)子串类子串要连向的就是这个节点上排在它后面的(A)类子串。
注意到我们要求的是最长路,因此只需要向后面第一个(A)类子串连边即可。
#include<queue>
#include<cstdio>
#include<cctype>
#include<vector>
#include<cstring>
#include<algorithm>
namespace IO
{
char ibuf[(1<<26)+1],*iS=ibuf;
void In(){fread(ibuf,1,1<<26,stdin);}
int read(){int x=0;while(!isdigit(*iS))++iS;while(isdigit(*iS))(x*=10)+=*iS++&15;return x;}
void gets(char*s){while(isspace(*iS))++iS;while(islower(*iS))*s++=*iS++;*s=' ';}
}
using i64=long long;
using IO::read;
const int N=200007;
int cnt,tot,now,link[N*2],len[N*4],trans[N*2][26],id[N],fa[20][N*2],a[N],b[N],is[N*4],las[N*2],in[N*4];
char str[N];i64 dis[N*4];
std::vector<int>e[N*4],g[N*2];std::queue<int>q;
void clear()
{
for(int i=0;i<20;++i) memset(fa[i]+1,0,cnt<<2);
for(int i=1;i<=cnt;++i) g[i].clear();
for(int i=1;i<=tot;++i) e[i].clear();
memset(trans[1],0,cnt*104),memset(is+1,0,tot<<2),memset(in+1,0,tot<<2),memset(dis+1,0,tot<<3),cnt=now=1;
}
void extend(int c)
{
int p=now,q;len[now=++cnt]=len[p]+1;
for(;p&&!trans[p][c];p=link[p]) trans[p][c]=now;
if(!p) return link[now]=1,void();
if(len[q=trans[p][c]]==len[p]+1) return link[now]=q,void();
len[++cnt]=len[p]+1,memcpy(trans[cnt],trans[q],104),link[cnt]=link[q],link[q]=link[now]=cnt;
for(;p&&trans[p][c]==q;p=link[p]) trans[p][c]=cnt;
}
void getin(int flg)
{
int l=read(),r=read()-l+1;l=id[l];
for(int i=19;~i;--i) if(fa[i][l]&&len[fa[i][l]]>=r) l=fa[i][l];
is[++tot]=flg,len[tot]=r,g[l].push_back(tot);
}
i64 toposort()
{
i64 ans=0;
for(int i=1;i<=tot;++i) if(!in[i]) q.push(i);
for(int u;!q.empty();)
{
u=q.front(),q.pop(),ans=std::max(ans,dis[u]+len[u]);
for(int v:e[u]) if(dis[v]=std::max(dis[v],dis[u]+len[u]),!--in[v]) q.push(v);
}
for(int i=1;i<=tot;++i) if(in[i]) return -1;
return ans;
}
int main()
{
IO::In();
for(int T=read(),n,m,na,nb;T;--T)
{
clear(),IO::gets(str+1),n=strlen(str+1);
for(int i=n;i;--i) extend(str[i]-'a'),id[i]=now;
memcpy(fa[0]+1,link+1,cnt<<2),tot=cnt;
for(int j=1;j<20;++j) for(int i=1;i<=cnt;++i) fa[j][i]=fa[j-1][fa[j-1][i]];
na=read();for(int i=1;i<=na;++i) getin(1),a[i]=tot;
nb=read();for(int i=1;i<=nb;++i) getin(0),b[i]=tot;
for(int i=1;i<=cnt;++i) std::sort(g[i].begin(),g[i].end(),[](int i,int j){return len[i]<len[j]||(len[i]==len[j]&&is[i]<is[j]);});
for(int i=1;i<=cnt;++i)
{
int u=i;
for(int v:g[i]) if(e[u].push_back(v),++in[v],!is[v]) u=v;
las[i]=u;
}
for(int i=2;i<=cnt;++i) e[las[link[i]]].push_back(i),++in[i];
for(int i=1;i<=tot;++i) if(!is[i]) len[i]=0;
m=read();for(int i=1,u,v;i<=m;++i) u=read(),v=read(),e[a[u]].push_back(b[v]),++in[b[v]];
printf("%lld
",toposort());
}
}
皮配
Luogu
LOJ
BZOJ
我们称有不喜欢的老师的学校为“刁的学校”,有刁的学校的城市为“刁的城市”。
考虑(k=0)的情况,此时阵营和派系是独立的,我们可以分别把城市/学校当做物品做背包得到(f_i,g_i),其中(f_i/g_i)分别表示有(i)名选手选择蓝阵营/鸭派系的方案数。那么(c)名选手在蓝阵营,(d)名选手在鸭派系的方案数为(sumlimits_{i=c-C_1}^{C_0}f_isumlimits_{j=d-C_0}^{C_1}g_i)。
现在有刁的学校和城市,因此先计算出不刁的城市及其学校的(f,g)。
然后考虑计算刁的城市/学校的方案数,设(F_{i,j})表示有(i)名选手选择蓝阵营,(j)名选手选择鸭派系的方案数,然后枚举刁的城市,再枚举刁的城市中的学校,根据讨厌的限制进行dp即可。
最后再枚举刁的城市中有多少选手选择蓝阵营,多少选手选择鸭派系即可求出答案。
时间复杂度为(O((n+c)m+kmmin(ks,m)))。
#include<cstdio>
#include<cstring>
#include<numeric>
#include<algorithm>
const int N=1007,M=2507,P=998244353;
int read(){int x;scanf("%d",&x);return x;}
void inc(int&a,int b){a+=b-P,a+=a>>31&P;}
int add(int a,int b){return a+=b-P,a+(a>>31&P);}
int sub(int a,int b){return a-=b,a+(a>>31&P);}
struct City{int sum,have_hate;}cy[N];
struct School{int bel,sum,hate;}sl[N];
int f[M],g[M],F[M][M],G[M][M];
int cal(int*s,int l,int r){return l>r? 0:sub(s[r],l? s[l-1]:0);}
void solve()
{
int n=read(),c=read(),c0=read(),c1=read(),d0=read(),d1=read(),cs=0,ds=0,all=0,ans=0;
memset(cy+1,0,8*c),memset(f,0,4*(c0+1)),memset(g,0,4*(d0+1)),memset(F,0,sizeof F),F[0][0]=f[0]=g[0]=1;
for(int i=1;i<=n;++i) sl[i]={read(),read(),-1},cy[sl[i].bel].sum+=sl[i].sum,all+=sl[i].sum;
for(int k=read(),x;k;--k) x=read(),sl[x].hate=read(),cy[sl[x].bel].have_hate=1;
for(int i=1;i<=c;++i) if(!cy[i].have_hate&&cy[i].sum) for(int j=c0,t=cy[i].sum;j>=t;--j) inc(f[j],f[j-t]);
for(int i=1;i<=n;++i) if(!~sl[i].hate) for(int j=d0,t=sl[i].sum;j>=t;--j) inc(g[j],g[j-t]);
std::partial_sum(f,f+c0+1,f,add),std::partial_sum(g,g+d0+1,g,add);
for(int city=1;city<=c;++city)
if(cy[city].have_hate)
{
cs=std::min(cs+cy[city].sum,c0),memcpy(G,F,sizeof F);
for(int school=1,t;school<=n;++school)
if(sl[school].bel==city&&~sl[school].hate)
{
ds=std::min(ds+(t=sl[school].sum),d0);
if(sl[school].hate==1) for(int i=0;i<=cs;++i) memmove(F[i]+t,F[i],4*std::max(0,ds-t+1)),memset(F[i],0,4*t);
if(sl[school].hate>=2) for(int i=0;i<=cs;++i) for(int j=ds;j>=t;--j) inc(F[i][j],F[i][j-t]);
if(sl[school].hate==3) for(int i=0;i<=cs;++i) memmove(G[i]+t,G[i],4*std::max(0,ds-t+1)),memset(G[i],0,4*t);
if(sl[school].hate<=1) for(int i=0;i<=cs;++i) for(int j=ds;j>=t;--j) inc(G[i][j],G[i][j-t]);
}
for(int j=0,t=cy[city].sum;j<=ds;++j)
{
for(int i=cs;i>=t;--i) F[i][j]=F[i-t][j];
for(int i=t-1;~i;--i) F[i][j]=0;
}
for(int i=0;i<=cs;++i) for(int j=0;j<=ds;++j) inc(F[i][j],G[i][j]);
}
for(int i=0;i<=cs;++i) for(int j=0;j<=ds;++j) inc(ans,1ll*cal(f,std::max(0,all-c1-i),c0-i)*cal(g,std::max(0,all-d1-j),d0-j)%P*F[i][j]%P);
printf("%d
",ans);
}
int main(){for(int T=read();T;--T)solve();}
春节十二响
Luogu
LOJ
BZOJ
考虑每个点维护一个堆表示该点分配的各个内存段大小。
我们知道一个点的儿子的子树的内存段是可以合并的,所以我们考虑从下往上把每个节点的儿子合并,再将该节点自己的(a_i)放进堆。
合并的时候把两个堆的元素一个个弹出,将同时弹出的两个取(max)放进合并后的堆,直到某个堆弹完了为止,然后把剩下的那个堆的元素也都放进合并后的堆。
利用启发式合并可以做到(O(nlog n))。
#include<queue>
#include<cctype>
#include<cstdio>
#include<vector>
#include<algorithm>
using i64=long long;
const int N=200007;
int a[N],fa[N];char ibuf[1<<22|1],*iS=ibuf;std::priority_queue<int>q[N];
int read(){int x=0;while(isspace(*iS))++iS;while(isdigit(*iS))(x*=10)+=*iS++&15;return x;}
void merge(int u,int v)
{
std::vector<int>t;
while(q[u].size()&&q[v].size()) t.push_back(std::max(q[u].top(),q[v].top())),q[u].pop(),q[v].pop();
if(q[v].size()) std::swap(q[u],q[v]);
for(int x:t) q[u].push(x);
}
int main()
{
fread(ibuf,1,1<<22,stdin);
int n=read();i64 ans=0;
for(int i=1;i<=n;++i) a[i]=read();
for(int i=2;i<=n;++i) fa[i]=read();
for(int i=n;i^1;--i) q[i].push(a[i]),merge(fa[i],i);
while(!q[1].empty()) ans+=q[1].top(),q[1].pop();
printf("%lld",ans+a[1]);
}
希望
Luogu
LOJ
BZOJ
如果我们固定了(k)个连通块,那么合法的中心点也会构成一个连通块。
考虑经典充斥,对于任意一个树上连通块都有(|V|=|E|+1),因此可以先枚举一个中心点(u)计算有(P_u)个能够到达该点连通块,再枚举一条边(e)计算有(Q_e)个连通块能够到达该条边两端。最后的答案就是(sumlimits_{uin V}P_u^k-sumlimits_{ein E}Q_e^k)。
考虑dp,设(f_{u,i})表示(u)子树中包含(u)点且任意点到(u)的距离不超过(i)的连通块数,(g_{u,i})表示(u)子树外包含(u)点且任意点到(u)的距离不超过(i)的连通块数,转移是非常简单的:
(f_{u,i}=prodlimits_{vin son_u}(f_{v,i-1}+1))
(g_{u,i}=1+f_{fa_u,i-1}prodlimits_{vin son_{fa}wedge v
e u}(f_{v,i-2}+1))
那么对点(u)而言,(P_u=f_{u,l}g_{u,l})。对边(e=(fa_u,u))而言,(Q_e=f_{v,l-1}(g_{v,l}-1))。
考虑长链剖分优化。
(f)从长儿子处继承dp数组,轻儿子暴力转移即可。(g)将父亲的dp数组继承给长儿子,轻儿子暴力转移即可。
后缀乘可以通过全局乘+前缀打除法标记来实现。
有一个问题是要求(prodlimits_{vin son_{fa}wedge v
e u}(f_{v,i-2}+1)),如果我们用(frac{f_{fa_u,i-1}}{f_{u,i-2}+1})来算的话可能会出现除以(0)的情况。
考虑枚举(fa_u)的儿子,实时维护右兄弟的积,利用求(f)时的信息计算左兄弟的积。这里需要用栈之类的结构来支持撤销。
#include<cctype>
#include<cstdio>
#include<vector>
#include<algorithm>
using pi=std::pair<int,int>;
const int N=1000007,P=998244353;
char ibuf[1<<24|1],*iS=ibuf;int read(){int x=0;while(isspace(*iS))++iS;while(isdigit(*iS))(x*=10)+=*iS++&15;return x;}
void inc(int&a,int b){a+=b-P,a+=a>>31&P;}
void dec(int&a,int b){a-=b,a+=a>>31&P;}
void mul(int&a,int b){a=1ll*a*b%P;}
int pow(int a,int k){int r=1;for(;k;k>>=1,mul(a,a))if(k&1)mul(r,a);return r;}
int n,l,k,ans,fa[N],h[N],son[N],dp[N],inv[N],pool[8*N],*it=pool,t0[N],t1[N];
struct node{int a,b,inv,pos,num;}E;
std::vector<int>e[N];std::vector<std::pair<node,std::vector<pi>>>stk[N];
void dfs(int u)
{
dp[u]=1;
for(int v:e[u]) if(v^fa[u]) if(fa[v]=u,dfs(v),mul(dp[u],dp[v]),h[v]>h[son[u]]) son[u]=v;
inc(dp[u],1),h[u]=h[son[u]]+1,inv[u]=pow(dp[u],P-2);
}
namespace calf
{
node t[2*N];int*f[2*N];
int get(int u,int i){return (1ll*(i<t[u].pos? f[u][i]:t[u].num)*t[u].a+t[u].b)%P;}
void put(int u,int i,int v){mul(f[u][i]=t[u].inv,v-t[u].b+P);}
void merge(int u,int v,int len)
{
node tmp=t[u];std::vector<pi>vec;
for(int i=1;i<=len;put(u,i,1ll*get(v,i-1)*get(u,i)%P),++i) if(vec.emplace_back(i,f[u][i]),i==t[u].pos) f[u][t[u].pos++]=t[u].num;
if(len<l)
{
int val=get(v,len);
if(!val) t[u].pos=len+1,t[u].num=P-1ll*t[u].b*t[u].inv%P;
else
{
vec.emplace_back(0,f[u][0]);
for(int i=0;i<=len;++i) put(u,i,1ll*get(u,i)*inv[v]%P);
mul(t[u].a,val),mul(t[u].b,val),mul(t[u].inv,inv[v]);
}
}
if(u<=n) stk[u].emplace_back(tmp,vec);
}
void dfs(int u)
{
if(son[u]) f[son[u]]=f[u]+1,dfs(son[u]),t[u]=t[son[u]]; else t[u]=E;
put(u,0,1);
for(int v:e[u]) if(v^fa[u]&&v^son[u]) f[v]=it,it+=h[v],dfs(v),merge(u,v,std::min(h[v]-1,l));
t0[u]=get(u,std::min(h[u],l)-1),t1[u]=get(u,std::min(h[u]-1,l)),++t[u].b;
}
void undo(int u){t[u]=stk[u].back().first;for(auto[p,x]:stk[u].back().second) f[u][p]=x;stk[u].pop_back();}
void work(){f[1]=it,it+=h[1],dfs(1);}
}
namespace calg
{
node t[N];int*g[N];
int get(int u,int i){return(1ll*(i<t[u].pos? g[u][i]:t[u].num)*t[u].a+t[u].b)%P;}
void put(int u,int i,int v){mul(g[u][i]=t[u].inv,v-t[u].b+P);}
void dfs(int u)
{
if(h[u]>=l+1) put(u,h[u]-l-1,1);
inc(ans,pow(1ll*t1[u]*get(u,h[u]-1)%P,k));
if(fa[u]) dec(ans,pow(1ll*t0[u]*(get(u,h[u]-1)-1+P)%P,k));
if(!son[u]) return ;
std::vector<int>ch;int mx=0,v;
for(int v:e[u]) if(v^fa[u]&&v^son[u]) ch.push_back(v),mx=std::max(mx,h[v]);
mx=std::min(mx,l),std::reverse(ch.begin(),ch.end()),calf::f[u+n]=it,it+=mx+1,calf::t[u+n]=E,calf::put(u+n,0,1);
for(int v:ch)
{
calf::undo(u),g[v]=it,it+=h[v];
for(int i=std::max(h[v]-l-1,0);i<h[v];++i) g[v][i]=l+i+1==h[v]? get(u,h[son[u]]-h[v]+i):1ll*get(u,h[son[u]]-h[v]+i)*calf::get(u,std::min(h[u]-1,l-h[v]+i))%P*calf::get(u+n,std::min(mx,l-h[v]+i))%P;
t[v]=E,calf::merge(u+n,v,std::min(h[v]-1,l)),dfs(v);
}
v=son[u],g[v]=g[u],t[v]=t[u];
for(int i=std::max(h[v]-l,0);i<=h[v]+mx-l-1;put(v,i,1ll*get(v,i)*calf::get(u+n,l-h[v]+i)%P),++i) if(t[v].pos==i) g[v][t[v].pos++]=t[v].num;
if(mx<l)
{
int val=1,iv=1;
for(int v:ch) mul(val,dp[v]),mul(iv,inv[v]);
if(!val) t[v].pos=h[v]+mx-l,t[v].num=P-1ll*t[v].b*t[v].inv%P;
else
{
for(int i=std::max(h[v]-l-1,0);i<=h[v]+mx-l-1;++i) put(v,i,1ll*get(v,i)*iv%P);
mul(t[v].a,val),mul(t[v].b,val),mul(t[v].inv,iv);
}
}
++t[v].b,dfs(v);
}
void work(){t[1]=E,g[1]=it,it+=h[1],dfs(1);}
}
int main()
{
fread(ibuf,1,1<<24,stdin),n=read(),l=read(),k=read(),E={1,1,1,n,0};
for(int i=1,u,v;i<n;++i) u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
dfs(1),calf::work(),calg::work(),printf("%d",ans);
}