A. Hacker Cups and Balls
二分答案,將$\geq mid$的數看成$1$,$<mid$的數看成$0$,用線段樹進行區間排序檢查即可。時間複雜度$O(n\log^2n)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=100010,M=262150; int n,m,i,a[N],e[N][2],l,r,MID,ans; int len[M],c1[M],tag[M]; inline void tag1(int x,int p){ c1[x]=p?len[x]:0; tag[x]=p; } inline void pb(int x){ if(~tag[x]){ tag1(x<<1,tag[x]); tag1(x<<1|1,tag[x]); tag[x]=-1; } } inline void up(int x){ c1[x]=c1[x<<1]+c1[x<<1|1]; } void build(int x,int a,int b){ len[x]=b-a+1; tag[x]=-1; if(a==b){ c1[x]=::a[a]>=MID; return; } int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid+1,b); up(x); } int ask(int x,int a,int b,int c,int d){ if(c<=a&&b<=d)return c1[x]; pb(x); int mid=(a+b)>>1,t=0; if(c<=mid)t=ask(x<<1,a,mid,c,d); if(d>mid)t+=ask(x<<1|1,mid+1,b,c,d); return t; } void change(int x,int a,int b,int c,int d,int p){ if(c<=a&&b<=d){tag1(x,p);return;} pb(x); int mid=(a+b)>>1; if(c<=mid)change(x<<1,a,mid,c,d,p); if(d>mid)change(x<<1|1,mid+1,b,c,d,p); up(x); } bool check(){ build(1,1,n); for(i=1;i<=m;i++){ int l=e[i][0],r=e[i][1]; bool u=l<r; if(l>r)swap(l,r); int len=r-l+1; int cnt=ask(1,1,n,l,r); change(1,1,n,l,r,0); if(!cnt)continue; if(u)change(1,1,n,r-cnt+1,r,1); else change(1,1,n,l,l+cnt-1,1); } return ask(1,1,n,(n+1)/2,(n+1)/2); } int main(){ scanf("%d%d",&n,&m); for(i=1;i<=n;i++)scanf("%d",&a[i]); for(i=1;i<=m;i++)scanf("%d%d",&e[i][0],&e[i][1]); l=1,r=n; while(l<=r){ MID=(l+r)>>1; if(check())l=(ans=MID)+1;else r=MID-1; } printf("%d",ans); }
B. Bored Dreamoon
設$f[i]$表示$i$的橫座標。對於$h[i]<h[j]$,若$j$在$i$前面則無解,若$i$在$j$前面則$f[j]\geq f[i]$,否則$f[j]<f[i]$。若存在環則無解,遞推求出所有$f$即可。時間複雜度$O(n^2)$。
#include<cstdio> #include<algorithm> #include<cstdlib> using namespace std; const int N=1010; int n,i,j,x,rk[N],s[N][N],ans;char ch[N]; pair<int,int>a[N]; int h,t; int q[N],d[N],f[N],g[N],v[N*N],w[N*N],nxt[N*N],ed,vis[N];bool in[N]; void NIE(){ puts("-1"); exit(0); } inline void add(int x,int y,int z){ v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed; d[y]++; } int main(){ scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d",&a[i].first),a[i].second=i; sort(a+1,a+n+1); for(i=1;i<=n;i++)rk[a[i].second]=i; for(i=1;i<=n;i++){ scanf("%s",ch+1); for(j=1;j<=n;j++){ s[rk[i]][rk[j]]=ch[j]-'0'; } } //s[i][j]=1 denotes j is in right front of i for(i=1;i<=n;i++)for(j=i+1;j<=n;j++){ if(s[i][j])NIE(); //s[i][j]=0 if(s[j][i]){//i is in right front of j add(i,j,0); //f[j]>=f[i] }else{ add(j,i,1); //f[i]>=f[j]+1 } } for(h=i=1;i<=n;i++){ f[i]=1; if(!d[i])q[++t]=i; } while(h<=t){ x=q[h++]; for(i=g[x];i;i=nxt[i]){ f[v[i]]=max(f[v[i]],f[x]+w[i]); if(!(--d[v[i]]))q[++t]=v[i]; } } if(t<n)NIE(); for(i=1;i<=n;i++)ans=max(ans,f[i]); printf("%d",ans); }
C. Crazy Dreamoon
列舉橫座標,縱座標對應的區間可以雙指標。時間複雜度$O(n^2)$。
#include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int N=2050; const double eps=1e-9; int n,i,j,ans;bool f[N][N]; int cnt; struct P{ int x,y; P(){} P(int _x,int _y){x=_x,y=_y;} P operator-(const P&b){return P(x-b.x,y-b.y);} int operator*(const P&b){return x*b.x+y*b.y;} }a,b,p,q; struct E{ double x,y; E(){} E(double _x,double _y){x=_x,y=_y;} }e[9]; inline int sgnd(double x){ if(x>eps)return 1; if(x<-eps)return -1; return 0; } inline bool cmp(const E&a,const E&b){ if(sgnd(a.x-b.x))return a.x<b.x; return a.y<b.y; } inline int sgn(int x){ if(x>0)return 1; if(x<0)return -1; return 0; } inline int cross(const P&a,const P&b){ return a.x*b.y-a.y*b.x; } inline bool point_on_segment(P p,P a,P b){ return sgn(cross(b-a,p-a))==0&&sgn((p-a)*(p-b))<=0; } inline bool has_intersection(){ int d1=sgn(cross(b-a,p-a)),d2=sgn(cross(b-a,q-a)); int d3=sgn(cross(q-p,a-p)),d4=sgn(cross(q-p,b-p)); if(d1*d2<0&&d3*d4<0)return 1; if(d1==0&&point_on_segment(p,a,b))return 1; if(d2==0&&point_on_segment(q,a,b))return 1; if(d3==0&&point_on_segment(a,p,q))return 1; if(d4==0&&point_on_segment(b,p,q))return 1; return 0; } inline void line_intersection(){ int U=cross(p-a,q-p); int D=cross(b-a,q-p); double t=1.0*U/D; e[++cnt]=E(t*(b.x-a.x)+a.x,t*(b.y-a.y)+a.y); } inline bool check(int x,int y){ //printf("check %d %d:\n",x,y); cnt=0; p=P(x,y); q=P(x+1,y); if(has_intersection())line_intersection(); q=P(x,y+1); if(has_intersection())line_intersection(); p=P(x+1,y); q=P(x+1,y+1); if(has_intersection())line_intersection(); p=P(x,y+1); if(has_intersection())line_intersection(); /*for(int i=1;i<=cnt;i++){ printf("%.8f %.8f\n",e[i].x,e[i].y); }*/ if(cnt<2)return 0; sort(e+1,e+cnt+1,cmp); int t=1; for(int i=2;i<=cnt;i++)if(e[i].x>e[i-1].x+eps||e[i].y>e[i-1].y+eps)t++; return t==2; } void solve(){ int A,B,C,D; scanf("%d%d%d%d",&A,&B,&C,&D); if(A==C||B==D)return; if(A>C)swap(A,C),swap(B,D); a=P(A,B); b=P(C,D); if(B<D){//up int L=B,R=B; //(i..i+1)(L..L+1) //(i..i+1)(R..R+1) for(int i=A;i<C;i++){ while(L<2000&&!check(i,L))L++; if(L==2000)return; if(R<L)R=L; while(R<1999&&check(i,R+1))R++; for(int j=L;j<=R;j++)f[i][j]=1; } }else{//down int L=B-1,R=B-1; //(i..i+1)(L..L+1) //(i..i+1)(R..R+1) for(int i=A;i<C;i++){ while(R>=0&&!check(i,R))R--; if(R<0)return; if(L>R)L=R; while(L>0&&check(i,L-1))L--; for(int j=L;j<=R;j++)f[i][j]=1; } } } int main(){ scanf("%d",&n); while(n--)solve(); for(i=0;i<=2000;i++)for(j=0;j<=2000;j++)if(f[i][j])ans++; printf("%d",ans); }
D. Forest Game
設$d(x,y)$為$x$到$y$路徑上點的個數,則$ans=n!\sum_{i=1}^n\sum_{j=1}^n\frac{1}{d(i,j)}$。
利用樹分治+FFT可以做到$O(n\log^2n)$。
#include<cstdio> #include<cmath> #include<algorithm> using namespace std; typedef long long ll; const int N=270000,P=1000000007; const double pi=acos(-1.0); struct comp{ double r,i; comp(double _r=0,double _i=0){r=_r;i=_i;} comp operator+(const comp&x){return comp(r+x.r,i+x.i);} comp operator-(const comp&x){return comp(r-x.r,i-x.i);} comp operator*(const comp&x){return comp(r*x.r-i*x.i,r*x.i+i*x.r);} }A[N],B[N]; int n,m,i,x,y,ed,a[N],g[N],nxt[N],v[N],ok[N],son[N],f[N],size,now; int inv[N],ans; ll cnt[N]; inline void add(int x,int y){v[++ed]=y,nxt[ed]=g[x],ok[ed]=1,g[x]=ed;} void findroot(int x,int pre){ son[x]=1;f[x]=0; for(int i=g[x];i;i=nxt[i])if(ok[i]&&v[i]!=pre){ findroot(v[i],x); son[x]+=son[v[i]]; if(son[v[i]]>f[x])f[x]=son[v[i]]; } if(size-son[x]>f[x])f[x]=size-son[x]; if(f[x]<f[now])now=x; } inline void FFT(comp a[],int n,int t){ for(int i=1,j=0;i<n-1;i++){ for(int s=n;j^=s>>=1,~j&s;); if(i<j)swap(a[i],a[j]); } for(int d=0;(1<<d)<n;d++){ int m=1<<d,m2=m<<1; double o=pi/m*t;comp _w(cos(o),sin(o)); for(int i=0;i<n;i+=m2){ comp w(1,0); for(int j=0;j<m;j++){ comp &A=a[i+j+m],&B=a[i+j],t=w*A; A=B-t;B=B+t;w=w*_w; } } } if(t==-1)for(int i=0;i<n;i++)a[i].r/=n; } inline void cal(int t){ int i,k; for(k=1;k<m+m;k<<=1); for(i=0;i<k;i++)A[i]=B[i]=comp(); for(i=1;i<=m;i++)A[i]=comp(a[i],0),B[i-1]=comp(a[i],0); FFT(A,k,1),FFT(B,k,1); for(i=0;i<k;i++)A[i]=A[i]*B[i]; FFT(A,k,-1); for(i=2;i<m+m;i++){ ll o=(ll)(A[i].r+0.5); if(t>0)cnt[i]+=o;else cnt[i]-=o; } for(i=1;i<=m;i++)a[i]=0;m=0; } void dfs(int x,int pre,int d){ a[d]++;if(d>m)m=d; for(int i=g[x];i;i=nxt[i])if(ok[i]&&v[i]!=pre)dfs(v[i],x,d+1); } void solve(int x){ int i; dfs(x,0,1),cal(1); for(i=g[x];i;i=nxt[i])if(ok[i])dfs(v[i],x,2),cal(-1); for(i=g[x];i;i=nxt[i])if(ok[i])ok[i^1]=0,f[0]=size=son[v[i]],findroot(v[i],now=0),solve(now); } int main(){ scanf("%d",&n); for(ed=i=1;i<n;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x); f[0]=size=n,findroot(1,now=0),solve(now); for(ans=n,inv[1]=1,i=2;i<=n;i++){ cnt[i]%=P; inv[i]=1LL*(P-inv[P%i])*(P/i)%P; ans=(cnt[i]*inv[i]+ans)%P; } for(i=2;i<=n;i++)ans=1LL*ans*i%P; printf("%d",ans); }
E. Lines Game
等價於選一個遞增序列,且任意兩個相鄰的點$(i,p[i]),(j,p[j])$中間的矩形內部沒有任何點。
設$f[i]$表示考慮前$i$個點,且$i$必選的最小代價。
考慮cdq分治,將左右的點按縱座標排序後從小到大加入,兩邊分別維護兩個關於橫座標的單調棧,然後用線段樹維護單調棧中的DP值。
時間複雜度$O(n\log^2n)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=100010,inf=~0U>>1; int n,i,a[N],w[N],f[N],v[262150]; int ca,cb,qa[N],qb[N],ta,tb,sa[N],sb[N]; inline bool cmp(int x,int y){return a[x]<a[y];} void build(int x,int a,int b){ v[x]=inf; if(a==b)return; int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid+1,b); } void change(int x,int a,int b,int c,int p){ if(a==b){v[x]=p;return;} int mid=(a+b)>>1; c<=mid?change(x<<1,a,mid,c,p):change(x<<1|1,mid+1,b,c,p); v[x]=min(v[x<<1],v[x<<1|1]); } int ask(int x,int a,int b,int c,int d){ if(c<=a&&b<=d)return v[x]; int mid=(a+b)>>1,t=inf; if(c<=mid)t=ask(x<<1,a,mid,c,d); if(d>mid)t=min(t,ask(x<<1|1,mid+1,b,c,d)); return t; } void solve(int l,int r){ if(l==r){ if(f[l]<inf)f[l]+=w[l]; return; } int mid=(l+r)>>1; solve(l,mid); int i,j; ca=cb=0; for(i=l;i<=mid;i++)qa[++ca]=i; for(i=r;i>mid;i--)qb[++cb]=i; sort(qa+1,qa+ca+1,cmp); sort(qb+1,qb+cb+1,cmp); ta=tb=0; for(i=j=1;i<=cb;i++){ while(j<=ca&&a[qa[j]]<a[qb[i]]){ while(ta&&qa[j]>sa[ta]){ change(1,0,n,a[sa[ta]],inf); ta--; } sa[++ta]=qa[j]; change(1,0,n,a[qa[j]],f[qa[j]]); j++; } while(tb&&qb[i]<sb[tb])tb--; f[qb[i]]=min(f[qb[i]],ask(1,0,n,a[sb[tb]],a[qb[i]])); sb[++tb]=qb[i]; } for(i=1;i<=ta;i++)change(1,0,n,a[sa[i]],inf); solve(mid+1,r); } int main(){ scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d",&a[i]); for(i=1;i<=n;i++)scanf("%d",&w[i]); n++; a[n]=n; for(i=1;i<=n;i++)f[i]=inf; build(1,0,n); solve(0,n); printf("%d",f[n]); }
F. Lonely Dreamoon 2
將序列排序。
當$n$是偶數時從中間劈開,然後對應排名相互配對。
當$n$是奇數時列舉shift的次數,取最優的進行配對。
#include<cstdio> #include<algorithm> using namespace std; const int N=200010; int n,m,i,j,x,y,a[N],w[N]; int main(){ scanf("%d",&n); for(i=0;i<n;i++)scanf("%d",&w[i]); sort(w,w+n); if(n&1){ m=n/2; for(i=j=m;i<n;i++)if(w[i]-w[i-m]<w[j]-w[j-m])j=i; x=j,y=(x+m)%n; for(i=0;i<n;i+=2,x=(x+n-1)%n)a[i]=x; for(i=1;i<n;i+=2,y=(y+n-1)%n)a[i]=y; }else{ for(i=1,j=0;i<n;i+=2,j++)a[i]=j; for(i=0;i<n;i+=2,j++)a[i]=j; } for(i=0;i<n;i++)printf("%d ",w[a[i]]); }
G. Dreamoon and NightMarket
將食物從小到大排序,用堆記錄$($代價$,$最貴的食物$)$,每次要麼新加入一個更貴的食物,要麼把最貴的換成更貴的。時間複雜度$O(n\log n+k\log k)$。
#include<cstdio> #include<algorithm> #include<queue> #include<vector> using namespace std; typedef long long ll; typedef pair<ll,int>P; int n,m,i,a[200010];P t;priority_queue<P,vector<P>,greater<P> >q; int main(){ scanf("%d%d",&n,&m); for(i=1;i<=n;i++)scanf("%d",&a[i]); sort(a+1,a+n+1); q.push(P(a[1],1)); while(m--){ t=q.top(); q.pop(); if(t.second==n)continue; q.push(P(t.first-a[t.second]+a[t.second+1],t.second+1)); q.push(P(t.first+a[t.second+1],t.second+1)); } printf("%I64d",t.first); }
H. Split Game
把點極角排序之後掃描線,考慮每個角對區域個數的影響。時間複雜度$O(n\log n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=100010; int n,i,j,b[N],t,x,ans; struct P{ int x,y; P(){} P(int _x,int _y){x=_x,y=_y;} P operator-(const P&b){return P(x-b.x,y-b.y);} }a[N]; inline ll cross(const P&a,const P&b){return 1LL*a.x*b.y-1LL*a.y*b.x;} inline bool cmp(int x,int y){ return cross(a[x],a[y])>0; } int main(){ scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y); a[0]=a[n]; a[n+1]=a[1]; for(i=1;i<=n;i++)b[i]=i; sort(b+1,b+n+1,cmp); for(i=1;i<=n;i=j){ x=0; for(j=i;j<=n&&!cross(a[b[i]],a[b[j]]);j++){ if(cross(a[b[j]+1],a[b[j]])>0&&cross(a[b[j]-1],a[b[j]])>=0){ if(cross(a[b[j]-1]-a[b[j]],a[b[j]+1]-a[b[j]])<0)t--; else x--; } if(cross(a[b[j]-1],a[b[j]])<0&&cross(a[b[j]+1],a[b[j]])<=0){ if(cross(a[b[j]-1]-a[b[j]],a[b[j]+1]-a[b[j]])>0)t++; else x++; } } ans=max(ans,t); t+=x; ans=max(ans,t); } printf("%d",ans+1); }
I. Tree Game
貪心,記錄每個子樹裡尚未配對的葉子數。
若有多於兩棵子樹尚未配對的葉子數為$1$,那麼這些只能配掉。
若有多於一棵子樹尚未配對的葉子數為$2$,那麼這些只能配掉。
若有多於一棵子樹尚未配對的葉子數為$1$,那麼先考慮能否去和葉子數為$2$的子樹進行配對。
否則最多向父親貢獻$2$個葉子。
時間複雜度$O(n)$。
#include<cstdio> const int N=100010; int n,i,x,y,g[N],v[N<<1],nxt[N<<1],ed,d[N],ans; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;d[x]++;} int dfs(int x,int y){ if(d[x]==1)return 1; int A=0,B=0; for(int i=g[x];i;i=nxt[i])if(v[i]!=y){ int t=dfs(v[i],x); if(t==1)A++; if(t==2)B++; } while(A>2)ans++,A-=2; while(B>1)ans++,B-=2; while(A>1&&B)ans++,A--,B--; A+=B*2; return A<2?A:2; } int main(){ scanf("%d",&n); if(n<=3)return puts("1"),0; for(i=1;i<n;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x); for(i=1;i<=n;i++)if(d[i]>1){ if(dfs(i,0)==2)ans++; return printf("%d",ans),0; } }
J. Zero Game
列舉一個區間,踢掉裡面所有的$1$,然後剩下的機會都從外面拿$0$進來。
設$s[i]$表示前$i$個裡有多少個$1$,那麼對於區間$[l+1,r]$,首先要滿足$s[r]-s[l]\leq k$,答案為$r-l-2(s[r]-s[l])+k$,即$(r-2s[r])-(l-2s[l])+k$。
列舉$l$,雙指標出可行的$r$,然後用單調佇列求出最優的$r$即可。
時間複雜度$O(nq)$。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=1000010; int n,m,k,i,j,s[N],f[N],h,t,q[N],ans;char a[N]; int main(){ scanf("%s",a+1); n=strlen(a+1); for(i=1;i<=n;i++)s[i]=s[i-1]+(a[i]=='1'); for(i=0;i<=n;i++)f[i]=i-s[i]*2; scanf("%d",&m); while(m--){ scanf("%d",&k); h=1,t=0; ans=-N*10; for(i=j=0;i<=n;i++){ while(j<=n&&s[j]-s[i]<=k){ while(h<=t&&f[j]>=f[q[t]])t--; q[++t]=j++; } while(h<=t&&q[h]<=i)h++; if(h<=t)ans=max(ans,f[q[h]]-f[i]); } ans+=k; ans=max(ans,0); ans=min(ans,n-s[n]); printf("%d\n",ans); } }