【做题记录】HZOJ 多校-数论/多校-字符串/多校-图论I
还剩三个专题,准备一块做,随了个做题顺序(当然太难了或者没学我就跳了)
做题顺序
01. 数论 M. [arc112_f]Die Siedler
考虑将一个状态 \(A=\{c_1,c_2,\dots,c_n\}\) 映射到一个数 \(X=\sum_{i=1}^{n}2^{i-1}(i-1)!c_i\),即将所有卡牌都倒推回 \(c_1\)。不难发现如果我们只进行 \(i\in[1,n-1]\) 的换牌则 \(X\) 不会改变,否则若进行 \(i=n\) 的换牌则 \(X\gets X-2^nn!+1\)。且对于最终状态,必有 \(X<2^nn!\)。
设初始的 \(X=P\),第 \(i\) 个卡包的 \(X=a_i\),最终的 \(X=Q\)。若第 \(i\) 个卡包使用了 \(x_i\) 次,进行了 \(y\) 次 \(i=n\) 的换牌,则有:
设 \(d=\gcd(a_1,a_2,\dots,a_n,2^nn!-1)\),则由裴蜀定理有 \(Q\equiv P\pmod{d}\)。设 \(r=P\bmod d\),于是 \(Q=kd+r,k\in\mathbb{N}\)。设 \(f(X)\) 表示最终状态最小有多少张牌,于是我们只需求 \(\min\{f(kd+r)\}\)。此时不难有两种暴力:
-
直接暴力枚举 \(k\),计算 \(f\) 的值。\(O(n\frac{2^nn!-1}{d})\)。
-
同余最短路。初始有 \(\forall i\in[0,n-1],u=2^ii!\bmod d,dis_u=1\),每次枚举选择哪种牌。时间复杂度 \(O(nd)\)。
考虑根号分治,时间复杂度为 \(O(nD)\),其中 \(D\) 为最大的不超过 \(\sqrt{2^nn!-1}\) 的约数,为 \(1214827\)。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e6+5,inf=1e18;
int n,m,a[19],b[55][19],c[19],dis[maxn];
bool vis[maxn];
queue<int> q;
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
c[0]=1;
int r=0;
for(int i=1;i<=n;i++){
cin>>a[i];
c[i]=c[i-1]*2*i;
r+=a[i]*c[i-1];
}
int d=c[n]-1;
for(int i=1;i<=m;i++){
int t=0;
for(int j=1;j<=n;j++){
cin>>b[i][j];
t+=b[i][j]*c[j-1];
}
d=__gcd(d,t);
}
r%=d;
// cout<<d<<'\n';
if(d<=1214827){
for(int i=0;i<n;i++){
dis[c[i]%d]=vis[c[i]%d]=1;
q.push(c[i]%d);
}
while(q.size()){
int u=q.front();
q.pop();
for(int i=0;i<n;i++){
int v=(u+c[i])%d;
if(!vis[v]){
vis[v]=1;
dis[v]=dis[u]+1;
q.push(v);
}
}
}
cout<<dis[r];
}else{
int ans=inf;
for(int i=r?r:d;i<c[n];i+=d){
int res=0,tmp=i;
for(int j=1;j<=n;j++){
res+=tmp%(2*j);
tmp/=2*j;
}
ans=min(ans,res);
}
cout<<ans;
}
return 0;
}
}
signed main(){return asbt::main();}
02. 数论 B. [POI2011] SEJ-Strongbox
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=2.5e5+5;
int n,m,a[maxn];
vector<int> prm;
set<int> vis;
il void div(int x){
for(int i=2;i<=x/i;i++){
if(x%i==0){
prm.pb(i);
while(x%i==0){
x/=i;
}
}
}
if(x>1){
prm.pb(x);
}
}
il void dfs(int x){
if(vis.count(x)){
return ;
}
vis.insert(x);
for(int p:prm){
if(x%p==0){
dfs(x/p);
}
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int d=__gcd(a[n],m);
div(d);
for(int i=1;i<n;i++){
dfs(__gcd(a[i],d));
}
int i=1;
for(;i<=d/i;i++){
if(d%i==0&&!vis.count(i)){
cout<<m/i;
return 0;
}
}
for(i--;i;i--){
if(d%i==0&&!vis.count(d/i)){
cout<<m/(d/i);
return 0;
}
}
return 0;
}
}
signed main(){return asbt::main();}
03. 数论 J. [arc084_b]Small Multiple
考虑一个数的各个数位和是怎么表示的。假设已经知道了 \(x\) 的各个数位和,不难想到两种转移:
于是跑同余最短路即可。由于边权只有 \(0\) 和 \(1\),可以使用 0-1bfs。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define pf push_front
#define pob pop_back
#define pof pop_front
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,dis[maxn];
bool vis[maxn];
deque<int> q;
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
memset(dis,0x3f,sizeof(dis));
dis[1]=1,q.pb(1);
while(q.size()){
int u=q.front();
q.pof();
if(vis[u]){
continue;
}
vis[u]=1;
if(u==0){
cout<<dis[u];
break;
}
if(!vis[u+1]&&dis[u]+1<dis[u+1]){
dis[u+1]=dis[u]+1,q.pb(u+1);
}
if(!vis[u*10%n]&&dis[u]<dis[u*10%n]){
dis[u*10%n]=dis[u],q.pf(u*10%n);
}
}
return 0;
}
}
int main(){return asbt::main();}
04. 图论 I. Turtle and Multiplication
\(x\cdot y\ne x'\cdot y'\) 的必要条件是 \((x,y)\ne(x',y')\),其中 \((x,y),(x',y')\) 都是无序对。当 \(x,y,x',y'\) 都是质数时,这个条件就是充要的了。于是考虑只取质数。
假设我们要取 \(m\) 个点,相当于要求我们在一张无向完全图(含有自环)上跑出一条欧拉路径。如果 \(m\) 是奇数,则每个点的度数都是偶数,有欧拉回路;否则,每个点的度数都是奇数,我们需要删去一些边使这张图成为半欧拉图,显然删去 \(2\to3,4\to5\dots,m-2\to m-1\) 是最优的。于是我们先二分出 \(m\),再跑一遍欧拉路径即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=2e3+5,maxm=2e5+5;
int T,n,hd[maxn],enm,prm[maxm],prn;
bool ban[maxn*maxn],npr[maxm];
vector<int> ans;
struct{
int v,nxt;
}e[maxn*maxn];
il void addedge(int u,int v){
e[++enm]={v,hd[u]};
hd[u]=enm;
}
il bool check(int x){
if(x&1){
return x*(x+1)/2+1>=n;
}else{
return x*(x+1)/2-x/2+2>=n;
}
}
il void euler(int n=2e5){
for(int i=2;i<=n;i++){
if(!npr[i]){
prm[++prn]=i;
}
for(int j=1;j<=prn&&i*1ll*prm[j]<=n;j++){
npr[i*prm[j]]=1;
if(i%prm[j]==0){
break;
}
}
}
}
il void dfs(int u){
for(int &i=hd[u];i;i=e[i].nxt){
if(ban[i]){
continue;
}
int v=e[i].v;
ban[i]=1;
if(v!=u){
ban[i^1]=1;
}
dfs(v);
}
ans.pb(prm[u]);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
euler();
cin>>T;
while(T--){
cin>>n;
int l=1,r=2e3;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)){
r=mid;
}else{
l=mid+1;
}
}
enm=1;
for(int i=1;i<=l;i++){
hd[i]=0;
}
for(int i=1;i<=l;i++){
for(int j=i+1;j<=l;j++){
if(l%2==0&&i%2==0&&j==i+1){
continue;
}
addedge(i,j),addedge(j,i);
}
}
for(int i=1;i<=l;i++){
addedge(i,i);
}
for(int i=1;i<=enm;i++){
ban[i]=0;
}
ans.clear();
dfs(1);
// cout<<l<<'\n';
for(int i=0;i<n;i++){
cout<<ans[i]<<' ';
}
cout<<'\n';
}
return 0;
}
}
int main(){return asbt::main();}
05. 数论 A. ConstructOR
首先考虑判无解。可以发现,如果 \(lowbit(a\operatorname{or}b)<lowbit(d)\),则必然无解。实际上它是充要的,因为如果不满足这个条件则一定可以构造。
我们考虑比原题要求更强一点的构造,题目要求 \(d|(a\operatorname{or}x),d|(b\operatorname{or}x)\),我们考虑直接构造 \(d|x\) 满足 \((a\operatorname{or}b)\subseteq x\)。那么实际上构造是非常简单的,从低往高扫每一位,如果 \(a\operatorname{or}b\) 这一位是 \(1\) 且当前答案 \(x\) 这一位不是 \(1\),就给 \(x\) 加上 \(d\) 的某个倍数,满足它的 \(lowbit\) 正好对到这一位。显然必然满足 \(x<2^{60}\)。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lowbit(x) ((x)&-(x))
using namespace std;
namespace asbt{
int T,a,b,d;
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>a>>b>>d;
a|=b;
int lab=__lg(lowbit(a)),ld=__lg(lowbit(d));
if(ld>lab){
cout<<-1<<'\n';
continue;
}
int x=0;
for(int i=0;i<=30;i++){
if((a>>i&1)&&!(x>>i&1)){
x+=(1ll<<(i-ld))*d;
}
}
cout<<x<<'\n';
}
return 0;
}
}
signed main(){return asbt::main();}
06. 字符串 G. Legen...
建 AC 自动机,广义矩阵快速幂即可。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=205;
int n,m,tot,a[maxn],b[maxn],tr[maxn][26],fail[maxn];
vector<int> e[maxn];
queue<int> q;
struct juz{
int a[maxn][maxn];
juz(){
memset(a,-0x3f,sizeof(a));
}
il int*operator[](int x){
return a[x];
}
il juz operator*(juz b)const{
juz c;
for(int i=0;i<=tot;i++){
for(int j=0;j<=tot;j++){
for(int k=0;k<=tot;k++){
c[i][j]=max(c[i][j],a[i][k]+b[k][j]);
}
}
}
return c;
}
}bas;
il juz qpow(juz x,int y){
juz res;
for(int i=0;i<=tot;i++){
res[i][i]=0;
}
while(y){
if(y&1){
res=res*x;
}
x=x*x,y>>=1;
}
return res;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
string s;
cin>>s;
int p=0;
for(char c:s){
int d=c-'a';
if(!tr[p][d]){
tr[p][d]=++tot;
}
p=tr[p][d];
}
b[p]+=a[i];
}
for(int i=0;i<=25;i++){
if(tr[0][i]){
q.push(tr[0][i]);
}
}
while(q.size()){
int u=q.front();
q.pop();
for(int i=0;i<=25;i++){
if(tr[u][i]){
fail[tr[u][i]]=tr[fail[u]][i];
q.push(tr[u][i]);
}else{
tr[u][i]=tr[fail[u]][i];
}
}
}
for(int i=1;i<=tot;i++){
if(fail[i]){
e[fail[i]].pb(i);
}else{
q.push(i);
}
}
while(q.size()){
int u=q.front();
q.pop();
for(int v:e[u]){
b[v]+=b[u],q.push(v);
}
}
for(int i=0;i<=tot;i++){
for(int j=0;j<=25;j++){
bas[i][tr[i][j]]=b[tr[i][j]];
}
}
bas=qpow(bas,m);
int ans=0;
for(int i=0;i<=tot;i++){
ans=max(ans,bas[0][i]);
}
cout<<ans;
return 0;
}
}
signed main(){return asbt::main();}
07. 图论 A. Ehab's Last Corollary
考虑先找环。如果找到了 \(\le k\) 的环,就直接输出。否则,要么没环,就是一棵树,跑一遍最大独立集再输出方案即可;要么必然有 \(>k\) 的环,在环上隔一个输出一个即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=4e5+5;
int n,m,kk,dp[maxn][2],cnt;
int vis[maxn],hd[maxn],enm=1;
int dfn[maxn],tot;
bool ban[maxn];
struct{
int v,nxt;
}e[maxn];
struct{
int u,v;
}E[maxn];
il void addedge(int u,int v){
e[++enm]={v,hd[u]},hd[u]=enm;
}
il void dfs1(int u,int fa){
dp[u][1]=1;
for(int i=hd[u];i;i=e[i].nxt){
int v=e[i].v;
if(v==fa){
continue;
}
dfs1(v,u);
dp[u][0]+=max(dp[v][0],dp[v][1]);
dp[u][1]+=dp[v][0];
}
}
il void dfs2(int u,int fa,int d){
if(d){
cout<<u<<' ';
if(++cnt==(kk+1)>>1){
exit(0);
}
}
for(int i=hd[u];i;i=e[i].nxt){
int v=e[i].v;
if(v==fa){
continue;
}
if(d){
dfs2(v,u,0);
}else{
dfs2(v,u,dp[v][1]>dp[v][0]);
}
}
}
il int dfs3(int u){
dfn[u]=++cnt;
for(int &i=hd[u];i;i=e[i].nxt){
if(ban[i]){
continue;
}
ban[i]=ban[i^1]=1;
int v=e[i].v;
if(!dfn[v]){
int r=dfs3(v);
if(r){
cout<<u<<' ';
if(r==u){
exit(0);
}
return r;
}
}else{
if(dfn[u]-dfn[v]+1<=kk){
cout<<2<<'\n'<<dfn[u]-dfn[v]+1<<'\n'<<u<<' ';
return v;
}
}
}
dfn[u]=0,cnt--;
return 0;
}
il int dfs4(int u){
// cout<<u<<'\n';
dfn[u]=++cnt;
for(int &i=hd[u];i;i=e[i].nxt){
if(ban[i]){
continue;
}
ban[i]=ban[i^1]=1;
int v=e[i].v;
if(!dfn[v]){
int r=dfs4(v);
if(r){
if(r==1){
cout<<u<<' ';
if(++tot==(kk+1)>>1){
exit(0);
}
}
return -r;
}
}else{
cout<<1<<'\n'<<u<<' ';
if(++tot==(kk+1)>>1){
exit(0);
}
return -1;
}
}
dfn[u]=0,cnt--;
return 0;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>kk;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
addedge(u,v),addedge(v,u);
E[i]={u,v};
}
if(m==n-1){
cout<<1<<'\n';
dfs1(1,0);
// for(int i=1;i<=n;i++){
// cout<<dp[i][0]<<' '<<dp[i][1]<<'\n';
// }
dfs2(1,0,dp[1][1]>dp[1][0]);
return 0;
}
dfs3(1);
// puts("666");
enm=1;
for(int i=1;i<=m;i++){
addedge(E[i].u,E[i].v);
addedge(E[i].v,E[i].u);
}
for(int i=1;i<=enm;i++){
ban[i]=0;
}
// for(int i=1;i<=n;i++){
// cout<<dfn[i]<<' ';
// }
dfs4(1);
return 0;
}
}
int main(){return asbt::main();}
09. 字符串 I. [NOI2011] 阿狸的打字机
首先建出 AC 自动机。考虑这个询问和 AC 自动机的关系,实际上就是从 \(y\) 的根链上的每个点不停跳 \(fail\),如果跳到了 \(x\) 的末尾就将答案加一。考虑到从同一个点跳 \(fail\) 不可能两次跳到同一个点,故可以转化为将 \(y\) 根链上的每个点的权值设为 \(1\),其它设为 \(0\),查询 \(fail\) 树上 \(x\) 末尾的子树的权值和。于是我们离线下来,给 \(fail\) 树跑个 \(dfn\),树状数组维护一下即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,ans[maxn],tot,tr[maxn][26],tra[maxn][26];
int fail[maxn],fa[maxn],end[maxn],dfn[maxn],cnt,sz[maxn];
string s;
vector<int> e[maxn],id[maxn];
vector<pii> qq[maxn];
queue<int> q;
il void dfs1(int u){
// cout<<u<<'\n';
dfn[u]=++cnt,sz[u]=1;
for(int v:e[u]){
dfs1(v);
sz[u]+=sz[v];
}
}
il void build(){
int p=0;
for(char i:s){
if(i=='B'){
p=fa[p];
}else if(i=='P'){
end[++n]=p,id[p].pb(n);
}else{
int d=i-'a';
if(!tr[p][d]){
tr[p][d]=tra[p][d]=++tot;
fa[tot]=p;
}
p=tr[p][d];
}
}
for(int i=0;i<=25;i++){
if(tra[0][i]){
q.push(tra[0][i]);
}
}
while(q.size()){
int u=q.front();
q.pop();
for(int i=0;i<=25;i++){
if(tra[u][i]){
fail[tra[u][i]]=tra[fail[u]][i];
q.push(tra[u][i]);
}else{
tra[u][i]=tra[fail[u]][i];
}
}
}
for(int i=1;i<=tot;i++){
e[fail[i]].pb(i);
}
dfs1(0);
}
struct{
#define lowbit(x) ((x)&-(x))
int tr[maxn];
il void add(int p,int x){
for(;p<=cnt;p+=lowbit(p)){
tr[p]+=x;
}
}
il int query(int p){
int res=0;
for(;p;p-=lowbit(p)){
res+=tr[p];
}
return res;
}
#undef lowbit
}F;
il void dfs2(int u){
// cout<<u<<'\n';
F.add(dfn[u],1);
for(int y:id[u]){
for(pii i:qq[y]){
int x=i.fir;
ans[i.sec]=F.query(dfn[end[x]]+sz[end[x]]-1)-F.query(dfn[end[x]]-1);
}
}
for(int i=0;i<=25;i++){
if(tr[u][i]){
dfs2(tr[u][i]);
}
}
F.add(dfn[u],-1);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>s>>m;
for(int i=1,x,y;i<=m;i++){
cin>>x>>y;
qq[y].pb(mp(x,i));
}
build();
// puts("666");
dfs2(0);
for(int i=1;i<=m;i++){
cout<<ans[i]<<'\n';
}
return 0;
}
}
int main(){return asbt::main();}
10. 图论 J. Tree and Queries
直接树上莫队就完了。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5,B=316;
int n,m,a[maxn],dfn[maxn],sz[maxn],cnt,stk[maxn];
int bnm,bel[maxn],tong[maxn],cun[maxn],ans[maxn];
vector<int> e[maxn];
struct node{
int l,r,k,id;
il bool operator<(const node &x)const{
return bel[l]!=bel[x.l]?l<x.l:bel[l]&1?r<x.r:r>x.r;
}
}q[maxn];
il void dfs(int u,int fa){
dfn[u]=++cnt,sz[u]=1,stk[cnt]=a[u];
for(int v:e[u]){
if(v==fa){
continue;
}
dfs(v,u);
sz[u]+=sz[v];
}
}
il void add(int x){
cun[++tong[x]]++;
}
il void del(int x){
cun[tong[x]--]--;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].pb(v),e[v].pb(u);
}
dfs(1,0);
for(int i=1,u,k;i<=m;i++){
cin>>u>>k;
q[i]={dfn[u],dfn[u]+sz[u]-1,k,i};
}
bnm=(n+B-1)/B;
for(int i=1,st=0,ed=0;i<=bnm;i++){
st=ed+1,ed=min(ed+B,n);
for(int j=st;j<=ed;j++){
bel[j]=i;
}
}
sort(q+1,q+m+1);
int l=1,r=0;
for(int i=1;i<=m;i++){
while(r<q[i].r){
add(stk[++r]);
}
while(l>q[i].l){
add(stk[--l]);
}
while(r>q[i].r){
del(stk[r--]);
}
while(l<q[i].l){
del(stk[l++]);
}
ans[q[i].id]=cun[q[i].k];
}
for(int i=1;i<=m;i++){
cout<<ans[i]<<'\n';
}
return 0;
}
}
int main(){return asbt::main();}
11. 数论 S. LCM Sum (hard version)
考虑算出 \(\operatorname{lcm}(i,j,k)<i+j+k\) 的三元组数量,再用 \({r-l+1\choose 3}\) 减掉即可。
设 \(\operatorname{lcm}(i,j,k)=x,i=\frac{x}{a},j=\frac{x}{b},k=\frac{x}{c}\),于是上面那个式子即为 \(\frac{1}{a}+\frac{1}{b}+\frac{1}{c}>1\),并且 \(a>b>c\)。首先考虑 \(c>1\) 的情况。
如果 \(c\ge3\),那么 \(\frac{1}{a}+\frac{1}{b}+\frac{1}{c}<\frac{1}{3}+\frac{1}{3}+\frac{1}{3}=1\),不成立。于是 \(c=2\)。
此时如果 \(b\ge4\),那么 \(\frac{1}{a}+\frac{1}{b}+\frac{1}{c}<\frac{1}{4}+\frac{1}{4}+\frac{1}{2}=1\),不成立。于是 \(b=3\)。
类似地可以得到,\(a=4\text{ 或 }5\)。于是有 \(i:j:k=3:4:6\text{ 或 }6:10:15\),方案数可以非常容易地直接算出来。
然后是 \(c=1\) 的情况。此时相当于要求 \(i,j|k\)。考虑对于 \(\forall i\in[l,r]\),假设其在 \([l,r]\) 中的因子有 \(cnt_i\) 个,则贡献为 \({cnt_i\choose2}\)。考虑离线,对 \(l\) 倒着扫描线,枚举所有倍数,在树状数组上修改贡献即可。时间复杂度线性对数方。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,ans[maxn],d[maxn];
vector<pii> q[maxn];
struct{
#define lowbit(x) ((x)&-(x))
int tr[maxn];
il void add(int p,int x){
for(;p<=2e5;p+=lowbit(p)){
tr[p]+=x;
}
}
il int query(int p){
int res=0;
for(;p;p-=lowbit(p)){
res+=tr[p];
}
return res;
}
#undef lowbit
}F;
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1,l,r;i<=n;i++){
cin>>l>>r;
int k=r-l+1;
ans[i]=k*(k-1)*(k-2)/6-max(0ll,r/6-(l+2)/3+1)-max(0ll,r/15-(l+5)/6+1);
q[l].pb(mp(r,i));
}
for(int l=2e5;l;l--){
for(int i=l*2;i<=2e5;i+=l){
F.add(i,(d[i]+1)*d[i]/2-d[i]*(d[i]-1)/2);
d[i]++;
}
for(pii i:q[l]){
int r=i.fir;
ans[i.sec]-=F.query(r)-F.query(l-1);
}
}
for(int i=1;i<=n;i++){
cout<<ans[i]<<'\n';
}
return 0;
}
}
signed main(){return asbt::main();}
12. 图论 K. XOR Tree
考虑将 \(a_u\) 变成一个非常大的 \(2\) 整次幂(如 \(2^{u+11^{4514}}\)),我们就可以保证所有经过 \(u\) 的路径合法。考虑贪心,如果 \(u\) 子树中有一条经过 \(u\) 的路径不合法,则必须将 \(a_u\) 值修改,此后便可以不考虑这棵子树。启发式合并,时间复杂度线性对数方。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,a[maxn],d[maxn],ans;
vector<int> e[maxn];
set<int> s[maxn];
il void dfs1(int u,int fa){
d[u]=d[fa]^a[u];
for(int v:e[u]){
if(v==fa){
continue;
}
dfs1(v,u);
}
}
il void dfs2(int u,int fa){
s[u].insert(d[u]);
bool flag=0;
for(int v:e[u]){
if(v==fa){
continue;
}
dfs2(v,u);
if(flag){
continue;
}
if(s[u].size()<s[v].size()){
swap(s[u],s[v]);
}
for(int x:s[v]){
if(s[u].count(x^a[u])){
ans++,flag=1,s[u].clear();
goto togo;
}
}
for(int x:s[v]){
s[u].insert(x);
}
togo:;
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].pb(v),e[v].pb(u);
}
dfs1(1,0),dfs2(1,0);
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
/*
10
7 4 6 7 6 6 7 5 7 5
8 7
4 5
9 6
2 5
4 8
9 10
4 3
9 4
1 8
*/

浙公网安备 33010602011771号