多校联训 DS 专题
CF1039D You Are Given a Tree
容易发现,当 \(k\) 不断增大时,答案不断减小,且 \(k\) 的答案不超过 \(\lfloor\frac {n}{k}\rfloor\) ,因此不同的答案个数是 \(\sqrt n\) 级别的,可以用一种类似整体二分的方式求解。
对于一个 \(k\) ,从叶子节点贪心向上匹配即可得到答案。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
int ver[200005],ne[200005],head[100005],cnt;
inline void link(int x,int y){
ver[++cnt]=y;
ne[cnt]=head[x];
head[x]=cnt;
}
int fa[100005];
vector<int> dfn;
void dfs(int x,int fi){
fa[x]=fi;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fi)continue;
dfs(u,x);
}dfn.push_back(x);
}
int dp[100005];
inline int Get(int mid){
for(int i=1;i<=n;i++)dp[i]=1;
int res=0;
for(auto it:dfn){
if(dp[it]==-1||dp[fa[it]]==-1)continue;
if(dp[it]+dp[fa[it]]>=mid)res++,dp[fa[it]]=-1;
else dp[fa[it]]=max(dp[fa[it]],dp[it]+1);
}
return res;
}
void solve(int l=2,int r=n,int ql=Get(2),int qr=Get(n)){
if(l>r||ql==qr){
for(int i=l;i<=r;i++)printf("%d\n",ql);
return ;
}
int mid=(l+r)>>1,tmpl=Get(mid),tmpr=Get(mid+1);
solve(l,mid,ql,tmpl);solve(mid+1,r,tmpr,qr);
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int x,y;scanf("%d%d",&x,&y);
link(x,y);link(y,x);
}
dfs(1,0);dp[0]=-1;
printf("%d\n",n);solve();
return 0;
}
CF983E NN country
考虑倍增,设 \(up[i][x]\) 表示从 \(x\) 点开始经过 \(2^i\) 条路线可以到达的最靠上的点,还需要讨论是否有一条路径能跨过 \(\text{lca}\) ,这是一个 \(\text{dfs}\) 序上的二维数点问题。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,q;
int ver[400005],ne[400005],head[200005],tot;
inline void link(int x,int y){
ver[++tot]=y;
ne[tot]=head[x];
head[x]=tot;
}
int fa[19][200005],dep[200005],dfn[200005],cnt,siz[200005];
void dfs1(int x,int fi){
fa[0][x]=fi;dep[x]=dep[fi]+1;dfn[x]=++cnt;siz[x]=1;
for(int i=1;i<19;i++)fa[i][x]=fa[i-1][fa[i-1][x]];
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fi)continue;
dfs1(u,x);siz[x]+=siz[u];
}
}
inline int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=18;~i;i--)if(dep[fa[i][x]]>=dep[y])x=fa[i][x];
if(x==y)return x;
for(int i=18;~i;i--){
if(fa[i][x]!=fa[i][y])x=fa[i][x],y=fa[i][y];
}
return fa[0][x];
}
int up[19][200005];
inline int cmp(int x,int y){
return dep[x]<dep[y]?x:y;
}
void dfs2(int x,int fi){
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fi)continue;
dfs2(u,x);up[0][x]=cmp(up[0][x],up[0][u]);
}
}
vector<int> vec[200005];
namespace Seg{
int tree[15000005],le[15000005],ri[15000005],cnt,rt[200005];
void insert(int loc,int &i,int old,int l=1,int r=n){
if(loc<l||loc>r)return ;
i=++cnt;tree[i]=tree[old]+1;le[i]=le[old];ri[i]=ri[old];
if(l==r)return ;
int mid=(l+r)>>1;
insert(loc,le[i],le[old],l,mid);insert(loc,ri[i],ri[old],mid+1,r);
}
int query(int fr,int to,int a,int b,int l=1,int r=n){
if(fr>r||to<l)return 0;
if(fr<=l&&to>=r)return tree[b]-tree[a];
int mid=(l+r)>>1;
return query(fr,to,le[a],le[b],l,mid)+query(fr,to,ri[a],ri[b],mid+1,r);
}
}
inline int solve(int x,int y){
int lc=lca(x,y),res=0;
for(int i=18;~i;i--)if(up[i][x]&&dep[up[i][x]]>dep[lc])res+=(1<<i),x=up[i][x];
for(int i=18;~i;i--)if(up[i][y]&&dep[up[i][y]]>dep[lc])res+=(1<<i),y=up[i][y];
if(x!=lc&&dep[up[0][x]]>dep[lc])return -1;
if(y!=lc&&dep[up[0][y]]>dep[lc])return -1;
return res+(lc==x||lc==y||Seg::query(dfn[x],dfn[x]+siz[x]-1,Seg::rt[dfn[y]-1],Seg::rt[dfn[y]+siz[y]-1])?1:2);
}
int main(){
scanf("%d",&n);
for(int i=2;i<=n;i++){
int x;scanf("%d",&x);
link(x,i);
}
dfs1(1,1);dep[0]=n;
scanf("%d",&m);
for(int i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
int lc=lca(x,y);vec[dfn[x]].push_back(dfn[y]);vec[dfn[y]].push_back(dfn[x]);
up[0][x]=cmp(up[0][x],lc);
up[0][y]=cmp(up[0][y],lc);
}
for(int i=1;i<=n;i++){
Seg::rt[i]=Seg::rt[i-1];
for(auto it:vec[i])Seg::insert(it,Seg::rt[i],Seg::rt[i]);
}
dfs2(1,1);
for(int i=1;i<19;i++){
for(int j=1;j<=n;j++)up[i][j]=up[i-1][up[i-1][j]];
}
scanf("%d",&q);
while(q--){
int x,y;scanf("%d%d",&x,&y);
printf("%d\n",solve(x,y));
}
return 0;
}
[AGC001F] Wide Swap
考虑原排列的逆变换,条件变为对于相邻两个数 \(Q_i,Q_j\) ,如果 \(|Q_i-Q_j|\ge k\) ,这两个数可以发生交换,要求数字 \(1\) 所在的位置尽量靠前。在此基础上,数字 \(2\) 所在的位置尽量靠前 ... 这样看起来就舒服多了。
发现对于所有满足 \(|u - v| < k\) 的 \((u, v)\),它们在 \(Q\) 中出现的相对位置被固定了。
考虑拓扑排序,将所有边反向,每次取队列中最大的元素倒着填充拓扑序,直接做是 \(O(n^2)\) 的,线段树优化即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,k;
int P[500005];
pair<int,int> tree[2000005];
void build(int l=1,int r=n,int i=1){
if(l==r){
tree[i]=make_pair(P[l],l);return ;
}
int mid=(l+r)>>1;
build(l,mid,i<<1);build(mid+1,r,i<<1|1);
tree[i]=max(tree[i<<1],tree[i<<1|1]);
}
void del(int loc,int l=1,int r=n,int i=1){
if(loc<l||loc>r)return ;
if(l==r){
tree[i]=make_pair(0,0);return ;
}
int mid=(l+r)>>1;
del(loc,l,mid,i<<1);del(loc,mid+1,r,i<<1|1);
tree[i]=max(tree[i<<1],tree[i<<1|1]);
}
pair<int,int> query(int fr,int to,int l=1,int r=n,int i=1){
if(fr>r||to<l)return make_pair(0,0);
if(fr<=l&&to>=r)return tree[i];
int mid=(l+r)>>1;
return max(query(fr,to,l,mid,i<<1),query(fr,to,mid+1,r,i<<1|1));
}
priority_queue<int> q;
int ans[500005];
bool vis[500005];
inline bool check(int x){
if(vis[x])return 0;
return query(x-k+1,x+k-1).second==x;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&P[i]);
build();
for(int i=1;i<=n;i++)if(check(i))vis[i]=1,q.push(i);
for(int i=n;i;i--){
int x=q.top();q.pop();
ans[x]=i;del(x);
int tmp=query(x-k+1,x-1).second;
if(tmp&&check(tmp))vis[tmp]=1,q.push(tmp);
tmp=query(x+1,x+k-1).second;
if(tmp&&check(tmp))vis[tmp]=1,q.push(tmp);
}
for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
return 0;
}
CF1578B Building Forest Trails
断环为链,几个点联通的条件是线段有交,对于每个点,维护其同一个集合内的前驱和后继,加入一条边时合并两个集合,然后检查这个区间内有没有点的前驱或后继伸到区间外面即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
namespace Seg1{
pair<int,int> tree[800005];
void build(int l=1,int r=n,int i=1){
if(l==r){
tree[i]=make_pair(l,l);
return ;
}
int mid=(l+r)>>1;
build(l,mid,i<<1);build(mid+1,r,i<<1|1);
tree[i]=max(tree[i<<1],tree[i<<1|1]);
}
pair<int,int> query(int fr,int to,int l=1,int r=n,int i=1){
if(fr>r||to<l)return make_pair(0,0);
if(fr<=l&&to>=r)return tree[i];
int mid=(l+r)>>1;
return max(query(fr,to,l,mid,i<<1),query(fr,to,mid+1,r,i<<1|1));
}
void update(int loc,int v,int l=1,int r=n,int i=1){
if(loc<l||loc>r)return ;
if(l==r){
tree[i]=make_pair(v,l);
return ;
}
int mid=(l+r)>>1;
update(loc,v,l,mid,i<<1);update(loc,v,mid+1,r,i<<1|1);
tree[i]=max(tree[i<<1],tree[i<<1|1]);
}
}
namespace Seg2{
pair<int,int> tree[800005];
void build(int l=1,int r=n,int i=1){
if(l==r){
tree[i]=make_pair(l,l);
return ;
}
int mid=(l+r)>>1;
build(l,mid,i<<1);build(mid+1,r,i<<1|1);
tree[i]=min(tree[i<<1],tree[i<<1|1]);
}
pair<int,int> query(int fr,int to,int l=1,int r=n,int i=1){
if(fr>r||to<l)return make_pair(1e9,1e9);
if(fr<=l&&to>=r)return tree[i];
int mid=(l+r)>>1;
return min(query(fr,to,l,mid,i<<1),query(fr,to,mid+1,r,i<<1|1));
}
void update(int loc,int v,int l=1,int r=n,int i=1){
if(loc<l||loc>r)return ;
if(l==r){
tree[i]=make_pair(v,l);
return ;
}
int mid=(l+r)>>1;
update(loc,v,l,mid,i<<1);update(loc,v,mid+1,r,i<<1|1);
tree[i]=min(tree[i<<1],tree[i<<1|1]);
}
}
set<int> s[200005];
int dsu[200005];
inline void insert(int v,int x){
auto it=s[x].insert(v).first;
if(it!=s[x].begin()){
auto L=it;--L;
Seg1::update(*L,v);Seg2::update(v,*L);
}
else Seg2::update(v,v);
auto R=it;++R;
if(R!=s[x].end())Seg1::update(v,*R),Seg2::update(*R,v);
else Seg1::update(v,v);dsu[v]=x;
}
inline void merge(int x,int y){
x=dsu[x];y=dsu[y];if(x==y)return ;
if(s[x].size()>s[y].size())swap(x,y);
for(auto it:s[x])insert(it,y);
}
inline void cover(int l,int r){
if(l>r)swap(l,r);
merge(l,r);auto it=Seg1::query(l,r-1);
while(it.first>r){
merge(it.second,r);
it=Seg1::query(l,r-1);
}
it=Seg2::query(l+1,r);
while(it.first<l){
merge(it.second,l);
it=Seg2::query(l+1,r);
}
}
int main(){
scanf("%d%d",&n,&m);
Seg1::build();Seg2::build();
for(int i=1;i<=n;i++)dsu[i]=i;
for(int i=1;i<=n;i++)s[i].insert(i);
while(m--){
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
if(op==1)cover(x,y);
else putchar(dsu[x]==dsu[y]?'1':'0');
}
return 0;
}
CF1129D Isolation
考虑暴力 \(dp\) 是 \(O(n^2)\) 的,线段树不太容易做,分块即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,k,s=500;
int a[100005],pre[100005],nxt[100005];
const int md=998244353;
int dp[100005],tmp[205][100005],high[100005],sum[205],tag[205];
int le[100005],ri[100005],sqr[100005];
inline void build(int x){
for(int i=le[x];i<=ri[x];i++)tmp[x][high[i]]=0;
for(int i=le[x];i<=ri[x];i++)high[i]=high[i]+tag[x];tag[x]=0;sum[x]=0;
for(int i=le[x];i<=ri[x];i++){
if(high[i]<=k)sum[x]=(sum[x]+dp[i])%md;
tmp[x][high[i]]=(tmp[x][high[i]]+dp[i])%md;
}
}
inline void update(int l,int r,int v){
for(int i=l;i<=r&&i<=ri[sqr[l]];i++){
tmp[sqr[i]][high[i]]=(tmp[sqr[i]][high[i]]-dp[i])%md;
if(high[i]+tag[sqr[i]]<=k)sum[sqr[i]]=(sum[sqr[i]]-dp[i])%md;
high[i]+=v;
tmp[sqr[i]][high[i]]=(tmp[sqr[i]][high[i]]+dp[i])%md;
if(high[i]+tag[sqr[i]]<=k)sum[sqr[i]]=(sum[sqr[i]]+dp[i])%md;
}
if(sqr[l]==sqr[r])return ;
for(int i=sqr[l]+1;i<sqr[r];i++){
if(~v&&k>=tag[i])sum[i]=(sum[i]-tmp[i][k-tag[i]])%md;
else if(v==-1&&k>=tag[i]-1)sum[i]=(sum[i]+tmp[i][k-tag[i]+1])%md;
tag[i]+=v;
}
for(int i=le[sqr[r]];i<=r;i++){
tmp[sqr[i]][high[i]]=(tmp[sqr[i]][high[i]]-dp[i])%md;
if(high[i]+tag[sqr[i]]<=k)sum[sqr[i]]=(sum[sqr[i]]-dp[i])%md;
high[i]+=v;
tmp[sqr[i]][high[i]]=(tmp[sqr[i]][high[i]]+dp[i])%md;
if(high[i]+tag[sqr[i]]<=k)sum[sqr[i]]=(sum[sqr[i]]+dp[i])%md;
}
}
inline int query(int l,int r){
int res=0;
for(int i=l;i<=r&&i<=ri[sqr[l]];i++)if(high[i]+tag[sqr[i]]<=k)res=(res+dp[i])%md;
if(sqr[l]==sqr[r])return res;
for(int i=sqr[l]+1;i<sqr[r];i++)res=(res+sum[i])%md;
for(int i=le[sqr[r]];i<=r;i++)if(high[i]+tag[sqr[i]]<=k)res=(res+dp[i])%md;
return res;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=0;i<=n;i++)sqr[i]=i/s+1;dp[0]=1;
for(int i=0;i<=n;i++)ri[sqr[i]]=i;
for(int i=n;~i;i--)le[sqr[i]]=i;
for(int i=1;i<=n;i++)pre[i]=1;
for(int i=1;i<=n;i++){
if(!nxt[a[i]])update(0,i-1,1),nxt[a[i]]=i;
else {
update(pre[a[i]]-1,nxt[a[i]]-1,-1);pre[a[i]]=nxt[a[i]]+1;nxt[a[i]]=i;
update(pre[a[i]]-1,nxt[a[i]]-1,1);
}
dp[i]=query(0,i-1);
tmp[sqr[i]][high[i]]=(tmp[sqr[i]][high[i]]+dp[i])%md;
if(high[i]+tag[sqr[i]]<=k)sum[sqr[i]]=(sum[sqr[i]]+dp[i])%md;
if(i==ri[sqr[i]])build(sqr[i]);
}
printf("%d",(dp[n]+md)%md);
return 0;
}
[AGC015E] Mr.Aoki Incubator
将点按 \(X\) 排序,容易发现可以给一个点染色的是一个区间,线段树优化 \(dp\) 即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int md=1e9+7;
struct dong{
int x,v,id;
}a[200005];
struct suan{
int l,r;
friend bool operator <(suan a,suan b){
return a.r<b.r||a.r==b.r&&a.l<b.l;
}
}b[200005];
int tree[800005],le[200005],ri[200005],f[200005];
int l,t,n,m,mi,mx,ans;
inline bool cmp(dong a,dong b){
return a.v<b.v;
}
inline bool cmp2(dong a,dong b){
return a.x<b.x;
}
void change(int i,int l,int r,int a,int b){
if(l==r){
tree[i]=(tree[i]+b)%md;
return;
}
int mid=(l+r)>>1;
if(a<=mid)change(i<<1,l,mid,a,b);else change(i<<1|1,mid+1,r,a,b);
tree[i]=(tree[i<<1]+tree[i<<1|1])%md;
}
int query(int i,int l,int r,int a,int b){
if(l==a&&r==b)return tree[i];
int mid=(l+r)>>1;
if(b<=mid)return query(i<<1,l,mid,a,b);
else if(a>mid)return query(i<<1|1,mid+1,r,a,b);
else return (query(i<<1,l,mid,a,mid)+query(i<<1|1,mid+1,r,mid+1,b))%md;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i].x,&a[i].v);
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)a[i].id=i;
sort(a+1,a+n+1,cmp2);
mi=n+1;
for(int i=n;i;i--){
mi=min(mi,a[i].id);
le[a[i].id]=mi;
}
mx=0;
for(int i=1;i<=n;i++){
mx=max(mx,a[i].id);
ri[a[i].id]=mx;
}
for(int i=1;i<=n;i++)b[i].l=le[i],b[i].r=ri[i];
sort(b+1,b+n+1);
for(int i=1;i<=n;i++){
if(b[i].l==1)f[i]++;
t=max(b[i].l-1,1);
f[i]=(f[i]+query(1,1,n,t,b[i].r))%md;
change(1,1,n,b[i].r,f[i]);
if(b[i].r==n)ans=(ans+f[i])%md;
}
printf("%d\n",ans);
return 0;
}
[AGC007E] Shik and Travel
考虑二分,发现对于一棵子树,可以伸出来两条路径,然后将两个儿子的四条路径两条匹配,两条留下,其中匹配长度之和不能超过 \(\text{mid}\) ,可以用 \(\text{map}\) 维护,匹配时直接 \(\text{upper_bound}\) 是 \(O(n\log^3 n)\) 的,双指针可以做到 \(O(n\log^2 n)\) ,实际运行时差别不大。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
int le[200005],ri[200005],val[200005];
map<long long,long long> mp[200005];
long long mid;
void dfs(int x){
if(!le[x]&&!ri[x]){
mp[x].clear();mp[x][val[x]]=val[x];
return ;
}
int L=le[x],R=ri[x];
dfs(L);dfs(R);
if(mp[L].size()>mp[R].size())swap(L,R);
vector<pair<long long,long long> > tmp;
for(auto it:mp[L]){
auto it1=mp[R].upper_bound(mid-it.second);
if(it1==mp[R].begin())continue;else it1--;
tmp.push_back(make_pair(it.first,it1->second));
tmp.push_back(make_pair(it1->second,it.first));
}
sort(tmp.begin(),tmp.end());
mp[x].clear();long long mn=1e18;
for(int i=0;i<tmp.size();i++){
if(mn>=tmp[i].second)mp[x][tmp[i].first+val[x]]=tmp[i].second+val[x],mn=tmp[i].second;
}
// cout<<"check "<<mid<<" "<<x<<endl;
// for(auto it:mp[x])cout<<it.first<<" "<<it.second<<endl;
}
int main(){
scanf("%d",&n);
for(int i=2;i<=n;i++){
int x;scanf("%d%d",&x,&val[i]);
if(le[x])ri[x]=i;else le[x]=i;
}
long long l=0,r=1e9,ans=1e9;
while(l<=r){
mid=(l+r)>>1;
dfs(1);
if(mp[1].size())ans=mid,r=mid-1;
else l=mid+1;
}
printf("%lld",ans);
return 0;
}
CF1446D2 Frequency Problem (Hard Version)
发现最大区间的两个众数中一定有一个是整个序列的众数,可以枚举另一个数做到 \(O(n|a|)\) ,考虑根号分治对于出现次数大于 \(\sqrt n\) 的数暴力做,对于小于 \(\sqrt n\) 的数枚举答案即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,s=500;
int a[200005],tot[200005],pre[400005];
int ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)tot[a[i]]++;
int Tmp=0;
for(int i=1;i<=n;i++)if(tot[i]>tot[Tmp])Tmp=i;
for(int t=1;t<=n;t++){
if(tot[t]<=s||tot[t]==Tmp)continue;
for(int i=0;i<=2*n;i++)pre[i]=0;
int cnt=0;
for(int i=1;i<=n;i++){
if(a[i]==Tmp)cnt++;
else if(a[i]==t)cnt--;
if(pre[cnt+n]||!cnt)ans=max(ans,i-pre[n+cnt]);
else pre[cnt+n]=i;
}
}
for(int t=1;t<=s;t++){
int cnt=0,L=0;
for(int i=1;i<=n;i++)tot[i]=0;
for(int i=1;i<=n;i++){
tot[a[i]]++;
if(tot[a[i]]==t)cnt++;
while(tot[a[i]]>t){
if(tot[a[L+1]]==t)cnt--;
tot[a[++L]]--;
}
if(cnt>=2)ans=max(ans,i-L);
}
}
printf("%d\n",ans);
return 0;
}
CF765F Souvenirs
考虑扫描线,先递归右儿子,再递归左儿子,如果在当前区间得到的答案比之前更新过的区间劣,直接退出即可,容易发现答案最多发生 \(\log a\) 次变化,时间复杂度 \(O(n\log n\log a)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[100005];
struct qry{
int l,r,id;
inline bool operator <(const qry &b)const{
return r<b.r;
}
}q[300005];
vector<int> vec[400005];
int ans[300005];
int tree[400005],res=1e9;
void build(int l=1,int r=n,int i=1){
for(int j=l;j<=r;j++)vec[i].push_back(a[j]);
sort(vec[i].begin(),vec[i].end());
tree[i]=1e9;
if(l==r)return ;
int mid=(l+r)>>1;
build(l,mid,i<<1);
build(mid+1,r,i<<1|1);
}
void update(int loc,int id,int l=1,int r=n,int i=1){
if(loc>r){
vector<int>::iterator it=lower_bound(vec[i].begin(),vec[i].end(),a[id]);
if(it!=vec[i].end()){
tree[i]=min(tree[i],abs(*it-a[id]));
}
if(it!=vec[i].begin()){
it--;
tree[i]=min(tree[i],abs(*it-a[id]));
}
// printf("%d %d %d\n",l,r,tree[i]);
}
if(loc<=l)return ;
if(tree[i]>=res||l==r)return ;
int mid=(l+r)>>1;
update(loc,id,mid+1,r,i<<1|1);
update(loc,id,l,mid,i<<1);
tree[i]=min(tree[i<<1],tree[i<<1|1]);
res=min(res,tree[i]);
}
int query(int fr,int l=1,int r=n,int i=1){
if(r<fr)return 1e9;
if(l>=fr)return tree[i];
int mid=(l+r)>>1;
return min(query(fr,l,mid,i<<1),query(fr,mid+1,r,i<<1|1));
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
build();
scanf("%d",&m);
for(int i=0;i<m;i++){
scanf("%d%d",&q[i].l,&q[i].r);q[i].id=i;
}
sort(q,q+m);
for(int i=1,j=0;i<=n;i++){
res=2e9;
// cout<<i<<"\n";
update(i,i);
// puts("");
while(q[j].r<=i&&j<m){
ans[q[j].id]=query(q[j].l);j++;
}
}
for(int i=0;i<m;i++)printf("%d\n",ans[i]);
return 0;
}
CF1458D Flip and Reverse
考虑记 \(0\) 为 \(-1\),\(1\) 为 \(+1\),这样可以得到一个长度为 \(|s|\) 的由 \(+1\) 和 \(-1\) 组成的序列。
然后对这个序列做一遍前缀和,并连一条 \(s_i\to s_{i+1}\) 的有向边,这样可以得到一张图,一个欧拉回路就对应着一个字符串。
考虑题目中那个奇怪的操作的本质。假设我们对区间 \([l,r]\) 进行操作。既然 \([l,r]\) 要求 \(01\) 个数相等,那么肯定有 \(s_{l-1}=s_r\) ,而翻转+反转实际上等于将这些边反向。所以实际上该操作等价于选择一个环然后将环上所有边反向。
原图任意一条欧拉回路(起点和终点必须与初始相同)代表的都可以由原字符串进行一系列操作得到,就像 \(\text{ETT}\) 那样通过翻转交换两个区间就好了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
char s[500005];
int tot[1000005][2];
inline void solve(){
scanf("%s",s+1);n=strlen(s+1);
int cnt=n;
for(int i=1;i<=n;i++){
tot[cnt][s[i]-'0']++;
if(s[i]=='0')cnt--;else cnt++;
}
cnt=n;
for(int i=1;i<=n;i++){
if(tot[cnt][0]&&tot[cnt-1][1])putchar('0'),tot[cnt--][0]--;
else if(tot[cnt][1])putchar('1'),tot[cnt++][1]--;
else putchar('0'),tot[cnt--][0]--;
}
puts("");
}
int main(){
scanf("%d",&T);
while(T--)solve();
return 0;
}
【UR #22】月球列车
发现我们需要统计的只是每一位 \(1\) 的个数的奇偶性,容易发现对每一位排序后,每一位会发生进位的是一段后缀,可以二分做到 \(O(n\log a\log n)\) ,发现每一位只有加 \(0\) 或 \(1\) 两种可能,可以通过分类讨论代替二分,时间复杂度 \(O(n\log a)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,t;
long long a[250005];
int nxt[64][250005][2],tmp[250005],stk[250005];
inline long long solve(long long x){
int lim=n;long long res=0;
for(int i=0;i<=60;i++){
int cnt=nxt[i][lim][1]-nxt[i][lim][0];
if((x>>i)&1)cnt=n-cnt,lim=nxt[i][lim][0];
else lim=nxt[i][lim][1];
if(cnt&1)res+=(1ll<<i);
}
return res;
}
int main(){
scanf("%d%d%d",&n,&m,&t);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)tmp[i]=i;
for(int t=0;t<=60;t++){
int cnt=0;
nxt[t][0][0]=cnt;
for(int i=1;i<=n;i++){
if(((a[tmp[i]]>>t)&1)==0)stk[++cnt]=tmp[i];
nxt[t][i][0]=cnt;
}
nxt[t][0][1]=cnt;
for(int i=1;i<=n;i++){
if((a[tmp[i]]>>t)&1)stk[++cnt]=tmp[i];
nxt[t][i][1]=cnt;
}
for(int i=1;i<=n;i++)tmp[i]=stk[i];
}
long long lstans=0;
while(m--){
long long x;scanf("%lld",&x);
x^=t*(lstans>>20);
printf("%lld\n",lstans=solve(x));
}
return 0;
}
GYM 103371M Yet Another Range Query Problem
CF643G Choosing Ads
如果 \(p=50+\varepsilon\) 会怎么样?
这便是一个经典问题:长度为 \(n\) 的数列中存在一个出现次数大于 \(n / 2\) 的数,设计一个算法找到它。
只要每次删除两个不同的数,最后留下的那个数(或那些数,但这些数全部相同)就是要求的答案。
原理是,如果一个数出现了 \(a\) 次,其中 \(a > n - a\),则两边都减去 \(1\),仍有 \(a - 1 > n - a - 1 = (n - 2) - (a - 1)\) 。
对于拓展情况我们如法炮制,令 \(\displaystyle k = \left\lfloor \frac{100}{p} \right\rfloor\) 。每次删除 \(k + 1\) 个数,则对的数一定会留下来。
因为 \(k\) 最大就是 \(5\),建立一棵线段树,使用每次 \(\mathcal O (k^2)\) 的时间合并区间,不难维护答案。
时间复杂度为 \(\mathcal O (n k^2 \log n)\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,p;
inline bool cmp(pair<int,int> x,pair<int,int> y){
return x.second>y.second;
}
struct node : vector<pair<int,int> >{
inline node operator +(node b){
for(auto x:*this){
bool is=0;
for(auto &y:b)if(y.first==x.first)is=1,y.second+=x.second;
if(!is)b.push_back(x);
}
sort(b.begin(),b.end(),cmp);
while(b.size()>p){
for(auto &x:b)x.second-=b.back().second;
b.pop_back();
}return b;
}
}tree[600005];
int a[150005];
void build(int l=1,int r=n,int i=1){
if(l==r){
tree[i].push_back(make_pair(a[l],1));
return ;
}
int mid=(l+r)>>1;
build(l,mid,i<<1);build(mid+1,r,i<<1|1);
tree[i]=tree[i<<1]+tree[i<<1|1];
}
int lazy[600005];
inline void solve(int i,int l,int r,int x){
tree[i].clear();tree[i].push_back(make_pair(x,r-l+1));
lazy[i]=x;
}
inline void push(int i,int l,int r){
int mid=(l+r)>>1;
solve(i<<1,l,mid,lazy[i]);solve(i<<1|1,mid+1,r,lazy[i]);
lazy[i]=0;
}
void update(int fr,int to,int v,int l=1,int r=n,int i=1){
if(fr>r||to<l)return ;
if(fr<=l&&to>=r){
solve(i,l,r,v);return ;
}
int mid=(l+r)>>1;
if(lazy[i])push(i,l,r);
update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
tree[i]=tree[i<<1]+tree[i<<1|1];
}
node query(int fr,int to,int l=1,int r=n,int i=1){
if(fr>r||to<l)return node();
if(fr<=l&&to>=r)return tree[i];
int mid=(l+r)>>1;
if(lazy[i])push(i,l,r);
return query(fr,to,l,mid,i<<1)+query(fr,to,mid+1,r,i<<1|1);
}
int main(){
scanf("%d%d%d",&n,&m,&p);p=100/p;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
build();
while(m--){
int op,l,r,x;
scanf("%d%d%d",&op,&l,&r);
if(op==1){
scanf("%d",&x);update(l,r,x);
}
else {
node tmp=query(l,r);
printf("%ld\n",tmp.size());
for(auto x:tmp)printf("%d ",x.first);puts("");
}
}
return 0;
}
CF1209G2 Into Blocks (hard version)
考虑把一个颜色 \(c\) 的出现区间化成左开右闭的形式 \([s_c,t_c)\) ,意思是对于区间中的每个 \(i\) ,要把 \(i,i+1\) 涂成一个颜色。那么可以发现如果对所有 \(c\) 的该区间进行区间 \(+1\) ,那么最后可以保留的颜色数就是相邻两个 \(0\) 之间出现最多的颜色的数量和。
考虑用线段树快速维护这个东西。考虑需要维护什么信息:
需要维护分界点。即区间中被覆盖次数为 \(0\) 的点。
需要维护颜色数。即区间中出现次数最多的颜色。
但是 \(2\) 中有要求,要求必须是连续段中出现次数最多的颜色。而这个连续段的「连续」对应于一段连续的、覆盖次数 \(>0\) 的段。
注意到要维护区间中出现次数的最大值,还要求连续且只有全局询问的话,有个小技巧。考虑让颜色 \(c\) 的出现次数 \(e(c)\) 放到 \(s_c\) 处维护,记作 \(w_{s_c}\) ,这样就只需要维护一个 \(\max \{w_i,w_{i+1}\}\) 状物。
发现难以维护分界点?考虑到只有全局询问,于是可以用维护「区间最小值」来代替维护「区间分界点」。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
int tag[200005];
set<int> col[200005];
int mx[800005],mn[800005],lx[800005],rx[800005],tree[800005];
inline void pushup(int i){
mn[i]=min(mn[i<<1],mn[i<<1|1]);mx[i]=max(mx[i<<1],mx[i<<1|1]);
if(mn[i<<1]<mn[i<<1|1]){
lx[i]=lx[i<<1];rx[i]=max(rx[i<<1],mx[i<<1|1]);
tree[i]=tree[i<<1];
}
else if(mn[i<<1]>mn[i<<1|1]){
lx[i]=max(lx[i<<1|1],mx[i<<1]);rx[i]=rx[i<<1|1];
tree[i]=tree[i<<1|1];
}
else {
lx[i]=lx[i<<1];rx[i]=rx[i<<1|1];
tree[i]=tree[i<<1]+max(rx[i<<1],lx[i<<1|1])+tree[i<<1|1];
}
}
int lazy[800005];
inline void push(int i){
mn[i<<1]+=lazy[i];lazy[i<<1]+=lazy[i];
mn[i<<1|1]+=lazy[i];lazy[i<<1|1]+=lazy[i];
lazy[i]=0;
}
void update(int fr,int to,int v,int l=1,int r=n,int i=1){
if(fr>r||to<l)return ;
if(fr<=l&&to>=r){
mn[i]+=v;lazy[i]+=v;
return ;
}
if(lazy[i])push(i);
int mid=(l+r)>>1;
update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
pushup(i);
}
void insert(int loc,int v,int l=1,int r=n,int i=1){
if(loc<l||loc>r)return ;
if(l==r){
mx[i]=lx[i]=v;
return ;
}
if(lazy[i])push(i);
int mid=(l+r)>>1;
insert(loc,v,l,mid,i<<1);insert(loc,v,mid+1,r,i<<1|1);
pushup(i);
}
inline void add(int x){
if(col[x].empty())return ;
update(*col[x].begin(),*col[x].rbegin()-1,1);
insert(*col[x].begin(),col[x].size());
}
inline void del(int x){
if(col[x].empty())return ;
update(*col[x].begin(),*col[x].rbegin()-1,-1);
insert(*col[x].begin(),0);
}
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){
scanf("%d",&tag[i]);
col[tag[i]].insert(i);
}
for(int i=1;i<=2e5;i++)add(i);
printf("%d\n",n-lx[1]-tree[1]-rx[1]);
while(q--){
int x,y;scanf("%d%d",&x,&y);
del(tag[x]);col[tag[x]].erase(x);add(tag[x]);
tag[x]=y;
del(tag[x]);col[tag[x]].insert(x);add(tag[x]);
printf("%d\n",n-lx[1]-tree[1]-rx[1]);
}
return 0;
}
CF1270H Number of Components
考虑枚举分界点,将序列转化成 \(01\) 序列,一个分界点对应着一个前缀全是 \(1\) 后缀全是 \(0\) 的划分,考虑对于每个值维护 \(10\) 交界处的数量,维护有多少个值可以使得这个序列只有一个 \(10\) 即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
int tree[4000005],tot[4000005],lazy[4000005];
inline void pushup(int i){
tree[i]=min(tree[i<<1],tree[i<<1|1]);
if(tree[i<<1]<tree[i<<1|1])tot[i]=tot[i<<1];
else if(tree[i<<1]>tree[i<<1|1])tot[i]=tot[i<<1|1];
else tot[i]=tot[i<<1]+tot[i<<1|1];
}
inline void push(int i){
tree[i<<1]+=lazy[i];lazy[i<<1]+=lazy[i];
tree[i<<1|1]+=lazy[i];lazy[i<<1|1]+=lazy[i];
lazy[i]=0;
}
void insert(int loc,int v,int l=0,int r=1e6+1,int i=1){
if(loc<l||loc>r)return ;
if(l==r){
tot[i]+=v;
return ;
}
if(lazy[i])push(i);
int mid=(l+r)>>1;
insert(loc,v,l,mid,i<<1);insert(loc,v,mid+1,r,i<<1|1);
pushup(i);
}
void update(int fr,int to,int v,int l=0,int r=1e6+1,int i=1){
if(fr>r||to<l)return ;
if(fr<=l&&to>=r){
tree[i]+=v;lazy[i]+=v;
return ;
}
if(lazy[i])push(i);
int mid=(l+r)>>1;
update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
pushup(i);
}
int query(int fr,int to,int l=0,int r=1e6+1,int i=1){
if(fr>r||to<l)return 0;
if(fr<=l&&to>=r)return tree[i]==1?tot[i]:0;
if(lazy[i])push(i);
int mid=(l+r)>>1;
return query(fr,to,l,mid,i<<1)+query(fr,to,mid+1,r,i<<1|1);
}
int a[500005];
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
a[0]=1e6+1;for(int i=0;i<=n;i++)update(a[i+1],a[i]-1,1);
for(int i=0;i<=n+1;i++)insert(a[i],1);
while(q--){
int x,y;scanf("%d%d",&x,&y);
update(a[x+1],a[x]-1,-1);update(a[x],a[x-1]-1,-1);insert(a[x],-1);
a[x]=y;
update(a[x+1],a[x]-1,1);update(a[x],a[x-1]-1,1);insert(a[x],1);
printf("%d\n",query(1,1e6));
}
return 0;
}
【UR #19】前进四
容易想到一种类似于楼房重建的方式做到 \(O(n\log^2n)\) ,但是这是不够的,考虑单 \(\log\) 做法。
发现询问都为后缀,以时间为轴开线段树,倒序扫描序列,对于每个位置,每个数出现的时间都是一段区间,吉如一线段树维护每个位置被取 \(\min\) 的次数即可,时间复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
struct data{
int x,l,r;
data(int _x,int _l,int _r){
x=_x;l=_l;r=_r;
}
};
vector<data> vec[1000005];
vector<int> qry[1000005];
struct node{
int mx1,mx2;
node(){}
node(int _mx1,int _mx2){
mx1=_mx1;mx2=_mx2;
}
inline node operator +(const node &b)const{
if(mx1>b.mx1)return node(mx1,max(mx2,b.mx1));
if(mx1<b.mx1)return node(b.mx1,max(b.mx2,mx1));
return node(mx1,max(mx2,b.mx2));
}
}tree[4000005];
int lazy[4000005];
inline void push(int i){
tree[i<<1].mx1=min(tree[i<<1].mx1,tree[i].mx1);
tree[i<<1|1].mx1=min(tree[i<<1|1].mx1,tree[i].mx1);
}
void build(int l=0,int r=q,int i=1){
tree[i]=node(1e9,0);
if(l==r)return ;
int mid=(l+r)>>1;
build(l,mid,i<<1);build(mid+1,r,i<<1|1);
}
void update(int fr,int to,int v,int l=0,int r=q,int i=1){
if(fr>r||to<l||tree[i].mx1<=v)return ;
if(fr<=l&&to>=r&&tree[i].mx2<v){
lazy[i]++;tree[i].mx1=v;
return ;
}
int mid=(l+r)>>1;push(i);
update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
tree[i]=tree[i<<1]+tree[i<<1|1];
}
int query(int loc,int l=0,int r=q,int i=1){
if(loc<l||loc>r)return 0;
if(l==r)return lazy[i];
int mid=(l+r)>>1;push(i);
return lazy[i]+query(loc,l,mid,i<<1)+query(loc,mid+1,r,i<<1|1);
}
int ans[1000005];
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
vec[i].push_back(data(x,0,q));
}
for(int i=1;i<=q;i++){
int op,x,y;
scanf("%d",&op);
if(op==1){
scanf("%d%d",&x,&y);
vec[x].back().r=i-1;vec[x].push_back(data(y,i,q));
}
else if(op==2){
scanf("%d",&x);qry[x].push_back(i);
}
}
build();
for(int i=n;i;i--){
for(auto it:vec[i])update(it.l,it.r,it.x);
for(auto it:qry[i])ans[it]=query(it);
}
for(int i=1;i<=q;i++)if(ans[i])printf("%d\n",ans[i]);
return 0;
}
CF1558F Strange Sort
CF1137F Matches Are Not a Child's Play
不妨令权值最大的点为根,并设 \(a_x\) 为 \(x\) 子树中权值最大的点的权值。那么,容易发现删点顺序就是以 \(a_x\) 递增为第一关键字,\(x\) 的深度递减为第二关键字。
考虑一次 \(up(x)\) 操作的影响,由于 \(x\) 将变成最大的点,所以要以 \(x\) 作为新的根。设原来的根是 \(rt\),那么显然,只有 \(x\) 到 \(rt\) 这条链上面的点的 \(a_i\) 发生了变化,且对路径上除 \(x\) 外任意一点 \(y\) 有 \(a_y\leftarrow a_{rt}\) ,而 \(a_x \leftarrow a_{rt}+1\) 。
我们注意到操作相当于链赋值,并且 \(a_i\) 相等的点构成的连通块一定是链,所以每个 \(a_i\) 相等的链可以用 \(\text{LCT}\) 的实链维护。上述 \(up\) 过程则可以魔改一下 \(\text{access}\) 函数,一边跳虚边一边在维护每个权值出现次数的树状数组上修改即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int ver[400005],ne[400005],head[200005],tot;
inline void link(int x,int y){
ver[++tot]=y;
ne[tot]=head[x];
head[x]=tot;
}
struct BIT{
int tree[400005];
inline void add(int x,int v){
while(x<=n+m){
tree[x]+=v;
x+=(x&-x);
}
}
inline int query(int x){
int res=0;
while(x){
res+=tree[x];
x&=(x-1);
}
return res;
}
}B;
namespace LCT{
int fa[200005],son[2][200005],siz[200005],lazy[200005],col[200005],rt,now;
bool rev[200005];
inline void pushup(int x){
siz[x]=siz[son[0][x]]+1+siz[son[1][x]];
}
inline bool isroot(int x){
return son[1][fa[x]]!=x&&son[0][fa[x]]!=x;
}
inline void reverse(int x){
rev[x]^=1;swap(son[0][x],son[1][x]);
}
inline void push(int x){
if(rev[x]){
reverse(son[0][x]);
reverse(son[1][x]);
}rev[x]=0;
if(lazy[x]){
col[son[0][x]]=lazy[son[0][x]]=lazy[x];
col[son[1][x]]=lazy[son[1][x]]=lazy[x];
}lazy[x]=0;
}
inline void rotate(int x){
int y=fa[x],z=fa[y];
if(!isroot(y))son[son[1][z]==y][z]=x;
bool is=(son[1][y]==x);
son[is][y]=son[!is][x];fa[son[is][y]]=y;
son[!is][x]=y;fa[y]=x;fa[x]=z;pushup(y);pushup(x);
}
int stk[200005],top;
inline void splay(int x){
stk[++top]=x;
for(int i=x;!isroot(i);i=fa[i])stk[++top]=fa[i];
while(top)push(stk[top--]);
while(!isroot(x)){
int y=fa[x],z=fa[y];
if(!isroot(y)){
if((son[1][y]==x)^(son[1][z]==y))rotate(x);
else rotate(y);
}rotate(x);
}
}
inline void access(int x,int v){
int i=0;
while(x){
splay(x);B.add(col[x],-siz[son[0][x]]-1);son[1][x]=i;pushup(x);
i=x;x=fa[x];
}
B.add(v,siz[i]);col[i]=lazy[i]=v;
}
inline void makert(int x){
access(x,col[rt]);B.add(col[rt],-1);splay(x);
reverse(x);col[x]=++now;B.add(now,1);rt=x;push(x);son[1][x]=0;
}
inline int query(int x){
splay(x);
return B.query(col[x]-1)+siz[son[1][x]]+1;
}
void dfs(int x,int fi){
fa[x]=fi;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fi)continue;
dfs(u,x);
}
}
int vis[200005];
inline void init(){
rt=now=n;
for(int i=n;i>=1;i--){
int x=i,y=0,cnt=0;
while(x&&!vis[x])col[x]=i,vis[x]=1,son[1][x]=y,pushup(x),cnt++,y=x,x=fa[x];
B.add(i,cnt);
}
}
}
char op[10];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int x,y;scanf("%d%d",&x,&y);
link(x,y);link(y,x);
}
LCT::dfs(n,0);LCT::init();
for(int i=1;i<=m;i++){
int x,y;scanf("%s%d",op,&x);
if(op[0]=='u')LCT::makert(x);
if(op[0]=='w')printf("%d\n",LCT::query(x));
if(op[0]=='c'){
scanf("%d",&y);
printf("%d\n",LCT::query(x)<LCT::query(y)?x:y);
}
}
return 0;
}
CF1034D Intervals of Intervals
先考虑弱化版,给一些区间,多次查询一些连续区间的并的长度。
把询问离线,按右端点排序。当右端点 \(R\) 不断右移(加入新区间),维护每个 \(L\) 到 \(R\) 的区间的并的长度 \(f[L]\) 。
不妨认为新加入的区间覆盖了先前的区间。那么新加入区间的贡献是什么呢?对于 \([L,R-1]\) ,加入 \(R\) 后,新增的被覆盖的是原本空的地方,就等于 \(|R|\) 减 原被覆盖的区间的长度。所以可以考虑加上 \(R\) 的长,再减去原本在这个区间中的那些区间的长。
以上可以使用 \(\text{ODT}\) 和树状数组维护,复杂度是 \(O(n \log n)\) 。
找前 \(k\) 大的区间并。不妨考虑求第 \(k\) 大的区间并,设其长为 \(len\) ,然后对于每个 \(R\),求 \(\sum_{[f(L)\ge len]}{f(L)}\) 。由于长度大于等于 \(len\) 的区间并的数量可能大于 \(k\) ,还需要计算数量并且减去多的。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,k;
struct node{
int l,r;
mutable int v;
node(){}
node(int _l,int _r,int _v){
l=_l,r=_r,v=_v;
}
inline bool operator <(const node a)const{
return l<a.l;
}
};
set<node> odt;
inline auto split(int x){
if(x>1e9)return odt.end();
auto it=--odt.upper_bound(node(x,0,0));
if(it->l==x)return it;
node v=*it;odt.erase(it);
odt.insert(node(v.l,x-1,v.v));
return odt.insert(node(x,v.r,v.v)).first;
}
vector<pair<int, int> > seg[300005];
inline void assign(int l,int r,int v){
auto ir=split(r+1),il=split(l);
for(auto it=il;it!=ir;it++){
if(it->v)seg[v].emplace_back(it->v,it->r-it->l+1);
}
odt.erase(il,ir);
odt.insert(node(l,r,v));
sort(seg[v].begin(),seg[v].end());
}
pair<int,int> a[300005];
long long ans;
int d[300005];
inline int check(int len){
int val=0;
for(int i=0;i<=n+1;i++)d[i]=0;
long long sum=0,res=0,cnt=0;
int l=1;
for(int r=1;r<=n;r++){
val+=a[r].second-a[r].first+1;
sum+=1ll*(a[r].second-a[r].first+1)*l;
d[r+1]-=a[r].second-a[r].first+1;
for(auto it:seg[r]){
if(it.first<l)sum-=1ll*it.first*it.second;
else {
val-=it.second;
sum-=1ll*l*it.second;
d[it.first+1]+=it.second;
}
}
while(val>=len){
++l;
val+=d[l];
sum+=val;
}
cnt+=l-1;
res+=sum-val;
}
if(cnt<k)return 0;
ans=res-1ll*(cnt-k)*len;
return 1;
}
int main(){
scanf("%d%d",&n,&k);
odt.insert(node(0,1e9+2,0));
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i].first,&a[i].second);
--a[i].second;
assign(a[i].first,a[i].second,i);
}
int l=0,r=1e9+1;
while(l<r){
int mid=(l+r+1)>>1;
if(check(mid))l=mid;
else r=mid-1;
}
printf("%lld\n",ans);
return 0;
}
【UNR #5】诡异操作
首先可以想到一个 \(O(128\times n+128\times q\log n )\) 的做法,空间开不下,时间也过不去。
考虑这一做法在计算机中的储存方式,每一个节点维护区间内 \(128\) 个数位每位 \(1\) 的数量,每个数量用 \(1\) 个 \(32\) 位的 \(\text{int}\) 来储存,实际上有用的不超过 \(\log {(r-l+1)}\) 位。
容易发现这是一个 \(128\times \log {(r-l+1)}\) 的 \(01\) 表,我们考虑沿对角线翻转它,复杂度就变成了 \(O(n\log n+q\log^2n)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
inline int read(){
int res=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')res=10*res+c-'0',c=getchar();
return res;
}
typedef __uint128_t u128;
inline u128 read_u(){
static char buf[200];
scanf("%s",buf);u128 res=0;
for(int i=0;buf[i];i++)res=res<<4|(buf[i]<='9'?buf[i]-'0':buf[i]-'a'+10);
return res;
}
inline void output(u128 res){
if(res>=16)output(res/16);
putchar(res%16>=10?'a'+res%16-10:'0'+res%16);
}
u128 *vec[1200005],pool[2000005],*lim=pool;
int empty[1200005],cnt[1200005];
u128 a[300005],lazy[1200005];
inline void pushup(int x){
u128 flag=0;
for(int i=0;i<cnt[x];i++){
u128 a=(cnt[x<<1]>i?vec[x<<1][i]:0),b=(cnt[x<<1|1]>i?vec[x<<1|1][i]:0);
vec[x][i]=a^b^flag;
flag=((a^b)&flag)|(a&b);
}
empty[x]=(empty[x<<1]&empty[x<<1|1]);
}
inline void And(int x,u128 v){
if(empty[x])return ;
for(int i=0;i<cnt[x];i++){
vec[x][i]&=v;
}
lazy[x]&=v;
}
inline void pushdown(int i){
if(~lazy[i])And(i<<1,lazy[i]),And(i<<1|1,lazy[i]);
lazy[i]=-1;
}
void build(int l=1,int r=n,int i=1){
while((1<<cnt[i])<=r-l+1)cnt[i]++;
vec[i]=lim;lim+=cnt[i];lazy[i]=-1;
if(l==r){
vec[i][0]=a[l];empty[i]=!a[l];
return ;
}
int mid=(l+r)>>1;
build(l,mid,i<<1);build(mid+1,r,i<<1|1);
pushup(i);
}
void Div(int fr,int to,u128 v,int l=1,int r=n,int i=1){
if(fr>r||to<l||empty[i])return ;
if(l==r){
vec[i][0]/=v;empty[i]=(!vec[i][0]);
return ;
}
int mid=(l+r)>>1;pushdown(i);
Div(fr,to,v,l,mid,i<<1);Div(fr,to,v,mid+1,r,i<<1|1);
pushup(i);
}
void update(int fr,int to,u128 v,int l=1,int r=n,int i=1){
if(fr>r||to<l||empty[i])return ;
if(fr<=l&&to>=r){
And(i,v);return ;
}
int mid=(l+r)>>1;pushdown(i);
update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
pushup(i);
}
u128 query(int fr,int to,int l=1,int r=n,int i=1){
if(fr>r||to<l||empty[i])return 0;
if(fr<=l&&to>=r){
u128 res=0;
for(int j=0;j<cnt[i];j++)res+=(vec[i][j]<<j);
return res;
}
int mid=(l+r)>>1;pushdown(i);
return query(fr,to,l,mid,i<<1)+query(fr,to,mid+1,r,i<<1|1);
}
int main(){
n=read();q=read();
for(int i=1;i<=n;i++)a[i]=read_u();
build();
while(q--){
int op,l,r;u128 v;
op=read();l=read();r=read();
if(op==1){
v=read_u();
if(v!=1)Div(l,r,v);
}
if(op==2){
v=read_u();
update(l,r,v);
}
if(op==3)output(query(l,r)),puts("");
}
return 0;
}
多校联训 DS 专题的更多相关文章
- 多校联训 DP 专题
[UR #20]跳蚤电话 将加边变为加点,方案数为 \((n-1)!\) 除以一个数,\(dp\) 每种方案要除的数之和即可. 点击查看代码 #include<bits/stdc++.h> ...
- Solution -「多校联训」数学考试
\(\mathcal{Description}\) Link. 给定 \(n\) 个函数,第 \(i\) 个有 \(f_i(x)=a_ix^3+b_ix^2+cx_i+d~(x\in[l_i, ...
- [DBSDFZOJ 多校联训] 就
就 背景描述 一排 N 个数, 第 i 个数是 Ai , 你要找出 K 个不相邻的数, 使得他们的和最大. 请求出这个最大和. 输入格式 第一行两个整数 N 和 K. 接下来一行 N 个整数, 第 i ...
- [DBSDFZOJ 多校联训] Password
Password password.in/.out 描述 你来到了一个庙前,庙牌上有一个仅包含小写字母的字符串 s. 传说打开庙门的密码是这个字符串的一个子串 t,并且 t 既是 s 的前缀又是 s ...
- Contest1893 - 2019年6月多校联训b层测试1
传送门 密码:waxadyt T1 暴力 对于任意相邻的两个值 中间能到达的最大高度是固定的 加上头尾,判一下就好了 代码//感谢Th Au K #include<bits/stdc++.h&g ...
- 2019西安多校联训 Day5
T1 光哥为了不让某初二奆佬恶心到我们而留下的火种 (貌似没这题平均分就100-了) 思路:就一横一竖让后就gztopa嘛 #include <bits/stdc++.h> using n ...
- 2019西安多校联训 Day4
T1 大水题!!难度简单,显然的贪心策略即可,but... 思路:首先我们按与i点作战后活下来的士兵排序,然后 若当前剩余兵力足够直接减掉战斗死亡人数,如果不够就加 够再打它,但是!我们在考完试观察测 ...
- 2019西安多校联训 Day3
试题链接:http://www.accoders.com/contest.php?cid=1895 考试密码请私信; 特别鸣谢:zkc奆佬帮助我优化本篇题解(语言表达方面) T1 显然二分求解的 ...
- 2019西安多校联训 Day2
试题链接:http://www.accoders.com/contest.php?cid=1894 考试密码请私信; T1 残忍WA 0,明明就是一道非常菜的字符串QAQ 思路:一共找四种东西,A ...
随机推荐
- Apache Doris 通过ODBC连接SQL Server
社区有小伙伴有使用Doris ODBC外表连接SQL Server数据库,使用中遇到不知道驱动怎么安装,苦于我这边也没有SQL Server的环境,正好社区有用户使用了这个数据库,也安装ODBC驱动测 ...
- 基于Arcgis Engine 10.2(C#)+PostgreSQL 11(Postgis 3)+pgRouting 3.0实现使用数据库进行路径规划
前言:最近在(被迫)使用ArcGIS Engine10.2(.NET平台)进行二次开发(桌面应用),因为想做一个最短路径查询的功能,而arcgis的网络分析又比较麻烦,于是想到了使用Postgis.但 ...
- 翻译《Mastering ABP Framework》
前言 大家好,我是张飞洪,谢谢你阅读我的文章. 自从土牛Halil ibrahim Kalkan的<Mastering ABP Framework>出版之后,我就开始马不停蹄进行学习阅读和 ...
- OpenHarmony 3.1 Beta版本关键特性解析——分布式DeviceProfile
(以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点) 成翔 OpenAtom OpenHarmony(以下简称"OpenHarmony")作为分布式操作 ...
- java中的stream是啥?
函数式编程的执行是惰性的,按顺序真正执行的时候才会执行相应的代码.方法: 函数式编程是安全的,用的是monad架构 1 public class StreamTest { 2 3 public sta ...
- 通过命令验证docker容器相当一个轻量级的Linux运行环境,且每个容器内都有一个属于自己的文件系统,容器之间相互隔离
一.docker的三个重要概念 1.镜像:打包项目带上环境,即镜像 Docker镜像是一个特殊的文件系统,除了提供容器运行时所需的程序.库.资源.配置等文件外,还包含了一些为运行时准备的配置参数.镜像 ...
- rabbitmq 安装延时队列插件rabbitmq-delayed-message-exchange
1.下载rabbitmq-delayed-message-exchange(注意版本对应) 链接:https://github.com/rabbitmq/rabbitmq-delayed-messag ...
- 使用Husky提升你的项目规范
使用 ESLint, Prettier, Husky, Lint-staged 提升你的项目规范 本文写于 2020 年 11 月 7 日 大家应该都知道 ESLint 与 prettier,他们的用 ...
- install dns server on ubuntu
参考 CSDN/Ubuntu环境下安装和配置DNS服务器 在 Ubuntu 上安裝 DNS server Install BIND 9 on Ubuntu and Configure It for U ...
- mysql外键与表查询
目录 自增特性 外键 外键关系 外键创建 外键的约束效果 级联更新级联删除 多对多关系 一对一关系 表查询关键字 select与from where筛选 group by分组 练习 关系练习 查询练习 ...