A. One and Two
题意:对于给定长度为
n
n
n 的序列
a
a
a (已知
a
i
=
1
a_{i}=1
ai=1 or
2
2
2),寻找是否存在最小
k
k
k 使得
a
1
×
a
2
×
.
.
.
×
a
k
=
a
k
+
1
×
a
k
+
2
×
.
.
.
×
a
n
a_{1} \times a_{2} \times ... \times a_{k} = a_{k+1} \times a_{k+2} \times ... \times a_{n}
a1×a2×...×ak=ak+1×ak+2×...×an 且
1
≤
k
≤
n
−
1
1 \leq k \leq n-1
1≤k≤n−1 ,若不存在输出
−
1
-1
−1
由于序列
a
a
a 中只包含数
1
1
1 或
2
2
2 ,只有
2
2
2 影响序列连乘结果,故统计序列中
2
2
2 的数量,当前缀中
2
2
2 的数量等于后缀中
2
2
2 的数量,则寻找到符合题意的答案
注意:由于
2
≤
n
≤
1000
2 \leq n \leq 1000
2≤n≤1000 ,故不可采用直接计算前缀连乘积,会爆数据范围
代码如下:
#include <bits/stdc++.h>
using namespace std;
void solve()
{
int n,k=-1;
scanf("%d",&n);
vector<int>ve(n+1);
for(int i=1;i<=n;i++)
{
scanf("%d",&ve[i]);ve[i]=ve[i-1]+(ve[i]==2);
}
for(int i=1;i<n;i++)
{
if(ve[i]*2==ve[n])
{
k=i;break;
}
}
printf("%d\n",k);
}
signed main() {
int T=1;
cin>>T;
while(T--)
solve();
return 0;
}
B. Sum of Two Numbers
题意:给定一个正整数
n
n
n ,要求寻找
x
x
x、
y
y
y ,满足
x
+
y
=
n
x+y=n
x+y=n 并且
x
x
x 各数位上数字之和与
y
y
y 各数位上数字之和相差不超过
1
1
1
显然对于大部分情况,构造
x
=
f
l
o
o
r
(
n
2
)
x=floor(\frac{n}{2})
x=floor(2n) 与
y
=
n
−
f
l
o
o
r
(
n
2
)
y=n-floor(\frac{n}{2})
y=n−floor(2n) 符合题意。
但对于
x
=
f
l
o
o
r
(
n
2
)
x=floor(\frac{n}{2})
x=floor(2n) 与
y
=
n
−
f
l
o
o
r
(
n
2
)
y=n-floor(\frac{n}{2})
y=n−floor(2n) 之间存在进位情况,例如
1999
=
999
+
1000
1999=999+1000
1999=999+1000 ,需进行特殊处理,将
x
=
f
l
o
o
r
(
n
2
)
x=floor(\frac{n}{2})
x=floor(2n) 末尾
9
9
9 数量进行统计,若为偶数进行均分,否则将一个
9
9
9 分为
4
4
4 与
5
5
5 分别放置在
x
x
x 与
y
y
y 的最后一位上,然后其他
9
9
9 进行均分即可
代码如下:
#include <bits/stdc++.h>
using namespace std;
void solve()
{
int n,x,y;
scanf("%d",&n);
x=n/2;y=n-x;
if(x<y)swap(x,y);
if(x%10==0&&x!=y)
{
int ty=y,tt=0,num=0,tnow=9;
while(ty%10==9)
{
tnow*=10;num++;ty/=10;
}
tnow/=10;
for(int i=1;i*2<=num;i++)
y-=tnow,tt+=tnow,tnow/=10;
x+=tt;
if(num&1)
x+=4,y-=4;
}
printf("%d %d\n",x,y);
}
signed main() {
int T=1;
cin>>T;
while(T--)
solve();
return 0;
}
C. Matching Numbers
题意:给定正整数
n
n
n ,判断是否能构造
n
n
n 个数对
(
a
i
,
b
i
)
(a_{i},b_{i})
(ai,bi),满足
{
a
1
,
b
1
,
a
2
,
b
2
,
.
.
.
a
n
,
b
n
}
\{ a_{1},b_{1},a_{2},b_{2},...a_{n},b_{n} \}
{a1,b1,a2,b2,...an,bn} 是
1
−
2
n
1-2n
1−2n 的排列,且
{
a
1
+
b
1
,
a
2
+
b
2
,
.
.
.
a
n
+
b
n
}
\{ a_{1}+b_{1},a_{2}+b_{2},...a_{n}+b_{n} \}
{a1+b1,a2+b2,...an+bn} 排列后是公差为
1
1
1 的等差数列
运用两次等差数列求和公式可得到,若存在符合题意的构造,则
{
a
1
+
b
1
,
a
2
+
b
2
,
.
.
.
a
n
+
b
n
}
\{ a_{1}+b_{1},a_{2}+b_{2},...a_{n}+b_{n} \}
{a1+b1,a2+b2,...an+bn} 排列后所得等差数列首项为
3
(
n
+
1
)
2
\frac{3(n+1)}{2}
23(n+1) ,末项为
5
n
+
1
2
\frac{5n+1}{2}
25n+1 。显然由首项公式可得到若存在构造,则
n
n
n 一定为奇数。接着这里采用
a
i
+
b
i
a_{i}+b_{i}
ai+bi 由
3
(
n
+
1
)
2
\frac{3(n+1)}{2}
23(n+1) 至
5
n
+
1
2
\frac{5n+1}{2}
25n+1 依次构造,保证
a
i
<
b
i
a_{i}<b_{i}
ai<bi 恒成立,
a
i
a_{i}
ai 先运用奇数后运用偶数参与构造,
b
i
b_{i}
bi 由
a
i
+
b
i
a_{i}+b_{i}
ai+bi 总和减去
a
i
a_{i}
ai 的值得到
#include <bits/stdc++.h>
using namespace std;
void solve()
{
int n,now;
scanf("%d",&n);
if(n%2==0)
{
puts("No");return ;
}
puts("Yes");
now=(n+1)*3/2;
for(int i=1;i<now-i;i+=2,now++)
printf("%d %d\n",i,now-i);
for(int i=2;now<=(5*n+1)/2;i+=2,now++)
printf("%d %d\n",i,now-i);
}
signed main() {
int T=1;
cin>>T;
while(T--)
solve();
return 0;
}
D. Moving Dots
题意:存在某点集,对于点集中的每个点,都将以相同的速度向离其最近的点移动(若某点与左右两点距离均相等,优先向左移动),直到遇到其他点后停止。现给定
n
n
n 个点坐标,求点集中的每个非空子集在移动操作后点聚集地数的总和
现对于点
i
i
i 与点
j
j
j
(
i
<
j
)
(i<j)
(i<j) ,计算点
i
i
i 向右运动点
j
j
j 向左运动后点
i
i
i 与点
j
j
j 相遇产生一个点聚集地可在几个点子集中对答案产生贡献
代码如下:
#include <bits/stdc++.h>
typedef long long LL;
const int MOD = 1e9+7;
using namespace std;
int n,pw2[3005],x[3005];
LL ans=0;
void solve()
{
scanf("%d",&n);
pw2[0]=1;
for(int i=1;i<=n;i++)
{
scanf("%d",&x[i]);
pw2[i]=(pw2[i-1]*2ll)%MOD;
}
int l,r;
for(int i=1;i<=n;i++)//现对于点i与点j(i<j),计算点i向右运动、点j向左运动后点i与点j相遇产生一个点聚集地可在几个点子集中对答案产生贡献
{
l=i-1,r=(i+1)+1;//x[j]-x[i]不断增大,故l不断减小、r不断增大
for(int j=i+1;j<=n;j++)
{
while(x[i]-x[l]<=x[j]-x[i]&&l>0)//寻找最右边的可添加点l不影响点i运动方向
l--;
while(x[r]-x[j]<x[j]-x[i]&&r<=n)//寻找最左边的可添加点r不影响点j运动方向
r++;
ans+=1ll*pw2[l]*pw2[n-r+1]%MOD;//[1,l]与[r,n]内点任选情况下的点子集均存在点i与点j相遇产生一个点聚集地的贡献
ans%=MOD;
}
}
printf("%lld\n",ans);
}
signed main() {
solve();
return 0;
}
E. Sum Over Zero
题意:对于给定长度为
n
n
n 的序列
a
a
a ,将序列
a
a
a 划分为若干个子段,寻找最大的子段长度总和,满足选择的每个子段总和大于等于
0
0
0
将
p
r
e
s
pres
pres 表示为
a
a
a 的前缀和。若一个段
[
x
,
y
]
[x,y]
[x,y] 可被选择,则满足
p
r
e
s
x
−
1
≤
p
r
e
s
y
pres_{x-1} \leq pres_{y}
presx−1≤presy 。
让我们把
f
i
f_{i}
fi 表示小于等于
i
i
i 且符合题意的段的长度之和的最大值。如果没有结束于i的段符合题意,
f
i
=
f
i
−
1
f_{i}=f_{i-1}
fi=fi−1 。如果存在段
[
k
+
1
,
i
]
[k+1,i]
[k+1,i] 符合题意,
f
i
=
m
a
x
p
r
e
s
k
≤
p
r
e
s
i
(
f
k
+
(
i
−
(
k
+
1
)
+
1
)
)
=
m
a
x
p
r
e
s
k
≤
p
r
e
s
i
(
f
k
−
k
)
+
i
f_{i}=max_{pres_{k} \leq pres_{i}}(f_{k}+(i-(k+1)+1))=max_{pres_{k} \leq pres_{i}}(f_{k}-k)+i
fi=maxpresk≤presi(fk+(i−(k+1)+1))=maxpresk≤presi(fk−k)+i 。整理可得:
f
i
=
m
a
x
(
f
i
−
1
,
m
a
x
p
r
e
s
k
≤
p
r
e
s
i
(
f
k
−
k
)
+
i
)
f_{i}=max(f_{i-1},max_{pres_{k} \leq pres_{i}}(f_{k}-k)+i)
fi=max(fi−1,maxpresk≤presi(fk−k)+i),此时可
O
(
N
2
)
O(N^2)
O(N2) 解决问题。
现在尝试用树状数组来加速
d
p
dp
dp 的转换。首先,在
p
r
e
s
i
pres_{i}
presi 上使用坐标压缩,我们只需看到一个前缀的和是否比另一个大。我们将维护一个树状数组,将
f
i
−
i
f_{i}-i
fi−i 存储在
p
r
e
s
i
pres_{i}
presi 的位置
i
n
v
i
inv_i
invi 。
f
i
=
m
a
x
(
f
i
−
1
,
m
a
x
p
r
e
s
k
≤
p
r
e
s
i
(
f
k
−
k
)
+
i
)
f_{i}=max(f_{i-1},max_{pres_{k} \leq pres_{i}}(f_{k}-k)+i)
fi=max(fi−1,maxpresk≤presi(fk−k)+i) ,则现在可以通过树状数组上的范围查询
[
0
,
i
n
v
i
]
[0, inv_i]
[0,invi] 解决
m
a
x
p
r
e
s
k
≤
p
r
e
s
i
(
f
k
−
k
)
max_{pres_{k} \leq pres_{i}}(f_{k}-k)
maxpresk≤presi(fk−k)。因此,对于每个
i
i
i 可以在
O
(
l
o
g
N
)
O(logN)
O(logN) 中解决
f
i
f_i
fi ,此时可
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN) 解决问题。
#include <bits/stdc++.h>
typedef long long LL;
const int N = 1e6 + 5;
using namespace std;
int n;
int lowbit(int x)
{
return x&-x;
}
void add(int x,int c,int tr[])
{
for(int i=x;i<=n;i+=lowbit(i))
tr[i]=max(tr[i],c);
}
int query(int x,int tr[])
{
int res=-2e5;
for(int i=x;i;i-=lowbit(i))
res=max(res,tr[i]);
return res;
}
int tr[N],f[N],a[N],inv[N],ans=0;
LL pres[N];
void solve()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&pres[i]);
pres[i]+=pres[i-1];
tr[i]=-2e5;a[i]=i;
}
sort(a+1,a+n+1,[&](int i,int j){
if(pres[i]!=pres[j])return pres[i]<pres[j];
else return i<j;
});
for(int i=1;i<=n;i++)
inv[a[i]]=i;
for(int i=1;i<=n;i++)
{
if(pres[i]>=0ll)f[i]=i;
else f[i]=f[i-1];
f[i]=max(f[i],i+query(inv[i],tr));
ans=max(ans,f[i]);
add(inv[i],f[i]-i,tr);
}
printf("%d\n",ans);
}
signed main() {
solve();
return 0;
}