• 2021年中国大学生程序设计竞赛女生专场


    A. 公交线路

    检查坐对方向和坐反方向两种情况对应的报站序列是否符合输入,如果都符合那就是''Unsure'',否则可以确定有没有坐反。

    #include<cstdio>
    const int N=15;
    int n,m,S,T,i,A,B,a[N],b[N];
    bool check(int d){
      int i,j;
      for(i=S+d,j=1;j<=m;j++,i+=d){
        if(i<1||i>n)return 0;
        if(a[i]!=b[j])return 0;
      }
      return 1;
    }
    int main(){
      scanf("%d%d%d",&n,&S,&T);
      for(i=1;i<=n;i++)scanf("%d",&a[i]);
      scanf("%d",&m);
      for(i=1;i<=m;i++)scanf("%d",&b[i]);
      A=check(S<T?1:-1);
      B=check(S<T?-1:1);
      if(A&&B)puts("Unsure");
      else if(A)puts("Right");
      else puts("Wrong");
    }
    

      

    B. 攻防演练

    考虑如何判断一个字符串 $t = t_1 t_2 \ldots t_k$ 是否是 $s_{l,r} = s_l s_{l+1} \ldots s_r$ 的子序列:用一个指针从 $l$ 开始往右进行扫描,找到第一个 $t_1$ 并停下来,然后从那个位置接着往右找到第一个 $t_2$,$\dots$ 如果按这个贪心方式能找完所有 $k$ 个字符,那么 $t$ 就是 $s_{l,r}$ 的子序列;如果指针扫到了 $r+1$ 甚至更远的地方,那么说明 $t$ 不是 $s_{l,r}$ 的子序列。

    现在考虑如何寻找一个最短的 $t$,使得 $t$ 不是 $s_{l,r}$ 的子序列。假设指针目前位于 $x$,$t$ 的下一个字符有 $m$ 种选项,选第 $i$ 种选项时,指针将扫到 $x$ 右边第一个字符 $i$ 的位置 $v_{x,i}$(如果不存在 $i$ 那么 $x$ 将扫到 $n + 1$)。那么为了使得指针往右扫描得尽量远,$t$ 的下一个字符应该选择 $v_{x,i}$ 最大的那个 $i$。因此,预处理出 $nxt_x$ 表示指针位于 $x$ 时,下一步将扫到哪个位置,无论询问的 $[l,r]$ 是什么,它都是一个定值,不随 $[l,r]$ 而改变。

    于是问题转化为:从 $l$ 开始沿着 $nxt$ 一路往右跳,要跳多少步才能跳到 $>r$ 的地方?这是个经典问题,可以使用倍增;也可以离线询问后,将 $nxt_x$ 当作 $x$ 的父亲建出一棵有根树,在树上进行二分查找。

    时间复杂度 $O(nm+q\log n)$。

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN=200005;
    const int MAXD=18;
    char str[MAXN];
    int go[MAXD][MAXN];
    int main()
    {
        int n,m,q;
        scanf("%d%d%s%d",&m,&n,str+1,&q);
        vector<int> nxt(m,n+1);
        go[0][n+1]=n+1;
        for(int i=n;i>=0;i--)
        {
            for(int j=0;j<m;j++)
                go[0][i]=max(go[0][i],nxt[j]);
            if(i)nxt[str[i]-'a']=i;
        }
        for(int i=1;i<MAXD;i++)
            for(int j=0;j<=n+1;j++)
                go[i][j]=go[i-1][go[i-1][j]];
        for(int i=0;i<q;i++)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            int t=l-1,res=0;
            for(int d=MAXD-1;d>=0;d--)
                if(go[d][t]<=r)t=go[d][t],res+=(1<<d);
            printf("%d\n",res+1);
        }
        return 0;
    }
    

      

    C. 连锁商店

    如果某家公司开的连锁店数量不超过 $1$,那么可以无视``每家公司的红包只能领一份''这个限制,这是因为任何一条路线都无法访问多次该公司开的商店。如果某家公司开的连锁店数量至少为 $2$,那么这样的公司数最多为 $\frac{n}{2}\leq 18$。

    由于第二类公司数量并不多,因此可以使用状态压缩动态规划来求解这个问题。设 $f[i][S]$ 表示从 $1$ 出发到达了 $i$ 点,一路上访问过的第二类公司集合为 $S$ 时,访问过的第一类公司的红包总价值最大是多少,枚举下一个景点进行转移。

    时间复杂度 $O(n^22^{\frac{n}{2}})$。

    #include<cstdio>
    const int N=40,M=18;
    int n,m,cnt,i,j,tmp,x,y,col[N],ap[N],w[N],id[N],g[N][N],f[N][(1<<M)+1],s[(1<<M)+1];
    inline void up(int&a,int b){if(a<b)a=b;}
    int main(){
      scanf("%d%d",&n,&m);
      for(i=1;i<=n;i++)scanf("%d",&col[i]),ap[col[i]]++;
      for(i=1;i<=n;i++){
        scanf("%d",&w[i]);
        if(ap[i]>=2)id[i]=cnt++;else id[i]=-1;
      }
      while(m--)scanf("%d%d",&x,&y),g[x][y]=1;
      for(i=1;i<=n;i++)for(j=0;j<1<<cnt;j++)f[i][j]=-1;
      if(~id[col[1]])f[1][1<<id[col[1]]]=0;else f[1][0]=w[col[1]];
      for(i=1;i<n;i++)for(j=0;j<1<<cnt;j++)if(~f[i][j]){
        tmp=f[i][j];
        for(x=i+1;x<=n;x++)if(g[i][x]){
          if(~id[col[x]])up(f[x][j|(1<<id[col[x]])],tmp);
          else up(f[x][j],tmp+w[col[x]]);
        }
      }
      for(i=1;i<1<<cnt;i++)for(j=1;j<=n;j++)if(~id[j])if(i>>id[j]&1)s[i]+=w[j];
      for(i=1;i<=n;i++){
        tmp=0;
        for(j=0;j<1<<cnt;j++)if(~f[i][j])up(tmp,f[i][j]+s[j]);
        printf("%d\n",tmp);
      }
    }
    

      

    D. 修建道路

    不妨设整个序列的最大值位于 $a_x$(若有多个最大值则取最小的 $x$),那么两端点都在 $[1,x-1]$ 或都在 $[x+1,n]$ 的边的边权不会超过 $a_x$,而横跨 $x$ 的边的边权都是 $a_x$,因此最优方案一定是左右两部分都分别连成一个连通块后,再和 $x$ 相连。

    于是对于每个 $x$ 计算它需要连多少条边:如果 $a_{x-1}<a_x$,那么左边对应的连通块要和 $x$ 连一条边;如果 $a_{x+1}\leq a_x$,那么右边对应的连通块要和 $x$ 连一条边。因此:\[ans=\sum_{i=1}^{n-1}\max(a_i,a_{i+1})\]

    #include<cstdio>
    int n,i,a[200005];long long ans;
    int main(){
      scanf("%d",&n);
      for(i=1;i<=n;i++)scanf("%d",&a[i]);
      for(i=2;i<=n;i++)ans+=a[i]>a[i-1]?a[i]:a[i-1];
      printf("%lld",ans);
    }
    

      

    E. 被遗忘的计划

    令每件商品的价值最大值为 $t$,那么选取 $k$ 件商品的价值最大值不超过 $k\times t$;另一方面,我们总可以把 $t$ 对应的商品重复选取 $k$ 次来得到 $k\times t$,因此数组 $f$ 的最大值一定对应 $k\times t$,于是我们得到了 $k$ 的唯一可能取值:即两个数组最大值的商。

    得到 $k$ 的唯一可能取值后,使用快速幂求出 $v$ 数组的循环卷积的 $k$ 次幂,判断是否等于 $f$ 数组即可。

    时间复杂度 $O(n^2\log k)$。

    #include<cstdio>
    #include<cstdlib>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N=1005;
    const ll inf=1LL<<60;
    int n,i;ll x,y,k,a[N],f[N],g[N],h[N<<1];
    void NO(){
      puts("-1");
      exit(0);
    }
    inline void up(ll&a,ll b){if(a<b)a=b;}
    void mul(ll a[],ll b[],ll f[]){
      int i,j;
      for(i=0;i<n;i++)h[i]=h[i+n]=-inf;
      for(i=0;i<n;i++)for(j=0;j<n;j++)up(h[i+j],a[i]+b[j]);
      for(i=0;i<n;i++)f[i]=max(h[i],h[i+n]);
    }
    int main(){
      scanf("%d",&n);
      for(i=1;i<=n;i++)scanf("%lld",&a[i%n]);
      for(i=0;i<n;i++)scanf("%lld",&f[i]);
      x=y=-inf;
      for(i=0;i<n;i++){
        x=max(x,a[i]);
        y=max(y,f[i]);
      }
      if(y%x)NO();
      y/=x;
      if(y<1||y>1000000000)NO();
      for(i=0;i<n;i++)g[i]=a[i];
      for(k=y-1;k;k>>=1,mul(a,a,a))if(k&1)mul(g,a,g);
      for(i=0;i<n;i++)if(g[i]!=f[i])NO();
      printf("%lld",y);
    }
    

      

    F. 地图压缩

    不难发现行与列是两个独立的问题,因此只需要求出行的最短循环节的长度,再求出列的最短循环节的长度,相乘就是答案。

    以行为例,首先通过 Hash 将问题转化为一维问题。一维问题则是经典问题,对于一个长度为 $n$ 的字符串,长度为 $d$ 的前缀是循环节当且仅当长度为 $n-d$ 的前后缀相等,因此需要找到这个字符串最长的前缀,满足该前缀也是该字符串的后缀。可以枚举所有可能的 $d$ 然后使用 Hash $O(1)$ 判断;也可以使用 KMP 算法求出 $nxt$ 数组,答案即为 $n-nxt[n]$。

    时间复杂度 $O(n^2+qn)$。

    #include<cstdio>
    typedef unsigned long long ull;
    const int N=2005,S=10007;
    int n,q,cnt,i,j,xl,xr,yl,yr,nxt[N];
    char a[N][N];
    ull p[N],f[N][N],g[N][N],w[N];
    inline int cal(){
      int i,j=0;
      for(i=2;i<=cnt;i++){
        while(j&&w[j+1]!=w[i])j=nxt[j];
        if(w[j+1]==w[i])j++;
        nxt[i]=j;
      }
      return cnt-j;
    }
    inline int calx(){
      ull base=p[yr-yl+1];
      cnt=0;
      for(int i=xl;i<=xr;i++)w[++cnt]=g[i][yr]-g[i][yl-1]*base;
      return cal();
    }
    inline int caly(){
      ull base=p[xr-xl+1];
      cnt=0;
      for(int i=yl;i<=yr;i++)w[++cnt]=f[xr][i]-f[xl-1][i]*base;
      return cal();
    }
    int main(){
      scanf("%d%d",&n,&q);
      for(i=1;i<=n;i++)scanf("%s",a[i]+1);
      for(p[0]=i=1;i<=n;i++)p[i]=p[i-1]*S;
      for(i=1;i<=n;i++)for(j=1;j<=n;j++){
        f[i][j]=f[i-1][j]*S+a[i][j];
        g[i][j]=g[i][j-1]*S+a[i][j];
      }
      while(q--){
        scanf("%d%d%d%d",&xl,&yl,&xr,&yr);
        printf("%d\n",calx()*caly());
      }
    }
    

      

    G. 3G网络

    当 $r \rightarrow +\infty$ 时,$n$ 个圆的并趋近于一个半径为 $r$ 的圆,因此答案为 $\frac{1}{n}$。

    #include<cstdio>
    int main(){
      int n;
      scanf("%d",&n);
      printf("%.15f",1.0/n);
    }
    

      

    H. 4G网络

    对于每个询问,我们要求的是这 $n$ 个半径为 $r$ 的圆的面积之并(除以它们的总面积 $n\pi r^2$)。

    根据微积分的概念,一个区域的面积等价于区域中每个微元累加的结果。注意到所有圆的半径都相等,对于平面中的每个点,它属于圆并当且仅当存在一个圆的圆心到它的距离不超过 $r$。因此对于每个点,我们将其放在离它最近的圆心处考虑,如果离它最近的圆心到它的距离不超过 $r$,那么它需要被计入答案。

    枚举每个圆心,找到在 $[-inf,inf]\times[-inf,inf]$ 这个矩形里它作为最近点的管辖区域,容易发现是一个凸多边形,如上图所示。枚举每个圆心,再枚举另一个圆心,可行区域是它们的垂直平分线的一侧,可以使用半平面交在 $O(n^2\log n)$ 时间内预处理出所有 $n$ 个凸多边形,即 Voronoi 图。根据 Voronoi 图的性质,所有凸多边形的边数之和为 $O(n)$。

    对于每个询问,枚举一个圆心 $O$,再枚举它管辖区域的凸多边形的一条边 $(A,B)$,那么对答案的贡献为三角形 $OAB$ 与圆 $O$ 的交,可以 $O(1)$ 计算得到。

    时间复杂度$O(n^2\log n+qn)$。

    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    typedef double ld;
    const int N=2015;
    const ld eps=1e-7,inf=30010,pi=acos(-1.0);
    int n,m,i,j,radius,cl,d[N];
    inline int sgn(ld x){
      if(x<-eps)return -1;
      if(x>eps)return 1;
      return 0;
    }
    inline bool Quadratic(ld A,ld B,ld C,ld*t0,ld*t1){
      ld discrim=B*B-4.f*A*C;
      if(discrim<0.)return 0;
      ld rootDiscrim=sqrt(discrim);
      ld q;
      if(B<0)q=-.5f*(B-rootDiscrim);
      else   q=-.5f*(B+rootDiscrim);
      *t0=q/A;*t1=C/q;
      if(*t0>*t1)swap(*t0,*t1);
      return 1;
    }
    struct P{
      ld x,y;
      P(){x=y=0;}
      P(ld _x,ld _y){x=_x,y=_y;}
      P operator+(const P&b)const{return P(x+b.x,y+b.y);}
      P operator-(const P&b)const{return P(x-b.x,y-b.y);}
      P operator*(ld b)const{return P(x*b,y*b);}
      P operator/(ld b)const{return P(x/b,y/b);}
      ld operator*(const P&b)const{return x*b.x+y*b.y;}
      ld len(){return hypot(x,y);}
      ld len_sqr(){return x*x+y*y;}
      void read(){
        int a,b;
        scanf("%d%d",&a,&b);
        x=a,y=b;
      }
    }a[N],pool[N][N],p[N];
    inline ld cross(const P&a,const P&b){return a.x*b.y-a.y*b.x;}
    struct Line{
      P p,v;ld a;
      Line(){}
      Line(const P&_p,const P&_v){p=_p,v=_v;}
      bool operator<(const Line&b)const{return a<b.a;}
      void cal(){a=atan2(v.y,v.x);}
    }line[N],q[N];
    inline bool left(const P&p,const Line&l){return cross(l.v,p-l.p)>eps;}
    inline P pos(const Line&a,const Line&b){
      P x=a.p-b.p;ld t=cross(b.v,x)/cross(a.v,b.v);
      return a.p+a.v*t;
    }
    inline void halfplane(P*pool,int&n){
      int i,h=1,t=1;
      for(i=1;i<=cl;i++)line[i].cal();
      sort(line+1,line+cl+1);
      q[1]=line[1];
      for(i=2;i<=cl;i++){
        while(h<t&&!left(p[t-1],line[i]))t--;
        while(h<t&&!left(p[h],line[i]))h++;
        if(fabs(cross(q[t].v,line[i].v))<eps)q[t]=left(q[t].p,line[i])?q[t]:line[i];
        else q[++t]=line[i];
        if(h<t)p[t-1]=pos(q[t],q[t-1]);
      }
      while(h<t&&!left(p[t-1],q[h]))t--;
      p[t]=pos(q[t],q[h]);
      n=0;
      for(i=h;i<=t;i++)pool[n++]=p[i];
    }
    inline ld get_angle(const P&a,const P&b){return fabs(atan2(fabs(cross(a,b)),a*b));}
    inline P lerp(const P&a,const P&b,ld t){return a*(1-t)+b*t;}
    inline bool circle_line_intersection(const P&c,const P&a,const P&b,ld*t0,ld*t1){
      P d=b-a;ld A=d*d;
      ld B=d*(a-c)*2.0;
      ld C=(a-c).len_sqr()-radius;
      return Quadratic(A,B,C,t0,t1);
    }
    inline ld circle_triangle_intersection_area(const P&c,const P&a,const P&b){
      if(sgn(cross(a-c,b-c))==0)return 0;
      static P q[5];int len=0;ld t0,t1;q[len++]=a;
      if(circle_line_intersection(c,a,b,&t0,&t1)){
        if(0<=t0&&t0<=1)q[len++]=lerp(a,b,t0);
        if(0<=t1&&t1<=1)q[len++]=lerp(a,b,t1);
      }
      q[len++]=b;ld s=0;
      for(int i=1;i<len;i++){
        P z=(q[i-1]+q[i])/2;
        if((z-c).len_sqr()<=radius)
          s+=fabs(cross(q[i-1]-c,q[i]-c))/2;
        else
          s+=radius*get_angle(q[i-1]-c,q[i]-c)/2;
      }
      return s;
    }
    inline ld circle_polygon_intersection_area(const P&c,P*v,int n){
      ld s=0;
      for(int i=0;i<n;i++){
        int j=(i+1)%n;
        s+=circle_triangle_intersection_area(c,v[i],v[j])*sgn(cross(v[i]-c,v[j]-c));
      }
      return fabs(s);
    }
    int main(){
      scanf("%d",&n);
      for(i=0;i<n;i++)a[i].read();
      for(i=0;i<n;i++){
        line[1]=Line(P(inf,0),P(0,1));
        line[2]=Line(P(-inf,0),P(0,-1));
        line[3]=Line(P(0,inf),P(-1,0));
        line[4]=Line(P(0,-inf),P(1,0));
        cl=4;
        for(j=0;j<n;j++)if(j!=i){
          P t=a[j]-a[i];
          swap(t.x,t.y);
          t.x*=-1;
          line[++cl]=Line((a[i]+a[j])/2,t);
        }
        halfplane(pool[i],d[i]);
      }
      scanf("%d",&m);
      while(m--){
        scanf("%d",&radius);
        radius*=radius;
        ld up=0,down=(pi*radius)*n;
        for(i=0;i<n;i++)up+=circle_polygon_intersection_area(a[i],pool[i],d[i]);
        up/=down;
        printf("%.15f\n",(double)up);
      }
    }
    

      

    I. 驾驶卡丁车

    按照题意逐指令逐步模拟即可。

    #include<cstdio>
    const int N=55,M=505;
    const int dx[8]={-1,-1,-1,0,1,1,1,0},dy[8]={-1,0,1,1,1,0,-1,-1};
    int n,m,i,j,cnt,X,Y,D,V;
    char a[N][N],op[M];
    inline bool check(int x,int y,int nx,int ny){
      if(nx<1||nx>n||ny<1||ny>m)return 0;
      if(a[nx][ny]=='#')return 0;
      if(x==nx||y==ny)return 1;
      if(a[x][ny]=='#'&&a[nx][y]=='#')return 0;
      return 1;
    }
    int main(){
      scanf("%d%d",&n,&m);
      for(i=1;i<=n;i++)scanf("%s",a[i]+1);
      scanf("%d%s",&cnt,op+1);
      for(i=1;i<=n;i++)for(j=1;j<=m;j++)if(a[i][j]=='*')X=i,Y=j;
      D=1;
      V=0;
      for(i=1;i<=cnt;i++){
        if(op[i]=='L')D=(D-1+8)%8;
        else if(op[i]=='R')D=(D+1)%8;
        else if(op[i]=='U')V++;
        else if(V)V--;
        for(j=1;j<=V;j++){
          if(!check(X,Y,X+dx[D],Y+dy[D])){
            printf("Crash! ");
            V=0;
            break;
          }
          X+=dx[D],Y+=dy[D];
        }
        printf("%d %d\n",X,Y);
      }
    }
    

      

    J. 最大权边独立集

    枚举位于最终边独立集上的加入的边权为 $p$ 的边的数量 $t$,那么 $0\leq t\leq k$ 且 $2t\leq n$,这是因为每条边将占据图中的两个点。

    假设最终要加入 $t$ 条边,那么需要从图中删去 $2t$ 个点,然后用 $t\times p\ +$ 剩下图的最大权边独立集来更新答案,这等价于在树上规定 $2t$ 个点不匹配其它点,然后计算树的带权最大匹配。

    使用自底向上的树形动态规划来解决这个问题:设 $f[i][j][0]$ 表示考虑了 $i$ 点的子树,$i$ 点的子树内删掉了 $j$ 个点,且 $i$ 不能往上匹配 $i$ 的父亲时的带权最大匹配;设 $f[i][j][1]$ 表示考虑了 $i$ 点的子树,$i$ 点的子树内删掉了 $j$ 个点,且 $i$ 能够往上匹配 $i$ 的父亲时的带权最大匹配。那么状态数为 $O(nk)$,在转移时需要合并两棵子树的信息,$j$ 这一维从 $0$ 开始枚举到 $\min(size_x,k)$ 即可保证时间复杂度为 $O(nk)$,其中 $size_x$ 表示 $x$ 目前的子树大小。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    #define rep(i,n) for(int i=0;i<=n;i++)
    typedef long long ll;
    const int N=100005,M=102;
    int n,m,p,i,x,y,z,g[N<<1],v[N<<1],w[N<<1],nxt[N<<1],ed;
    int size[N];ll f[N][M*2][2],h[M*2][2],ans;
    inline void add(int x,int y,int z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;}
    inline void up(ll&a,ll b){if(a<b)a=b;}
    void dfs(int x,int y){
      rep(j,1)rep(k,1)f[x][j][k]=-1;
      f[x][0][1]=f[x][1][0]=0;
      size[x]=1;
      for(int i=g[x];i;i=nxt[i]){
        int u=v[i];
        if(u==y)continue;
        dfs(u,x);
        int A=size[x],B=size[u],C=A+B,W=w[i];
        A=min(A,m),B=min(B,m),C=min(C,m);
        size[x]+=size[u];
        rep(j,C)rep(k,1)h[j][k]=-1;
        rep(j,A)rep(k,1)if(~f[x][j][k]){
          ll F=f[x][j][k];
          for(int J=0;J<=B&&j+J<=m;J++)rep(K,1)if(~f[u][J][K]){
            up(h[j+J][k],F+f[u][J][K]);
            if(k&&K)up(h[j+J][0],F+W+f[u][J][K]);
          }
        }
        rep(j,C)rep(k,1)f[x][j][k]=h[j][k];
      }
    }
    int main(){
      scanf("%d%d%d",&n,&m,&p);
      m*=2;
      for(i=1;i<n;i++)scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
      dfs(1,0);
      for(i=0;i<=m&&i<=n;i+=2)rep(j,1){
        ll tmp=f[1][i][j];
        if(tmp<0)continue;
        up(ans,tmp+1LL*(i/2)*p);
      }
      printf("%lld",ans);
    }
    

      

    K. 音乐游戏

    根据输入数据的合法性,问题等价于统计输入的所有字符串中''-''的个数。一个简单的实现方式是:while(~scanf("%s",s)) 统计 $s$ 中''-''的个数。

    #include<cstdio>
    int i,ans;char s[99];
    int main(){
      while(~scanf("%s",s))for(i=0;s[i];i++)if(s[i]=='-')ans++;
      printf("%d",ans);
    }
    

      

  • 相关阅读:
    UVALIVE 6958 Indoorienteering
    Calendar Game
    Wizard's Tour
    2017 ACM-ICPC 亚洲区(西安赛区)网络赛 G. Xor
    Alliances
    2017沈阳网络赛hdu6199 gems gems gems
    pytorch 的max函数
    pytorch调用gpu
    jupyter notebook实现代码自动补全
    pytorch强大的自动求导功能
  • 原文地址:https://www.cnblogs.com/clrs97/p/15805761.html
Copyright © 2020-2023  润新知