zoukankan      html  css  js  c++  java
  • 8.18爆炸记

    T1

    改造二叉树

    Description

    在计算机科学中,二叉树是每个结点最多有两个子结点的有序树。通常子结点被称作“左孩子”和“右孩子”。二叉树常被用作二叉搜索树和二叉堆。
      我们再讨论二叉搜索树。什么是二叉搜索树呢?二叉搜索树首先是一棵二叉树。设key[p]表示结点p上的数值,对于其中的每个结点p,若其存在左孩子lch,则key[p] > key[lch];若其存在右孩子rch,则key[p] < key[rch]。注意,应该是所有左子树中的key小于当前key,所有右子树中的key大于当前key。
      对于每个结点,无论如何改变其数值(整数),费用总等于1。
      现在给定一棵二叉树,可以任意修改结点的数值。要求用最小的费用将其变成一棵二叉搜索树。

    Input

      输入文件bst.in包括两行,第一行是一个整数n,表示二叉树的结点数。
      第二行包含n个整数,用空格分隔,第i个整数ai是第i个结点的原始数值。
      此后n-1行每行两个整数,第i行描述编号为i-1的结点的父亲编号以及父子关系(0表示为左孩子,1表示为右孩子)。编号为1的结点一定是二叉树的根。

    Output

     输出文件bst.out包括一行,这一行只包含一个整数,也就是最小的费用值。输入数据保证这个值小于2^31。

    Sample Input

    3 
    2 2 2
    1 0
    1 1

    Sample Output

    2

    Hint

    【数据范围】
      对于50%的数据,保证n<=100且0<=ai<=200;
      对于100%的数据,保证n<=100000;

    二叉查找树的中序遍历具有一个优秀的性质,那就是严格递增。对于当前树,我们可以处理出它的中序遍历,问题就变为了:求最少在中序遍历中修改几个数,使它严格递增。

    我们自然会想到求出它的最长上升子序列,并用n减去它的长度就是答案。但如何保证严格递增呢?常见的一个thick就是把每个数减去当前数的下标,但值得注意的是:此时求的就是最长不降子序列的长度了。

    因为n≤105,故要用二分优化。

    #include<iostream>
    #include<iomanip>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    #define INF 0x7fffffff
    inline int read() {
        char ch;
        bool bj=0;
        while(!isdigit(ch=getchar()))
            bj|=(ch=='-');
        int res=ch^(3<<4);
        while(isdigit(ch=getchar()))
            res=(res<<1)+(res<<3)+(ch^(3<<4));
        return bj?-res:res;
    }
    void printnum(int x) {
        if(x>9)printnum(x/10);
        putchar(x%10+'0');
    }
    inline void print(int x,char ch) {
        if(x<0) {
            putchar('-');
            x=-x;
        }
        printnum(x);
        putchar(ch);
    }
    int n,a[200005],c[200005][2];
    int d[200005],Mid[200005],tot;
    void DFS(int x) {
        if(c[x][0])DFS(c[x][0]);
        Mid[++tot]=a[x];
        if(c[x][1])DFS(c[x][1]);
    }
    int Half_LIS() {
        int len=1;
        d[1]=Mid[1];
        for(int i=2; i<=n; i++) {
            int l=1,r=len;
            if(d[len]<=Mid[i]) {
                len++;
                d[len]=Mid[i];
                continue;
            }
            while(l<=r) {
                int    mid=(l+r)>>1;
                if(d[mid]<=Mid[i])l=mid+1;
                else r=mid-1;
            }
            d[l]=Mid[i];
        }
        return len;
    }
    signed main() {
        n=read();
        for(int i=1; i<=n; i++)a[i]=read();
        int x,y;
        for(int i=2; i<=n; i++) {
            x=read();
            y=read();
            c[x][y]=i;
        }
        DFS(1);
        for(int i=1; i<=n; i++)Mid[i]-=i;
        cout<<n-Half_LIS()<<endl;
        return 0;
    }

    T2

    数字对

    Description

     小H是个善于思考的学生,现在她又在思考一个有关序列的问题。
      她的面前浮现出一个长度为n的序列{ai},她想找出一段区间[L, R](1 <= L <= R <= n)。
      这个特殊区间满足,存在一个k(L <= k <= R),并且对于任意的i(L <= i <= R),ai都能被ak整除。这样的一个特殊区间 [L, R]价值为R - L。
      小H想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些区间又分别是哪些呢?你能帮助她吧。

    Input

      第一行,一个整数n.
      第二行,n个整数,代表ai.

    Output

      第一行两个整数,num和val,表示价值最大的特殊区间的个数以及最大价值。
      第二行num个整数,按升序输出每个价值最大的特殊区间的L.

    Sample Input

    【样例输入1】
    5
    4 6 9 3 6
    
    【样例输出1】
    1 3
    2

    Sample Output

    【样例输入2】
    5
    2 3 5 7 11
    
    【样例输出2】
    5 0
    1 2 3 4 5

    Hint

    【数据范围】
      30%: 1 <= n <= 30 , 1 <= ai <= 32.
      60%: 1 <= n <= 3000 , 1 <= ai <= 1024.
      80%: 1 <= n <= 300000 , 1 <= ai <= 1048576.
      100%: 1 <= n <= 500000 , 1 <= ai < 2 ^ 31.

    30pts:暴力,枚举每个区间,暴力扫。

    60pts:ST表预处理区间最小和区间gcd,显然成为特殊区间的条件就是区间最小和区间gcd相等,枚举每个区间,O(1)算(笔者没算gcd的复杂度)

    100pts:是否可以二分?如果我们二分点i向右延伸的长度的话,这个答案是不具有单调性的。但是如果二分最大价值就可以,设当前二分的答案是mid,O(n)暴力判一遍[i,i+mid]所有区间是否为特殊区间,求答案的最大值即可

    不用ST表用线段树的话会T飞,同桌亲测与暴力分一样。推荐遇到静态问题或多次查询的题都用ST表。

    #include<iostream>
    #include<iomanip>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<map>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define int long long
    inline int read() {
        char ch;
        bool bj=0;
        while(!isdigit(ch=getchar()))
            bj|=(ch=='-');
        int res=ch^(3<<4);
        while(isdigit(ch=getchar()))
            res=(res<<1)+(res<<3)+(ch^(3<<4));
        return bj?-res:res;
    }
    void printnum(int x) {
        if(x>9)printnum(x/10);
        putchar(x%10+'0');
    }
    inline void print(int x,char ch) {
        if(x<0) {
            putchar('-');
            x=-x;
        }
        printnum(x);
        putchar(ch);
    }
    int p[500005][25],a[500005],n,log_2[500005],minn[500005][25];
    int maxn=-INF;
    int tot,ans[500005];
    int gcd(int x,int y) {
        return y?gcd(y,x%y):x;
    }
    inline void ST() {
        for(int j=1; j<=log_2[n]; j++)
            for(int i=1; i<=n; i++)
                if(i+(1<<j)-1<=n) {
                    p[i][j]=gcd(p[i][j-1],p[i+(1<<(j-1))][j-1]);
                    minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
                }
    }
    inline int check(int x,int y) {
        int k=log_2[y-x+1];
        return gcd(p[x][k],p[y-(1<<k)+1][k])==min(minn[x][k],minn[y-(1<<k)+1][k]);
    }
    inline void Solve(int x) {
        for(int i=1; i<=n; i++)
            if(i+x<=n&&check(i,i+x))
                ans[++tot]=i;
    }
    signed main() {
        n=read();
        for(int i=2; i<=n; i++)log_2[i]=log_2[i>>1]+1;
        for(int i=1; i<=n; i++) {
            a[i]=read();
            p[i][0]=minn[i][0]=a[i];
        }
        ST();
        int l=0,r=n;
        while(l<=r) {
            int mid=(l+r)>>1;
            tot=0;
            Solve(mid);
            if(tot) {
                maxn=mid;
                ans[0]=tot;
                l=mid+1;
            } else r=mid-1;
        }
        print(ans[0],' ');
        print(maxn,'
    ');
        for(int i=1; i<=ans[0]; i++)print(ans[i],' ');
        return 0;
    }

    T3

    交换

    Description

    给定一个{0, 1, 2, 3, … , n - 1}的排列 p。一个{0, 1, 2 , … , n - 2}的排列q被认为是优美的排列,当且仅当q满足下列条件:
    对排列s = {0, 1, 2, 3, ..., n - 1}进行n – 1次交换。
    1.交换s[q0],s[q0 + 1]
    2.交换s[q1],s[q1 + 1]
    …
    最后能使得排列s = p.
    问有多少个优美的排列,答案对10^9+7取模。

     Input

    第一行一个正整数n.
    第二行n个整数代表排列p.

    Output

    仅一行表示答案。

    Sample Input

    3
    1 2 0

    Sample Output

    【样例输出】
    1

    Hint

    【样例解释】
    q = {0,1} {0,1,2} ->{1,0,2} -> {1, 2, 0}
    q = {1,0} {0,1,2} ->{0,2,1} -> {2, 0, 1}
    【数据范围】
    30%: n <= 10
    100%: n <= 50

    30pts:q的全排列,暴力交换判断。

    100pts:考虑把操作反过来,即p序列交换为s序列,不影响答案

    枚举交换的分界点i和i+1,若对于[1,i]的所有元素都在[0,i-1]的范围内,且[i+1,n]的所有元素都在[i,n-1]的范围内,则可以分成两个子问题,递归求解。

    eg: 设序列p={1,2,0,4,3,5},交换元素0和4是左右是满足子问题结构的,交换3和5同理。

    每次递归子问题会返回左边的方案数f1和右边的方案数f2,根据乘法原理,当前方案数f=f1×f2。

    Is it all?显然不是的,因为左右两区间不相交,所以左右两边的交换不影响,但是在计数中算两种方案,所以我们还需考虑交换的时间顺序。

    设当前区间为[l,r],分界点i,总交换次数是r-l,交换了i和i+1后剩r-l-1次。左边的区间是[l,i],共i-l+1个数,有i-l次交换;右边的区间是[i+1,r],共r-i个数,有r-i-1次交换。

    显然确定了前一半,后一半也随之确定,所以只需要在r-l-1中选i-l(或r-i-1)次就行了,即C(r-l-1,i-l)

    设f[l][r]为(l,r)区间的方案数

    f[l][r]=DFS(l,i)×DFS(i+1,r)×C(r-l-1,i-l)

    没了,加个记忆化。

    #include<iostream>
    #include<iomanip>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    #define int long long
    inline int read() {
        char ch;
        bool bj=0;
        while(!isdigit(ch=getchar()))
            bj|=(ch=='-');
        int res=ch^(3<<4);
        while(isdigit(ch=getchar()))
            res=(res<<1)+(res<<3)+(ch^(3<<4));
        return bj?-res:res;
    }
    void printnum(int x) {
        if(x>9)printnum(x/10);
        putchar(x%10+'0');
    }
    inline void print(int x,char ch) {
        if(x<0) {
            putchar('-');
            x=-x;
        }
        printnum(x);
        putchar(ch);
    }
    const int Mod=1e9+7;
    int p[55],f[55][55],n,c[55][55];
    int DFS(int l,int r) {
        if(l==r)return f[l][r]=1;
        if(~f[l][r])return f[l][r];
        f[l][r]=0;
        for(int i=l; i<r; i++) {
            int bj=1;
            swap(p[i],p[i+1]);
            for(int j=l; j<=i; j++)
                if(p[j]<l||p[j]>i) {
                    bj=0;
                    break;
                }
            for(int j=i+1; j<=r; j++)
                if(p[j]<i+1||p[j]>r) {
                    bj=0;
                    break;
                }
            if(bj)f[l][r]=(f[l][r]+DFS(l,i)*DFS(i+1,r)%Mod*c[r-l-1][i-l]%Mod)%Mod;
            swap(p[i],p[i+1]);
        }
        return f[l][r];
    }
    signed main() {
        n=read();
        for(int i=1; i<=n; i++)p[i]=read()+1;
        memset(f,-1,sizeof(f));
        for(int i=0; i<=n; i++) {
            c[i][0]=1;
            for(int j=1; j<=i; j++)
                c[i][j]=(c[i-1][j-1]+c[i-1][j])%Mod;
        }
        print(DFS(1,n),'
    ');
        return 0;
    }

    得分10(100)+60(60)+30(30)=100(190)

    OI尚未结束,蒟蒻仍需努力

  • 相关阅读:
    渗透测试中的文件传输通道1- cmd下下载文件
    内网渗透常用命令
    技术剖析中国菜刀原理
    win8 iis安装及网站发布
    C++与C的区别一
    C语言实现单链表,并完成链表常用API函数
    C语言实现数组及链表的快速排序
    使用C语言封装数组,动态实现增删改查
    C语言中静态断言的使用
    二分查找法C语言实现
  • 原文地址:https://www.cnblogs.com/soledadstar/p/11373766.html
Copyright © 2011-2022 走看看