【SCOI补全记】SCOI2016 bzoj4567-4572


T1:背单词(bzoj4567)

题解

关键词:trie树 贪心

题意有些难懂,简单解释一下。

对于在位置 x x 的单词:

  • 单词没有后缀,吃x颗泡椒

    • 所有后缀在 1 1 ~x1中出现,且后缀最靠后位置为 y y ,吃xy颗泡椒
    • 有后缀出现在 x x 以后,吃n2颗泡椒
    • 显然最后一种情况是不优的,那么保证所有单词出现在它的后缀以后即可。

      将单词reverse建立一颗“后缀”trie树,即从根出发的任意一条向下路径为某个单词的后缀。按深度遍历保证了不会出现第三种情况。

      剩下的就是贪心了,我们将trie树上代表单词终止点的结点取出来建一棵虚树,把每个点的子节点按子树大小排序,在dfs时先遍历子树size大的显然最优。

      一道看似简单的题,实际上代码还是有点技巧的(时间标记,前驱等等,打WA了几次)。

      代码

      #include<bits/stdc++.h>
      #define RI register
      using namespace std;
      typedef long long ll;
      const int N=510050;
      
      int n,cnt,len,sz[N],bel[N];
      int ch[N][26],ed[N],tim[N],bs;ll ans;
      vector<int>son[N];
      char s[N];
      
      inline bool cmp(const int&x,const int&y){return sz[x]>sz[y];}
      
      inline void build(int u)
      {
          if(ed[u]) {
              son[bel[u]].push_back(u);
              bel[u]=u;
          }
          for(RI int i=0;i<26;++i) if(ch[u][i]){
              bel[ch[u][i]]=bel[u];
              build(ch[u][i]);
          }
      
      }
      
      inline void getsz(int u)
      {
           sz[u]=ed[u];
           for(RI int i=son[u].size()-1;~i;--i){
              getsz(son[u][i]);
              sz[u]+=sz[son[u][i]];
           }
           sort(son[u].begin(),son[u].end(),cmp);
      }
      
      inline void dfs(int dep,int u,int pre)
      {
          if(ed[u]) {
              tim[u]=++bs;
              ans+= pre? tim[u]-tim[pre]:bs; 
              pre=u;
          }
          for(RI int i=son[u].size()-1;~i;--i)
           dfs(dep+1,son[u][i],pre);
      }
      
      int main(){
          RI int i,j,u,alp;
          scanf("%d",&n);
          for(i=1;i<=n;++i){
              scanf("%s",s+1);
              len=strlen(s+1);
              for(u=0,j=len;j>=1;--j){
                  alp=s[j]-'a';
                  if(!ch[u][alp]) ch[u][alp]=++cnt;
                  u=ch[u][alp];
              }
              ed[u]=1;
          }
          build(0);
          getsz(0);
          dfs(0,0,0);
          printf("%lld\n",ans);
      }

      T2:幸运数字(bzoj4568)

      题解

      关键词:线性基 倍增LCA

      线性基是可以 O(logGi) O ( l o g G i ) 建立和合并的。所以倍增LCA中同样可以存储节点 i i 到第2j次幂个祖先的链上的线性基。

      熟悉线性基的话,很快就能码出来了。时间复杂度 O((n+m)log2Gi) O ( ( n + m ) l o g 2 G i )

      代码

      #include<bits/stdc++.h>
      #define RI register
      #define gc getchar()
      #define si isdigit(c)
      using namespace std;
      typedef long long ll;
      const int N=2e4+100;
      int n,m,F[N][16],d[N];
      int head[N],to[N<<2],nxt[N<<2],tot;
      ll bin[62],ans;
      
      struct P{
        ll bs[62];
      }f[N][16],tp,v[N];
      
      char c;
      template<class T>
      inline void rd(T &x)
      {
          c=gc;x=0;
          for(;!si;c=gc);
          for(;si;c=gc) x=x*10+(c^48);
      }
      
      inline void lk(int u,int v)
      {to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
      
      inline P merge(P a,P b)
      {
          RI int i,j;ll res;
          for(i=60;~i;--i){
              res=b.bs[i];
              for(j=i;~j && res;--j) if(res&bin[j]){
                  if(!a.bs[j]) a.bs[j]=res;
                  res^=a.bs[j];
              }
           }
          return a;
      }
      
      void dfs(int x)
      {
          RI int i,j;
          for(i=1;bin[i]<=d[x];++i) 
            F[x][i]=F[F[x][i-1]][i-1]; 
          for(i=1;bin[i]<=d[x];++i) 
            f[x][i]=merge(f[x][i-1],f[F[x][i-1]][i-1]);
          for(i=head[x];i;i=nxt[i]){
              j=to[i];if(j==F[x][0]) continue;
              F[j][0]=x;f[j][0]=merge(v[x],v[j]);
              d[j]=d[x]+1;dfs(j);
          }
      }
      
      inline P get(int x,int y)
      {
          tp=v[x];
          if(d[x]<d[y]) swap(x,y);
          RI int i,t=d[x]-d[y];
          for(i=0;bin[i]<=t;++i)
           if(t&bin[i]){
              tp=merge(tp,f[x][i]);
              x=F[x][i];
           }
          if(x==y) return tp;
          for(i=14;~i;--i)
           if(F[x][i]!=F[y][i]){
              tp=merge(tp,f[x][i]);
              tp=merge(tp,f[y][i]);
              x=F[x][i];y=F[y][i];
           }
          tp=merge(tp,merge(f[x][0],f[y][0]));
          return tp;
      }
      
      int main(){
          RI int i,j,k,x,y;ll val;
          bin[0]=1;
          for(i=1;i<=60;++i) bin[i]=bin[i-1]<<1;
          rd(n);rd(m);
          for(i=1;i<=n;++i){
            rd(val);for(j=60;~j;--j) if(val&bin[j]) {v[i].bs[j]=val;break;}
          }
          for(i=1;i<n;++i){rd(x);rd(y);lk(x,y);lk(y,x);}
          dfs(1);
          for(;m;--m){
              ans=0;
              rd(x);rd(y);
              get(x,y);
              for(i=60;~i;--i) 
                ans=max(ans,ans^tp.bs[i]);
              printf("%lld\n",ans);
          }
      }

      T3:萌萌哒(bzoj4569)

      关键词:倍增优化并查集
      传送门


      T4:妖怪(bzoj4570)

      关键词: 凸包
      传送门


      T5:美味(bzoj4571)

      题解

      关键词:主席树 拆位

      因为每次只需要选择最优的一道菜,所以可以拆二进制位对所有菜的评价值建主席树, ai<105 a i < 10 5 ,建立范围为 [0,aimax] [ 0 , a i m a x ] 每次往最优的区间走查询这段区间中是否能取到0/1,从高位到低位逐位选择即可。

      想到拆位就比较好做的一道题。时间复杂度 O(mlog2n) O ( m log 2 ⁡ n )

      代码

      #include<bits/stdc++.h>
      #define RI register
      #define gc getchar()
      #define si isdigit(c)
      #define mid (((l)+(r))>>1)
      using namespace std;
      const int N=2e5+10,mx=(1<<18)-1;
      int now,n,m,qb,qx,ql,qr,des,bin[25];
      int rt[N],ls[N*20],rs[N*20],sz[N*20],tot;
      
      char c;
      inline void rd(int &x)
      {
          c=gc;x=0;
          for(;!si;c=gc);
          for(;si;c=gc) x=x*10+(c^48);
      }
      
      inline void build(int pre,int &nw,int l,int r,int pos)
      {
          nw=++tot;sz[nw]=sz[pre]+1;
          if(l==r) return;
          if(pos<=mid){
             rs[nw]=rs[pre];
             build(ls[pre],ls[nw],l,mid,pos);
          }else{
             ls[nw]=ls[pre];
             build(rs[pre],rs[nw],mid+1,r,pos);
          }
      }
      
      inline bool get(int pre,int nw,int l,int r,int L,int R)
      {
          if(L<=l && r<=R) return (sz[nw]-sz[pre]>0);
          bool re=false;
          if(L<=mid) re=get(ls[pre],ls[nw],l,mid,L,R);
          if(!re && R>mid) re=get(rs[pre],rs[nw],mid+1,r,L,R);
          return re;
      }
      
      int main(){
          RI int i,j,l,r;
          bin[0]=1;for(i=1;i<=20;++i) bin[i]=bin[i-1]<<1;
          rd(n);rd(m);
          for(i=1;i<=n;++i){
             rd(j);
             build(rt[i-1],rt[i],0,mx,j);
          }
          for(;m;--m){
              rd(qb);rd(qx);rd(ql);rd(qr);
              des=mx&(~qb);now=0;
              for(i=17;~i;--i){
                 if(!(qb&bin[i])) now^=bin[i];
                 l=max(now-qx,0);r=(now|(bin[i]-1))-qx;
                 if(r<0 || (!get(rt[ql-1],rt[qr],0,mx,l,r))) now^=bin[i];
              }
              printf("%d\n",now^qb);
          }
      }

      T6:围棋(bzoj4572)

      关键词:轮廓线DP
      传送门


      总结

      考点:
      字符串 × × 1
      倍增 × × 2
      计算几何 × × 1
      线性基/二进制拆位 × × 2
      简单数据结构(简单主席树) × × 1
      DP(轮廓线) × × 1

      倍增/二进制位出现频率较高?
      数据结构较基础?
      DP D P 普遍毒瘤?
      算几年年出现?

      T1,2,5不能错
      T3是一道不可多得的思维好题
      T4算几还需要多加练习
      T6轮廓线DP还不熟练,考试码不出来(似乎是道压轴题,雾)
      思维题偏少

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值