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(n\sqrt{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\n",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\n",i,k,p,ret);
            ans[i]=((ll)ans[i]+(ll)ans[k-1]*(ret-p)%m)%m;
            ret=p;
            cnt++;
        }
        //printf("ans[%d]=%d\n",i,ans[i]);
        sum=(sum+ans[i])%m;
        if(i==2000000)printf("cnt=%d\n",cnt);
    }
    printf("%d\n",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\n",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\n",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\n",m);
        for(int i=1;i<=m;i++)
            printf("%d%c",ans[i],(i==m)?'\n':' ');
    }
    return 0;
}
View Code

 

posted @ 2021-08-26 16:55  Zinn  阅读(43)  评论(0编辑  收藏  举报