Codeforces Round #851 (Div. 2)题解

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 1kn1 ,若不存在输出 − 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 2n1000 ,故不可采用直接计算前缀连乘积,会爆数据范围
代码如下:

#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=nfloor(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=nfloor(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 12n 的排列,且 { 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} presx1presy
让我们把 f i f_{i} fi 表示小于等于 i i i 且符合题意的段的长度之和的最大值。如果没有结束于i的段符合题意, f i = f i − 1 f_{i}=f_{i-1} fi=fi1 。如果存在段 [ 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=maxpreskpresi(fk+(i(k+1)+1))=maxpreskpresi(fkk)+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(fi1,maxpreskpresi(fkk)+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 fii 存储在 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(fi1,maxpreskpresi(fkk)+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) maxpreskpresi(fkk)。因此,对于每个 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值