• 寒武纪camp Day1


    补题进度:8/10

    A(组合计数)

    题意:

      一个人站在数轴原点,每秒有1/4概率向前走一步,1/4概率向后走一步,1/2概率不动,问t秒后在p位置的概率。

      t,p<=100000

    分析:

      枚举不动的个数,于是向前走的个数和向后走的个数都确定了,然后就可组合计数了。

    B(平面图k小割)

    题意:

      给出一个n个点的树,1是根节点,每个点有点权,输出前k小的包含1节点的连通块的权值。

      n<=10^5,k<=10^5,点权<=10^9

    分析:

      连通块不好处理,一个连通块实际上对于一个割集,我们可以这样转化:

      把1当作源点,建一个汇点,把每个叶子向汇点连边,然后树上每条边的边权表示其子树里的点权和,那么这样一个割就对应了包含S点的一个连通块的点权和,所以问题就变成了求该图的k大割

      求k大割是没法求的,但注意到该问题是平面图,所以可以建出对偶图跑k短路。

      对于k短路,可以用可持久化堆来实现。

      1 /*
      2 可持久化堆优化k短路
      3 求最短路径树:O(nlogn)
      4 求k短路:O(mlogm+klogk)
      5 空间复杂度:O(mlogm),但具体开数组要注意常数,要看执行merge操作的次数
      6 */
      7 #include<bits/stdc++.h>
      8 using namespace std;
      9 const int maxn=100000,maxm=200000,maxsize=3000000;//maxsize是堆的最大个数
     10 const long long inf=1000000000000000LL;
     11 int a[maxn+5];
     12 long long s[maxn+5];
     13 int n,k,m,len,tot;
     14 int S,T;
     15 vector<int> g1[maxn+5];
     16 int mi[maxn+5],mx[maxn+5];
     17 int head[maxn+5],nx[maxm+5];
     18 long long dis[maxn+5];
     19 int pos[maxn+5],rt[maxn+5];
     20 struct Edge
     21 {
     22     int to;
     23     long long w;
     24 }e[maxm+5];
     25 struct node
     26 {
     27     /*
     28     u是当前堆顶的节点编号
     29     key是当前堆顶对应边的权值,边的权值定义为:走这条边要多绕多少路
     30     l,r分别是堆左右儿子的地址
     31     */
     32     int u;
     33     long long key;
     34     int l,r;
     35 }H[maxsize+5];
     36 struct heapnode
     37 {
     38     /*
     39     求k短路时候用到的数据结构
     40     len表示1~倒数第二条边的边权和
     41     root表示倒数第二条边的tail的H[tail],其中堆顶就是最后一条边
     42     */
     43     long long len;
     44     int root;
     45     bool operator < (const heapnode& x) const
     46     {
     47         return len+H[root].key>x.len+H[x.root].key;
     48     }
     49 };
     50 priority_queue<heapnode> q;
     51 void addedge(int u,int v,long long w)
     52 {
     53 
     54     //printf("%d %d %lld
    ",u,v,w);
     55     e[++len]={v,w};
     56     nx[len]=head[u];
     57     head[u]=len;
     58 }
     59 void dfs(int k,int fa)
     60 {
     61     s[k]=a[k];
     62     bool flag=0;
     63     mi[k]=n+1;
     64     mx[k]=0;
     65     for(int i=0;i<g1[k].size();++i)
     66         if(g1[k][i]!=fa)
     67         {
     68             flag=1;
     69             dfs(g1[k][i],k);
     70             s[k]+=s[g1[k][i]];
     71             mi[k]=min(mi[k],mi[g1[k][i]]);
     72             mx[k]=max(mx[k],mx[g1[k][i]]);
     73         }
     74     if(!flag) mi[k]=mx[k]=++m;
     75 }
     76 int newnode(int u,long long key)
     77 {
     78     ++tot;
     79     H[tot]={u,key,0,0};
     80     return tot;
     81 }
     82 int merge(int u,int v)
     83 {
     84     /*
     85     merge两个堆u和v
     86     这里是采用随机堆,方便合并,方便持久化
     87     */
     88     if(!u) return v;
     89     if(!v) return u;
     90     if(H[v].key<H[u].key) swap(u,v);
     91     int k=++tot;
     92     H[k]=H[u];
     93     if(rand()%2) H[k].l=merge(H[k].l,v);
     94     else H[k].r=merge(H[k].r,v);
     95     return k;
     96 }
     97 void Kshort()
     98 {
     99     /*
    100     求k短路
    101     */
    102     dis[T]=0;
    103     for(int i=0;i<T;++i) dis[i]=inf;
    104     tot=0;
    105     for(int i=m-1;i>=0;--i)
    106     {
    107         /*
    108         DAG图求最短路径树
    109         */
    110         int fa=0;
    111         for(int j=head[i];j!=-1;j=nx[j])
    112             if(dis[i]>e[j].w+dis[e[j].to])
    113             {
    114                 dis[i]=e[j].w+dis[e[j].to];
    115                 pos[i]=j;
    116                 fa=e[j].to;
    117             }
    118         rt[i]=rt[fa];
    119         for(int j=head[i];j!=-1;j=nx[j])
    120             if(j!=pos[i])
    121             {
    122                 //printf("ce : %d %d
    ",i,e[j].to);
    123                 rt[i]=merge(rt[i],newnode(e[j].to,e[j].w+dis[e[j].to]-dis[i]));
    124             }
    125     }
    126     //printf("tot : %d
    ",tot);
    127     //printf("len : %d
    ",len);
    128     //printf("m : %d
    ",m);
    129     //for(int i=0;i<=T;++i) printf("%d : %lld %d
    ",i,dis[i],pos[i]);
    130     printf("%lld
    ",dis[S]+s[1]);
    131     heapnode now={0LL,rt[S]};
    132     if(now.root) q.push(now);
    133     while(--k&&!q.empty())
    134     {
    135         /*
    136         每次从优先队列队首取出最小的边集
    137         每个边集对对应一种合法的k短路走法
    138         有两种扩展方法
    139         第一种:将最后一条边换成所在堆的次小元素(相当于从堆里把堆顶删除)
    140         第二种:新加一条边,即从最后一条边继续往后走
    141         */
    142         now=q.top();
    143         q.pop();
    144         printf("%lld
    ",now.len+H[now.root].key+dis[S]+s[1]);
    145         int id=merge(H[now.root].l,H[now.root].r);
    146         //printf("%d : %d %lld
    ",id,H[id].u,H[id].key);
    147         if(id)
    148             q.push({now.len,id});
    149         now.len+=H[now.root].key;
    150         if(rt[H[now.root].u])
    151             q.push({now.len,rt[H[now.root].u]});
    152     }
    153 }
    154 int main()
    155 {
    156     srand(time(0));
    157     scanf("%d%d",&n,&k);
    158     for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    159     for(int i=1;i<n;++i)
    160     {
    161         int u,v;
    162         scanf("%d%d",&u,&v);
    163         g1[u].push_back(v);
    164         g1[v].push_back(u);
    165     }
    166     dfs(1,0);
    167     for(int i=0;i<=n+1;++i) head[i]=-1;
    168     for(int i=2;i<=n;++i) addedge(mi[i]-1,mx[i],-s[i]);
    169     for(int i=0;i<m;++i) addedge(i,i+1,0);
    170     S=0,T=m;
    171     Kshort();
    172     return 0;
    173 }
    View Code

    C(随机算法)

    题意:

      给出一个n个点的树,每个点有自己的颜色,选出一个包含点数最少的连通块,使得其中有k种不同的颜色。

      n<=10000,k<=5,每个点颜色<=n

    分析:

      如果每个点的颜色是1~5,那就很好办了,我们只需要做树形dp就可以了,dp[i][j]表示以i为根的子树,颜色包含情况为j的最少点数

      那么这个dp就是n*4^5的

      但现在颜色数量有很多,无法表示状态

      考虑随机算法,我们将1~n颜色随机映射到1~k,我们来分析下正确的概率:

      分母很明显是$k^n$

      成功当且仅当作为答案的那一组颜色被染成了k种不同的颜色,所以分子就是$k!*k^{n-k}$

      所以成功的概率是$frac{k^n}{k!*k^{n-k}} = 0.2$

      于是随个30次就行了

    D(递推)

    题意:

      k-left tree指的是一类树,需要满足两个性质:

        1、从根向每个叶子走的过程中经过的左孩子的个数<k

        2、除叶子节点外的其它节点都有2个孩子

      求n个叶子节点的k-left tree有多少种

      n,k<=5000

    分析:

      一个k-left tree的左子树一定是k-1 left tree 右子树一定是k left tree树,只需要枚举左右两边分配的叶子节点个数就行了,O(n^3)的算法是显然的。

      我们通过O(n^3)的算法算出了所有的ans[1..n][1..k],已经不能再优化了,但该题只需要求固定k下的ans[1..n],所以我们只能换思路用O(n^2)的去解决。

      考虑已经知道了k限制下,有n个节点的所有树,如何更新到n+1的答案

      我们发现该树有n个叶子说明有n-1个非叶子,我们只需要去考虑这些非叶子的形状就行了

      从n更新到n+1实际上给n对应的树里面添加一个树枝,但为了不重复计数,我们人为规定该树枝只能添加到先序遍历最后的位置(如果在中间添加,可以看成删去最后的那个树枝,保留目前这个树枝,将这个树作为n的答案)

      于是dp[i][j]表示i个树枝,从根到最后一个树枝经过了j个左儿子的树的总数,那么显然对于i+1,我们可以在这j个左儿子那添加一条向右的树枝,所以可以转移到dp[i+1][j+1],dp[i+1][j],....,dp[i+1][1],前缀和优化即可。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int N=5005,Mod=1e9+9;
     4 int k,n,f[N][N];
     5 void inc(int &x,long long y)
     6 {
     7     x=(x+y+Mod)%Mod;
     8 }
     9 int main()
    10 {
    11 
    12     cin>>k>>n;
    13     /*for(int i=2;i<=k;++i) f[1][i]=1;
    14     for(int j=3;j<=k;++j)
    15         for(int i=1;i<=n;++i)
    16             for(int l=1;l<i;++l)
    17                 inc(f[i][j],1LL*f[l][j-1]*f[i-l][j]%Mod);*/
    18     /*for(int j=1;j<=k;++j)
    19         for(int i=1;i<=n;++i)
    20             printf("%d %d : %d
    ",i,j,f[i][j]);*/
    21     k-=2;
    22     f[1][1]=1;
    23     f[2][1]=1;
    24     for(int i=2;i<n;++i)
    25     {
    26         for(int j=1;j<=k;++j)
    27             inc(f[i+1][min(j+1,k)],f[i][j]);
    28         for(int j=k-1;j>=1;--j) inc(f[i+1][j],f[i+1][j+1]);
    29     }
    30     for(int i=1;i<=n;i++)
    31     {
    32         int ans=0;
    33         for(int j=1;j<=k;++j) inc(ans,f[i][j]);
    34         printf("%d
    ",ans);
    35     }
    36     return 0;
    37 }
    View Code

    E(找规律)

    F(Fibonacci树递推)

    题意:

      问深度为n的fibonacci树的白点距离情况,即相距1,2,...,,2*n的白点对分别有多少对。

      n<=5000

      fibonacci树指的是根是白点,一个白点下面一个黑点,一个黑点下面一个白点和一个黑点形成的树。

    分析:

      fibonacci树有这样的性质:每个深度的点的个数、每个深度的黑点个数、每个深度的白点个数分别都是fibonacci数列

      dp[i][0/1]表示深度为i的fibonacci树,根节点是白/黑点情况下的白色点对相距情况(保存的是1~2*i的数组)

      很明显dp[i][0]=dp[i-1][1],dp[i][1]=dp[i-1][0]+dp[i-1][1]+两个子树之间贡献的答案

      两个子树之间的贡献的答案就是两个fibonacci的卷积,所以这样是O(n^3)

      如果卷积用FFT,那么是O(n^2logn)也是不能通过的

      实际上两个fibonacci的卷积可以这样做:

      我们枚举a的下标j,然后将a[j]*b[1,2,...,i-1]放到对应的ans[j+1,j+2,...,j+i-1]位置上,问题就变成了给区间加上一个fibonacci数列,最终求区间的每一个位置的数字

      这我们可以用类似差分的做法来O(1)修改,O(n)询问

      所以时间复杂度就是降到O(n^2),但是常数有点大,大概是std的1.5倍,在某oj上不能通过。

    G(可持久化线段树优化建边)

    题意:

      有n个人,每个人三个属性a,b,c,若一个人的某两项属性大于另外一个人的对应两项属性,那么这个人可以击败另一个人。问有多少个人可以击败其它所有人。

      n<=100000

    分析:

      如果n比较小,那么就暴力建图然后缩点就行了。

      这里需要用数据结构优化建边。

      对于三个属性,我们每次提取两维考虑,假设这两维是x,y

      按照x从小到大排序,然后从左往右扫,假设现在来到了第i个人,那么连边就是“向1~i-1个人中y属性小于自己的人建边”

      如果只有“向y属性小于自己的人建边”,那么建一个权值线段树,把每个线段树节点作为辅助节点就行了

      现在有了“向1~i-1个人中”这个限制,那也很简单,我们建可持久化线段树就行了

      建可持久化线段树唯一注意的是,新点u向旧点u也要连一条边

      这样边数就从O(n^2)下降到了O(nlogn),点数从O(n)上升到了O(nlogn)

      然后缩点,问题变成了有一个DAG图,有一些关键节点(1~n),我们想知道这关键节点当中有哪些点可以到达其它关键节点

      我们只需要对这个DAG图把所有非关键节点都拓扑掉,剩下关键节点就被剥出来了,若关键节点是唯一入度为0的节点,那么该关键节点对应强连通分量里面的所有原来的节点答案就是1,其它答案是0。

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 const int maxn=100000,maxsize=100000*20*3,maxm=6*maxsize;
      4 int a[maxn+5],b[maxn+5],c[maxn+5],id[maxn+5];
      5 int rt[maxn+5];
      6 int n,tot,num,sz,len;
      7 int head[maxsize+5],head1[maxsize+5];
      8 int nx[maxm+5],e[maxm+5];
      9 int t[maxsize+5],color[maxsize+5];
     10 bool v[maxsize+5];
     11 int ans[maxsize+5],d[maxsize+5];
     12 struct tree
     13 {
     14     int lc,rc;
     15 }T[maxsize+5];
     16 void addedge(int u,int v)
     17 {
     18     if(!u||!v) return;
     19     //printf("%d %d
    ",u,v);
     20     ++len;
     21     e[len]=v;
     22     nx[len]=head[u];
     23     head[u]=len;
     24     ++len;
     25     e[len]=u;
     26     nx[len]=head1[v];
     27     head1[v]=len;
     28 }
     29 void adde(int u,int v)
     30 {
     31     ++len;
     32     e[len]=v;
     33     nx[len]=head1[u];
     34     head1[u]=len;
     35 }
     36 int build(int last,int l,int r,int x,int id)
     37 {
     38     int k=++tot;
     39     T[k]=T[last];
     40     addedge(k,last);
     41     if(l==r)
     42     {
     43         addedge(k,id);
     44         return k;
     45     }
     46     int mid=(l+r)>>1;
     47     if(x<=mid) T[k].lc=build(T[last].lc,l,mid,x,id),addedge(k,T[k].lc);
     48     else T[k].rc=build(T[last].rc,mid+1,r,x,id),addedge(k,T[k].rc);
     49     return k;
     50 }
     51 void add(int k,int l,int r,int x,int id)
     52 {
     53     if(!k) return;
     54     if(l==r)
     55     {
     56         addedge(id,k);
     57         return;
     58     }
     59     int mid=(l+r)>>1;
     60     if(x<=mid) add(T[k].lc,l,mid,x,id);
     61     else addedge(id,T[k].lc),add(T[k].rc,mid+1,r,x,id);
     62 }
     63 void work(int *x,int *y)
     64 {
     65     for(int i=1;i<=n;++i) id[x[i]]=i;
     66     for(int i=1;i<=n;++i)
     67     {
     68         rt[i]=build(rt[i-1],1,n,y[id[i]],id[i]);
     69         add(rt[i],1,n,y[id[i]],id[i]);
     70     }
     71 }
     72 void dfs(int k)
     73 {
     74     v[k]=1;
     75     for(int i=head[k];i!=-1;i=nx[i])
     76         if(!v[e[i]]) dfs(e[i]);
     77     ++sz;
     78     t[sz]=k;
     79 }
     80 void dfs1(int k)
     81 {
     82     v[k]=1;
     83     color[k]=num;
     84     for(int i=head1[k];i!=-1;i=nx[i])
     85         if(!v[e[i]]) dfs1(e[i]);
     86 }
     87 queue<int> q;
     88 int main()
     89 {
     90     int size = 256 << 20; // 256MB
     91     char *p = (char*)malloc(size) + size;
     92     __asm__("movl %0, %%esp
    " :: "r"(p));
     93     scanf("%d",&n);
     94     for(int i=1;i<=n;++i) scanf("%d%d%d",&a[i],&b[i],&c[i]);
     95     for(int i=0;i<=maxsize;++i) head[i]=head1[i]=-1;
     96     tot=n;
     97     work(a,b);
     98     //printf("%d %d
    ",tot,len);
     99     work(a,c);
    100     //printf("%d %d
    ",tot,len);
    101     work(b,c);
    102     //printf("%d %d
    ",tot,len);
    103     for(int i=1;i<=tot;++i) if(!v[i]) dfs(i);
    104     memset(v,0,sizeof(v));
    105     for(int i=sz;i>=1;--i) if(!v[t[i]]) ++num,dfs1(t[i]);
    106     memset(v,0,sizeof(v));
    107     for(int i=1;i<=n;++i)
    108         v[color[i]]=1;
    109     for(int i=0;i<=num;++i) head1[i]=-1;
    110     for(int i=1;i<=tot;++i)
    111         for(int j=head[i];j!=-1;j=nx[j])
    112         {
    113             int u=color[i],vv=color[e[j]];
    114             if(u==vv) continue;
    115             ++d[vv];
    116             adde(u,vv);
    117                 //printf("ok : %d
    ",color[vv]);
    118         }
    119     for(int i=1;i<=num;++i)
    120         if(!d[i]&&!v[i]) q.push(i);
    121     while(!q.empty())
    122     {
    123         int u=q.front();
    124         q.pop();
    125         for(int i=head1[u];i!=-1;i=nx[i])
    126         {
    127             int to=e[i];
    128             --d[to];
    129             if(d[to]==0&&!v[to]) q.push(to);
    130         }
    131     }
    132     //printf("num : %d
    ",num);
    133     //for(int i=1;i<=n;++i) printf("%d : %d
    ",i,color[i]);
    134     int sum=0;
    135     int winner=0;
    136     for(int i=1;i<=num;++i)
    137         if(v[i]&&d[i]==0) ++sum,winner=i;
    138     if(sum==1)
    139         for(int i=1;i<=n;++i)
    140             if(color[i]==winner) ans[i]=1;
    141     for(int i=1;i<=n;++i) printf("%d
    ",ans[i]);
    142     //cout<<clock();
    143     return 0;
    144 }
    View Code

    H(贪心+set)

    题意:

      你有n个商品,Alice买第i件商品花ai元,Bob买第i件商品花bi元,一个商品只有一个,显然一个商品不能被两人同时买,Alice可以最多买A个,Bob可以最多买B个,问他们该如何买,你才能获得最大的收益。

      A+B<=n<=10^5

      ai,bi可正可负

    分析:

      我们先把所有负权值修改为0,那么“最多买”就变成了“恰好买”

      我们按照ai从大到小排个序,首先Alice买前A个,Bob买后n-A个中bi最大的B个

      然后我们去枚举Alice买的最后一个商品i,那么1~i每个商品都被Alice或者Bob买了,不会有商品没有被买(假如这样,那么Alice去买那个没人买的一定比买现在的第i个要来的划算)

      很显然这样的答案就是1~i里面a[i]的和加上i-A个最大的b[i]-a[i]的和(即i个商品里面Alice买A个,Bob买i-A个,Bob买的这i-A个一定是b[i]-a[i]最大的i-A个),然后Bob还可以买B-(i-A)个,就是从i+1~n里面挑b[i]最大的B-(i-A)个买,这个显然可以用两个平衡树去维护

      但当然因为个数恰好是递增、递减的,所以我们也可以在i递增的过程中用两个set来维护

      时间复杂度O(nlogn)

    I(推导+容斥计算)

    待填坑

    K(推导+杜教筛)

    待填坑

  • 相关阅读:
    安卓系统浏览器中select下拉按钮无法弹出选择面板奇怪问题解决
    Webkit浏览器点击控件时出现的边框消除
    UML序列图总结
    UML序列图总结
    UML类图几种关系的总结
    UML类图几种关系的总结
    UML用例图总结
    UML用例图总结
    类与类之间的关系
    java核心技术----Object类
  • 原文地址:https://www.cnblogs.com/wmrv587/p/8424421.html
Copyright © 2020-2023  润新知