zoukankan      html  css  js  c++  java
  • 2019牛客暑期多校训练营(第四场)

    传送门

    参考资料:

      [1]:官方题解(提取码:5kim)

      [2]:标程(提取码:76lh)

    A.meeting(树的直径)

    •题意

      有一颗由 n 个节点组成的树;

      树上标记了 k 个点;

      求树上某个节点到这 k 个点的最远距离的最小值;

    •题解

      

    •Code

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define mem(a,b) memset(a,b,sizeof(a))
     4 const int maxn=1e5+50;
     5 
     6 int n,k;
     7 int num;
     8 int head[maxn];
     9 struct Edge
    10 {
    11     int to;
    12     int next;
    13 }G[maxn<<1];
    14 void addEdge(int u,int v)
    15 {
    16     G[num]={v,head[u]};
    17     head[u]=num++;
    18 }
    19 int x[maxn];
    20 int dis[maxn];
    21 
    22 void DFS(int u,int f,int d)
    23 {
    24     dis[u]=d;
    25     for(int i=head[u];~i;i=G[i].next)
    26     {
    27         int v=G[i].to;
    28 
    29         if(v != f)
    30             DFS(v,u,d+1);
    31     }
    32 }
    33 int Solve()
    34 {
    35     if(k <= 1)
    36         return 0;
    37 
    38     DFS(x[1],x[1],0);
    39 
    40     int cur=x[1];
    41     for(int i=1;i <= k;++i)
    42         if(dis[cur] < dis[x[i]])
    43             cur=x[i];
    44 
    45     DFS(cur,cur,0);
    46 
    47     int ans=0;
    48     for(int i=1;i <= k;++i)
    49         ans=max(ans,dis[x[i]]);
    50 
    51     return (ans+1)/2;
    52 }
    53 void Init()
    54 {
    55     num=0;
    56     mem(head,-1);
    57 }
    58 int main()
    59 {
    60     Init();
    61     scanf("%d%d",&n,&k);
    62 
    63     for(int i=1;i < n;++i)
    64     {
    65         int u,v;
    66         scanf("%d%d",&u,&v);
    67 
    68         addEdge(u,v);
    69         addEdge(v,u);
    70     }
    71     for(int i=1;i <= k;++i)
    72         scanf("%d",x+i);
    73 
    74     printf("%d
    ",Solve());
    75 
    76     return 0;
    77 }
    View Code

     B.xor(线性基求交+线段树区间查询)

    •题意

      给你 n 个集合,每个集合中都有不超过 32 个数;

      m 次询问,每次询问区间 [L, R] 中的所有集合,是否都有一个异或和等于 x 的子集。

      $n ≤le≤ 5e4 , m le 5e4$,所有数值域 [0,232)。

    •题解

      线段树维护区间所有基底的交集;

      然后 O(log n) 解决询问;

      所以说,这道题的难点在于如何快速求解不同基底的交集,可以参考一下我的这篇博客

    •Code

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 #define ls(x) (x<<1)
      4 #define rs(x) (x<<1|1)
      5 #define ll long long
      6 #define mem(a,b) memset(a,b,sizeof(a))
      7 const int maxn=5e4+50;
      8  
      9 int n,m;
     10 struct Seg
     11 {
     12     int l,r;
     13     ll base[40];
     14     int mid(){return l+((r-l)>>1);}
     15     void Init()
     16     {
     17         mem(base,0);
     18     }
     19     void Insert(ll x)
     20     {
     21         for(int i=32;i >= 0;--i)
     22             if(x>>i&1)
     23             {
     24                 if(!base[i])
     25                 {
     26                     base[i]=x;
     27                     return ;
     28                 }
     29                 x ^= base[i];
     30             }
     31     }
     32     bool Find(ll x)
     33     {
     34         for(int i=32;i >= 0;--i)
     35             if(x>>i&1)
     36                 x ^= base[i];
     37         return x == 0 ? true:false;
     38     }
     39 }seg[maxn<<2];
     40  
     41 void pushUp(int pos)///将ls(pos),rs(pos)的base的交集更新到pos的base上
     42 {
     43     ll all[40];
     44     ll B[40];
     45     memcpy(all,seg[ls(pos)].base,sizeof(all));
     46     mem(B,0);
     47  
     48     for(int i=32;i >= 0;--i)
     49     {
     50         ll x=seg[rs(pos)].base[i];
     51         ll y=x;
     52  
     53         for(int j=32;j >= 0;--j)
     54             if(x>>j&1)
     55             {
     56                 if(!all[j])
     57                 {
     58                     all[j]=x;
     59                     B[j]=y;
     60                     break;
     61                 }
     62                 x ^= all[j];
     63                 y ^= B[j];
     64             }
     65         if(!x)
     66             seg[pos].Insert(y);
     67     }
     68 }
     69 void build(int l,int r,int pos)
     70 {
     71     seg[pos]={l,r};
     72     seg[pos].Init();
     73  
     74     if(l == r)
     75     {
     76         int siz;
     77         scanf("%d",&siz);
     78         for(int i=1;i <= siz;++i)
     79         {
     80             ll x;
     81             scanf("%lld",&x);
     82             seg[pos].Insert(x);
     83         }
     84         return ;
     85     }
     86  
     87     int mid=seg[pos].mid();
     88     build(l,mid,ls(pos));
     89     build(mid+1,r,rs(pos));
     90  
     91     pushUp(pos);
     92 }
     93 bool query(int pos,int l,int r,ll x)///查询[l,r]区间的所有集合的子集是否都可以异或出x
     94 {
     95     if(seg[pos].l == l && seg[pos].r == r)
     96         return seg[pos].Find(x);
     97  
     98     int mid=seg[pos].mid();
     99     if(r <= mid)
    100         return query(ls(pos),l,r,x);
    101     else if(l > mid)
    102         return query(rs(pos),l,r,x);
    103     else
    104         return query(ls(pos),l,mid,x)&query(rs(pos),mid+1,r,x);
    105 }
    106 void Solve()
    107 {
    108     build(1,n,1);
    109  
    110     while(m--)
    111     {
    112         int l,r;
    113         ll x;
    114         scanf("%d%d%lld",&l,&r,&x);
    115  
    116         if(query(1,l,r,x))
    117             puts("YES");
    118         else
    119             puts("NO");
    120     }
    121 }
    122 int main()
    123 {
    124     scanf("%d%d",&n,&m);
    125     Solve();
    126  
    127     return 0;
    128 }
    View Code

    C.sequence(单调栈+线段树)

    •题意

      给你两个数组 a,b;

      求 $max_{l}^{r} ig{ min(a_{l},...,a_{r}) cdot sum_{l}^{r}b_{i} ig}$ , 1 ≤ l ≤ r ≤ n;

    •题解

      这道题和之前做的一道题(2019南昌网络预选赛)很相似,几乎就是一样的题;

      通过单调栈求出以 $a_{i}$ 为最小值的最大范围 [L,R];

      在 [L,R] 内:

      ①如果 ai > 0,那么,就需要找 [L,R] 内包含 ai 的 $sum_{max}$;

      ②反之,如果 a≤ 0,那么,就需要找 [L,R] 内包含 ai 的 $sum_{min}$;

      如何快速求解 $sum_{max}$ 和 $sum_{min}$ 呢?

      对于情况①而言,$sum_{max}$ = max{ sumi,sumi+1,...,sumR}-min{ sumL-1,.....,sumi-1};

      而对于情况②而言,正好与①相反,$sum_{min}$ = min{ sumi,sumi+1,...,sumR}-max{ sumL-1,.....,sumi-1};

      看着求解公式有没有种线段树区间查询的味道?

    •Code

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 #define ls(x) (x<<1)
      4 #define rs(x) (x<<1|1)
      5 #define mem(a,b) memset(a,b,sizeof(a))
      6 #define ll long long
      7 #define INFll 0x3f3f3f3f3f3f3f3f
      8 const int maxn=3e6+50;
      9 
     10 int n;
     11 int a[maxn];
     12 int b[maxn];
     13 int l[maxn];
     14 int r[maxn];
     15 ll sum[maxn];
     16 int sta[maxn];
     17 struct Seg
     18 {
     19     int l,r;
     20     ll Max;
     21     ll Min;
     22     int mid(){return l+((r-l)>>1);}
     23 }seg[maxn<<2];
     24 void pushUp(int pos)
     25 {
     26     seg[pos].Max=max(seg[ls(pos)].Max,seg[rs(pos)].Max);
     27     seg[pos].Min=min(seg[ls(pos)].Min,seg[rs(pos)].Min);
     28 }
     29 void buildSeg(int l,int r,int pos)
     30 {
     31     seg[pos].l=l;
     32     seg[pos].r=r;
     33 
     34     if(l == r)
     35     {
     36         seg[pos].Max=seg[pos].Min=sum[l];
     37         return ;
     38     }
     39 
     40     int mid=l+((r-l)>>1);
     41     buildSeg(l,mid,ls(pos));
     42     buildSeg(mid+1,r,rs(pos));
     43 
     44     pushUp(pos);
     45 }
     46 ll queryMax(int l,int r,int pos)
     47 {
     48     if(seg[pos].l == l && seg[pos].r == r)
     49         return seg[pos].Max;
     50 
     51     int mid=seg[pos].mid();
     52 
     53     if(r <= mid)
     54         return queryMax(l,r,ls(pos));
     55     else if(l > mid)
     56         return queryMax(l,r,rs(pos));
     57     else
     58         return max(queryMax(l,mid,ls(pos)),queryMax(mid+1,r,rs(pos)));
     59 }
     60 ll queryMin(int l,int r,int pos)
     61 {
     62     if(seg[pos].l == l && seg[pos].r == r)
     63         return seg[pos].Min;
     64 
     65     int mid=seg[pos].mid();
     66 
     67     if(r <= mid)
     68         return queryMin(l,r,ls(pos));
     69     else if(l > mid)
     70         return queryMin(l,r,rs(pos));
     71     else
     72         return min(queryMin(l,mid,ls(pos)),queryMin(mid+1,r,rs(pos)));
     73 }
     74 void Work()
     75 {
     76     int k=0;
     77     for(int i=1;i <= n;++i)
     78     {
     79         while(k > 0 && a[sta[k]] >= a[i])
     80             k--;
     81 
     82         l[i]=k == 0 ? 1:sta[k]+1;
     83         sta[++k]=i;
     84     }
     85 
     86     k=0;
     87     for(int i=n;i >= 1;--i)
     88     {
     89         while(k > 0 && a[sta[k]] >= a[i])
     90             k--;
     91 
     92         r[i]=k == 0 ? n:sta[k]-1;
     93         sta[++k]=i;
     94     }
     95 }
     96 
     97 ll Solve()
     98 {
     99     buildSeg(0,n,1);///线段树
    100     Work();///单调栈
    101 
    102     ll ans=-INFll;
    103     for(int i=1;i <= n;++i)
    104     {
    105         if(a[i] > 0)
    106         {
    107             ll x=queryMax(i,r[i],1);
    108             ll y=queryMin(l[i]-1,i-1,1);
    109 
    110             ans=max(ans,(x-y)*a[i]);
    111         }
    112         else
    113         {
    114             ll x=queryMin(i,r[i],1);
    115             ll y=queryMax(l[i]-1,i-1,1);
    116 
    117             ans=max(ans,(x-y)*a[i]);
    118         }
    119     }
    120     return ans;
    121 }
    122 
    123 int main()
    124 {
    125     scanf("%d",&n);
    126 
    127     for(int i=1;i <= n;++i)
    128         scanf("%d",a+i);
    129 
    130     sum[0]=0;
    131     for(int i=1;i <= n;++i)
    132     {
    133         scanf("%d",b+i);
    134         sum[i]=sum[i-1]+b[i];
    135     }
    136 
    137     printf("%lld
    ",Solve());
    138     return 0;
    139 }
    View Code

    •段子

      比赛的时候,求解区间最值用的是 ST 表,MLE 有没有.......

      然后询问出题人:

      

      用 ST 表提交的时候,那是一个惨呀;

      

      内存超限了好几次,然后,想试一下运气,万一数据里的 n 都是小于 1e6 的呢;

      然后,就有了 AC 前的一个 WA;

      然后,突然,脑洞大开,为啥不用线段树呢!!!!


    D.triples(构造)

    •题意

      给你 a,求满足 x%3 == 0 , y%3 == 0 , x | y = a 的 x,y;

      输出 x,y;

    •题解

      将 a 用二进制数表示为:

        $a=2^{k_1}+2^{k_2}+...+2^{k_n}$;

      

      也就是说,如果 a%3 ≠ 0,那么 n ≥ 3;

      那么,对于任意一位 $2^{k_i}$ 有:

        $2^{k_i} mod 3=1 or 2$;

      假设 $2^{k_i} mod 3=1$ 有:

        $2^{f_1},2^{f_2},...,2^{f_x}$;

      $2^{k_i} mod 3=2$ 有:

        $2^{g_1},2^{g_2},...,2^{g_y}$;

      ①如果 a%3 == 0
        直接输出 a;

      ②如果 a%3 == 1

        1)x > 1

          构造 $ans1=a-2^{f_1},ans2=a-2^{f_2}$;

        2)x == 1

          构造 $ans1=a-2^{f_1},ans2=a-2^{g_1}-2^{g_2}$;

        3)x == 0

          那么,y > 3,因为如果 y == 3 ,那么 a%3 == 0;

          构造 $ans1=a-2^{g_1}-2^{g_2},ans2=a-2^{g_3}-2^{g_4}$;

          或者构造 $ans1=a-2^{g_1}-2^{g_2},ans2=2^{g_1}+2^{g_2}+2^{g_3}$;

      ③如果 a%3 == 2

        与 a%3 == 1 正好对称;

      解释:为什么要做减法,而不做加法?

         如果做加法的话,会产生进位,那么,ans1 | ans2 后可能大于 a;

    •Code

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define ll long long
     4 
     5 ll a;
     6 vector<ll >v[3];
     7 
     8 void Solve()
     9 {
    10     if(a%3 == 0)
    11     {
    12         printf("1 %lld
    ",a);
    13         return ;
    14     }
    15 
    16     v[1].clear();
    17     v[2].clear();
    18 
    19     for(int i=0;i < 62;++i)
    20     {
    21         ll k=1ll*1<<i;
    22         if(a&k)
    23             v[k%3].push_back(k);
    24     }
    25 
    26     ll ans1,ans2;
    27     if(a%3 == 1)
    28     {
    29         if(v[1].size() > 1)
    30             ans1=a-v[1][0],ans2=a-v[1][1];
    31         else if(v[1].size())
    32             ans1=a-v[1][0],ans2=a-v[2][0]-v[2][1];
    33         else
    34             ans1=a-v[2][0]-v[2][1],ans2=a-v[2][2]-v[2][3];
    35     }
    36     else
    37     {
    38         if(v[2].size() > 1)
    39             ans1=a-v[2][0],ans2=a-v[2][1];
    40         else if(v[2].size())
    41             ans1=a-v[2][0],ans2=a-v[1][0]-v[1][1];
    42         else
    43             ans1=a-v[1][0]-v[1][1],ans2=a-v[1][2]-v[1][3];
    44     }
    45     printf("2 %lld %lld
    ",ans1,ans2);
    46 }
    47 int main()
    48 {
    49     int test;
    50     scanf("%d",&test);
    51     while(test--)
    52     {
    53         scanf("%lld",&a);
    54         Solve();
    55     }
    56     return 0;
    57 }
    View Code

    K.number(思维)

    •题意

      给你一个 串s,s 由 '0'~'9' 的数字组成;

      求能整除 300 的不同的子串的个数;

      对于相同内容的子串,如果含有位置不同的字符,当作不同子串处理;

      并且可以有前导0;

    •题解

      能被 3 整除的数的特征为:各个位上的加和可以被 3 整除;

      由于本题要找的是可以被 300 整除的串的个数;

      那么,满足条件的子串肯定以 ≥ 两个0 结尾;

      那么,关注点就在找连续的 0,并且连续的 0 的个数  ≥ 2 的位置;

      每次从当前的 0 向前遍历,找包含当前位置 0 的满足条件的子串的个数;

      注意,串 0300 也是满足条件的;

      定义 zero[ i ] 表示以 i 位置为结尾的连续的 0 的个数;

      sum[ i ] 表示 [1,i] 中包含第 i 个 0 且不能全部为 0 的满足条件的子串的个数;

      例如:12300032100 (下标从 1 开始)

      zero[4]=1 , zero[5]=2 , zero[6]=3 , zero[10]=1 , zero[11]=2;

      zero[other]=0;

      sum[6]=2 , sum[11]=7;

      sum[other]=0;

      sum[6]包含的子串为:3000,123000(不包含0,00,000这种情况,这种情况单独处理);

      为什么 sum[11]=7 呢?

      2100 , 32100 , 032100 , 0032100 , 00032100 , 300032100 , 12300032100;

      你会发现,其实,你会发现在这种条件下,只有 2100,32100 需要遍历找到,其余的都可以通过前面的信息找到;

      sum[11] = 2 + sum[6] + zero[6] = 7;

      你发现啥了没?

      可以重用之前求的数据;

      但是不是符合所有情况?

      答案是否定的;

      例如:12300021100

      此时 sum[11] = 1; (2300021100)

      也就是说,只有当 cur (cur = s[7]+s[8]+s[9] = 3+2+1 = 6,第一个例子) 整除 3 时才能重用之前求得的数据;

       不然,如果 cur ( cur = s[7]+s[8]+s[9] = 2+1+1 = 4,第二个例子) 不整除 3,那么,就只有接着向前遍历了;

      当然,最后不要忽略了只包含 0 得情况;

    •Code

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define ll long long
     4 #define mem(a,b) memset(a,b,sizeof(a))
     5 const int maxn=1e5+50;
     6 
     7 char s[maxn];
     8 ll zero[maxn];
     9 ll sum[maxn];
    10 
    11 ll Solve()
    12 {
    13     mem(zero,0);
    14     mem(sum,0);
    15 
    16     ll ans=0;
    17 
    18     int len=strlen(s+1);
    19     int index=1;
    20 
    21     while(index <= len)
    22     {
    23         for(;index <= len && s[index] != '0';index++);
    24 
    25         for(;index <= len && s[index] == '0';index++)
    26             zero[index]=zero[index-1]+1;
    27 
    28         ll n=zero[index-1];///以index-1结尾的连续的0的个数
    29 
    30         if(n > 1)
    31         {
    32 
    33             ll cur=0;
    34             ll cnt=0;
    35 
    36             int i=index-n-1;
    37             while(i >= 1)
    38             {
    39                 if(zero[i] > 1 && cur%3 == 0)///只有当cur%3==0时才能重用数据
    40                 {
    41                     cnt += sum[i]+zero[i];
    42                     break;
    43                 }
    44                 ///否则,向前遍历
    45                 cur += s[i]-'0';
    46                 if(cur%3 == 0)
    47                     cnt++;
    48 
    49                 i--;
    50             }
    51 
    52             sum[index-1]=cnt;
    53             
    54             ans += sum[index-1]*(n-1);
    55         }
    56         ans += n*(1+n)/2;///n个连续的0有n*(n+1)/2种串整除300
    57     }
    58     return ans;
    59 }
    60 
    61 int main()
    62 {
    63 //    freopen("C:\Users\hyacinthLJP\Desktop\in&&out\contest","r",stdin);
    64     scanf("%s",s+1);
    65 
    66     printf("%lld
    ",Solve());//23
    67     return 0;
    68 }
    View Code

    •分析

      这种思路实现的代码,时间复杂度应该可以退化到 O(n2) 吧,为啥过了呢?

      暴力+AC = 未出现卡这种方法的数据;

    •另一种思路

      定义 sumi 为前 i 为的和 mod 3 的值;

      区间 [L,R] 满足条件,当且仅当 s[R] = s[R-1] = 0,并且 sumR-2 = sumL-1

      这样的话,只需记录一下 R-2 之前有多少值为 sumR-2 的即可;

      也就是记录 0,1,2 的个数;

      不要忘记计算全部为 0 的情况;

    •Code

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define ll long long
     4 const int maxn=1e5+50;
     5 
     6 char s[maxn];
     7 
     8 ll Solve()
     9 {
    10     ll ans=0;
    11     int sum=0;
    12     int index=1;
    13     int cnt[3]={1,0,0};
    14     int len=strlen(s+1);
    15 
    16     while(index <= len)
    17     {
    18         for(;index <= len && s[index] != '0';index++)
    19         {
    20             sum += s[index]-'0';
    21             sum %= 3;
    22             cnt[sum]++;
    23         }
    24         ll zero=0;
    25         for(;index <= len && s[index] == '0';zero++,index++);
    26     
    27         ///cnt[sum]记录的是区间[1,index-1]的 0,1,2 的个数
    28         ///需要减掉 index-1 对应的 sum%3 的值
    29         if(zero > 1)///(zero-1)种情况,每种都有cnt[sum]-1个满足条件的子串
    30             ans += (cnt[sum]-1)*(zero-1);
    31 
    32         ans += zero*(zero+1)/2;///全为0的情况
    33 
    34         cnt[sum] += zero;
    35     }
    36     return ans;
    37 }
    38 int main()
    39 {
    40     scanf("%s",s+1);
    41     s[0]='#';
    42 
    43     printf("%lld
    ",Solve());
    44 
    45     return 0;
    46 }
    View Code
  • 相关阅读:
    【黑金视频连载】FPGA NIOSII视频教程(08)RTC实验
    【黑金研发】【ABS05】函数信号发生器进行中…
    【黑金视频连载】NIOSII视频教程(05)电平中断实验
    【新品发布】正式发布 [ ABS05 ] 函数信号发生器
    【黑金视频连载】FPGA NIOSII视频教程(13)IIC实验实验
    【黑金视频连载】NIOS II视频教程(01)软件安装
    解决input设置背景后,在ie7下浏览内容过长背景跟着滚动
    命令开启数据库服务器
    装win7 旗舰版的蛋疼经历,装后疑难杂症及解决办法
    海量jquery插件
  • 原文地址:https://www.cnblogs.com/violet-acmer/p/11255809.html
Copyright © 2011-2022 走看看