题意: 有一个长度为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\n");
return 0;
}
if(flag)
for(int i=0; i<p.size(); i++)printf("%d%c",p[i],i==p.size()?'\n':' ');
else printf("-1\n");
}
后来突然发现该题标签是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\n");
break;
}
}
if(!flag)
for(int i=0; i<n; i++)
printf("%lld%c",p[i],i==n-1?'\n':' ');
}
else printf("-1\n");
}
else printf("-1\n");
}