2018.9.11--2018.10.28BZOJ好题总结
BZOJ 4282 慎二的随机数列
https://www.lydsy.com/JudgeOnline/problem.php?id=4282
JZYshuraK考试题目,当时写了暴力。
刚刚考试完看了Claris的题解心里大骂JZY,讲完题解后感到豁然开朗。
我们对于每一个K都减去它前面N的个数,之后重新跑LIS。
原因是我们对于每一个N一定要选,但是它会与某些K相同,这样我们用K减去N就是最简便的方式了。
1 #include<stdio.h> 2 int n; 3 char b[2]; 4 int a[100001],idx; 5 int f[100001],x,ans; 6 int main() 7 { 8 scanf("%d",&n); 9 for(int i=1;i<=n;i++) 10 { 11 scanf("%s",b); 12 if(b[0]=='K') 13 { 14 scanf("%d",&x); 15 a[++idx]=x-ans; 16 } 17 else 18 ans++; 19 } 20 f[1]=a[1]; 21 int length=1; 22 for(int i=2;i<=idx;i++) 23 { 24 int l=1,r=length+1; 25 while(l<r) 26 { 27 int mid=(l+r)/2; 28 if(f[mid]>=a[i]) 29 r=mid; 30 else 31 l=mid+1; 32 } 33 if(l>length) 34 length++; 35 f[l]=a[i]; 36 } 37 printf("%d",length+ans); 38 }
BZOJ 1756: Vijos1083 小白逛公园
https://www.lydsy.com/JudgeOnline/problem.php?id=1756
线段树比较恶心的一题了。
求动态区间连续子段和。
我们开4个数组 max,maxl,maxr,sum。其中sum为该区间的和,max为该区间上的最大子段和,maxl为必须包含左端点的最大子段和,maxr为必须包含右端点的最大子段和。
我们对于一段区间的max数组,有几种方式更新
1.左区间的max值
2.右区间的max值
3.maxl与maxr合并
该节点的maxl有两种情况
1.为左儿子的maxl
2.左儿子sum和右儿子maxl和的最大值。
maxr同理
sum就不说了
之后就没什么了。
#include<stdio.h> #include<algorithm> #define max(a,b) ((a)>(b)?(a):(b)) using namespace std; #define N 501000 int n,m; int a[N]; struct SmallMax { int sum,maxL,maxR,maxX; }smallMax[N<<2]; SmallMax operator + (const SmallMax &a,const SmallMax &b) { SmallMax ans; ans.sum=a.sum+b.sum; ans.maxL=max(a.maxL,a.sum+b.maxL); ans.maxR=max(b.maxR,b.sum+a.maxR); ans.maxX=max(a.maxR+b.maxL,a.maxX); ans.maxX=max(ans.maxX,b.maxX); return ans; } void push_up(int date){smallMax[date]=smallMax[date<<1]+smallMax[date<<1|1];} void build(int date,int l,int r) { int mid=(l+r)>>1; if(l==r) { smallMax[date].maxL=a[l]; smallMax[date].maxR=a[l]; smallMax[date].maxX=a[l]; smallMax[date].sum=a[l]; return; } build(date<<1,l,mid); build(date<<1|1,mid+1,r); push_up(date); } void update(int date,int l,int r,int to,int delta) { if(l==r) { a[l]=delta; smallMax[date].maxL=delta; smallMax[date].maxR=delta; smallMax[date].maxX=delta; smallMax[date].sum=delta; return; } int mid=(l+r)>>1; if(to<=mid) update(date<<1,l,mid,to,delta); if(to>mid) update(date<<1|1,mid+1,r,to,delta); push_up(date); } SmallMax query(int date,int l,int r,int L,int R) { if(L<=l&&R>=r) return smallMax[date]; int mid=(l+r)>>1; if(R<=mid) return query(date<<1,l,mid,L,R); else if(L>mid) return query(date<<1|1,mid+1,r,L,R); else return query(date<<1,l,mid,L,R)+query(date<<1|1,mid+1,r,L,R); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); build(1,1,n); while(m--) { int q,x,y; scanf("%d",&q); scanf("%d%d",&x,&y); if(q==1) { if(x>y) swap(x,y); printf("%d\n",query(1,1,n,x,y).maxX); } else update(1,1,n,x,y); } return 0; }
BZOJ 3039 玉蟾宫
https://www.lydsy.com/JudgeOnline/problem.php?id=3039
这道题真的是让我大开眼界了。
悬线法真是可以了。
设h(i,j)表示以(i,j)为下端点的悬线的最长长度。
预处理l(i,j)和r(i,j),它们分别表示点(i,j)能扩展到的左边和右边的最近的障碍。
L(i,j)和R(i,j)分别表示使悬线左边最近的障碍和右边最近的障碍。
答案即为max(h(i,j)*(R(i,j)-L(i,j)+1)。
说白了就是一条垂直的线在矩阵中飘荡
#include<stdio.h> #include<algorithm> using namespace std; int a[1001][1001],h[1001][1001],L[1001][1001],l[1001][1001]; int R[1001][1001],r[1001][1001]; int ans; char s[2]; int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { scanf("%s",s); if(s[0]=='F') a[i][j]=1; } } for(int i=1;i<=n;i++) { int tmp=0; for(int j=1;j<=m;j++) { if(a[i][j]) l[i][j]=tmp; else { L[i][j]=0; tmp=j; } } tmp=m+1; for(int j=m;j>=1;j--) { if(a[i][j]) r[i][j]=tmp; else { R[i][j]=m+1; tmp=j; } } } for(int i=1;i<=m+1;i++) R[0][i]=m+1; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(a[i][j]) { h[i][j]=h[i-1][j]+1; L[i][j]=max(l[i][j]+1,L[i-1][j]); R[i][j]=min(r[i][j]-1,R[i-1][j]); ans=max((R[i][j]-L[i][j]+1)*h[i][j],ans); } } } printf("%d",ans*3); }
BZOJ 3910 火车
这是我无意中发现的考试题了。
https://www.lydsy.com/JudgeOnline/problem.php?id=3910
这题显然是需要LCA的。
我们考虑倍增LCA可以迅速求解两点间的LCA,但是不知道经过哪些点。
朴素LCA可以记录经过哪些点,但是速度较慢。
我们可以取其精华去其糟粕,两者结合。
我们用倍增LCA先求出到达的点,把答案累加。
之后再用朴素LCA记录哪些点被走过,用并查集维护就好了。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N=500001; int fa[22][N]; bool vis[N]; int dep[N]; int cnt2; int head[N<<1]; int f[N]; int nex[N<<1]; int to[N<<1]; void addedge(int x,int y) { nex[++cnt2]=head[x]; head[x]=cnt2; to[cnt2]=y; } void dfs(int x,int f) { for(int i=head[x];i;i=nex[i]) if(to[i]!=f) { fa[0][to[i]]=x; dep[to[i]]=dep[x]+1; dfs(to[i],x); } } int n; void init() { for(int i=1;i<=20;i++) for(int j=1;j<=n;j++) fa[i][j]=fa[i-1][fa[i-1][j]]; } long long ans=0; int Lca(int x,int y) { if(dep[x]<dep[y]) swap(x,y); int tmp=dep[x]-dep[y]; for(int i=20;i>=0;i--) if(tmp>=(1<<i)) { tmp-=(1<<i); x=fa[i][x]; } if(x==y) return x; for(int i=20;i>=0;i--) if(fa[i][x]!=fa[i][y]) { x=fa[i][x]; y=fa[i][y]; } return fa[0][y]; } int find(int x) { if(f[x]==x) return x; return f[x]=find(f[x]); } void update(int x) { vis[x]=1; for(int i=head[x];i;i=nex[i]) if(to[i]!=fa[0][x]) f[to[i]]=x; } void Lca2(int x,int y) { int z=Lca(x,y); while(dep[x]>dep[z]) { update(x); x=fa[0][find(x)]; } while(dep[y]>dep[z]) { update(y); y=fa[0][find(y)]; } update(z); } int main() { int m, sta; scanf("%d%d%d",&n,&m,&sta); for(int i=1;i<n;i++) { int a, b; scanf("%d%d",&a,&b); addedge(a,b); addedge(b,a); } dfs(1,1); init(); int pre; pre=sta; for(int i=1;i<=n;i++) f[i]=i; for(int i=1;i<=m;i++) { int a; scanf("%d",&a); if(!vis[a]) { int z=Lca(pre,a); ans+=dep[pre]+dep[a]-2*dep[z]; Lca2(pre,a); pre=a; } } printf("%lld",ans); return 0; }
BZOJ 3714: [PA2014]Kuglarz
jiangminghong的考试题。非常巧妙的一道题,当时没想出来
询问的是区间和,我们可以将它转化为两个前缀和相减的形式,那么只要知道了[1,i-1],[1,j],[i,j]中的两个,就能得到第三个。
所以连一条(i-1,j)的边,暴力跑最小生成树就行了
#include<cstdio> #include<algorithm> using namespace std; #define N 4001 int n,idx; int fa[N]; int size[N]; struct Tree { int x,y,val; }a[N*N/2]; bool cmp(const Tree &a,const Tree &b){return a.val<b.val;} int find(int x) { if(fa[x]!=x) fa[x]=find(fa[x]); return fa[x]; } long long ans; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) { a[++idx].x=i; a[idx].y=j+1; scanf("%d",&a[idx].val); } for(int i=1;i<=n+1;i++) fa[i]=i,size[i]=1; sort(a+1,a+idx+1,cmp); int tot=0; for(int i=1;i<=idx;i++) { int fx=find(a[i].x); int fy=find(a[i].y); if(fx!=fy) { if(size[fx]>size[fy]) fa[fx]=fy; else fa[fy]=fx; tot++; ans+=a[i].val; if(tot==n) break; } } printf("%lld",ans); return 0; }
BZOJ 1821: [JSOI2010]Group 部落划分 Group
还是MST,非常巧。合成一个块当且仅当它的边权是在前n-k中的,我们只要用Kruskal输出第n-k+1条边即可。
#include<cstdio> #include<algorithm> #include<cmath> using namespace std; #define N 2001 int n,k; struct Tree { int x,y; double val; }a[N*N]; int xx[N]; int yy[N]; int size[N]; int idx; double ans=0; bool cmp(const Tree &a,const Tree &b) { return a.val<b.val; } int fa[N]; int find(int x) { if(fa[x]!=x) fa[x]=find(fa[x]); return fa[x]; } void addedge(int d,int b,double c) { a[++idx].x=d; a[idx].y=b; a[idx].val=c; } int main() { int tot=0; scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d%d",&xx[i],&yy[i]); for(int i=1;i<=n;i++) fa[i]=i,size[i]=1; for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) addedge(i,j,(double)sqrt((xx[i]-xx[j])*(xx[i]-xx[j])+(yy[i]-yy[j])*(yy[i]-yy[j]))); sort(a+1,a+idx+1,cmp); if(k==1) { printf("0"); return 0; } for(int i=1;i<=idx;i++) { int fx=find(a[i].x); int fy=find(a[i].y); if(fx!=fy) { if(size[fx]>size[fy]) fa[fy]=fx; else fa[fx]=fy; tot++; ans=a[i].val; if(tot==n-k+1) break; } } printf("%.2lf",ans); }
BZOJ 4403 序列统计
好久没做数学题了。
这道题答案显然与L,R无关,而与R-L+1有关,我们设它为m。
现在的模型就变成m个盒子中放入n个小球,答案显然是C_{n+m-1}^{m-1}
之后就变成了求和。
我们可以想到杨辉三角的递推式f[i][j]=f[i-1][j]+f[i-1][j-1];
填拆项(初中豆浆老师教的)就得到答案C_{n+m}^{m}-1;
因为P为质数,所以暴力Lucas。
#include<cstdio> #define mod 1000003 #define ll long long using namespace std; int T; ll n,l,r,fac[mod+5],ifac[mod+5]; ll lucas(ll a,ll b) { if(a<b) return 0; if(a<mod&&b<mod) return fac[a]*ifac[b]%mod*ifac[a-b]%mod; return lucas(a%mod,b%mod)*lucas(a/mod,b/mod)%mod; } int main() { scanf("%d",&T),fac[0]=1,ifac[1]=ifac[0]=1; for(ll i=1;i<mod;i++) fac[i]=fac[i-1]*i%mod; for(ll i=2;i<mod;i++) ifac[i]=(mod-mod/i)*ifac[mod%i]%mod; for(ll i=2;i<mod;i++) (ifac[i]*=ifac[i-1])%=mod; while(T--) { scanf("%lld%lld%lld",&n,&l,&r); printf("%lld\n",(lucas(n+r-l+1,r-l+1)+mod-1)%mod); } return 0; }
BZOJ 2724[Violet 6]蒲公英
在线区间众数模版题
直接Pass掉除了分块的NOIP所有数据结构。
答案显然由3种情况组成
1.不足一整段[l,L)出现过的数
2.[L,R]之间的答案
3.(R,r]之间出现过的数
我们预处理每块的答案,在查询时开一个Vector,保存所有数出现的位置,之后我们二分查找2次,得到答案。
#include <cstdio> #include <algorithm> #include <cmath> using namespace std; int block,pos[100001],sum[1001][100001],num[100001],belong[100001],n,m,ans,lim,a[1001][1001]; int start[1001],cnt[100001],l,r; int main() { scanf("%d%d",&n,&m); block=(int)sqrt(n); for(int i=1;i<=n;i++) scanf("%d",&num[i]),belong[i]=num[i]; sort(belong+1,belong+n+1); lim=unique(belong+1,belong+n+1)-belong-1; for(int i=1;i<=n;i++) { ++sum[pos[i]=(i-1)/block+1][num[i]=lower_bound(belong+1,belong+lim+1,num[i])-belong]; if(pos[i]!=pos[i-1])start[pos[i]]=i; } for(int i=1;i<=pos[n];i++) for(int j=1;j<=lim;j++) sum[i][j]+=sum[i-1][j]; for(int i=1;i<=pos[n];i++) { for(int j=start[i];j<=n;j++) { if(pos[j]!=pos[j-1]) a[i][pos[j]]=a[i][pos[j]-1]; if(++cnt[num[j]]>cnt[a[i][pos[j]]]||(cnt[num[j]]==cnt[a[i][pos[j]]]&&num[j]<a[i][pos[j]])) a[i][pos[j]]=num[j]; } for(int j=start[i];j<=n;j++) cnt[num[j]]--; } for(int i=1;i<=m;i++) { scanf("%d%d",&l,&r); l=(l+ans-1)%n+1; r=(r+ans-1)%n+1; if(l>r) l^=r^=l^=r; ans=0; if(pos[r]-pos[l]<=2) { for(int j=l;j<=r;j++) if(++cnt[num[j]]>cnt[ans]||(cnt[num[j]]==cnt[ans]&&num[j]<ans)) ans=num[j]; for(int j=l;j<=r;j++) cnt[num[j]]--; } else { ans=a[pos[l]+1][pos[r]-1]; for(int j=l;pos[j]==pos[l];j++) if(++cnt[num[j]]+sum[pos[r]-1][num[j]]-sum[pos[l]][num[j]]>cnt[ans]+sum[pos[r]-1][ans]-sum[pos[l]][ans]|| (cnt[num[j]]+sum[pos[r]-1][num[j]]-sum[pos[l]][num[j]]==cnt[ans]+sum[pos[r]-1][ans]-sum[pos[l]][ans]&& num[j]<ans)) ans=num[j]; for(int j=start[pos[r]];j<=r;j++) if(++cnt[num[j]]+sum[pos[r]-1][num[j]]-sum[pos[l]][num[j]]>cnt[ans]+sum[pos[r]-1][ans]-sum[pos[l]][ans]|| (cnt[num[j]]+sum[pos[r]-1][num[j]]-sum[pos[l]][num[j]]==cnt[ans]+sum[pos[r]-1][ans]-sum[pos[l]][ans]&& num[j]<ans)) ans=num[j]; for(int j=l;pos[j]==pos[l];j++) cnt[num[j]]--; for(int j=start[pos[r]];j<=r;j++) cnt[num[j]]--; } ans=belong[ans]; printf("%d\n",ans); } return 0; }
BZOJ 1179 Atm
这道题我们需要用tarjan+spfa(用来跑最长路)
首先要做的是把图上的点跑一边tarjan求出所有的强连通分量,把强连通分量上的点的父节点都设成该强连通分量的ROOT
之后我们把边权下传到点上。
然后将所有不在强连通分量中的点以及所有强连通分量的根为新的点,重新建图跑一下spfa求最长路
最后找出所有酒吧的父节点找出来,找出这些节点中到起点值最大的就OK了
#include<cstdio> #include<algorithm> using namespace std; #define N 500001 int n,m; int nex[N]; int head[N]; int to[N]; int money[N]; int idx; int top; int stack[N]; int dep[N]; int low[N]; int cnt; int vis[N]; int blob[N]; int sum; int num[N]; int S,sumbar; int bar[N]; int x[N],y[N]; int HEAD[N]; int TO[N]; int NEX[N]; int VAL[N]; int f[N]; int que[N]; int inq[N]; int IDX; int inz[N]; int ans; void addedge(int a,int b) { nex[++idx]=head[a]; head[a]=idx; to[idx]=b; } void ADDEDGE(int A,int B,int C) { NEX[++IDX]=HEAD[A]; HEAD[A]=IDX; TO[IDX]=B; VAL[IDX]=C; } void tarjan(int x) { dep[x]=low[x]=++cnt; stack[++top]=x; vis[x]=inz[x]=1; for(int i=head[x];i;i=nex[i]) { if(!vis[to[i]]) { tarjan(to[i]); low[x]=min(low[x],low[to[i]]); } else if(inz[to[i]]) low[x]=min(low[x],dep[to[i]]); } if(dep[x]==low[x]) { sum++; int here; do { here=stack[top--]; blob[here]=sum; money[sum]+=num[here]; inz[here]=0; }while(here!=x); } } void push(int &x) { x++; if(x==N) x=1; } void spfa(int S) { f[0]=0; int front=0,tail=0; que[tail]=0; push(tail); inq[0]=1; while(front!=tail) { int x=que[front]; push(front); inq[x]=0; for(int i=HEAD[x];i;i=NEX[i]) { if(f[TO[i]]<f[x]+VAL[i]) { f[TO[i]]=f[x]+VAL[i]; if(!inq[TO[i]]) { inq[TO[i]]=1; que[tail]=TO[i]; push(tail); } } } } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d",&x[i],&y[i]); addedge(x[i],y[i]); } for(int i=1;i<=n;i++) scanf("%d",&num[i]); scanf("%d%d",&S,&sumbar); for(int i=1;i<=sumbar;i++) scanf("%d",&bar[i]); for(int i=1;i<=n;i++) if(!vis[i]) tarjan(i); ADDEDGE(0,blob[S],money[blob[S]]); for(int i=1;i<=m;i++) if(blob[x[i]]!=blob[y[i]]) ADDEDGE(blob[x[i]],blob[y[i]],money[blob[y[i]]]); spfa(0); for(int i=1;i<=sumbar;i++) ans=max(ans,f[blob[bar[i]]]); printf("%d",ans); }
BZOJ 4412: [Usaco2016 Feb]Circular Barn
把每个点-1,跑最大连续子段和。子段和开始的点即为分界点(我不会证).
#include<algorithm> #include<cstdio> using namespace std; #define N 100010 int n,a[N],top,b[N],c[N]; long long ans; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { int x; scanf("%d",&x); while(x--) a[++top]=i; } for(int i=1;i<=n;i++) b[i]=a[i]-i; int k=0,maxn=0; for(int i=1;i<=n;i++) if(b[i]>maxn) maxn=b[i],k=i; c[k]=a[k]; for(int i=k-1;i>=1;--i) c[i]=c[i+1]-1,c[i]%=n; for(int i=k+1;i<=n;i++) c[i]=c[i-1]+1,c[i]%=n; for(int i=1;i<=n;i++) { if(c[i]<a[i]) c[i]+=n; ans+=(long long)(c[i]-a[i])*(long long)(c[i]-a[i]); } printf("%lld\n",ans); return 0; }
BZOJ 2058 [Usaco2010 Nov]Cow Photographs
JZY考试题,刚开始想偏了。
刚开始的情况即为逆序对,我们发现新序列就是ans加上i+n的贡献减去i的贡献,每个数求下最大值即可
#include<cstdio> #include<algorithm> using namespace std; #define N 100010 long long min(long long x,long long y) { if(x<y) return x; return y; } int n; int s[N]; int c[N]; int boom[N]; int pos[N]; int sum[N]; long long ans2; long long allans=1e15; struct number { int id,num; }a[N]; bool cmp(const number &a,const number &b) { if(a.id!=b.id) return a.num<b.num; return a.id<b.id; } bool cmp2(const number &a,const number &b) {return a.id<b.id;} void update(int x,int delta) { while(x<=n) { c[x]+=delta; x+=x&-x; } } int query(int x) { int ans=0; while(x) { ans+=c[x]; x-=x&-x; } return ans; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i].num); pos[a[i].num]=i; a[i].id=i; } sort(a+1,a+n+1,cmp); int idx=1; for(int i=1;i<=n;i++) { if(i!=1&&a[i].num!=a[i-1].num) idx++; boom[a[i].id]=idx; } long long ans=0; for(int i=1;i<=n;i++) { update(boom[i],1); ans+=i-query(boom[i]); sum[boom[i]]=i-query(boom[i]); } ans2=ans; for(int i=1;i<n;i++) { ans2+=n-2*pos[i]+1; allans=min(allans,ans2); } printf("%lld",allans); return 0; }