• cf 1561(div2)


    比赛链接:http://codeforces.com/contest/1561

    前三题照例很简单(虽然C题WA了两次)。做出了D1。

    D

    题意:

    对于一个数(n),每次可以减(1—(n-1))中的一个数,或者除以(2—n)中的一个数,问有多少种方式变成(1)。

    分析:

    从(x)位置跳到1,我们考虑第一步跳什么。第一步可以减某个数,所以答案是前面所有答案的和;第一步也可以除某个数,答案加上除到的位置的答案。 

    除到的位置是一段一段的,比如(x=6)的时候是:

    (1(6/6), 1(6/5), 1(6/4), 2(6/3), 3(6/2) )

    我们要跳着取,相同的一段直接算。

    其实这里要严谨地考虑时间复杂度的话,应该是分成(<sqrt{x})和(>sqrt{x})两部分。时间复杂度(O(nsqrt{n}))。但都跳也能过。

    代码如下:

    #include<iostream>
    #include<ctime>
    #define ll long long
    using namespace std;
    int const N=4e6+5;
    int n,m,ans[N];
    int main()
    {
        double st=clock();
        scanf("%d%d",&n,&m); ans[1]=1; ans[2]=2; ll sum=3;
        for(int i=3;i<=n;i++)
        {
            ans[i]=sum; int ret=i; int cnt=0;
            //printf("i=%d sum=%lld
    ",i,sum);
            for(int k=2;k<i&&ret>1;k=i/(i/k)+1)
            {
                int p=i/k;
                //printf("i=%d k=%d p=%d ret=%d
    ",i,k,p,ret);
                ans[i]=((ll)ans[i]+(ll)ans[k-1]*(ret-p)%m)%m;
                ret=p;
                cnt++;
            }
            //printf("ans[%d]=%d
    ",i,ans[i]);
            sum=(sum+ans[i])%m;
            if(i==2000000)printf("cnt=%d
    ",cnt);
        }
        printf("%d
    ",ans[n]);
        double ed=clock();
        //cout<<(ed-st)/CLOCKS_PER_SEC<<endl;
        return 0;
    }
    D1

    D2和D1的唯一不同就是(n)的范围变成(4*10^6)了,所以不能再用上面的做法。比赛时我就开始干瞪眼了。

    实际上,这里有个“关键观察”:考虑(x)和(x-1)能除到的地方有什么不同。先上几个例子:

    (2: 1)

    (3: 1, 1)

    (4: 1, 1, 2)

    (5: 1, 1, 1, 2)

    (6: 1, 1, 1, 2, 3)

    观察一番,再结合一些思考,可以发现(x)能除到的地方和(x-1)相比,首先是多了一个(1)((x/x = 1))。其次,对于(x)的一个因数(p)(不包括(1)和(x)),它会把(x-1)能除到的位置中的一个(p-1)换成(p)。仔细想想,确实是这样。

    所以一种做法可以是记录每个(x)可以除到的这些位置的答案之和(f[x])。计算(x)的答案时,枚举(x)的每个因子,把(f[x-1])更新成(f[x]),再计算当前的答案。

    然而枚举因子还是不够优秀。我们不妨从因子出发,直接考虑它对它的倍数的影响。

    也就是再记录一个(ad[x])。计算出当前(i)的答案(ans[i]),需要枚举(i)的所有倍数(j),(ad[j]=ad[j]-ans[i-1]+ans[i])。于是(f[x] = f[x-1] + ad[x] )。

    这样做时间复杂度(O(nlnn)),空间复杂度(O(n))。

    代码如下:

    #include<iostream>
    #define ll long long
    using namespace std;
    int const N=4e6+5;
    int n,m,f[N],ad[N],ans[N];
    int mo(ll x){x%=m; if(x<0)x+=m; return x;}
    int main()
    {
        scanf("%d%d",&n,&m); int sum=0; ans[1]=1;
        for(int i=2;i<=n;i++)ad[i]=1;//i/i->1
        for(int i=2;i<=n;i++)
        {
            f[i]=mo(f[i-1]+ad[i]);
            ans[i]=mo((ll)sum+1+f[i]);
            //printf("i=%d sum=%d f=%d ans=%d
    ",i,sum,f[i],ans[i]);
            for(int j=2*i;j<=n;j+=i)
                ad[j]=mo((ll)ad[j]-ans[i-1]+ans[i]);
            sum=mo((ll)sum+ans[i]);
        }
        printf("%d
    ",ans[n]);
        return 0;
    }
    D2

    E

    题意:

    给一个奇数长度的排列,每次只能翻转长度为奇数的一个前缀,问能否在(frac{5n}{2})次内将其排序,并给出方案。

    分析:

    首先,由于翻转的是奇数长度的前缀,所以奇数位置的数只能移动到奇数位置,偶数位置的数只能移动到偶数位置。如果有(a[i])和(i)的奇偶性不同,那么无解。

    否则一定有解,只要按下面的方法:

    首先,我们把(n)和(n-1)移动到数列末尾,就可以不再管它们了。

    这启发我们每次把最后两个数排好——这只需要五步:(假设当前要排的是(nw)和(nw-1))

    1.找到(nw)所在的位置(x)(奇数),翻转(1—x),此时(nw)在第一个位置;

    2.找到(nw-1)所在的位置(y)(偶数),翻转(1—(y-1)),此时(nw)在(nw-1)前一个位置;

    3.翻转(1—(y+1)),此时(nw)在第三个位置,(nw-1)在第二个位置;

    4.翻转(1—3),此时(nw)在第一个位置,(nw-1)在第二个位置;

    5.翻转(1—nw),此时(nw)和(nw-1)就排好了。

    所以最多五步排好两个数,最终答案不超过(frac{5n}{2})。

    好娱乐啊,这个题。

    代码如下:

    #include<iostream>
    using namespace std;
    int const N=2022;
    int T,n,a[N],ans[N*5];
    void rev(int l,int r)
    {
        int mid=((l+r)>>1);
        for(int i=l,j=r;i<mid;i++,j--)swap(a[i],a[j]);
    }
    int main()
    {
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d",&n); bool fl=1;
            for(int i=1;i<=n;i++)
            {
                scanf("%d",&a[i]);
                if((a[i]&1)!=(i&1))fl=0;
            }
            if(!fl){puts("-1"); continue;}
            int m=0,nw=n,x,y;
            while(nw>1)
            {
                if(a[nw]==nw&&a[nw-1]==nw-1){nw-=2; continue;}
                for(int i=1;i<=n;i++)
                    if(a[i]==nw){x=i; break;}
                if(x>1){ans[++m]=x; rev(1,x);}
                for(int i=1;i<=n;i++)
                    if(a[i]==nw-1){y=i; break;}
                if(y-1>1){ans[++m]=y-1; rev(1,y-1);}
                ans[++m]=y+1; rev(1,y+1);
                ans[++m]=3; rev(1,3);
                ans[++m]=nw; rev(1,nw);
                nw-=2;
            }
            printf("%d
    ",m);
            for(int i=1;i<=m;i++)
                printf("%d%c",ans[i],(i==m)?'
    ':' ');
        }
        return 0;
    }
    View Code
  • 相关阅读:
    抽象与接口的综合练习
    java构造函数能否被继承,为什么?
    题解 【USACO 4.2.1】草地排水
    题解 【NOI2010】超级钢琴
    题解 [USACO Mar08] 奶牛跑步
    题解 【NOIP2016】魔法阵
    题解 对称二叉树
    题解 【NOIP2014】解方程
    题解 【NOIP2010】关押罪犯
    题解 贪吃蛇
  • 原文地址:https://www.cnblogs.com/Zinn/p/15190501.html
Copyright © 2020-2023  润新知