Educational Codeforces Round 171 (Rated for Div. 2) 10.28 ABCD题解

Educational Codeforces Round 171 (Rated for Div. 2) 10.28 (ABCD)题解

A. Perpendicular Segments

数学(math)计算几何(geometry)

题意:

给定一个\(X,Y,K\)。需要求解出二维坐标系中的四个点\(A,B,C,D\),满足:\(0 \leq A_x,B_x,C_x,D_x \leq X\)\(0 \leq A_y,B_y,C_y,D_y \leq Y\)。并且\(|AB| ,|CD| \geq K\)
如果有多个输出,输出任意一个即可。

输入:

第一行一个整数\(t\),表示\(t\)组测试用例。\((1 \leq t \leq 5000)\)
每组一行三个整数\(X,Y,K\)\((1 \leq X,Y \leq 1000 ;1 \leq K \leq 1414 )\)
输入附加限制:输入的\(X,Y,K\)保证存在答案。

输出:

每组数据两行,第一行四个整数表示\(A_x,A_y,B_x,B_y\),中间用空格隔开。
第二行四个整数表示\(C_x,C_y,D_x,D_y\),中间用空格隔开。

样例输入:

4
1 1 1
3 4 1
4 3 3
3 4 4

样例输出:

0 0 1 0
0 0 0 1
2 4 2 2
0 1 1 1
0 0 1 3
1 2 4 1
0 1 3 4
0 3 3 0

样例解释:

在第一个样例中,四个点可以如下:

在第二个样例中,四个点可以如下:

在第三个样例中,四个点可以如下:

在第四个样例中,四个点可以如下:

分析:

由于题目给定的输出限制是保证一定有解,那么我们逆向思维,考虑什么限制条件他是无解的。
对于一个矩阵,如果他是正方形对角线一定垂直。这是合理的,那如果是长方形呢?
假设输入的\(K > \sqrt{2} \ast min(X,Y)\),也就是他比在矩阵中最大正方形的对角线还要大。也就是这种情况\((X > Y)\)

那么我们考虑与\(AB\)垂直的最长的线段\(CD\),判断其是否符合解的情况。

我们证明,可以得到\(A\)的坐标为\((0,0)\),\(B\)的坐标为\((t,Y)\),其中\(t = \sqrt{K^2 - Y^2}\)并且有\(t > Y\)
我们可以得到\(AB\)的直线方程为\(y = \frac{Y}{t} \ast x\)。由于\(AB\)\(CD\)垂直,那么我们可以得到\(CD\)的直线方程为\(y = -\frac{t}{Y} \ast x + Y\)
我们发现,欲求解出最长的\(CD\),可以定下来,一定有个点在\((0,Y)\),平移可以证明,这里不证明了。
带入\(C\)\((0,Y)\)。我们可以解出\(D\)\((\frac{Y^2}{t},0)\),接下来我们可以求解出:
\(|CD|=Y \ast \sqrt{\frac{Y^2}{t^2}+1}\)
\(|AB|=\sqrt{Y^2+t^2}=t \ast \sqrt{\frac{Y^2}{t^2}+1} ==K\)
由于\(t > Y\),所以,\(|CD| < K\)。我们得到长度最大的\(|CD|\)都无法满足条件,也就是说,输入的一个隐含前提是:\(K \leq \sqrt{2} \ast min(X,Y)\)
那么我们可以有贪心得到:每次都取最大正方形的对角线作为\(|AB|\)\(|CD|\)
所以我们输出
\(A_x=0,A_y=0\)
\(B_x=min(X,Y),B_y=min(X,Y)\)
\(C_x=0,C_y=min(X,Y)\)
\(D_x=min(X,Y),D_y=0\) 即可。

ac 代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
int a[N];
int t,n;
int x,y,k;
signed main() {
    cin>>t;
    while(t--){
        cin>>x>>y>>k;
        int A_x,A_y,B_x,B_y,C_x,C_y,D_x,D_y;
        int m=min(x,y);
        A_x=0,A_y=0;
        B_x=m,B_y=m;
        C_x=0,C_y=m;
        D_x=m,D_y=0;
        cout<<A_x<<" "<<A_y<<" "<<B_x<<" "<<B_y<<endl;
        cout<<C_x<<" "<<C_y<<" "<<D_x<<" "<<D_y<<endl;
    }
    return 0;
}

