zoukankan      html  css  js  c++  java
  • 2016-2017 National Taiwan University World Final Team Selection Contest

    A. Hacker Cups and Balls

    二分答案,将$geq mid$的数看成$1$,$<mid$的数看成$0$,用线段树进行区间排序检查即可。时间复杂度$O(nlog^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:
    ",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
    ",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}^nsum_{j=1}^nfrac{1}{d(i,j)}$。

    利用树分治+FFT可以做到$O(nlog^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(nlog^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(nlog n+klog 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(nlog 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
    ",ans);
      }
    }
    

      

  • 相关阅读:
    LOJ 10160
    LOJ 10155
    2018-11-1 NOIP 模拟赛解题报告
    联考前停课集训随笔
    一个博客园代码高亮的方案
    详解使用 Tarjan 求 LCA 问题(图解)
    NOIP2018普及初赛解析
    关于CCR测评器的自定义校验器(Special Judge)
    日常,异常处理
    Androidstudio 编译慢 这样的体验肯定很多人都有!!!
  • 原文地址:https://www.cnblogs.com/clrs97/p/6738141.html
Copyright © 2011-2022 走看看