• Codeforces Round #547 C. Polycarp Restores Permutation(二分枚举/数学+模拟)


    在这里插入图片描述

    题意: 有一个长度为N的序列p,该序列保证存在1~N每个值都存在,现在给出一个序列q,长度为N-1,表示序列p相邻两数之差,根据序列q输出序列p

    题解: 一开始想复杂了,以为要找什么最大差值最小差值来扩展,又或者枚举的话可能因为不断尝试的过程需要递归搜索,复杂度会很大。。。。

    其实非常简单,想复杂了自己吓自己了,首先可以明确的是,给出了该序列的相邻两值差值,那么直接枚举第一个值就能计算出整个序列,再计算的过程中判断是否合法即可。而题目中给出的条件1~N必须存在的作用就是给出了第一个值的枚举范围,若N=4,第一个差值是-3,那么第一个值绝对不可能小于4,因为在N中-3已经达到了最大值和最小值之间的差值,即4和1,那么我们只需枚举4即可通过加减差值得到整个序列,并且这个序列是唯一的,复杂度O(n)。

    大部分情况下给出的差值将限定第一个值的合法枚举范围,只需遍历这个范围,不断计算每一个可能的唯一序列,并判断是否合法即可。

    可以知道,若差值的第一个数是q[0],若q[0]为负数,那么这个枚举范围即:1-q[0] --------> N
    否则枚举范围是q[0]+1 -----------> n-q[0]

    一开始用for循环枚举判断每个值,这个复杂度可能是O(N*N)的,最复杂情况,当2e5个差值是等差序列,差值为1,但是最后一个值的差值存在巨大异常,那么枚举的范围将是2e5,而得到不合法序列的时间将是计算到最后才知道。【第41组case】

    考虑两个优化,首先我们知道,在差值确定的情况下,当我们重复计算出某个值的时候,无论枚举任何数,都会出现重复值,因为题意是必须出现1~N所有值,因此不可能出现重复值,一旦出现重复值,就不继续构造该序列,直接无解。

    第二个也是最重点的优化,即for枚举改成二分枚举,因为可以知道,当计算一个值出现不合法情况是,如计算出来的值是大于N的或小于0的,可以根据结果调整枚举值的范围,想象该序列是一段折线,那么当某个值大于N时,我们将枚举的第一个值向下调整,当小于0时,第一个值向上调整,那么整个序列都将上移,二分枚举不断找到一个令所有值都合法的序列
    在这里插入图片描述
    最后复杂度,O(N*logN)
    代码如下:

    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    const int maxn=2e5+7;
    int n,q[maxn];
    bool vis[maxn<<1];
    vector<int>p;
    int main()
    {
        scanf("%d",&n);
        bool flag=false,gg=false;
        for(int i=1; i<n; i++)scanf("%d",&q[i]);
        int l,r;
        if(q[0]>0) r=n-q[0],l=q[0]+1;//二分边界
        else l=1-q[0],r=n;
        while(l<=r)
        {
            int i=(l+r)/2,tmp;
            memset(vis,false,sizeof vis);
            vis[i]=true;
            p.push_back(i);
            for(int j=1; j<n; j++)
            {
                tmp=p.back()+q[j];
                if(vis[tmp]==false&&tmp>0&&tmp<=n)
                {
                    vis[p.back()+q[j]]=true;
                    p.push_back(p.back()+q[j]);
                    if(p.size()==n)flag=true;
                }
                else
                {
                    if(tmp>0&&vis[tmp]==true)gg=true;
                    break;
                }
            }
            if(flag||gg)break;
            p.clear();
            if(tmp>n)r=i-1;
            else l=i+1;
        }
        if(gg)//相同值时无解
        {
            printf("-1
    ");
            return 0;
        }
        if(flag)
            for(int i=0; i<p.size(); i++)printf("%d%c",p[i],i==p.size()?'
    ':' ');
        else printf("-1
    ");
    }
    
    

    后来突然发现该题标签是math【数学题】
    感谢大佬@satomiyo提供数学算法思路

    首先对于原序列a有:
    a1 a2 a3 a4 a5 a6 a7 …an

    对于差值序列x有:
    x1 x2 x3 x4 x5 x6 …x(n-1)

    我们对差值序列求和可以得到
    y1 y2 y3 y4 y5 y6 …y(n-1)

    该差值的求和可以表示,an - a1=y(n-1)

    a4 - a1 = y3

    接着我们列出式子:

    a1 +(a2-a1) +(a3-a1) +(a4-a1) +(a5-a1) +(a6-a1) +…+(an-a1)

    其中括号内的每一个值即y1,y2,y3,y4…

    那么经过整理后将所有正数提出来,将所有-a1整合在一起,用等差数列求和公式计算a1+…+an,得到式子:
    n × (n+1) / 2 - (n-1) × a1 = a1 + (y1 + …+ y(n-1))

    移项,并对y1~y(n-1)求和为sum后得到
    n × (n+1) / 2 - n × a1 = sum

    这个式子中只有a1是未知的,a1=( n × (n+1) / 2 -sum)/n

    最后拿这个确定的a1计算整个式子的值,输出即可,注意计算式子时仍要判断是否符合条件【也可能重复】,不符合输出-1。并且a1的计算结果也可能不合法,即直接输出-1

    代码如下:

    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    const int maxn=2e5+7;
    LL n;
    LL q[maxn];
    LL ss[maxn];
    bool vis[maxn];
    vector<LL>p;
    int main()
    {
        scanf("%lld",&n);
        LL sum=n*(n+1)/2;
        for(int i=1; i<n; i++)
        {
            scanf("%lld",&q[i]);
            ss[i]=ss[i-1]+q[i];
        }
        for(int i=1; i<n; i++)ss[i]+=ss[i-1];
        LL num=sum-ss[n-1];
        bool flag=false;
        if(num%n==0)
        {
            LL a1=num/n;
            if(a1>0&&a1<=n)
            {
                for(int i=0; i<n; i++)
                {
                    if(a1+q[i]>0&&a1+q[i]<=n&&vis[a1+q[i]]==false)
                    {
                        a1+=q[i];
                        p.push_back(a1);
                        vis[a1]=true;
                    }
                    else
                    {
                        flag=true;
                        printf("-1
    ");
                        break;
                    }
                }
                if(!flag)
                    for(int i=0; i<n; i++)
                        printf("%lld%c",p[i],i==n-1?'
    ':' ');
    
            }
            else printf("-1
    ");
        }
        else printf("-1
    ");
    }
    
    
  • 相关阅读:
    facedetect
    gmm
    拉格朗日对偶
    基于 MeanShift 算法的目标跟踪问题研究
    视频目标跟踪算法综述
    AtCoder Beginner Contest 088 C Takahashi's Information
    L3-016. 二叉搜索树的结构
    垒骰子
    1130. Infix Expression (25)
    1129. Recommendation System (25)
  • 原文地址:https://www.cnblogs.com/kuronekonano/p/11135662.html
Copyright © 2020-2023  润新知