B. Black Cells

二分(binary search) 暴力枚举(brute force)

题意:

有一个长方形格子,从左到右编号为\(0\)\(10^{18}\)
给定长度为\(n\)的数组\(a\),起初每个元素对应的格子都是白色的。
每次可以选取数组中的两个元素,\(a_i\)\(a_j\),如果\(i \neq j\)并且,\(|i-j| \leq k\),就可以将这两个格子涂成黑色。
数组\(a\)中的元素必须要全部涂成黑色,此外,最多有一个不在\(a\)中的元素可以被涂成黑色。
我们需要求解出符合题意\(k\)的最小值。

输入:

第一行一个整数\(t\),表示\(t\)组测试用例。\((1 \leq t \leq 500)\)
每组第一行一个整数\(t\)\((1 \leq n \leq 2000)\)
接下来一行\(t\)个整数\(a_1, a_2, \dots, a_n\)。(\(0 < a_i < 10^{18}\); \(a_i < a_{i + 1}\))

输出:

每组一行一个整数\(k\),表示符合题意答案的最小值。

样例输入:

4
2
1 2
1
7
3
2 4 9
5
1 5 8 10 13

样例输出:

1
1
2
3

样例解释:

在第一个样例中,使用\(k=1\)可以绘制单元格 \((1,2)\)
在第二个样例中,使用\(k=1\)可以绘制单元格\((7,8)\)
在第三个样例中,使用\(k=2\)可以绘制单元格\((2,4)\)\((8,9)\)
在第四个样例中,使用\(k=3\)可以绘制单元格\((0,1)\)\((5,8)\)\((10,13)\)

分析:

我们发现这个题目,就是需要求出比较合适数对之间最小值,它允许一个元素单独涂。
那么我们很容易发现,当数组长度\(n\)是偶数的时候,不存在一个元素单独涂的情况,因为每次涂两个,势必还会再单一个元素。
如果\(n\)是奇数,他就可以单一个元素不考虑。并且这个单独涂的元素也有限制,他只能是奇数位。下标从\(1\)开始。
因为假设他是偶数位,他前面一定有奇数个元素,再因为两两配对涂色,就一定会导致存在一个单独的元素,前后矛盾了,所以他只能在奇数位单独涂。
基于上面的分析,我们可以采用暴力枚举来完成。
先判断\(n\)的奇偶,为偶,则直接枚举两个元素,求出差值最大值
如果是奇数,枚举奇数位单独的情况,再里面枚举两个元素差值的最大值,最后所有这样的单独位得到的结果取最小值

我们再讲一下另一个思路,由于我们发现结果一定有:\(1 \leq k \leq (max(a)-min(a))\)
所以我们考虑二分。枚举这两个端点,二分的条件就是判断这个\(mid\)是否满足二分条件

我们来思考一下这个二分\(check\)应该如何写?
首先我们的大前提是所有\(a\)中元素涂色,并且,最多只能有一个其他元素进行涂色。
所以我们每次\(check\)的时候枚举数组,两个元素直接求差值,如果他比\(mid\)小,就说明这个数对满足条件,再往后枚举。
相反如果不符合,那么直接就将这个元素单独涂色,再往后枚举。
如果上述操作后,有多个元素需要单独处理,就说明我们的\(mid\)比预期的要小,就更新二分的点。

ac 代码:

