【题解】CF1479D Odd Mineral Resource
目录
- 题意简述
- 思路一
- 大常数
O(n\sqrt{q}\log{n}+q\log{n}) 算法 - 小常数
O(n\sqrt{q}\log{n}+q\log{n}) 算法- 代码
- 正解
O(n\sqrt{q}+q\sqrt{n}) - 代码
- 大常数
- 思路二
- 分析 + 题解
- 代码
- 有关
X 的随机生成
题意简述
给定一棵
有 -1。
数据范围:
思路一
看完题面,很容易想到树上莫队,用以维护当前每种权值出现的奇偶性,但是如何求得区间
大常数 O(n\sqrt{q}\log{n}+q\log{n}) 算法
我们可以将新增的出现奇数次的权值放进 set 里,将新增的出现偶数次的权值从 set 中删除,查询时二分查找即可。单次修改和单次查询都是大常数
小常数 O(n\sqrt{q}\log{n}+q\log{n}) 算法
我们也可以在树状数组上将新增的出现奇数次和偶数次的权值对应位置 +1 和 -1,单次修改是小常数
由于 Time limit exceeded on test 7。
代码
由于是赛时写的,写得丑请见谅。(结构体变量 l,r,L,R 和局部变量 L,R,ll,rr 不要弄混了,本人因为弄混在赛时获得了 Wrong answer on pretest 1 的好成绩——当然这里已经修正)
#include<bits/stdc++.h>
using namespace std;
namespace IO
{
const int buffer_size=1e5+5;
char buf[buffer_size],*S,*T;
bool flag_EOF;
inline char read_char()
{
if(S==T)
T=(S=buf)+fread(buf,1,buffer_size,stdin);
return S!=T?*(S++):EOF;
}
inline int read_int()
{
int flag=1;
char c=read_char();
while(c<'0'||c>'9')
{
if(c==EOF)
{
flag_EOF=true;
return 0;
}
flag=(c=='-'?-1:1);
c=read_char();
}
int x=0;
while(c>='0'&&c<='9')
{
x=x*10+(c^48);
c=read_char();
}
return x*flag;
}
char st[13];
int _top;
inline void Write(int x)
{
if(!x)
{
putchar('0');
return;
}
if(x<0)
{
putchar('-');
x=-x;
}
while(x)
{
st[++_top]=x%10+'0';
x/=10;
}
while(_top>0)
putchar(st[_top--]);
}
}
int n,m;
const int max_n=3e5+5;
vector<int> edge[max_n];
int id[max_n<<1],Time,dfn[max_n][2];
void dfs(int x,int fa)
{
dfn[x][0]=++Time;
id[Time]=x;
for(int i=0;i<int(edge[x].size());++i)
{
int y=edge[x][i];
if(y!=fa)
dfs(y,x);
}
dfn[x][1]=++Time;
id[Time]=x;
}
int dep[max_n],sz[max_n],son[max_n],fath[max_n];
void dfs1(int x,int fa)
{
dep[x]=dep[fa]+1;
sz[x]=1;
son[x]=-1;
fath[x]=fa;
int max_sz=0;
for(int i=0;i<int(edge[x].size());++i)
{
int y=edge[x][i];
if(y!=fa)
{
dfs1(y,x);
sz[x]+=sz[y];
if(sz[y]>max_sz)
{
max_sz=sz[y];
son[x]=y;
}
}
}
}
int _top[max_n];
void dfs2(int x,int top_now)
{
_top[x]=top_now;
if(son[x]!=-1)
dfs2(son[x],top_now);
for(int i=0;i<int(edge[x].size());++i)
{
int y=edge[x][i];
if(y!=fath[x]&&y!=son[x])
dfs2(y,y);
}
}
inline int get_LCA(int x,int y)
{
while(_top[x]!=_top[y])
{
if(dep[_top[x]]<dep[_top[y]])
swap(x,y);
x=fath[_top[x]];
}
return dep[x]<dep[y]?x:y;
}
namespace BIT
{
int c[max_n];
inline void modify(int k,int v)
{
for(int i=k;i<=n;i+=i&(-i))
c[i]+=v;
}
inline int query(int k)
{
int res=0;
for(int i=k;i>0;i-=i&(-i))
res+=c[i];
return res;
}
}
const int max_m=3e5+5;
int ans[max_m];
struct Query
{
int l,blo,r,lca,L,R,id;
}q[max_m];
inline bool cmp(const Query &a,const Query &b)
{
return a.blo!=b.blo?a.blo<b.blo:a.r<b.r;
}
int a[max_n];
bool odd[max_n];
inline void work(int x)
{
if(odd[a[x]])
{
BIT::modify(a[x],-1);
odd[a[x]]=false;
}
else
{
BIT::modify(a[x],1);
odd[a[x]]=true;
}
}
int main()
{
n=IO::read_int(),m=IO::read_int();
for(int i=1;i<=n;++i)
a[i]=IO::read_int();
for(int i=1;i<=n-1;++i)
{
int x=IO::read_int(),y=IO::read_int();
edge[x].push_back(y);
edge[y].push_back(x);
}
dfs(1,0);
dfs1(1,0);
dfs2(1,1);
int s=int(2*n/sqrt(m));
for(int i=1;i<=m;++i)
{
int u=IO::read_int(),v=IO::read_int(),l=IO::read_int(),r=IO::read_int();
if(dfn[u][0]>dfn[v][0])
swap(u,v);
int p=get_LCA(u,v);
if(p==u)
q[i].l=dfn[u][0],q[i].r=dfn[v][0];
else
q[i].l=dfn[u][1],q[i].r=dfn[v][0],q[i].lca=p;
q[i].L=l,q[i].R=r,q[i].id=i;
q[i].blo=(q[i].l-1)/s+1;
}
sort(q+1,q+m+1,cmp);
int L=1,R=0;
for(int i=1;i<=m;++i)
{
while(L<q[i].l)
work(id[L++]);
while(L>q[i].l)
work(id[--L]);
while(R<q[i].r)
work(id[++R]);
while(R>q[i].r)
work(id[R--]);
if(q[i].lca)
work(q[i].lca);
int v1=BIT::query(q[i].L-1);
int v2=BIT::query(q[i].R);
if(v1==v2)
ans[q[i].id]=-1;
else
{
int ll=q[i].L,rr=q[i].R,mid,res=q[i].R;
while(ll<=rr)
{
mid=(ll+rr)>>1;
if(BIT::query(mid)>=v1+1)
res=mid,rr=mid-1;
else
ll=mid+1;
}
ans[q[i].id]=res;
}
if(q[i].lca)
work(q[i].lca);
}
for(int i=1;i<=m;++i)
printf("%d\n",ans[i]);
return 0;
}
正解 O(n\sqrt{q}+q\sqrt{n})
注意到时间复杂度的瓶颈在于修改,即要求单次修改
具体来说,用一个 bool 数组记录每种权值是否出现奇数次,再用一个 int 数组记录每一块有多少个出现奇数次的权值。修改时把当前这种权值及其所在块修改;查询时暴力扫描左右两端的部分,中间块若出现个数
代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int max_n=3e5+5;
vector<int> edge[max_n];
int id[max_n<<1],Time,dfn[max_n][2];
void dfs(int x,int fa)
{
dfn[x][0]=++Time;
id[Time]=x;
for(int i=0;i<int(edge[x].size());++i)
{
int y=edge[x][i];
if(y!=fa)
dfs(y,x);
}
dfn[x][1]=++Time;
id[Time]=x;
}
int dep[max_n],sz[max_n],son[max_n],fath[max_n];
void dfs1(int x,int fa)
{
dep[x]=dep[fa]+1;
sz[x]=1;
son[x]=-1;
fath[x]=fa;
int max_sz=0;
for(int i=0;i<int(edge[x].size());++i)
{
int y=edge[x][i];
if(y!=fa)
{
dfs1(y,x);
sz[x]+=sz[y];
if(sz[y]>max_sz)
{
max_sz=sz[y];
son[x]=y;
}
}
}
}
int _top[max_n];
void dfs2(int x,int top_now)
{
_top[x]=top_now;
if(son[x]!=-1)
dfs2(son[x],top_now);
for(int i=0;i<int(edge[x].size());++i)
{
int y=edge[x][i];
if(y!=fath[x]&&y!=son[x])
dfs2(y,y);
}
}
inline int get_LCA(int x,int y)
{
while(_top[x]!=_top[y])
{
if(dep[_top[x]]<dep[_top[y]])
swap(x,y);
x=fath[_top[x]];
}
return dep[x]<dep[y]?x:y;
}
const int max_m=3e5+5;
int ans[max_m];
struct Query
{
int l,blo,r,lca,L,R,id;
}q[max_m];
inline bool cmp(const Query &a,const Query &b)
{
return a.blo!=b.blo?a.blo<b.blo:a.r<b.r;
}
int a[max_n];
bool odd[max_n];
int sn;
const int max_sqrt_n=548+5;
int cnt[max_sqrt_n];
inline void work(int x)
{
if(odd[a[x]])
{
--cnt[(a[x]-1)/sn+1];
odd[a[x]]=false;
}
else
{
++cnt[(a[x]-1)/sn+1];
odd[a[x]]=true;
}
}
inline int query(int l,int r)
{
int id_l=(l-1)/sn+1,id_r=(r-1)/sn+1;
if(id_l==id_r)
{
for(int i=l;i<=r;++i)
{
if(odd[i])
return i;
}
return -1;
}
for(int i=l;i<=id_l*sn;++i)
{
if(odd[i])
return i;
}
for(int i=(id_r-1)*sn+1;i<=r;++i)
{
if(odd[i])
return i;
}
for(int i=id_l+1;i<=id_r-1;++i)
{
if(cnt[i])
{
for(int j=(i-1)*sn+1;j<=i*sn;++j)
{
if(odd[j])
return j;
}
}
}
return -1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",a+i);
for(int i=1;i<=n-1;++i)
{
int x,y;
scanf("%d%d",&x,&y);
edge[x].push_back(y);
edge[y].push_back(x);
}
dfs(1,0);
dfs1(1,0);
dfs2(1,1);
sn=int(sqrt(n));
int s=int(2*n/sqrt(m));
for(int i=1;i<=m;++i)
{
int u,v,l,r;
scanf("%d%d%d%d",&u,&v,&l,&r);
if(dfn[u][0]>dfn[v][0])
swap(u,v);
int p=get_LCA(u,v);
if(p==u)
q[i].l=dfn[u][0],q[i].r=dfn[v][0];
else
q[i].l=dfn[u][1],q[i].r=dfn[v][0],q[i].lca=p;
q[i].L=l,q[i].R=r,q[i].id=i;
q[i].blo=(q[i].l-1)/s+1;
}
sort(q+1,q+m+1,cmp);
int L=1,R=0;
for(int i=1;i<=m;++i)
{
while(L<q[i].l)
work(id[L++]);
while(L>q[i].l)
work(id[--L]);
while(R<q[i].r)
work(id[++R]);
while(R>q[i].r)
work(id[R--]);
if(q[i].lca)
work(q[i].lca);
ans[q[i].id]=query(q[i].L,q[i].R);
if(q[i].lca)
work(q[i].lca);
}
for(int i=1;i<=m;++i)
printf("%d\n",ans[i]);
return 0;
}
PS:出题人应该是想放这种解法过的,因为另外一种时间复杂度更优秀的正解只需 1s 左右,但时限是 5s,而这份代码最大用时 4742 ms。
思路二
分析 + 题解
题目的要求相当于“奇数次算出现,偶数次算没出现”,这跟异或的性质类似,考虑用异或值来刻画询问是否有解。
定义
但即使有解,
具体来说,设
当
我们知道,原问题相当于求区间
先来一个常用套路,一条路径拆成四条节点到根的链:
因此只需维护
PS:由于
代码
需要注意的是,查询时一旦找到答案,就不再继续求解,否则时间复杂度会变成
#include<bits/stdc++.h>
using namespace std;
int n,q;
const int max_n=3e5+5;
int End[max_n<<1],Last[max_n],Next[max_n<<1],e;
inline void add_edge(int x,int y)
{
End[++e]=y;
Next[e]=Last[x];
Last[x]=e;
End[++e]=x;
Next[e]=Last[y];
Last[y]=e;
}
int dep[max_n],fath[max_n],sz[max_n],son[max_n];
void dfs1(int x,int fa)
{
dep[x]=dep[fa]+1;
fath[x]=fa;
sz[x]=1;
son[x]=-1;
int max_sz=0;
for(int i=Last[x];i;i=Next[i])
{
int y=End[i];
if(y!=fa)
{
dfs1(y,x);
sz[x]+=sz[y];
if(sz[y]>max_sz)
{
max_sz=sz[y];
son[x]=y;
}
}
}
}
int _top[max_n];
void dfs2(int x,int top_now)
{
_top[x]=top_now;
if(son[x]!=-1)
dfs2(son[x],top_now);
for(int i=Last[x];i;i=Next[i])
{
int y=End[i];
if(y!=fath[x]&&y!=son[x])
dfs2(y,y);
}
}
inline int get_LCA(int x,int y)
{
while(_top[x]!=_top[y])
{
if(dep[_top[x]]<dep[_top[y]])
swap(x,y);
x=fath[_top[x]];
}
return dep[x]<dep[y]?x:y;
}
int a[max_n];
unsigned long long X[max_n];
namespace SegmentTree
{
const int max_sz=20*3e5+5;
unsigned long long val[max_sz];
int ls[max_sz],rs[max_sz],cnt_node;
void modify(int old,int &p,int l,int r,int k,unsigned long long v)
{
p=++cnt_node;
val[p]=val[old]^v,ls[p]=ls[old],rs[p]=rs[old];
if(l<r)
{
int mid=(l+r)>>1;
if(k<=mid)
modify(ls[old],ls[p],l,mid,k,v);
else
modify(rs[old],rs[p],mid+1,r,k,v);
}
}
int res;
void query(int p1,int p2,int p3,int p4,int l,int r)
{
if(l==r)
{
res=l;
return;
}
int mid=(l+r)>>1;
if(val[ls[p1]]^val[ls[p2]]^val[ls[p3]]^val[ls[p4]])
query(ls[p1],ls[p2],ls[p3],ls[p4],l,mid);
else
query(rs[p1],rs[p2],rs[p3],rs[p4],mid+1,r);
}
void query(int p1,int p2,int p3,int p4,int l,int r,int a,int b)
{
if(a<=l&&r<=b)
{
if(val[p1]^val[p2]^val[p3]^val[p4])
query(p1,p2,p3,p4,l,r);
return;
}
int mid=(l+r)>>1;
if(a<=mid)
query(ls[p1],ls[p2],ls[p3],ls[p4],l,mid,a,b);
if(b>mid&&res==-1)
query(rs[p1],rs[p2],rs[p3],rs[p4],mid+1,r,a,b);
}
}
int root[max_n];
void dfs(int x,int fa)
{
SegmentTree::modify(root[fa],root[x],1,n,a[x],X[a[x]]);
for(int i=Last[x];i;i=Next[i])
{
int y=End[i];
if(y!=fa)
dfs(y,x);
}
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i)
scanf("%d",a+i);
for(int i=1;i<=n-1;++i)
{
int x,y;
scanf("%d%d",&x,&y);
add_edge(x,y);
}
dfs1(1,0);
dfs2(1,1);
srand(time(NULL));
for(int i=1;i<=n;++i)
X[i]=1ull*rand()<<60|1ull*rand()<<45|1ull*rand()<<30|1ull*rand()<<15|1ull*rand();
dfs(1,0);
while(q--)
{
int u,v,l,r;
scanf("%d%d%d%d",&u,&v,&l,&r);
int p=get_LCA(u,v);
SegmentTree::res=-1;
SegmentTree::query(root[u],root[v],root[p],root[fath[p]],1,n,l,r);
printf("%d\n",SegmentTree::res);
}
return 0;
}
有关 X 的随机生成
上述代码中使用的方法是,将 rand() 函数通过移位进行叠加,即:
srand(time(NULL));
for(int i=1;i<=n;++i)
X[i]=1ull*rand()<<60|1ull*rand()<<45|1ull*rand()<<30|1ull*rand()<<15|1ull*rand();
(在 Windows 下,rand() 的取值范围为
你也可以使用比较高级的随机数:
mt19937_64 rd(chrono::steady_clock::now().time_since_epoch().count());
for(int i=1;i<=n;++i)
X[i]=rd();
事实上,本人一开始使用的是下面这种方法,会被 test 7 的第 -1,但代码输出 -1。
srand(time(NULL));
for(int i=1;i<=n;++i)
for(int j=0;j<64;++j)
X[i]=X[i]<<1|(rand()&1);
(即从高到低每一个二进制位均为 rand()&1,我试过其它种子,都会被同样卡掉)
个人理解是因为 rand() 的均匀随机性不高,容易出现循环——这只是感性理解,如果你能给出更严谨的解答或构造这样的 Hack 数据,请务必私信告知我,我会将其添加至该篇文章,供大家学习。