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; }