//暴力枚举
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2020;
int a[N];
int t,n;
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin>>t;
    while(t--){
        int n;
        cin>>n;
        for (int i=0;i<n;i++)cin>>a[i];
        int ans=0;
        if(n%2==0) {
            for (int i=0;i<n;i+=2){
                ans=max(ans,a[i+1]-a[i]);
            }
        } 
        else{
            ans=0x3f3f3f3f3f3f3f3f;
            for (int i=0;i<n;i+=2) {
                int res=0;
                for (int j=0;j<n;j+=2) {
                    if (j==i){
                        j--;
                        continue;
                    }
                    res=max(res,a[j+1]-a[j]);
                }
                ans=min(ans,res);
            }
        }
        if(ans)cout<<ans<<endl;
        else cout<<1<<endl;
    }
    return 0;
}
//二分
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2010;
int a[N],b[N];
int t,n;
bool check(int mid){
    int fl=0;
    for(int i=0;i<n;i++){
        if(i+1<n&&a[i+1]-a[i]<=mid)i += 1;
        else fl++;
    }
    return fl<=1;
}
signed main() {
    cin>>t;
    while(t--){
        cin>>n;
        vector<int>B(n);
        for(int i=0;i<n;i++)cin>>a[i];
        int l=1;
        int r=max(l,a[n-1]-a[0]);
        int ans=r;
        while(l<=r){
            int mid=l+r>>1;
            if(check(mid)) {
                ans=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        cout<<ans<<endl;
    }
    return 0;
}

C. Action Figures

贪心(greedy) 数据结构(data structures)

题意:

\(Monocarp\)要去商店买玩具一共有\(n\)个玩具即将推出,在第\(i\)天的时候,商店会推出一个新的玩具,价格为\(i\)个硬币。
即第\(i\)个玩具只能在\(i\)\(n\)天购买,但价格不会变化。并且如果\(Monocarp\)在某一天一次购买多个玩具,那么价格最高的将会免费
但是\(Monocarp\)并不会每天都去商店,给一个字符串,其中\(0\)表示第\(i\)天不去商店,\(1\)表示第\(i\)天去商店。由于第\(n\)个玩具只能在第\(n\)天购买,所以他一定是\(1\)
需要求解出\(Monocarp\)买下这些玩具至少需要多少个硬币?

输入:

第一行一个整数\(t\),表示\(t\)组测试用例。\((1 \leq t \leq 10^4)\)
每组第一行一个整数\(n\),表示一共有\(n\)个玩具。(\(1 \leq n \leq 4 \ast 10^5\))
接下来一行一个长度为\(n\)的字符串\(s\),其中每位为\(1\)或者\(0\),表示去商店或者不去商店。
输入限制,保证\(s\)末尾一定是\(1\)

样例输入:

4
1
1
6
101101
7
1110001
5
11111

样例输出:

1
8
18
6

样例解释:

在第一个样例中,\(Monocarp\)可以在第\(1\)天购买第\(1\)个玩具,花费\(1\)个硬币。
在第一个样例中,\(Monocarp\)可以在第\(3\)天购买第\(1\)个和第\(3\)个玩具,花费\(1\)个硬币,
然后可以在第\(4\)天购买第\(2\)个和第\(4\)个玩具,花费\(2\)个硬币。
最后可以在第\(6\)天购买第\(5\)个和第\(6\)个玩具,花费\(5\)个硬币,所以答案是\(1+2+5=8\)

在第一个样例中,\(Monocarp\)可以在第\(3\)天购买第\(2\)个和第\(3\)个玩具,花费\(2\)个硬币。
之后他在第\(7\)天买剩下所有的玩具,花费\(1+4+5+6=16\)个硬币,总共花费\(16+2=18\)个硬币。

分析:

我们发现,如果存在对应\(0\),那么这个玩具一定会被原价购买。
如何证明?

假设第\(i\)天字符串中对应的为\(0\),即\(Monocarp\)不去商店购买东西,则第\(i\)个玩具只能放在后面的时间购买。
又因为后面的价格高于\(i\),那么后面贵的元素优先考虑打折,所以,不论如何操作,\(0\)对应的玩具一定会被购买。
那么也就是\(1\)对应的是可能购买,我们不妨给他存起来。

然后我们再细化考虑其中细节。
我们已经分析了\(0\)对应的玩具一定会被购买,那么我们还需要最优考虑,
如果\(0\)后面还存在比他价格高,且是\(1\)对应的,那么这个玩具就应该是免费的
由于这个玩具就不需要购买了, 就可以移除掉。
我们可以用队列来实现上述操作。
最后实现完之后考虑队列中元素,我们由于可以免费,所以我们只需要对半向上取整再考虑后半部分较小的元素即可。

ac 代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
    int n;
    string s;
    cin>>n;
    cin>>s;
    s=' '+s;
    int ans=0;
    int fg=0;
    queue<int> q;
    for(int i=s.size()-1;i>=1;i--)
    {
        if(s[i]=='0')
        {
            if(q.size()) q.pop();
            ans+=i;
        }
        else
        {
            q.push(i);
        }
    }
    int sz=(q.size()/2);
    int tot=0;
    int bk=0;
    while(q.size())
    {
        tot+=q.front();

        if(sz)
        {
            sz--;
            bk+=q.front();
        }
        q.pop();
    }
    tot-=bk;
    cout<<ans+tot<<endl;

}
signed main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int tt=1;
    cin >> tt;
    while(tt--) solve();
}

