• 寒武纪camp Day2


    补题进度:8/10

    A(计数+BIT)

    题意:

      给一个长度为n的数组a[],任意选0<=i<=j<n,将a[i]~a[j]从小到大排序,形成新的数组。问有多少个不同的新数组。

      N,a[i]<=1000000

    分析:

      对答案有贡献的ij一定是a[i]不是i~j的最小值,a[j]不是i~j的最大值,于是我们就去统计这样区间的数量

      我们用单调栈搞出r[i]表示i右边第一个比a[i]小的位置,l[i]表示i左边第一个比a[i]大的位置

      枚举区间左端点,那么可行的右区间在[r[i],n]中,然后我们只需要统计其中有多少位置的l[j]>=i就行了

      这个可以离线+BIT解决

    B(并查集维护基环外向树森林)

    题意:

      一个长度为n的01数组,最初都是0,有m个颜色,每个位置有两种染色选择,每种颜色只可以染一次。现在你需要找出一个染色方案满足:

        1、被染色的位置数量尽可能多

        2、在1的前提下,若用1表示该位置被染,用2表示该位置没被染,你需要让这个字典序尽量大

        3、在2的前提下,一个位置如果被染那么它要么是1要么是2(表示染了第一种备选颜色还是第二种),你需要让这个字典序尽量小

      n,m<=5*10^5

    分析:

      如果是问最多染色的个数,那显然只需要维护一下基环外向树森林就行了

      现在要输出方案,我们发现第2个条件其实就是让我们尽可能优先用先读入的边,这个在我们维护基环外向树森林的时候就成功处理了

      第三个条件其实是让我们考虑最后的基环树/树的染色分配

      首先对于基环树,只有环上的有两种选择,树边是定死的,我们只需要枚举两种情况即可

      对于树,我们贪心来看,取出id最小的边,令其为1,按照方向去dfs涂色,再取出id次小的边,以此类推……

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 const int maxn=1e6,inf=1e7;
      4 int ans[maxn+5],l[maxn+5],r[maxn+5];
      5 int f[maxn+5];
      6 bool c[maxn+5],incircle[maxn+5];
      7 bool vis[maxn+5];
      8 int n,m,len,top;
      9 vector<int> circle,E;
     10 struct edge
     11 {
     12     int from,to,id;
     13 }e[maxn*2+5];
     14 int head[maxn+5],nx[maxn+5];
     15 int s[maxn+5];
     16 bool use[maxn*2+5];
     17 void addedge(int u,int v,int id)
     18 {
     19     ++len;
     20     e[len]={u,v,id};
     21     nx[len]=head[u];
     22     head[u]=len;
     23     ++len;
     24     e[len]={v,u,id};
     25     nx[len]=head[v];
     26     head[v]=len;
     27 }
     28 int find(int x)
     29 {
     30     if(f[x]==x) return x;else return f[x]=find(f[x]);
     31 }
     32 void dfs(int k)
     33 {
     34     if(vis[k])
     35     {
     36         int now=top;
     37         circle.push_back(s[now]);
     38         --now;
     39         while(now>=1&&e[s[now]].to!=k) circle.push_back(s[now--]);
     40         return;
     41     }
     42     vis[k]=1;
     43     for(int i=head[k];i!=-1;i=nx[i])
     44     {
     45         if(use[i]) continue;
     46         edge u=e[i];
     47         s[++top]=i;
     48         use[i]=use[i^1]=1;
     49         dfs(u.to);
     50         --top;
     51     }
     52 }
     53 void paint(int k,int fa)
     54 {
     55     for(int i=head[k];i!=-1;i=nx[i])
     56     {
     57         edge u=e[i];
     58         if(u.to==fa||incircle[u.to]) continue;
     59         if(k==l[u.id]) ans[u.id]=2;else ans[u.id]=1;
     60         paint(u.to,k);
     61     }
     62 }
     63 void workcircle()
     64 {
     65     for(int i=0;i<circle.size();++i) incircle[e[circle[i]].to]=1;
     66     for(int i=0;i<circle.size();++i) paint(e[circle[i]].to,0);
     67     int minid=n+1;
     68     for(int i=0;i<circle.size();++i)
     69     {
     70         edge u=e[circle[i]];
     71         if(u.to==l[u.id]) ans[u.id]=1;else ans[u.id]=2;
     72         minid=min(minid,u.id);
     73     }
     74     if(ans[minid]==2)
     75         for(int i=0;i<circle.size();++i) ans[e[circle[i]].id]=3-ans[e[circle[i]].id];
     76 }
     77 void dfs1(int k,int fa)
     78 {
     79     for(int i=head[k];i!=-1;i=nx[i])
     80     {
     81         edge u=e[i];
     82         if(u.to==fa) continue;
     83         E.push_back(i);
     84         dfs1(u.to,k);
     85     }
     86 }
     87 bool cmp(const int x,const int y)
     88 {
     89     return e[x].id<e[y].id;
     90 }
     91 void worktree(int start)
     92 {
     93     E.clear();
     94     dfs1(start,0);
     95     sort(E.begin(),E.end(),cmp);
     96     for(int i=0;i<E.size();++i)
     97     {
     98         edge u=e[E[i]];
     99         if(ans[u.id]) continue;
    100         ans[u.id]=1;
    101         if(u.to==l[u.id]) paint(u.to,u.from);else paint(u.from,u.to);
    102     }
    103 }
    104 int main()
    105 {
    106     int size = 256 << 20; // 256MB
    107     char *p = (char*)malloc(size) + size;
    108     __asm__("movl %0, %%esp
    " :: "r"(p));
    109     freopen("ce.in","r",stdin);
    110     freopen("ce.out","w",stdout);
    111     scanf("%d%d",&n,&m);
    112     for(int i=1;i<=m;++i) f[i]=i,head[i]=-1;
    113     len=-1;
    114     for(int i=1;i<=n;++i)
    115     {
    116         scanf("%d%d",&l[i],&r[i]);
    117         int u=find(l[i]),v=find(r[i]);
    118         if(c[u]&&c[v]) continue;
    119         if(u!=v) f[u]=v,c[v]|=c[u];
    120         else
    121             if(!c[u]&&!c[v]) f[u]=v,c[v]=1;
    122             else continue;
    123         addedge(l[i],r[i],i);
    124     }
    125     for(int i=1;i<=m;++i)
    126         if(!vis[i])
    127         {
    128             circle.clear();
    129             top=0;
    130             dfs(i);
    131             if(!circle.empty()) workcircle();
    132             else worktree(i);
    133         }
    134     for(int i=1;i<=n;++i) printf("%d",ans[i]);
    135     return 0;
    136 }
    View Code

    C(计数+线段树)

    题意:

      给出一个长度为n的数组a[],问有多少对(x,y)满足:

        1、0<=x<=y<n

        2、任意i∈[x,y] & j∉[x,y],有a[i]≠a[j]

      n<=1000000

    分析:

      直观来讲,我们是要找区间[x,y]使得颜色被分开了

      我们把同样的颜色提取出来,那么对于每个位置我们都能方便的求出l[i],r[i]表示a[i]这个颜色最左边在哪,最右边在哪

      那么x只能是一个颜色的起点,y只能是一个颜色的终点

      我们去枚举y,计算有多少个合法的x

      即要满足max{r[x],r[x+1],...,r[y]}<=y 且 min{l[x],l[x+1],...,l[y]}>=x

      那么对于每个y,我们可以用线段树求出最左边的x满足max{r[x],r[x+1],...,r[y]}<=y,记为posl[y]

      对于每个x,我们可以用线段树求出最右边的y满足min{l[x],l[x+1],...,l[y]}>=x,即为posr[x]

      那么我需要找出[posl[y],y]里面有多少少符合题意的x,即那些posr[x]>=y的x,一眼直接用可持久化线段树支持询问

      但仔细分析会发现这样的posr[x]是单减的,所以同样只需要用线段树找出那个分界就行了

    D(组合计数)

    E(构造)

    题意:

      给你一颗n个点的二叉树,你需要给每个点重新编号1~n使得{fa[2],fa[3],fa[4],...,fa[n]}这个序列字典序最小

      n<=2e5

    分析:

      显然是用bfs的顺序给每个点重新编号,但主要矛盾点就是某个点有两个孩子,两个孩子谁先编号的问题

      显然贪心选择这两个孩子当中size最大的那个

      若这两个孩子的size又相同呢……?

      那么再比较这两个孩子的孩子……这样深入下去

      这样时间复杂度能保证吗?看似是O(n^2)的啊

      每次考察一个点u,向下需要一直深入到size有分歧的时候,考虑最差情况就是完全二叉树

      完全二叉树的情况下复杂度是O(nlogn)的,所以这个算法是work的

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int maxn=2e5;
     4 vector<int>g[maxn+5];
     5 int ans[maxn+5],fa[maxn+5];
     6 int n;
     7 bool check(int x,int y)
     8 {
     9     queue<int> p,q;
    10     p.push(x);
    11     q.push(y);
    12     while(true)
    13     {
    14         if(p.empty()) return true;
    15         if(q.empty()) return false;
    16         x=p.front(),p.pop();
    17         y=q.front(),q.pop();
    18         if(g[x].size()!=g[y].size()) return g[x].size()<g[y].size();
    19         for(int i=0;i<g[x].size();++i) p.push(g[x][i]);
    20         for(int i=0;i<g[y].size();++i) q.push(g[y][i]);
    21     }
    22     return true;
    23 }
    24 void dfs(int k)
    25 {
    26     for(int i=0;i<g[k].size();++i) dfs(g[k][i]);
    27     if(g[k].size()==2)
    28         if(check(g[k][0],g[k][1])) swap(g[k][0],g[k][1]);
    29 }
    30 void paint()
    31 {
    32     queue<int> q;
    33     vector<int> a;
    34     q.push(1);
    35     int id=0;
    36     while(!q.empty())
    37     {
    38         int u=q.front();
    39         //printf("ce : %d
    ",u);
    40         q.pop();
    41         ++id;
    42         ans[u]=id;
    43         if(u!=1) a.push_back(ans[fa[u]]-1);
    44         for(int i=0;i<g[u].size();++i) q.push(g[u][i]);
    45     }
    46     for(int i=0;i<a.size()-1;++i) printf("%d ",a[i]);printf("%d",a[a.size()-1]);
    47     printf("
    ");
    48 }
    49 int main()
    50 {
    51     freopen("ce.in","r",stdin);
    52     int T;
    53     scanf("%d",&T);
    54     while(T--)
    55     {
    56         scanf("%d",&n);
    57         for(int i=0;i<=n;++i) g[i].clear();
    58         for(int i=2;i<=n;++i)
    59         {
    60             int x;
    61             scanf("%d",&x);
    62             ++x;
    63             g[x].push_back(i);
    64             fa[i]=x;
    65         }
    66         dfs(1);
    67         //printf("ok
    ");
    68         paint();
    69     }
    70     return 0;
    71 }
    View Code

    F

    待填坑

    G(模拟)

    题意:

      在二维平面上有n个点,你需要开车走折线依次通过这些点,你最初在1号点,问你最少需要转弯多少次才能依次经过这n个点?

      n<=1000

    分析:

      样例比较良心

      我们把所有点按照顺序连一下,发现只有出现下面这个情形的时候我们才会做更改

      

      那么我们只需要从前到后去看有多少个这个结构就行了

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int maxn=1e3;
     4 struct Point
     5 {
     6     int x,y;
     7     Point()
     8     {
     9 
    10     }
    11     Point(int _x,int _y)
    12     {
    13         x=_x,y=_y;
    14     }
    15     Point operator - (const Point &a) const
    16     {
    17         return Point(x-a.x,y-a.y);
    18     }
    19 }p[maxn+5];
    20 bool flag[maxn+5];
    21 int n,top,ans;
    22 int cross(Point a,Point b)
    23 {
    24     return a.x*b.y-a.y*b.x;
    25 }
    26 bool pall(Point a,Point b)
    27 {
    28     if(cross(a,b)!=0) return false;
    29     return a.x*b.x+a.y*b.y>0;
    30 }
    31 int main()
    32 {
    33     freopen("ce.in","r",stdin);
    34     //freopen("ce.out","w",stdout);
    35     int T;
    36     scanf("%d",&T);
    37     while(T--)
    38     {
    39         scanf("%d",&n);
    40         for(int i=1;i<=n;++i) scanf("%d%d",&p[i].x,&p[i].y);
    41         for(int i=0;i<=n;++i) flag[i]=0;
    42         if(n<=2)
    43         {
    44             printf("0
    ");
    45             continue;
    46         }
    47         ans=0;
    48         for(int i=3;i<=n;++i)
    49         {
    50             if(!pall(p[i]-p[i-1],p[i-1]-p[i-2])) ++ans;
    51             //printf("%d
    ",ans);
    52             if(i!=3)
    53             if(!flag[i-1]&&!flag[i-2]&&1LL*cross(p[i-1]-p[i-2],p[i]-p[i-1])*cross(p[i-2]-p[i-3],p[i-1]-p[i-2])>0&&cross(p[i]-p[i-1],p[i-2]-p[i-3])!=0)
    54             {
    55                 if(1LL*cross(p[i-2]-p[i-3],p[i]-p[i-1])*cross(p[i-2]-p[i-3],p[i-1]-p[i-2])>0)
    56                 {
    57 
    58 
    59                 --ans;
    60                 flag[i-1]=1;
    61                 }
    62             }
    63 
    64         }
    65         printf("%d
    ",ans);
    66     }
    67     return 0;
    68 }
    View Code

    H(状压dp+递归)

    题意:

      有m道题目,有n个人,用0/1表示每个人是否会做每道题。现在你需要从m道题中挑出8道题组成一场考试,n个人进行这场考试。若一个人至少做出了5题,那么他就及格了。现在问你有多少种组题方案使得最后及格的人数在[l,r]之间并且是3的倍数。

      3<=n<=500000 8<=m<=20 3<=l<=r<=n

    分析:

      枚举每一种组题的方案,计算该组题情况下最后及格的人数

      容易想到需要将每个人的解题状态找一个共同的性质把其压缩起来,才能够减少最后的状态数,不然对于每种组题方案都要O(n)遍历

      dp[i][s1][j][s2]表示对于前i道题,题目的选择情况是s1,答对了j个题,且这些人后面的答题情况是s2的总人数

      很明显s1只有前i位有用,s2只有i后面的有用,于是我们把s1和s2状态合并到一起

      那么显然就能状压dp了

      分析一下复杂度,是$20*2^{20}*5$的

      这是有点超时的,优化常数也不能在1s内通过

      研究下原因是题目限定了“选8道题”,所以由很多选题状态都是无用的

      于是我们用dfs去代替for循环枚举,就能800ms过了

    I(计数+BIT)

    题意:

      

      n<=1e6

    分析:

      仔细分析会发现这个上升序列和下降序列一定会交于唯一位置(枚举这个位置给上升还是下降,会产生两种方案)

      那么我们只需要枚举这个交点i,然后统计这个交点是否能对答案产生贡献

      能产生贡献那就是:i前面比a[i]小的数字呈上升趋势,比a[i]大的数字呈下降趋势;i后面比a[i]小的数字呈下降趋势,比a[i]大的数字呈上升趋势

      那么如何判断呢?以判断i前面比a[i]小的数字是否呈上升趋势为例

      我们只需要统计出两个东西,一个东西是从左往右以a[i]为栈顶的单调栈的元素个数,另一个东西是1~i中<=a[i]的数字个数

      第二个东西我们只需要用BIT来计算就行了

    J

    待填坑

  • 相关阅读:
    <linux程序设计> 第四章 [ 程序参数 / 环境变量 / 日期与时间]
    阻塞分析
    架构设计分类
    软件测试
    C#中常用的加密类
    SQL2005
    用异或的性质实现简单加密解密
    在sql中取系统时间?日期?年?
    SQL Server常用到的几个设置选项
    Connection Command[ExecuteNonQuery ExecuteScalar ExecuteReader] DataReader DataAdapter DataSet
  • 原文地址:https://www.cnblogs.com/wmrv587/p/8440019.html
Copyright © 2020-2023  润新知