我做过的Ynoi题
P5354 [Ynoi Easy Round 2017] 由乃的 OJ
题意
给你一个有 \(n\) 个点的树,每个点的包括一个位运算 \(opt\) 和一个权值 \(x\),位运算有&,|,^ 三种,分别用 \(1,2,3\) 表示。
每次询问包含三个整数 \(x,y,z\),初始选定一个数 \(v\)。然后 \(v\) 依次经过从 \(x\) 到 \(y\) 的所有节点,每经过一个点 \(i\),\(v\) 就变成 \(v\ opt_i\ x_i\),所以他想问你,最后到 \(y\) 时,希望得到的值尽可能大,求最大值。给定的初始值 \(v\) 必须是在 \([0,z]\) 之间。
每次修改包含三个整数 \(x,y,z\),意思是把 \(x\) 点的操作修改为 \(y\),数值改为 \(z\)。
其中, \(1 \leq n,m \leq 10 ^ 5\) , \(1 \leq k \leq 64\) ,其中 \(k\) 的意义是每个点上的数和询问中的 \(z\) 都小于 \(2 ^ k\) 。
解法
发现运算过程中不同的二进制位之间没有任何影响,可以分开考虑。
我们从高位向低位考虑,每次贪心的选取即可。
现在加上了修改操作,怎么做?
线段树+树剖
有一个显然的做法是树剖+线段树来维护树上修改和询问。
对于每一个位,我们维护从左向右和从右向左得到的是 \(0\) 还是 \(1\) ,询问的时候直接合并区间即可。
复杂度 \(O(nk \log ^ 2 n)\) ,这显然是无法通过的,考虑把 \(k\) 给优化掉。
优化
我们可以把一堆二进制位压进一个 long long 里面。
因为二进制位之间是没有任何影响的,所以可以合并。
那么有 \(f_0[u][i]=(f_0[l][i]\;bitand\;f_1[r][i])\;bitor\;(!f_0[l][i]\;bitand\;f_0[r][i])\)
其他同理。
复杂度 \(O(n \log ^ 2 n)\) ,可以通过。
代码
#include<bits/stdc++.h>
#define ll unsigned long long
#define ls rt*2
#define rs ls+1
#define mid (l+r)/2
#define lson ls,l,mid
#define rson rs,mid+1,r
using namespace std;
const int N=1e5+100;
int n,m,k;
int fa[N],dep[N],son[N],dfn[N],dfncnt,topf[N],sz[N];
struct node{
ll lans[2],rans[2];//lans:从左到右 rans:从右到左
}t[N*8];
vector<int>g[N];
void dfs1(int u,int f,int d){
fa[u]=f,dep[u]=d+1;
sz[u]++;
for(int v:g[u]){
if(v==f)continue;
dfs1(v,u,d+1);
if(sz[v]>sz[son[u]])son[u]=v;
sz[u]+=sz[v];
}
}
void dfs2(int u,int f,int tf){
topf[u]=tf,dfn[u]=++dfncnt;
if(son[u])dfs2(son[u],u,tf);
for(int v:g[u]){
if(v==f||v==son[u])continue;
dfs2(v,u,v);
}
}
node merge(node l,node r){
node res;
res.lans[0]=(~l.lans[0]&r.lans[0])|(l.lans[0]&r.lans[1]);
res.lans[1]=(~l.lans[1]&r.lans[0])|(l.lans[1]&r.lans[1]);
res.rans[0]=(~r.rans[0]&l.rans[0])|(r.rans[0]&l.rans[1]);
res.rans[1]=(~r.rans[1]&l.rans[0])|(r.rans[1]&l.rans[1]);
return res;
}
void pushup(int rt,int l,int r){
t[rt]=merge(t[ls],t[rs]);
}
void update(int rt,int l,int r,int x,int opt,ll v){
if(l==r){
if(opt==1)t[rt].lans[0]=t[rt].rans[0]=0,t[rt].lans[1]=t[rt].rans[1]=v;
if(opt==2)t[rt].lans[0]=t[rt].rans[0]=v,t[rt].lans[1]=t[rt].rans[1]=-1;
if(opt==3)t[rt].lans[0]=t[rt].rans[0]=v,t[rt].lans[1]=t[rt].rans[1]=(~v);
return;
}
if(x<=mid)update(lson,x,opt,v);
else update(rson,x,opt,v);
pushup(rt,l,r);
}
node query(int rt,int l,int r,int L,int R){
if(L<=l&&R>=r)return t[rt];
if(L<=mid&&R>mid)return merge(query(lson,L,R),query(rson,L,R));
if(L<=mid)return query(lson,L,R);
return query(rson,L,R);
}
ll pathquery(int u,int v,ll z){
int cnt=0;
node ures={{0,(ll)-1},{0,(ll)-1}},vres={{0,(ll)-1},{0,(ll)-1}};
while(topf[u]!=topf[v]){
int tu=topf[u],tv=topf[v];
if(dep[topf[u]]<dep[topf[v]]){
swap(u,v);
swap(tu,tv);
swap(ures,vres);
cnt++;
}
ures=merge(query(1,1,n,dfn[tu],dfn[u]),ures);
u=fa[tu];
}
if(dep[u]<dep[v])swap(u,v),swap(ures,vres),cnt++;
swap(vres.lans,vres.rans);
ures=merge(vres,merge(query(1,1,n,dfn[v],dfn[u]),ures));
if(!(cnt%2))swap(ures.lans,ures.rans);
ll ans=0,now=0;
for(int i=k-1;i>=0;i--){
if(ures.lans[0]&(1ull<<i))ans+=(1ull<<i);
else if((ures.lans[1]&(1ull<<i))&&now+(1ull<<i)<=z){
ans+=(1ull<<i);
now+=(1ull<<i);
}
}
return ans;
}
ll val[N];int op[N];
int main(){
scanf("%d %d %d",&n,&m,&k);
for(int i=1;i<=n;i++)scanf("%d %llu",&op[i],&val[i]);
for(int i=1,u,v;i<n;i++){
scanf("%d %d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1,0,1);
dfs2(1,0,1);
for(int i=1;i<=n;i++)update(1,1,n,dfn[i],op[i],val[i]);
while(m--){
int op,x,y;ll z;
scanf("%d %d %d %llu",&op,&x,&y,&z);
if(op==1)printf("%llu\n",pathquery(x,y,z));
else update(1,1,n,dfn[x],y,z);
}
}
P5309 [Ynoi2011] 初始化
题意
给定长为 \(n\) 的序列 \(a_i\) ,有两种操作 \(m\) :
x,y,z:将下标除以 \(x\) 余 \(y\) 的数增加 \(z\) 。l,r:求 \([l,r]\)区间中所有数的和,答案对 \(10 ^ 9 + 7\) 取模。
其中,\(1 \leq n,m \leq 2 \times 10 ^ 5\) 。
解法
如果操作 \(1\) 中 \(x\) 很大,那么修改的数就会很少。
于是考虑根号分治,设 \(B\) 为块长。
\(x \gt B\)
直接暴力做,但是如果用树状数组那么修改复杂度为 \(O(\frac{n\log n}{B})\) ,时间复杂度有点高。
考虑分块,每 \(\sqrt{n}\) 个数为一个块,修改复杂度为 \(O(\frac{n}{B})\) ,查询复杂度为 \(O(\sqrt{n})\) 。
\(x \leq B\)
因为修改的数实在太多了,考虑打标记。
设 \(f_{x,y}\) 表示查询 \(x,y\) 时 \(z\) 的总和。
修改复杂度为 \(O(1)\) ,查询复杂度为 \(O(B ^ 2)\) 。
这时 \(B\) 取 \(n ^ {\frac{2}{3}}\) 最优,但是总复杂度为 \(O(n ^ {\frac{5}{3}})\) ,无法通过。
发现如果 \(x\) 相同, \(y\) 变化,可以理解为一些下标在左右平移,于是可以前缀和优化,查询复杂度为 \(O(B)\) 。
这时 \(B\) 取 \(\sqrt{n}\) 最优,总复杂度为 \(O(n \sqrt{n})\) 。
在这个题中 \(B\) 不宜过大,开 \(100\) 到 \(250\) 最优。
代码
代码很短。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+100,s=120,M=N/s+100,mod=1e9+7;
int n,m,a[N],b[M],id[N],bl[N],br[N],add[s+10][s+10];ll ans;
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)id[i]=(i-1)/s+1;
for(int i=1;i<=n;i++){
if(id[i]!=id[i-1])bl[id[i]]=i;
if(id[i]!=id[i+1])br[id[i]]=i;
}
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)(b[id[i]]+=a[i])%=mod;
while(m--){
int op,l,r,k;
scanf("%d %d %d",&op,&l,&r);
if(op==1){
scanf("%d",&k);
if(l>s)for(int i=r;i<=n;i+=l)(b[id[i]]+=k)%=mod,(a[i]+=k)%=mod;
else for(int i=r;i<=l;i++)(add[l][i]+=k)%=mod;
}else{
ans=0;
if(id[l]==id[r])for(int i=l;i<=r;i++)ans+=a[i];
else{
for(int i=l;i<=br[id[l]];i++)ans+=a[i];
for(int i=id[l]+1;i<=id[r]-1;i++)ans+=b[i];
for(int i=bl[id[r]];i<=r;i++)ans+=a[i];
}
for(int i=1;i<=s;i++)ans+=(ll)r/i*add[i][i]%mod+add[i][r%i]-(ll)(l-1)/i*add[i][i]%mod-add[i][(l-1)%i];
ans=(ans%mod+mod)%mod;
printf("%lld\n",ans);
}
}
}
P5356 [Ynoi Easy Round 2017] 由乃打扑克
题意
给定长度为 \(n\) 的序列 \(a_i\) ,需要支持 \(m\) 次操作:
- 将 \([l,r]\) 区间中的数加 \(k\) 。
- 查询 \([l,r]\) 中第 \(k\) 小的数。
解法
前言
一开始看到这道题我还以为是树套树板子,结果发现是假的。
正片
考虑分块,设块长为 \(B\) 。
我们将原数组复制一份,在块内排序。
修改
对于整块,直接打标记即可,对于散块,直接暴力修改,暴力重新排序。
复杂度 \(O(\frac{n}{B} + B\log B)\) 。
查询
考虑二分答案,设当前二分的中点为 \(mid\) ,只需求有几个数比 \(mid\) 小。
对于整块,直接二分即可,对于散块,直接暴力求。
复杂度 \(O(B + \frac{n\log V}{B})\) 。
其中,\(B\) 取 \(\sqrt{n}\log n\) 时复杂度最优为 \(O(q\sqrt{n}\log n)\) ,但在实际实现时,要取小一点。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+100,s=200;
const ll inf=1e12+100;
ll a[N],b[N],tag[N];
int n,q,B,id[N],bl[N],br[N];
void update1(int l,int r,int k){
for(int i=l;i<=r;i++)a[i]+=k;
for(int i=bl[id[l]];i<=br[id[r]];i++)b[i]=a[i];
sort(b+bl[id[l]],b+br[id[l]]+1);
}
void update(int l,int r,int k){
if(id[l]==id[r]){
update1(l,r,k);
return;
}
update1(l,br[id[l]],k);
for(int i=id[l]+1;i<=id[r]-1;i++)tag[i]+=k;
update1(bl[id[r]],r,k);
}
int ch(int l,int r,int x){
int num=0;
if(id[l]==id[r]){
for(int i=l;i<=r;i++)if(a[i]+tag[id[l]]<=x)num++;
return num;
}
for(int i=l;i<=br[id[l]];i++)if((a[i]+tag[id[l]])<=x)num++;
for(int i=id[l]+1;i<=id[r]-1;i++){
if(b[bl[i]]+tag[i]>x)continue;
if(b[br[i]]+tag[i]<=x){
num+=br[i]-bl[i]+1;
continue;
}
int L=bl[i],R=br[i];
while(L<R){
int mid=(L+R)/2+1;
if(b[mid]+tag[i]<=x)L=mid;
else R=mid-1;
}
if(b[L]+tag[i]<=x)num+=L-bl[i]+1;
}
for(int i=bl[id[r]];i<=r;i++)if((a[i]+tag[id[r]])<=x)num++;
return num;
}
ll getmax(int l,int r){
ll res=-inf;
if(id[l]==id[r]){
for(int i=l;i<=r;i++)res=max(res,a[i]+tag[id[l]]);
return res;
}
for(int i=l;i<=br[id[l]];i++)res=max(res,a[i]+tag[id[l]]);
for(int i=id[l]+1;i<=id[r]-1;i++)res=max(res,b[br[i]]+tag[i]);
for(int i=bl[id[r]];i<=r;i++)res=max(res,a[i]+tag[id[r]]);
return res;
}
ll getmin(int l,int r){
ll res=inf;
if(id[l]==id[r]){
for(int i=l;i<=r;i++)res=min(res,a[i]+tag[id[l]]);
return res;
}
for(int i=l;i<=br[id[l]];i++)res=min(res,a[i]+tag[id[l]]);
for(int i=id[l]+1;i<=id[r]-1;i++)res=min(res,b[bl[i]]+tag[i]);
for(int i=bl[id[r]];i<=r;i++)res=min(res,a[i]+tag[id[r]]);
return res;
}
ll query(int L,int R,int k){
if(k<1||k>R-L+1)return -1;
ll l=getmin(L,R),r=getmax(L,R),ans=-1;
while(l<=r){
int mid=(l+r)/2;
if(ch(L,R,mid)<k){
l=mid+1;
}else{
r=mid-1;
ans=mid;
}
}
return ans;
}
int main(){
scanf("%d %d",&n,&q);
for(int i=1;i<=n;i++){scanf("%lld",&a[i]);b[i]=a[i];}
B=(n-1)/s+1;
for(int i=1;i<=n;i++)id[i]=(i-1)/s+1;
for(int i=1;i<=n;i++){
if(id[i]!=id[i-1])bl[id[i]]=i;
if(id[i]!=id[i+1])br[id[i]]=i;
}
for(int i=1;i<=B;i++)sort(b+bl[i],b+br[i]+1);
while(q--){
int op,l,r,k;
scanf("%d %d %d %d",&op,&l,&r,&k);
if(op==1){
printf("%lld\n",query(l,r,k));
}else{
update(l,r,k);
}
}
}

浙公网安备 33010602011771号