D. Sums of Segments

数学(math)二分(binary search)

题意:

给定一个长度为\(n\)的数组\([a_1, a_2, \dots, a_n]\)
设定\(s(l,r)\)是从\(a_l\)\(a_r\)的元素之和,即: \(s(l,r) = \sum\limits_{i=l}^{r} a_i\)
再构造一个长度为\(\frac{n(n+1)}{2}\)的数组\(b\)\(b = [s(1,1), s(1,2), \dots, s(1,n), s(2,2), s(2,3), \dots, s(2,n), s(3,3), \dots, s(n,n)]\)
例如:\(a = [1, 2, 5, 10]\),那么\(b = [1, 3, 8, 18, 2, 7, 17, 5, 15, 10]\)
之后会有\(q\)次查询,每次给定\(l,r\)需要求解出\(\sum \limits_{j=l_i}^{r_i} b_j\),即数组\(b\)\(l\)\(r\)的元素之和。

输入:

第一行一个整数\(n\),表示数组\(a\)的元素个数。\((1 \leq n \leq 3 \ast 10^5)\)
第二行\(n\)个整数\(a_i\)\((-10 \leq a_i \leq 10)\)
第三行一个整数\(q\),表示\(q\)次查询。\((1 \leq q \le 3 \ast 10^5)\)
接下来\(q\)行,每一行两个整数\(l_i\)\(r_i\)\((1 \le l_i \le r_i \le \frac{n(n+1)}{2})\)

样例输入:

4
1 2 5 10
15
1 1
1 2
1 3
1 4
1 5
1 10
5 10
6 10
2 8
3 4
3 10
3 8
5 6
5 5
1 8

样例输出:

1
4
12
30
32
86
56
54
60
26
82
57
9
2
61
分析:

我们发现这是一个数组构造的题目。那么我们来找规律
我们单纯考虑样例。

索引 1 2 3 4
a 1 2 5 10

\(b = [1, 3, 8, 18, 2, 7, 17, 5, 15, 10]\)可以拆分成4行,第一行是\(1, 3, 8, 18\),第二行是\(2, 7, 17\),第三行是\(5, 15\),第四行是\(10\)
那么我们也列出表格

a 1 2 5 10
b 1 3 8 18
2 7 17
5 15
10

我们可以立马发现,在第二行中,每个元素都减去了\(a_1\),这个由定义也可以得到,同理第三行第四行均都有这样的操作,并且还多减去\(a_2\)。第四行多减去了一个\(a_3\)
那么我们再更新一下这个表格,补全前面空余的部分。

a 1 2 5 10
b 1 3 8 18
-4 2 7 17
-4 -6 5 15
-4 -6 -10 10

这前面的负数,表示第一行的和加上这几个负值,就可以得到这一行的和。

怎么求?
我们定义的这个负值,是表示这一行减去了多少个\(a_i\)
所以\(sub_i=a_i \ast (n-i+1)\)

例如:第一行和是\(1+3+8+18=30\),第二行的和是\(30+(-4)=26=2+7+17\)
那么这里就可以迅速得到:
前两行的和就是\(30*2+(-4)=56\)
前三行的和就是\(30*3+(-4)+(-4-6)=76\)
我们发现每一行减去的都是\(sub\)的一个前缀,所以我们将\(sub\)取一个前缀和得到:\(s_{sub}\)
我们又发现快速求前几行,他每次都是减去的\(s_{sub}\)的前缀,所以我们再对他取一个前缀和得到:\(s_{s_{sub}}\)
对于我们每次对标的第一行,不难发现,他其实是\(a\)的前缀和的前缀和。我们定义\(s_{s_a}\)\(a\)的前缀和的前缀和。
这样操作,我们取前k行结果就是 \({s_{s_a}}[n]\ast k - {s_{s_{sub}}}[k-1]\)

