2024 Noip 做题记录(六)
个人训练赛题解(六)
Round #21 - 2024.10.15
A. [ARC112E] Move
题目大意
给定序列
,初始 ,每次操作可以把其中一个元素放到开头或末尾,求有多少种长度为 的操作序列能够使得最终 。 数据范围:
。
思路分析
显然一个数如果被操作多次,只有最后一次有用。
那么每个元素有三种情况:最后一次在放在开头,最后一次放到末尾,未操作过。
前两种操作对应
假设前缀有
预处理后直接
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3005,MOD=998244353;
int n,a[MAXN],m;
ll S[MAXN][MAXN],C[MAXN][MAXN],pw[MAXN],ans=0;
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
S[0][0]=1;
for(int i=1;i<=m;++i) for(int j=1;j<=i;++j) S[i][j]=(S[i-1][j]*j+S[i-1][j-1])%MOD;
for(int i=0;i<=m;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
for(int i=pw[0]=1;i<=m;++i) pw[i]=pw[i-1]*2%MOD;
for(int i=0;i<=n;++i) {
for(int j=i+1;j<=n+1;++j) {
if(j-i-1>=2&&a[j-2]>a[j-1]) break;
int l=i,r=n+1-j;
if(l+r<=m) ans=(ans+S[m][l+r]*pw[m-l-r]%MOD*C[l+r][l])%MOD;
}
}
printf("%lld\n",ans);
return 0;
}
B. [ARC116F] Pop
题目大意
给定
个序列,总长为 ,A 和 B 轮流操作,每次可以删除一个序列的开头或结尾(不能把序列删空)。 A 要最大化剩余元素之和,B 要最小化剩余元素之和,求最后剩余元素之和。
数据范围:
。
思路分析
先考虑
如果序列
先手可以选择第一步操作开头,那么结果是
那么 A 先手结果是
如果
此时 A 先手结果是
我们发现
因此所有人会轮流在长度为偶数的序列中取一个元素,直到所有序列长度为奇数。
此时如果先手操作了一步,当前序列长度为偶数,那么后手必然会操作当前序列。
因此所有偶数序列操作之后,接下来的一个人会依次在每个序列当先手。
因此长度为偶数的序列第一次取开头或末尾对答案的贡献是一定的,按差排序即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
long long s=0;
int f(vector<int>&a,int l,int r,int op) {
if(l==r) return a[l];
int i=(l+r)>>1;
if(op) return min(max(a[i],a[i-1]),max(a[i],a[i+1]));
return max(min(a[i],a[i-1]),min(a[i],a[i+1]));
}
vector <int> a[MAXN];
signed main() {
int n,op=0;
scanf("%d",&n);
for(int t=1,m;t<=n;++t) {
scanf("%d",&m),a[t].resize(m);
for(int &i:a[t]) scanf("%d",&i);
op^=!(m&1);
}
vector <int> q;
for(int i=1;i<=n;++i) {
int m=a[i].size();
if(m&1) s+=f(a[i],0,m-1,op);
else {
int x=f(a[i],0,m-2,op),y=f(a[i],1,m-1,op);
s+=min(x,y),q.push_back(abs(x-y));
}
}
sort(q.begin(),q.end(),greater<int>());
for(int i=0;i<(int)q.size();i+=2) s+=q[i];
printf("%lld\n",s);
return 0;
}
*C. [ARC112F] Carry
题目大意
给定
,以及 个长度为 的序列 ,可以进行如下两种操作任意多次:
- 选择
,令所有 加上 。 - 选择
,将 减去 ,将 加一(要求 )。 最小化最终的
。 数据范围:
。
思路分析
如果只能进行第二类操作,那么最优策略肯定是把每个能操作的
定义一个序列最终的结果是不断进行二操作最终剩余的元素个数。
我们发现我们实际上可以进行逆操作,即把一个
容易发现进行若干逆操作后并不影响最终的结果,在任何时候进行操作一也不影响最终的结果。
因此我们可以先进行所有一操作,然后全部逆操作到
那么我们实际上可以把每个
容易发现一个
因此我们可以求出每个局面对应的
其中
记
但这样的做法在
对于
可以看成从
直接同余最短路,但要钦定不能一个元素都不放。
取
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll MAXP=1.3e6+5,inf=1e18;
int n,m;
ll b[18],f[MAXP];
ll read() {
ll s=0;
for(int i=0,x;i<n;++i) scanf("%d",&x),s+=b[i]*x;
return s;
}
signed main() {
scanf("%d%d",&n,&m);
for(int i=b[0]=1;i<=n;++i) b[i]=b[i-1]*2*i;
ll a=read(),p=b[n]-1;
for(int i=1;i<=m;++i) p=__gcd(p,read());
if(p<b[n]/p) {
memset(f,0x3f,sizeof(f));
queue <int> q;
for(int i=0;i<n;++i) f[b[i]%p]=1,q.push(b[i]%p);
while(q.size()) {
int u=q.front(); q.pop();
for(int i=0;i<n;++i) {
int v=(u+b[i])%p;
if(f[v]>f[u]+1) q.push(v),f[v]=f[u]+1;
}
}
printf("%lld\n",f[a%p]);
} else {
ll ans=inf;
for(ll u=a%p;u<b[n];u+=p) if(u) {
ll x=u,s=0;
for(int i=1;i<=n;++i) s+=x%(2*i),x/=2*i;
ans=min(ans,s);
}
printf("%lld\n",ans);
}
return 0;
}
*D. [ARC114F] Shuffle
题目大意
给定
排列,求一种把排列划分成 段的方法,使得段间重排得到的最大字典序排列字典序最小。 数据范围:
。
思路分析
很显然段间重排一定是按每个段的段首倒序排列。
我们只要最小化最大的段首,
对于剩余的情况,重排后字典序不会变小,只需要最大化两个排列的 lcp,很显然 lcp 长度
设 lcp 为
对每个
对于构造方案,很显然
容易发现最优解只需要最小化
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,m,a[MAXN],pos[MAXN],st[MAXN],cnt[MAXN];
bool vis[MAXN];
int chk(int x) {
int tp=0;
for(int i=1;i<=x;++i) if(a[i]<=a[1]) {
int j=lower_bound(st+1,st+tp+1,a[i],greater<int>())-st;
st[j]=a[i],tp=max(tp,j);
}
memset(cnt,0,sizeof(cnt));
for(int i=x+1;i<=n;++i) ++cnt[a[i]];
for(int i=1;i<=n;++i) cnt[i]+=cnt[i-1];
for(int i=tp;i;--i) if(i+cnt[st[i]]>=m) return i;
return 0;
}
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),pos[a[i]]=i;
if(a[1]<=m) {
for(int i=m;i;--i) {
printf("%d ",i);
for(int j=pos[i]+1;j<=n&&a[j]>m;++j) printf("%d ",a[j]);
}
return puts(""),0;
}
int l=1,r=n,z=0;
while(l<=r) {
int mid=(l+r)>>1;
if(chk(mid)) z=mid,l=mid+1;
else r=mid-1;
}
vector <int> vi{1};
for(int i=1,p=m-chk(z);p&&i<a[1];++i) if(pos[i]>z) vi.push_back(pos[i]),--p;
sort(vi.begin(),vi.end(),[&](int x,int y){ return a[x]>a[y]; });
for(int i:vi) vis[i]=true;
for(int i:vi) {
printf("%d ",a[i]);
for(int j=i+1;j<=n&&!vis[j];++j) printf("%d ",a[j]);
}
puts("");
return 0;
}
Round #22 - 2024.10.16
*A. [P9337] Cold
题目大意
给定长度为
的序列 ,以及 的排列 ,回答 个询问: 给定
,查询: 数据范围:
。
思路分析
考虑对
对于出现次数较小的颜色,枚举每一对同色的
但直接离线所有点的空间复杂度是
注意到对于三个同颜色的点
因此我们只要记录所有相邻同色点对之间的贡献,并把这些点对离线,对值域扫描线的时候只考虑最小值为
然后考虑出现次数较多的颜色
对于一组相邻同色对
由于我们只会在相邻的点之间连边,因此用回滚莫队配合链表即可维护答案。
对单种颜色求解的复杂度是
但实际上我们的值域不需要到
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5,B=3000;
int n,m,a[MAXN],b[MAXN],L[MAXN],R[MAXN];
ll ans[MAXN];
namespace IO {
int ow,olim=(1<<23)-100;
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<23];
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read() {
int x=0; char c=gc();
while(!isdigit(c)) c=gc();
while(isdigit(c)) x=x*10+(c^48),c=gc();
return x;
}
void flush() {
fwrite(obuf,ow,1,stdout),ow=0;
}
void write(int x) {
if(!x) obuf[ow++]='0';
else {
int t=ow;
for(;x;x/=10) obuf[ow++]=(x%10)^48;
reverse(obuf+t,obuf+ow);
}
if(ow>=olim) flush();
}
void putc(char c) {
obuf[ow++]=c;
if(ow>=olim) flush();
}
#undef gc
}
struct Seg {
int l,r;
inline friend Seg operator +(const Seg &u,const Seg &v) { return {min(u.l,v.l),max(u.r,v.r)}; }
};
struct STtable {
Seg f[MAXN][20];
int bit(int x) { return 1<<x; }
void init() {
for(int i=1;i<=n;++i) f[i][0]={b[i],b[i]};
for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
f[i][k]=f[i][k-1]+f[i+bit(k-1)][k-1];
}
}
Seg qry(int l,int r) {
int k=__lg(r-l+1);
return f[l][k]+f[r-bit(k)+1][k];
}
} ST;
struct SqrtTree {
ll s1[MAXN],s2[MAXN],s3[MAXN];
void add(int x,ll y) { s1[x]+=y,s2[x>>7]+=y,s3[x>>14]+=y; }
ll qry(int x) {
ll s=0;
for(int i=((x>>7)<<7);i<=x;++i) s+=s1[i];
for(int i=((x>>14)<<7);i<(x>>7);++i) s+=s2[i];
for(int i=0;i<(x>>14);++i) s+=s3[i];
return s;
}
} TR;
vector <int> id[MAXN],ql[MAXN],las[MAXN],ord;
vector <Seg> cur[MAXN];
int rk[MAXN];
namespace tsk {
Seg st[MAXN];
int pr[MAXN],sf[MAXN];
array <int,2> ml[MAXN],mr[MAXN];
vector <int> qrys[MAXN];
bool w[MAXN];
int wl[MAXN],wr[MAXN],vl[MAXN],tl[MAXN],tr[MAXN],tid[MAXN];
int stk[MAXN];
void solve(int x) {
int s=id[x].size();
for(int i=1;i<s;++i) {
st[i]=ST.qry(id[x][i-1],id[x][i]);
w[st[i].l]=true,w[st[i].r]=true;
}
int N=0;
for(int i=1;i<=n;++i) if(w[i]) vl[++N]=i,wl[i]=wr[i]=N;
for(int i=1;i<=n;++i) if(!wl[i]) wl[i]=wl[i-1];
for(int i=n;i>=1;--i) if(!wr[i]) wr[i]=wr[i+1];
for(int i=1;i<s;++i) {
int l=st[i].l=wl[st[i].l],r=st[i].r=wr[st[i].r];
ml[l][ml[l][0]>0]=i,mr[r][mr[r][0]>0]=i;
}
int Q=0;
for(int i:ord) if(L[i]<=vl[N]&&vl[1]<=R[i]&&wr[L[i]]<=wl[R[i]]) {
++Q,tl[Q]=wr[L[i]],tr[Q]=wl[R[i]],tid[Q]=i;
}
if(!Q) return ;
int blk=N/sqrt(Q)+1;
for(int i=1;i<=Q;++i) qrys[(tl[i]-1)/blk+1].push_back(i);
for(int b=1;b<=(N-1)/blk+1;++b) if(qrys[b].size()) {
int o=min(b*blk,N),rp=o+1;
for(int i=1;i<=s;++i) pr[i]=sf[i]=i;
ll sum=0;
auto link=[&](int e) {
sum+=(sf[e]-pr[e]+1)*(sf[e+1]-pr[e+1]+1);
sf[pr[e]]=sf[e+1],pr[sf[e+1]]=pr[e];
};
for(auto q:qrys[b]) {
if(tr[q]<=o) {
int tp=0;
for(int i=tl[q];i<=tr[q];++i) for(int e:ml[i]) if(e&&st[e].r<=tr[q]) {
link(e),stk[++tp]=e;
}
ans[tid[q]]+=sum,sum=0;
for(int i=1;i<=tp;++i) sf[pr[stk[i]]]=stk[i],pr[sf[stk[i]+1]]=stk[i]+1;
continue;
}
for(;rp<=tr[q];++rp) for(int e:mr[rp]) if(st[e].l>o) link(e);
int tp=0; ll rem=sum;
for(int lp=o;lp>=tl[q];--lp) for(int e:ml[lp]) if(e&&st[e].r<=tr[q]) {
link(e),stk[++tp]=e;
}
ans[tid[q]]+=sum,sum=rem;
for(int i=1;i<=tp;++i) sf[pr[stk[i]]]=stk[i],pr[sf[stk[i]+1]]=stk[i]+1;
}
qrys[b].clear();
}
memset(w,0,sizeof(w));
memset(wl,0,sizeof(wl));
memset(wr,0,sizeof(wr));
for(int i=1;i<=N;++i) ml[i]=mr[i]={0,0};
}
}
signed main() {
ios::sync_with_stdio(false);
n=IO::read(),m=IO::read();
for(int i=1;i<=n;++i) a[i]=IO::read(),id[a[i]].push_back(i);
for(int i=1;i<=n;++i) b[i]=IO::read();
for(int i=1;i<=m;++i) L[i]=IO::read(),R[i]=IO::read(),ql[L[i]].push_back(i),ord.push_back(i);
sort(ord.begin(),ord.end(),[&](int x,int y){ return R[x]<R[y]; });
ST.init();
for(int i=1;i<=n;++i) if((int)id[i].size()<=B) {
int s=id[i].size();
for(int x=0;x<s;++x) rk[id[i][x]]=x;
for(int x=0;x<s-1;++x) {
Seg o=ST.qry(id[i][x],id[i][x+1]);
cur[i].push_back(o);
las[o.l].push_back(id[i][x]);
}
} else tsk::solve(i);
for(int i=n;i>=1;--i) {
for(int o:las[i]) {
int c=a[o],x=rk[o],s=id[c].size();
vector<Seg> &sg=cur[c];
for(int l=x,mx=sg[x].r;l>=0;--l) {
if(l<x&&sg[l].l<=i) break;
mx=max(mx,sg[l].r);
for(int r=x+1,vr=mx;r<s;++r) {
if(sg[r-1].l<i) break;
vr=max(vr,sg[r-1].r);
TR.add(vr,1);
}
}
}
for(int o:ql[i]) ans[o]+=TR.qry(R[o]);
}
for(int i=1;i<=m;++i) IO::write(ans[i]),IO::putc('\n');
IO::flush();
cerr<<"time = "<<clock()<<"\n";
return 0;
}
B. [P5313] Include
题目大意
给你一个长为
的序列,有 次查询操作。 每次查询操作给定参数
,需输出最大的 ,使得存在一个 ,满足 ,使得 都在区间 内至少出现过一次。 数据范围:
。
思路分析
从全局询问开始,可以用 bitset
维护每个值的出现次数,然后按 bitset
,求出前缀
这部分的复杂度是
但这个做法在
此时我们可以暴力枚举每个
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=1e5+105,LIM=100;
struct Qry {
int l,r,b,id;
};
int n,a[MAXN],m,ans[MAXN];
namespace b1 {
int cnt[MAXN];
ull f[MAXN],g[MAXN];
void ins(int x) { if(!cnt[x]++) f[x>>6]^=1ull<<(x&63); }
void ers(int x) { if(!--cnt[x]) f[x>>6]^=1ull<<(x&63); }
ull qry(int i) {
int q=i>>6,r=i&63;
if(!r) return f[q];
return f[q]>>r|f[q+1]<<(64-r);
}
void solve(vector <Qry> &qy) {
if(qy.empty()) return ;
int B=n/sqrt(qy.size())+1;
sort(qy.begin(),qy.end(),[&](auto i,auto j){
if(i.l/B!=j.l/B) return i.l<j.l;
return (i.l/B)&1?i.r>j.r:i.r<j.r;
});
int l=1,r=0;
for(auto q:qy) {
while(r<q.r) ins(a[++r]);
while(l>q.l) ins(a[--l]);
while(l<q.l) ers(a[l++]);
while(r>q.r) ers(a[r--]);
int b=q.b,k=(b-1)>>6;
for(int i=0;i<=k;++i) g[i]=-1;
if(b&63) g[k]=(1ull<<(b&63))-1;
for(int &x=ans[q.id]=0;;++x) {
bool flg=0;
for(int i=0;i<=k;++i) g[i]&=qry(x*b+(i<<6)),flg|=g[i];
if(!flg) break;
}
}
}
}
namespace b2 {
int id[MAXN];
struct SegmentTree {
static const int N=1<<17;
int tr[N<<1];
void init() { memset(tr,0,sizeof(tr)); }
void upd(int x,int v) { for(tr[x+=N]=v,x>>=1;x;x>>=1) tr[x]=min(tr[x<<1],tr[x<<1|1]); }
int qsuf(int x,int v) {
for(x+=N-1;x^1;x>>=1) if((~x&1)&&tr[x^1]<v) {
for(x^=1;x<=N;x=x<<1|(tr[x<<1]>=v));
return x-N;
}
return N+1;
}
} T;
void solve(int B,vector <Qry> &qy) {
if(qy.empty()) return ;
for(int i=0,x=0;i<B;++i) for(int j=i;j<MAXN;j+=B) id[j]=++x;
sort(qy.begin(),qy.end(),[&](auto i,auto j){ return i.r<j.r; });
T.init();
auto it=qy.begin();
for(int i=1;i<=n;++i) {
T.upd(id[a[i]],i);
for(;it!=qy.end()&&it->r==i;++it) {
for(int r=0;r<B;++r) {
ans[it->id]=max(ans[it->id],T.qsuf(id[r],it->l)-id[r]);
}
}
}
}
}
vector <Qry> qys[LIM];
signed main() {
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
cin>>m;
for(int i=1,l,r,b;i<=m;++i) {
cin>>l>>r>>b;
qys[b<LIM?b:0].push_back({l,r,b,i});
}
b1::solve(qys[0]);
for(int i=1;i<LIM;++i) b2::solve(i,qys[i]);
for(int i=1;i<=m;++i) cout<<ans[i]<<"\n";
return 0;
}
*C. [P9061] Push
题目大意
给定
个点 ,你需要按顺序处理 次操作。每次操作给出 ,
- 首先进行修改:
- 若
则将满足 的点的 修改为 ; - 若
则将满足 的点的 修改为 。 - 然后进行查询,询问满足
的点数。 数据范围:
。
思路分析
我们发现所有被修改过至少一次的点会构成一条从左上到右下的折线,称为轮廓线。
并且每次操作对轮廓线的影响是有限的,我们会发现每个在轮廓线上的相对顺序是不会改变的,即任何时候轮廓线上的点的
维护轮廓线还要知道每个点第一次进入轮廓线的操作,即第一个
然后就要处理轮廓线外的点,直接计算的话还要处理时间一维变成三维偏序。
特判掉
因此这部分的点不需要考虑进入轮廓线的时间这一维,又变成二维偏序,再简单计算所有未被插入的点中分别满足
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
mt19937 rnd(5437578);
namespace IO {
int ow,olim=(1<<23)-100;
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<23];
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read() {
int x=0; char c=gc();
while(!isdigit(c)) c=gc();
while(isdigit(c)) x=x*10+(c^48),c=gc();
return x;
}
void flush() {
fwrite(obuf,ow,1,stdout),ow=0;
}
void write(int x) {
if(!x) obuf[ow++]='0';
else {
int t=ow;
for(;x;x/=10) obuf[ow++]=(x%10)^48;
reverse(obuf+t,obuf+ow);
}
}
void putc(char c) {
obuf[ow++]=c;
}
#undef gc
}
const int MAXN=1e6+5;
inline void chkmin(int &x,const int &y) { x=x<y?x:y; }
inline void chkmax(int &x,const int &y) { x=x>y?x:y; }
struct Treap {
struct Node {
int x,y,tx,ty,pri,ls,rs,siz;
} tr[MAXN];
inline void adt(Node &p,const int &x,const int &y) {
if(x) p.x=p.tx=x;
if(y) p.y=p.ty=y;
}
inline void psd(Node &p) {
adt(tr[p.ls],p.tx,p.ty),adt(tr[p.rs],p.tx,p.ty),p.tx=p.ty=0;
}
void splix(int p,const int &k,int &u,int &v) {
if(!p) return u=v=0,void();
Node &o=tr[p]; psd(o);
if(o.x<=k) u=p,splix(o.rs,k,o.rs,v);
else v=p,splix(o.ls,k,u,o.ls);
o.siz=tr[o.ls].siz+tr[o.rs].siz+1;
}
void spliy(int p,const int &k,int &u,int &v) {
if(!p) return u=v=0,void();
Node &o=tr[p]; psd(o);
if(o.y>=k) u=p,spliy(o.rs,k,o.rs,v);
else v=p,spliy(o.ls,k,u,o.ls);
o.siz=tr[o.ls].siz+tr[o.rs].siz+1;
}
void spliz(int p,const int &k,int &u,int &v) {
if(!p) return u=v=0,void();
Node &o=tr[p]; psd(o);
if(o.x-o.y<=k) u=p,spliz(o.rs,k,o.rs,v);
else v=p,spliz(o.ls,k,u,o.ls);
o.siz=tr[o.ls].siz+tr[o.rs].siz+1;
}
int merge(int u,int v) {
if(!u||!v) return u|v;
Node &p=tr[u],&q=tr[v]; psd(p),psd(q);
if(p.pri<q.pri) return p.siz+=q.siz,p.rs=merge(p.rs,v),u;
else return q.siz+=p.siz,q.ls=merge(u,q.ls),v;
}
inline int szx(int p,const int &k) {
int ans=0;
while(p) {
psd(tr[p]);
if(tr[p].x>k) p=tr[p].ls;
else ans+=tr[tr[p].ls].siz+1,p=tr[p].rs;
}
return ans;
}
inline int szy(int p,const int &k) {
int ans=0;
while(p) {
psd(tr[p]);
if(tr[p].y<k) p=tr[p].ls;
else ans+=tr[tr[p].ls].siz+1,p=tr[p].rs;
}
return ans;
}
} T;
int n,m,ax[MAXN],ay[MAXN],ux[MAXN],uy[MAXN],qx[MAXN],qy[MAXN],op[MAXN],ans[MAXN];
struct poi {
int x,y,id;
inline friend bool operator <(const poi &u,const poi &v) {
return u.x^v.x?u.x>v.x:u.id>v.id;
}
};
vector <int> ins[MAXN];
struct Bit1 {
int tr[MAXN],s;
void init() { fill(tr+1,tr+n+1,m+1); }
inline void upd(int x,const int &v) { for(;x;x&=x-1) chkmin(tr[x],v); }
inline int qry(int x) { for(s=m+1;x<=n;x+=x&-x) chkmin(s,tr[x]); return s; }
} Tmn;
struct Bit2 {
int tr[MAXN],s;
inline void upd(int x,const int &v) { for(;x;x&=x-1) chkmax(tr[x],v); }
inline int qry(int x) { for(s=0;x<=n;x+=x&-x) chkmax(s,tr[x]); return s; }
} Tmx;
struct Bit3 {
int tr[MAXN],s;
inline void add(int x,const int &v) { for(;x;x&=x-1) tr[x]+=v; }
inline int qry(int x) { for(s=0;x<=n;x+=x&-x) s+=tr[x]; return s; }
} To,Tx,Ty;
signed main() {
n=IO::read(),m=IO::read();
vector <poi> Q;
for(int i=1;i<=n;++i) ax[i]=IO::read(),ay[i]=IO::read(),Q.push_back({ax[i],ay[i],i});
for(int i=1;i<=m;++i) {
op[i]=IO::read(),ux[i]=IO::read(),uy[i]=IO::read();
qx[i]=IO::read(),qy[i]=IO::read();
Q.push_back({ux[i],uy[i],i+n});
}
sort(Q.begin(),Q.end());
Tmn.init();
for(auto o:Q) {
if(o.id>n) Tmn.upd(o.y,o.id-n);
else ins[Tmn.qry(o.y)].push_back(o.id);
}
Q.clear();
for(int i=1;i<=n;++i) Q.push_back({ax[i],ay[i],i});
for(int i=1;i<=m;++i) Q.push_back({qx[i],qy[i],i+n});
sort(Q.begin(),Q.end());
for(auto o:Q) {
if(o.id>n) ans[o.id-n]=To.qry(o.y+1);
else To.add(o.y,1);
}
for(int i=1;i<=n;++i) Tx.add(ax[i],1),Ty.add(ay[i],1);
int rt=0;
for(int i=1,ot=n;i<=m;++i) {
int u,o1,o2,o3;
T.splix(rt,ux[i],o1,o2);
T.spliy(o1,uy[i]+1,o3,u);
if(op[i]==2) T.adt(T.tr[u],ux[i],0);
else T.adt(T.tr[u],0,uy[i]);
rt=T.merge(T.merge(o3,u),o2);
for(int j:ins[i]) {
Tx.add(ax[j],-1),Ty.add(ay[j],-1),--ot;
op[i]==2?ax[j]=ux[i]:ay[j]=uy[i];
T.spliz(rt,ax[j]-ay[j],o1,o2);
T.tr[j]={ax[j],ay[j],0,0,(int)rnd(),0,0,1};
rt=T.merge(T.merge(o1,j),o2);
}
Tmx.upd(ux[i],uy[i]);
if(Tmx.qry(qx[i]+1)>qy[i]) { IO::putc('0'),IO::putc('\n'); continue; }
ans[i]+=ot+T.szx(rt,qx[i])-T.szy(rt,qy[i]+1);
ans[i]-=Tx.qry(qx[i]+1)+Ty.qry(qy[i]+1);
IO::write(ans[i]),IO::putc('\n');
}
IO::flush();
return 0;
}
*D. [P7447] Minus
题目大意
给定一个长为
的序列 ,需要实现 次操作:
将区间
中所有 的元素减去 。 询问区间
的和,最小值,最大值。 数据范围:
。
思路分析
考虑值域分块,对于每个
考虑一次操作对每个块的影响,设
但这种情况可能把一个元素减到
然后考虑第
时间复杂度
但此时空间复杂度
首先我们可以改变值域分块的底层块长,按
但此时的空间复杂度依然难以接受,可以对线段树采用底层分块优化,即将线段树底层
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline void chkmin(int &x,const int &y) { x=x<y?x:y; }
inline void chkmax(int &x,const int &y) { x=x>y?x:y; }
const int MAXN=5e5+5,inf=2e9,MOD=(1<<20)-1;
const int MAXS=32775,B=32;
const int H=10,pw[]={1,8,64,512,4096,32768,262144,2097152,16777216,134217728};
namespace IO {
int ow;
const int olim=(1<<21)-30;
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<21];
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read() {
int x=0; char c=gc();
while(!isdigit(c)) c=gc();
while(isdigit(c)) x=x*10+(c^48),c=gc();
return x;
}
inline void flush() {
fwrite(obuf,ow,1,stdout),ow=0;
}
inline void write(ll x) {
if(!x) obuf[ow++]='0';
else {
int t=ow;
for(;x;x/=10) obuf[ow++]=(x%10)^48;
reverse(obuf+t,obuf+ow);
}
if(ow>=olim) flush();
}
inline void putc(const char &c) {
obuf[ow++]=c;
if(ow>=olim) flush();
}
#undef gc
}
inline int vblk(int x) { return upper_bound(pw,pw+H,x)-pw-1; }
int n,m,K,a[MAXN],b[MAXN],lp[MAXS],rp[MAXS],bel[MAXN];
struct Node {
int id;
array<int,H> mn,mx,tg,cnt;
array<ll,H> sum;
} tr[MAXS];
inline void blk_psu(const int &o,Node &p,const int &x) {
p.mn[x]=inf,p.mx[x]=p.cnt[x]=p.sum[x]=0;
for(int i=lp[o];i<=rp[o];++i) if(b[i]==x) {
chkmin(p.mn[x],a[i]);
chkmax(p.mx[x],a[i]);
p.sum[x]+=a[i],++p.cnt[x];
}
}
inline void blk_psu(const int &o,Node &p) {
p.mn.fill(inf),p.mx.fill(0),p.cnt.fill(0),p.sum.fill(0);
for(int i=lp[o];i<=rp[o];++i) {
chkmin(p.mn[b[i]],a[i]);
chkmax(p.mx[b[i]],a[i]);
p.sum[b[i]]+=a[i],++p.cnt[b[i]];
}
}
inline void psu(Node &p,const Node &ls,const Node &rs,const int &x) {
p.mn[x]=min(ls.mn[x],rs.mn[x]);
p.mx[x]=max(ls.mx[x],rs.mx[x]);
p.sum[x]=ls.sum[x]+rs.sum[x];
p.cnt[x]=ls.cnt[x]+rs.cnt[x];
}
inline void psu(const int &p,const int &x) { psu(tr[p],tr[p<<1],tr[p<<1|1],x); }
inline void psu(const int &p) { for(int x=0;x<H;++x) psu(p,x); }
inline void blk_psd(const int &o,Node &p,const int &x) {
for(int i=lp[o];i<=rp[o];++i) if(b[i]==x) a[i]-=p.tg[x];
p.tg[x]=0;
}
inline void blk_psd(const int &o,Node &p) {
for(int i=lp[o];i<=rp[o];++i) a[i]-=p.tg[b[i]];
p.tg.fill(0);
}
inline void adt(Node &p,const int &x,const int &k) {
if(!p.cnt[x]) return ;
p.tg[x]+=k,p.mn[x]-=k,p.mx[x]-=k,p.sum[x]-=1ll*p.cnt[x]*k;
}
inline void psd(int p,const int &x) {
if(tr[p].tg[x]) adt(tr[p<<1],x,tr[p].tg[x]),adt(tr[p<<1|1],x,tr[p].tg[x]),tr[p].tg[x]=0;
}
inline void psd(const int &p) { for(int x=0;x<H;++x) psd(p,x); }
void init(int l=1,int r=K,int p=1) {
if(l==r) return tr[p].id=l,blk_psu(l,tr[p]);
int mid=(l+r)>>1;
init(l,mid,p<<1),init(mid+1,r,p<<1|1);
psu(p);
}
int qmn,qmx,ul,ur,cl,cr; ll qsum;
inline void blk_qry(const int &o) {
for(int i=max(lp[o],ul);i<=min(rp[o],ur);++i) qsum+=a[i],chkmin(qmn,a[i]),chkmax(qmx,a[i]);
}
void qry(int l=1,int r=K,int p=1) {
if(ul<=lp[l]&&rp[r]<=ur) {
for(int x=0;x<H;++x) qsum+=tr[p].sum[x],chkmin(qmn,tr[p].mn[x]),chkmax(qmx,tr[p].mx[x]);
return ;
}
if(l==r) return blk_psd(l,tr[p]),blk_qry(l);
psd(p);
int mid=(l+r)>>1;
if(cl<=mid) qry(l,mid,p<<1);
if(mid<cr) qry(mid+1,r,p<<1|1);
}
inline void leaf_psd(const int &x,const int &p) {
for(int d=__lg(p);d;--d) psd(p>>d,x);
blk_psd(tr[p].id,tr[p],x);
}
inline void leaf_psu(const int &x,const int &p) {
blk_psu(tr[p].id,tr[p],x);
for(int q=p>>1;q;q>>=1) psu(q,x);
}
inline void blk_upd(const int &x,const int &k,const int &o,const int &p) {
for(int i=max(lp[o],ul);i<=min(rp[o],ur);++i) if(b[i]==x&&a[i]>k) {
a[i]-=k;
if(a[i]<pw[x]) {
int y=vblk(a[i]);
leaf_psd(y,p),b[i]=y,leaf_psu(y,p);
}
}
}
void upd(const int &x,const int &k,int l=1,int r=K,int p=1) {
if(tr[p].mx[x]<=k) return ;
if(ul<=lp[l]&&rp[r]<=ur&&tr[p].mn[x]-k>=pw[x]) return adt(tr[p],x,k);
if(l==r) return blk_psd(l,tr[p],x),blk_upd(x,k,l,p),blk_psu(l,tr[p],x);
int mid=(l+r)>>1; psd(p,x);
if(cl<=mid) upd(x,k,l,mid,p<<1);
if(mid<cr) upd(x,k,mid+1,r,p<<1|1);
psu(p,x);
}
signed main() {
n=IO::read(),m=IO::read();
for(int i=1;i<=n;++i) a[i]=IO::read(),b[i]=vblk(a[i]);
K=(n-1)/B+1;
for(int i=1;i<=K;++i) {
lp[i]=(i-1)*B+1,rp[i]=min(i*B,n);
fill(bel+lp[i],bel+rp[i]+1,i);
}
init();
int lst=0;
for(int op,k,i=1;i<=m;++i) {
op=IO::read(),ul=IO::read()^lst,ur=IO::read()^lst,cl=bel[ul],cr=bel[ur];
if(op==1) {
k=IO::read()^lst;
for(int x=vblk(k);x<H;++x) upd(x,k);
} else {
qmn=inf,qmx=qsum=0,qry();
IO::write(qsum),IO::putc(' ');
IO::write(qmn),IO::putc(' ');
IO::write(qmx),IO::putc('\n');
lst=qsum&MOD;
}
}
IO::flush();
return 0;
}
E. [P8528] Virtual
题目大意
给定
个点的树, 次询问 中有多少个子区间构成虚树。 数据范围:
。
思路分析
对于三个点
- 如果
没有影响。 - 如果
那么会把 的区间删除。 - 如果
那么会删除 的区间删除。
很显然对于 LCA 相同的若干点对,如果
那么根据第一类支配点对的结论,dsu on tree 合并时,只插入每个点的前驱后继线段,可以得到
然后就是一个形如数矩形面积并的问题,线段树维护区间删除次数
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,m,a[MAXN];
ll ans[MAXN];
vector <int> G[MAXN];
set <int> F[MAXN];
vector <array<int,3>> op[MAXN];
vector <array<int,2>> qy[MAXN];
void ins(int u,int v,int r) {
if(r<u) op[v].push_back({r+1,u,1});
if(v<r) op[v].push_back({1,u,1}),op[r].push_back({1,u,-1});
}
void dfs(int u) {
for(int v:G[u]) {
dfs(v);
if(F[v].size()>F[u].size()) swap(F[u],F[v]);
for(int x:F[v]) {
auto it=F[u].upper_bound(x);
if(it!=F[u].end()) ins(x,*it,a[u]);
if(it!=F[u].begin()) ins(*--it,x,a[u]);
}
for(int x:F[v]) F[u].insert(x);
}
F[u].insert(a[u]);
}
struct SegmentTree {
ll sum[MAXN<<2];
int btg[MAXN<<2],stg[MAXN<<2],mn[MAXN<<2],mc[MAXN<<2];
void psu(int p) {
mn[p]=min(mn[p<<1],mn[p<<1|1]),sum[p]=sum[p<<1]+sum[p<<1|1];
mc[p]=(mn[p<<1]==mn[p]?mc[p<<1]:0)+(mn[p<<1|1]==mn[p]?mc[p<<1|1]:0);
}
void adtb(int p,int k) { btg[p]+=k,mn[p]+=k; }
void adts(int p,int k) { stg[p]+=k,sum[p]+=1ll*k*mc[p]; }
void psd(int p) {
if(btg[p]) adtb(p<<1,btg[p]),adtb(p<<1|1,btg[p]),btg[p]=0;
if(stg[p]) {
if(mn[p<<1]==mn[p]) adts(p<<1,stg[p]);
if(mn[p<<1|1]==mn[p]) adts(p<<1|1,stg[p]);
stg[p]=0;
}
}
void init(int l=1,int r=n,int p=1) {
mc[p]=r-l+1;
if(l==r) return ;
int mid=(l+r)>>1;
init(l,mid,p<<1),init(mid+1,r,p<<1|1);
psu(p);
}
void add(int ul,int ur,int z,int l=1,int r=n,int p=1) {
if(ul<=l&&r<=ur) {
if(z) adtb(p,z);
else if(!mn[p]) adts(p,1);
return ;
}
int mid=(l+r)>>1; psd(p);
if(ul<=mid) add(ul,ur,z,l,mid,p<<1);
if(mid<ur) add(ul,ur,z,mid+1,r,p<<1|1);
psu(p);
}
ll qry(int ul,int ur,int l=1,int r=n,int p=1) {
if(ul<=l&&r<=ur) return sum[p];
int mid=(l+r)>>1; ll s=0; psd(p);
if(ul<=mid) s+=qry(ul,ur,l,mid,p<<1);
if(mid<ur) s+=qry(ul,ur,mid+1,r,p<<1|1);
return s;
}
} T;
signed main() {
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=2,fz;i<=n;++i) cin>>fz,G[fz].push_back(i);
dfs(1);
for(int i=1,l,r;i<=m;++i) cin>>l>>r,qy[r].push_back({l,i});
T.init();
for(int i=1;i<=n;++i) {
for(auto o:op[i]) T.add(o[0],o[1],o[2]);
T.add(1,i,0);
for(auto o:qy[i]) ans[o[1]]=T.qry(o[0],i);
}
for(int i=1;i<=m;++i) cout<<ans[i]<<"\n";
return 0;
}
*F. [P7710] Distance
题目大意
给你一棵
个点的树,需要支持两种操作 次:
把
子树中所有与 的距离模 等于 的节点权值加 。 查询
节点的权值。 数据范围:
。
思路分析
首先用 dfs 序转成区间加单点查询。
先对序列分块,散块暴力修改,对于整块打标记考虑对
如果
时空复杂度
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5,B=1505,LIM=200;
int n,m,d[MAXN],fa[MAXN],dfl[MAXN],dfr[MAXN],dcnt;
vector <int> G[MAXN];
int a[MAXN],bel[MAXN],lp[MAXN],rp[MAXN];
int t1[205][205][205],t2[205][MAXN];
void dfs(int u,int fz) {
dfl[u]=++dcnt,d[dfl[u]]=d[dfl[fz]]+1;
for(int v:G[u]) dfs(v,u);
dfr[u]=dcnt;
}
int qry(int x) {
int s=a[x];
for(int i=1;i<=LIM;++i) s+=t1[bel[x]][i][d[x]%i];
for(int i=1;i<=bel[x];++i) s+=t2[i][d[x]];
return s;
}
void upd(int l,int r,int x,int y,int z) {
if(bel[l]==bel[r]) {
for(int i=l;i<=r;++i) if(d[i]%x==y) a[i]+=z;
return ;
}
for(int i=l;i<=rp[bel[l]];++i) if(d[i]%x==y) a[i]+=z;
for(int i=lp[bel[r]];i<=r;++i) if(d[i]%x==y) a[i]+=z;
if(bel[r]-bel[l]>1) {
if(x<=LIM) {
for(int i=bel[l]+1;i<=bel[r]-1;++i) t1[i][x][y]+=z;
} else {
for(;y<=n;y+=x) t2[bel[l]+1][y]+=z,t2[bel[r]][y]-=z;
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=2,fz;i<=n;++i) cin>>fz,G[fz].push_back(i);
dfs(1,0);
for(int i=1;i<=(n-1)/B+1;++i) {
lp[i]=(i-1)*B+1,rp[i]=min(i*B,n);
fill(bel+lp[i],bel+rp[i]+1,i);
}
for(int op,u,x,y,z;m--;) {
cin>>op>>u;
if(op==1) cin>>x>>y>>z,upd(dfl[u],dfr[u],x,(y+d[dfl[u]])%x,z);
else cout<<qry(dfl[u])<<"\n";
}
return 0;
}
*G. [P6579] Pair
题目大意
给定
排列, 次询问 区间,值域在 中的顺序对个数。 数据范围:
。
思路分析
先对序列分块,拆成包含散块的贡献,以及整块内部的贡献。
先考虑一个散块对一个区间的贡献,可以差分成前缀
然后要计算单个整块内和若干整块间
先考虑如何分别计算
逐块处理,由于每个块只有
然后考虑两个整块间的顺序对贡献,对值域差分,分别计算值域
第一部分可以对值域扫描线,每次加入一个元素,动态计算每对块间的逆序对个数。
维护
然后要计算第一个元素
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MAXQ=2e5+5,B=320,S=325;
struct SqrtTree {
int s1[MAXN],s2[MAXN],s3[MAXN];
inline void init() {
memset(s1,0,sizeof(s1)),memset(s2,0,sizeof(s2)),memset(s3,0,sizeof(s3));
}
inline void add(const int &x) {
for(int i=((x>>6)<<6);i<=x;++i) ++s1[i];
for(int i=((x>>12)<<6);i<(x>>6);++i) ++s2[i];
for(int i=0;i<(x>>12);++i) ++s3[i];
}
inline int q(const int &x) { return s1[x]+s2[x>>6]+s3[x>>12]; }
inline int qry(const int &l,const int &r) { return q(l)-q(r+1); }
} TR;
struct info { int l,r,x,y,i; } q[MAXQ];
int n,m,a[MAXN],p[MAXN],bl[MAXN],lp[S],rp[S];
ll ans[MAXQ];
namespace SL { //left in small
vector <info> f[MAXN];
vector <int> g[S];
void main() {
TR.init();
for(int i=1;i<=n;++i) {
TR.add(a[i]);
for(int x:g[bl[i]]) if(q[x].l<=i&&i<=q[x].r&&q[x].x<=a[i]&&a[i]<=q[x].y) {
ans[x]-=TR.qry(a[i],q[x].y);
}
for(auto o:f[i]) for(int x=o.l;x<=o.r;++x) if(o.x<=a[x]&&a[x]<=o.y) {
ans[o.i]+=TR.qry(a[x],o.y);
}
}
}
}
namespace SR { //right in small
vector <info> f[MAXN];
vector <int> g[S];
void main() {
TR.init();
for(int i=n;i>=1;--i) {
TR.add(a[i]);
for(int x:g[bl[i]]) if(q[x].l<=i&&i<=q[x].r&&q[x].x<=a[i]&&a[i]<=q[x].y) {
ans[x]-=TR.qry(q[x].x,a[i]);
}
for(auto o:f[i]) for(int x=o.l;x<=o.r;++x) if(o.x<=a[x]&&a[x]<=o.y) {
ans[o.i]+=TR.qry(o.x,a[x]);
}
}
}
}
namespace B2 { //cross block
vector <info> f[MAXN];
int c[S];
ll w[S][S];
void main() {
for(int i=1;i<=n;++i) {
int b=bl[p[i]];
++c[b];
for(int j=b-1,s=0;j;--j) s+=c[j],w[j][b]+=s;
for(auto x:f[i]) {
ll s=0;
for(int j=x.r;j>x.l;--j) s+=w[x.l][j];
ans[x.i]+=x.x*s;
}
}
}
}
namespace B1 { //in same block
vector <int> g[S];
int vl[B+5],rk[MAXN],pr[MAXN],sf[MAXN],c[MAXN],lst[MAXQ];
ll w[B+5][B+5];
void solve(int b) {
const int L=lp[b],R=rp[b];
int k=0;
for(int i=L;i<=R;++i) vl[++k]=a[i];
sort(vl+1,vl+k+1);
memset(c,0,sizeof(c));
memset(pr,0,sizeof(pr));
memset(sf,0,sizeof(sf));
for(int i=L;i<=R;++i) {
rk[i]=lower_bound(vl+1,vl+k+1,a[i])-vl;
pr[a[i]]=sf[a[i]]=rk[i],++c[a[i]];
}
for(int i=1;i<=n;++i) c[i]+=c[i-1];
for(int i=1;i<=n;++i) if(!pr[i]) pr[i]=pr[i-1];
for(int i=n;i>=1;--i) if(!sf[i]) sf[i]=sf[i+1];
memset(w,0,sizeof(w));
for(int i=L;i<=R;++i) for(int j=i+1;j<=R;++j) if(a[i]<a[j]) ++w[rk[i]][rk[j]];
for(int i=1;i<=k;++i) for(int j=i+1;j<=k;++j) w[i][j]+=w[i][j-1];
for(int j=1;j<=k;++j) for(int i=j-1;i>=1;--i) w[i][j]+=w[i+1][j];
for(int i=1;i<=m;++i) {
int x=q[i].x,y=q[i].y,lx=bl[q[i].l],rx=bl[q[i].r];
if(lx<b&&b<rx) {
ans[i]+=w[sf[x]][pr[y]];
if(lx+1<b) { //erase [1,a) + [a,b] between block
ans[i]-=(c[y]-c[x-1])*(TR.qry(1,x-1)-lst[i]);
}
}
}
for(int i=L;i<=R;++i) TR.add(a[i]);
for(int x:g[b]) lst[x]=TR.qry(1,q[x].x-1);
}
void main() {
TR.init();
for(int i=1;i<=bl[n];++i) solve(i);
}
}
signed main() {
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;++i) cin>>a[i],p[a[i]]=i;
for(int i=1;i<=(n-1)/B+1;++i) {
lp[i]=(i-1)*B+1,rp[i]=min(i*B,n);
fill(bl+lp[i],bl+rp[i]+1,i);
}
for(int i=1,l,r,x,y;i<=m;++i) {
cin>>l>>r>>x>>y,q[i]={l,r,x,y,i};
if(bl[l]==bl[r]) {
SL::f[r].push_back(q[i]);
SL::g[bl[l]].push_back(i);
continue;
}
int lx=bl[l],rx=bl[r];
SL::f[r].push_back({l,rp[lx],x,y,i});
SL::g[lx].push_back(i);
SR::f[rp[lx]+1].push_back({lp[rx],r,x,y,i});
SR::g[rx].push_back(i);
if(rx-lx>1) B1::g[lx].push_back(i);
if(rx-lx>2) {
B2::f[x-1].push_back({lx+1,rx-1,-1,0,i});
B2::f[y].push_back({lx+1,rx-1,1,0,i});
}
}
SL::main(),SR::main(),B2::main(),B1::main();
for(int i=1;i<=m;++i) cout<<ans[i]<<"\n";
return 0;
}
Round #23 - 2024.10.21
A. [AGC003E] Repeat
题目大意
给定序列
,初始 , 次操作,每次操作给定 ,将 变成 ,求最终每种元素出现次数。 数据范围:
。
思路分析
对于相邻的两次操作
设最终的操作为
停止递归时
暴力模拟,用 map
把本质相同操作合并,此时会访问
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
int n=1,N,q;
ll a[MAXN],s[MAXN];
signed main() {
scanf("%lld%d",&a[1],&q),N=a[1];
for(int i=1;i<=q;++i) {
ll x; scanf("%lld",&x);
while(n&&a[n]>=x) --n;
a[++n]=x;
}
map <ll,ll> o;
o[a[n]]=1;
while(o.size()) {
ll x=o.rbegin()->first,k=o.rbegin()->second;
o.erase(--o.end());
if(x<=a[1]) s[x]+=k;
else {
int i=lower_bound(a+1,a+n+1,x)-a-1;
o[a[i]]+=(x/a[i])*k;
if(x%a[i]) o[x%a[i]]+=k;
}
}
for(int i=N;i>=1;--i) s[i]+=s[i+1];
for(int i=1;i<=N;++i) printf("%lld\n",s[i]);
return 0;
}
B. [AGC004E] Robot
题目大意
矩阵上有若干机器人和一个出口,每次操作可以让所有机器人同时朝一个方向动一步。 机器人越过边界或到出口时消失,求最多能让多少个机器人到出口。
数据范围:
。
思路分析
看成出口和边界在无穷大网格上移动,边界范围外的点全部删除。
容易发现出口运动的范围一定是一个矩形,设这个矩形四个边界距离出口长度为
那么前
dp 维护
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define si short
using namespace std;
inline void chkmax(si &x,const si &y) { x=x>y?x:y; }
char s[105][105];
int n,m,x,y;
si R[105][105],C[105][105],f[105][105][105][105],ans;
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%s",s[i]+1);
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
R[i][j]=R[i][j-1]+(s[i][j]=='o');
C[i][j]=C[i-1][j]+(s[i][j]=='o');
if(s[i][j]=='E') x=i,y=j;
}
for(int u=0;u<x;++u) for(int d=0;d<=n-x;++d) for(int l=0;l<y;++l) for(int r=0;r<=m-y;++r) {
si z=f[u][d][l][r]; ans=max(ans,z);
if(u+d<x-1) chkmax(f[u+1][d][l][r],z+R[x-u-1][min(y+r,m-l)]-R[x-u-1][max(y-l-1,r)]);
if(u+d<n-x) chkmax(f[u][d+1][l][r],z+R[x+d+1][min(y+r,m-l)]-R[x+d+1][max(y-l-1,r)]);
if(l+r<y-1) chkmax(f[u][d][l+1][r],z+C[min(x+d,n-u)][y-l-1]-C[max(x-u-1,d)][y-l-1]);
if(l+r<m-y) chkmax(f[u][d][l][r+1],z+C[min(x+d,n-u)][y+r+1]-C[max(x-u-1,d)][y+r+1]);
}
printf("%d",(int)ans);
return 0;
}
C. [AGC003F] Fraction
题目大意
给定
黑白网格 ,从一个黑格开始进行 次迭代,每次迭代把所有黑格换成 ,白格换成 全白矩阵。 保证初始
中黑格连通,求最终黑格连通块数量。 数据范围:
。
思路分析
考虑从任意矩阵开始进行一次递归:
-
如果
的第一列和最后一列连通,第一行和最后一行连通,那么连通块数量不变,最终答案为 。 -
如果行列都不连通,那么连通块数变为黑格数量,最终答案为
,其中 为 黑格数量。 -
如果只有行连通(只有列连通同理),那么连通块数变为每行极长连续段数量,即黑格数量减去左右都是黑格的点对数量。
左右都是黑格的点对数量容易维护:一次迭代会把每个黑格加上
内部点对数量,且原网格的一对点对会变成 中第一列和最后一列的点对数量,容易用矩阵表示转移。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef array<array<ll,2>,2> Mat;
const int MAXN=1005,MOD=1e9+7;
Mat operator *(const Mat &u,const Mat &v) {
Mat w{0,0,0,0};
for(int i:{0,1}) for(int j:{0,1}) for(int k:{0,1}) w[i][j]=(w[i][j]+u[i][k]*v[k][j])%MOD;
return w;
}
Mat A,B;
ll ksm(ll a,ll b) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int n,m; ll K,z=0;
char s[MAXN][MAXN];
signed main() {
scanf("%d%d%lld",&n,&m,&K);
for(int i=1;i<=n;++i) scanf("%s",s[i]+1);
int t1=0,t2=0;
for(int i=1;i<=m;++i) t1+=(s[1][i]=='#'&&s[n][i]=='#');
for(int i=1;i<=n;++i) t2+=(s[i][1]=='#'&&s[i][m]=='#');
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) z+=s[i][j]=='#';
if(t1&&t2) puts("1");
else if(t1) {
int c=0;
for(int i=1;i<n;++i) for(int j=1;j<=m;++j) c+=(s[i][j]=='#'&&s[i+1][j]=='#');
A={z,c,0,t1},B={1,0,0,0};
for(--K;K;K>>=1,A=A*A) if(K&1) B=B*A;
printf("%lld\n",(B[0][0]+MOD-B[0][1])%MOD);
}
else if(t2) {
int c=0;
for(int i=1;i<=n;++i) for(int j=1;j<m;++j) c+=(s[i][j]=='#'&&s[i][j+1]=='#');
A={z,c,0,t2},B={1,0,0,0};
for(--K;K;K>>=1,A=A*A) if(K&1) B=B*A;
printf("%lld\n",(B[0][0]+MOD-B[0][1])%MOD);
}
else printf("%lld\n",ksm(z,K-1));
return 0;
}
D. [AGC004F] Reverse
题目大意
给定
个点的树或基环树,每个点初始是白色,每次操作可以把相邻的两个同色点反转颜色,求把所有点变成黑色的最小操作次数。 数据范围:
。
思路分析
先考虑树的情况,对树黑白染色,把黑色位置上的点颜色取反,此时相当于交换相邻的两个点颜色,最终交换所有点颜色。
首先要求每个点颜色相等,然后可以把黑点看成棋子,白点看成空位,可以移动棋子到空位,目标是把所有棋子放到空位上。
分析每条边的贡献,容易发现最小值就是每个子树内棋子与空位个数差的绝对值之和。
构造时先尽可能进行内部匹配,剩余的棋子 / 空位就向外界移动。
然后考虑基环树,如果是偶环,依然不能改变黑白棋子个数,假设额外边是返祖边,且有
如果是奇环,只能给某种颜色的棋子个数
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
vector <int> G[MAXN];
int n,m,dsu[MAXN],f[MAXN],d[MAXN],fa[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void dfs(int u,int fz) {
d[u]=d[fz]+1,f[u]=(d[u]&1?-1:1),fa[u]=fz;
for(int v:G[u]) if(v^fz) dfs(v,u),f[u]+=f[v];
}
signed main() {
scanf("%d%d",&n,&m);
int rt=1,o=0;
iota(dsu+1,dsu+n+1,1);
for(int i=1,u,v;i<=m;++i) {
scanf("%d%d",&u,&v);
if(find(u)!=find(v)) dsu[u]=v,G[u].push_back(v),G[v].push_back(u);
else rt=u,o=v;
}
dfs(rt,0);
if(n==m) {
if(d[o]&1) {
if(f[rt]&1) return puts("-1"),0;
for(int x=o;x;x=fa[x]) f[x]-=f[rt]>>1;
} else {
if(f[rt]) return puts("-1"),0;
vector <int> w;
for(int x=o;x;x=fa[x]) w.push_back(f[x]);
sort(w.begin(),w.end());
int z=w[(d[o]-1)/2];
for(int x=o;x;x=fa[x]) f[x]-=z;
}
} else if(f[rt]) return puts("-1"),0;
long long ans=0;
for(int i=1;i<=n;++i) ans+=abs(f[i]);
printf("%lld\n",ans);
return 0;
}
*E. [AGC001F] Swap
题目大意
给定
排列 ,如果 且 ,可以交换 ,求能得到的最小字典序排列。 数据范围:
。
思路分析
考虑逆排列
可以证明这样的每一组
把限制放回到
那么把
然后满足字典序限制,每次找到最大的没有出度的点放在序列末尾。
一个点
并且取出一个
用线段树求最大值,堆取出每次最大的
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int n,K,a[MAXN];
struct zkw {
static const int N=1<<19;
int tr[N<<1];
int cmp(int x,int y) { return a[x]>a[y]?x:y; }
void init() {
for(int i=1;i<=n;++i) tr[i+N]=i;
for(int i=N-1;i;--i) tr[i]=cmp(tr[i<<1],tr[i<<1|1]);
}
void del(int x) {
for(tr[x+=N]=0,x>>=1;x;x>>=1) tr[x]=cmp(tr[x<<1],tr[x<<1|1]);
}
int qry(int l,int r) {
l=max(l,1),r=min(r,n);
int s=0;
for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) {
if(~l&1) s=cmp(s,tr[l^1]);
if(r&1) s=cmp(s,tr[r^1]);
}
return s;
}
} T;
int w[MAXN];
bool inq[MAXN];
priority_queue <int> q;
void ins(int x) {
if(x&&!inq[x]&&T.qry(x-K+1,x+K-1)==x) q.push(x),inq[x]=true;
}
signed main() {
scanf("%d%d",&n,&K);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
T.init();
for(int i=1;i<=n;++i) ins(i);
for(int i=n;i;--i) {
int u=q.top(); q.pop(),w[u]=i,T.del(u);
ins(T.qry(u-K+1,u));
ins(T.qry(u,u+K-1));
}
for(int i=1;i<=n;++i) printf("%d\n",w[i]);
return 0;
}
Round #24 - 2024.10.22
A. [AGC005D] Permutation
题目大意
计数有多少个
排列 满足所有 。 数据范围:
。
思路分析
建立
考虑容斥,需要计算在这些不能选的位置中选
而这些不能选的点只有
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4005,MOD=924844033;
bool a[MAXN];
ll f[MAXN][MAXN],fac[MAXN];
signed main() {
int n,k,p=0;
scanf("%d%d",&n,&k);
a[++p]=1;
for(int i=0;i<n%k;++i) a[p+=n/k]=1,a[p+=n/k]=1;
for(int i=0;i<k-n%k;++i) a[p+=n/k-1]=1,a[p+=n/k-1]=1;
f[0][0]=1;
for(int i=1;i<p;++i) {
for(int j=0;j<=i;++j) {
f[i][j]=f[i-1][j];
if(j) f[i][j]=(f[i][j]+f[i-2+a[i]][j-1])%MOD;
}
}
ll ans=0;
for(int i=fac[0]=1;i<=n;++i) fac[i]=fac[i-1]*i%MOD;
for(int i=0;i<=n;++i) ans=(ans+(i&1?-1:1)*fac[n-i]*f[p-1][i])%MOD;
printf("%lld\n",(ans+MOD)%MOD);
return 0;
}
B. [AGC006D] Median
题目大意
给定
排列,每次操作同时把所有 换成 的中位数(删除开头结尾),求最终的 。 数据范围:
。
思路分析
考虑二分,只要做值域为
我们发现如果
因此如果
否则序列形如
如果此时
不断重复判断
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,a[MAXN];
bool b[MAXN];
bool chk(int x) {
for(int i=1;i<2*n;++i) b[i]=a[i]>=x;
for(int i=1;i<n;++i) {
if(b[n-i]==b[n]||b[n+i]==b[n]) return b[n];
b[n]^=1;
}
return b[n];
}
signed main() {
scanf("%d",&n);
for(int i=1;i<2*n;++i) scanf("%d",&a[i]);
int l=1,r=2*n-1,p=0;
while(l<=r) {
int mid=(l+r)>>1;
if(chk(mid)) p=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",p);
return 0;
}
C. [AGC005E] Chase
题目大意
给定两棵树,A 和 B 分别有一棵棋子在树上轮流运动,初始在
,A 可以把棋子沿第一棵树的边移动或不动,B 可以把棋子沿第二棵树的边移动或不动。 如果两人到相同点游戏结束,A 要最大化游戏轮数,B 要最小化游戏轮数,输出最终轮数。
数据范围:
。
思路分析
在第二棵树上考虑,如果第一棵树中有一条边在第二棵树上距离
否则在第二棵树以
因此对于一个点
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
vector <int> A[MAXN],B[MAXN];
int fa[MAXN],d[MAXN];
void dfs0(int u) {
for(int v:B[u]) if(v^fa[u]) fa[v]=u,d[v]=d[u]+1,dfs0(v);
}
int z=0;
bool e[MAXN];
void dfs1(int u,int fz,int s) {
if(e[u]) puts("-1"),exit(0);
z=max(z,d[u]*2);
for(int v:A[u]) if(v!=fz&&s+1<d[v]) dfs1(v,u,s+1);
}
signed main() {
int n,a,b;
scanf("%d%d%d",&n,&a,&b);
for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),A[u].push_back(v),A[v].push_back(u);
for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),B[u].push_back(v),B[v].push_back(u);
dfs0(b);
for(int u=1;u<=n;++u) for(int v:A[u]) if(d[u]>=d[v]) {
if((d[u]-d[v]>2)||(fa[u]!=fa[v]&&fa[u]!=v&&fa[fa[u]]!=v)) e[u]=e[v]=true;
}
dfs1(a,0,0);
printf("%d",z);
return 0;
}
*D. [AGC006E] Rotate
题目大意
给定
网格,每次旋转 子矩阵 ,判定是否能得到给定目标网格。 数据范围:
。
思路分析
翻转
因此每一列的内部元素不变,只有可能反转,那么每列都必须是
我们只关心每一列的
那么我们可以交换
使得
只需要调整每个元素的符号,我们发现可以按如下的方式操作:
1 2 3 4 5
-3 -2 -1 4 5 [1,3]
-3 -2 -5 -4 1 [3,5]
5 2 3 -4 1 [1,3]
5 2 -1 4 -3 [3,5]
1 -2 5 4 -3 [1,3]
1 -2 3 -4 5 [3,5]
此时可以取反距离为
并且对于
1 2 3 4
-3 -2 -1 4 [1,3]
-3 -4 1 2 [2,4]
-1 4 3 2 [1,3]
-1 -2 -3 -4 [2,4]
因此只要奇数下标和偶数下标中符号错误的元素分别有偶数个即可。
交换奇数下标的两个元素,会让偶数下标中符号错误的元素
我们知道排列奇偶性等于
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,a[MAXN][3],p[MAXN];
bool vis[MAXN],x[2];
signed main() {
scanf("%d",&n);
for(int i:{0,1,2}) for(int j=1;j<=n;++j) scanf("%d",&a[j][i]);
for(int i=1;i<=n;++i) {
if(a[i][1]==a[i][0]+1&&a[i][2]==a[i][1]+1) p[i]=(a[i][1]+1)/3;
else if(a[i][1]==a[i][0]-1&&a[i][2]==a[i][1]-1) p[i]=-(a[i][1]+1)/3;
else return puts("No"),0;
if((i-p[i])&1) return puts("No"),0;
if(p[i]<0) x[i&1]^=1,p[i]*=-1;
}
for(int i=1;i<=n;++i) if(!vis[i]) {
int k=0;
for(int j=i;!vis[j];j=p[j]) vis[j]=true,++k;
x[(i&1)^1]^=(k-1)&1;
}
puts(x[0]||x[1]?"No":"Yes");
return 0;
}
E. [AGC010E] Coprime
题目大意
给定
,重排 ,使得按如下操作得到的最大字典序序列字典序最小:
- 对于
,交换 。 数据范围:
。
思路分析
对于一个序列,能得到的最大字典序序列可以刻画:如果
那么把
对每个连通块分别构造,我们先找到最小值
然后将
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005;
vector <int> G[MAXN],E[MAXN];
int n,a[MAXN],d[MAXN];
bool vis[MAXN];
void dfs(int u) {
vis[u]=true;
for(int v:G[u]) if(!vis[v]) E[u].push_back(v),++d[v],dfs(v);
}
signed main() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) if(__gcd(a[i],a[j])>1) G[i].push_back(j),G[j].push_back(i);
for(int i=1;i<=n;++i) if(!vis[i]) dfs(i);
priority_queue <int> q;
for(int i=1;i<=n;++i) if(!d[i]) q.push(i);
while(q.size()) {
int u=q.top(); q.pop();
printf("%d ",a[u]);
for(int v:E[u]) if(!--d[v]) q.push(v);
}
puts("");
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示