对于这个样例,当前的值如下:

a 1 2 5 10
\(s_a\) 1 3 8 18
\(s_{s_a}\) 1 4 12 30
\(sub\) -4 -6 -10
\(s_{sub}\) -4 -10 -20
\(s_{sub}\) -4 -14 -34

那么我们取前k行的操作就结束了。
我们再考虑如果他是第二行的第一个元素咋办?
我们只需要取\(s_{s_a}[2]\),这样得到4。我们再看需要减去这两个元素多出来的\(a_1\)但是我们并没有预处理好的两倍的\(a_1\)。我们仅仅有\(sub_1\),表示减去了4次。
但是我们可以加呀,我们后面的元素可以快速找出来,有\(2\)个。我们先给他减去\(sub_1\),然后再加上\(2*a_1\)就可以得到这一行的结果为\(2\)

同理我们也可以搜索第二行第二个数,这样就需要重复减去\(a_1\)\(a_2\)这个我们用\(a\)前缀和\(s_a\)可以快速实现。

接下来我们回到问题本身,给出询问,找到\(b\)数组中,第\(l\)到第\(r\)之间的元素和\(\sum \limits_{j=l}^{r} b_j\)
那么我们可以看成第\(1\)个到第\(r\)个减去第\(1\)个到第\(l-1\)个,即\(ans = \sum \limits_{j=1}^{r} b_j - \sum \limits_{j=1}^{l-1} b_j\)

我们根据上面的思路,先搜索他在\(l-1\)\(r\)在第几层。我们可以用二分快速搜,搜到他在第\(k\)层。

如何搜?
我们通过公式发现,他是一个\(n,n-1,n-2, \ldots ,1\)的一个序列,我们前\(k\)层的元素有多少个?
\(\frac{(n+n-k+1)}{2}\)个,用这个条件来二分即可。

我们搜到的结果是向下取整的(如果正好是整的,就不需要后面的操作了),然后再按照上面思路加上第k+1层前面的这些元素就可以了。

ac 代码:

//建议大家按照上面的思路自己写一下,我这个代码可读性很差。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=300010;
int a[N],s[N],ss[N];
int b[N],bb[N];
int t,n,q;

bool check(int k,int x){
    int tmp=(2*n-k+1)*k/2;
    if(tmp>x)return false;
    else return true;
}

signed main() {
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)s[i]=a[i]+s[i-1];
    for(int i=1;i<=n;i++)ss[i]=s[i]+ss[i-1];

    for(int i=1;i<n;i++){
        b[i]=a[i]*(n-i+1);
    }
    for(int i=1;i<n;i++)b[i]+=b[i-1];
    for(int i=1;i<n;i++)bb[i]=b[i]+bb[i-1];

    cin>>q;
    while(q--){
        int l,r;
        cin>>l>>r;
        l-=1;
        int k_l=0,k_r=n;
        int L=0,R=0;
        while(k_l<k_r){
            int mid=k_r+k_l+1>>1;
            if(check(mid,l)){
                k_l=mid;
            }
            else{
                k_r=mid-1;
            }
        }
        
        L+=ss[n]*k_l-bb[k_l-1];
        int tmp=l-(2*n-k_l+1)*k_l/2;
        int k=k_l+tmp;
        if(tmp)L+=ss[k]-b[k_l]+s[k_l]*(n-k);
        
        k_l=0,k_r=n;
        while(k_l<k_r){
            int mid=k_r+k_l+1>>1;
            if(check(mid,r)){
                k_l=mid;
            }
            else{
                k_r=mid-1;
            }
        }
        R+=ss[n]*k_l-bb[k_l-1];
        tmp=r-(2*n-k_l+1)*k_l/2;
        k=k_l+tmp;
        if(tmp)R+=ss[k]-b[k_l]+s[k_l]*(n-k);
        int ans=R-L;
        cout<<ans<<endl;
    }
    return 0;
}

posted on 2024-10-29 23:30  落雁单飞  阅读(80)  评论(0编辑  收藏  举报

导航