算法竞赛进阶指南0x08 总结练习(中)

防线

达达学习数学竞赛的时候受尽了同仁们的鄙视,终于有一天…受尽屈辱的达达黑化成为了黑暗英雄怪兽达达。

就如同中二漫画的情节一样,怪兽达达打算毁掉这个世界。

数学竞赛界的精英 lqr 打算阻止怪兽达达的阴谋,于是她集合了一支由数学竞赛选手组成的超级行动队。

由于队员们个个都智商超群,很快,行动队便来到了怪兽达达的黑暗城堡的下方。

但是,同样强大的怪兽达达在城堡周围布置了一条“不可越过”的坚固防线。

防线由很多防具组成,这些防具分成了 N 组。

我们可以认为防线是一维的,那么每一组防具都分布在防线的某一段上,并且同一组防具是等距离排列的。

也就是说,我们可以用三个整数 S, E 和 D 来描述一组防具,即这一组防具布置在防线的 S,S + D,S + 2D,…,S + KD(K∈ Z,S + KD≤E,S + (K + 1)D>E)位置上。

黑化的怪兽达达设计的防线极其精良。如果防线的某个位置有偶数个防具,那么这个位置就是毫无破绽的(包括这个位置一个防具也没有的情况,因为 0 也是偶数)。

只有有奇数个防具的位置有破绽,但是整条防线上也最多只有一个位置有奇数个防具。

作为行动队的队长,lqr 要找到防线的破绽以策划下一步的行动。

但是,由于防具的数量太多,她实在是不能看出哪里有破绽。作为 lqr 可以信任的学弟学妹们,你们要帮助她解决这个问题。

输入格式
输入文件的第一行是一个整数 T,表示有 T 组互相独立的测试数据。

每组数据的第一行是一个整数 N。

之后 N 行,每行三个整数 Si,Ei,Di,代表第 i 组防具的三个参数,数据用空格隔开。

输出格式
对于每组测试数据,如果防线没有破绽,即所有的位置都有偶数个防具,输出一行 “There’s no weakness.”(不包含引号) 。

否则在一行内输出两个空格分隔的整数 P 和 C,表示在位置 P 有 C 个防具。当然 C 应该是一个奇数。

数据范围
防具总数不多于108,

Si≤Ei,

1≤T≤5,

N≤200000,

0≤Si,Ei,Di≤231−1
输入样例:
3
2
1 10 1
2 10 1
2
1 10 1
1 10 1
4
1 10 1
4 4 1
1 5 1
6 10 1
输出样例:
1 1
There’s no weakness.
4 3

这题有个非常重要的性质,或者说我们怎么想到这道题可以用二分来优化的呢?
我们可以发现题目隐含了一个条件:破绽最多有一个,那我们便可以用二分来优化了,如果前半段的和是奇数那我们可以收缩空间[l,mid],反之,如果前半段的和不是奇数我们可以把空间锁定再右侧[mid+1,r]

#include
#include
#include

#define ll long long

using namespace std;

const int N=200010;

struct Seq{
    int s,e,d;
};

Seq seq[N];

int n;

ll calc(int x)
{
    ll res=0;
    for(int i=0;i<n;i++)
    {
        if(seq[i].s<=x)
        {
            int s=seq[i].s;
            int e=min(seq[i].e,x);
            int d=seq[i].d;
            res+=(0ll+e-s)/d+1;
        }
    }
    return res;
}

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        int l=0,r=0;
        for(int i=0;i<n;i++)
        {
            scanf("%d%d%d",&seq[i].s,&seq[i].e,&seq[i].d);
            r=max(r,seq[i].e);
        }
        while(l<r)
        {
            int mid=(ll)l+r>>1;
            if(calc(mid)&1) r=mid;
            else l=mid+1; 
        }
        ll res=calc(r)-calc(r-1);
        if(res&1) printf("%d %lld\n",r,res);
        else puts("There's no weakness.");
    }

    return 0;
}

赶牛入圈

农夫约翰希望为他的奶牛们建立一个畜栏。

这些挑剔的畜生要求畜栏必须是正方形的,而且至少要包含C单位的三叶草,来当做它们的下午茶。

畜栏的边缘必须与X,Y轴平行。

约翰的土地里一共包含N单位的三叶草,每单位三叶草位于一个1 x 1的土地区域内,区域位置由其左下角坐标表示,并且区域左下角的X,Y坐标都为整数,范围在1到10000以内。

多个单位的三叶草可能会位于同一个1 x 1的区域内,因为这个原因,在接下来的输入中,同一个区域坐标可能出现多次。

只有一个区域完全位于修好的畜栏之中,才认为这个区域内的三叶草在畜栏之中。

请你帮约翰计算一下,能包含至少C单位面积三叶草的情况下,畜栏的最小边长是多少。

输入格式
第一行输入两个整数 C 和 N。

接下来 N 行,每行输入两个整数 X 和 Y,代表三叶草所在的区域的X,Y坐标。

同一行数据用空格隔开。

输出格式
输出一个整数,代表畜栏的最小边长。

数据范围
1≤C≤500,
C≤N≤500
输入样例:
3 4
1 2
2 1
4 1
5 2
输出样例:
4

这题用二分+暴力+离散化就过了,这题很容易想到使用二分,但是显然直接二分+暴力求前缀和是过不去的,10000数据摆在那,我们离散化一下就可以了,来看代码:

#include
#include
#include
#include

#define pii pair

using namespace std;

const int N=1010;

vector<int> v;
pii lawn[N];
int c,n,sum[N][N];

bool calc(int len)
{
    for(int x1=0,x2=1;x2<v.size();x2++)
    {
        while(v[x2]-v[x1+1]+1>len) x1++;
        for(int y1=0,y2=1;y2<v.size();y2++)
        {
            while(v[y2]-v[y1+1]+1>len) y1++;
            if(sum[x2][y2]-sum[x1][y2]-sum[x2][y1]+sum[x1][y1]>=c)
                return true;
        }    
    }
    return false;
}

int get(int x)
{
    int l=0,r=v.size()-1;
    while(l<r)
    {
        int mid=l+r>>1;
        if(v[mid]>=x) r=mid;
        else l=mid+1;
    }
    return r;
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    scanf("%d%d",&c,&n);
    int l=1,r=10000;
    for(int i=1;i<=n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        v.push_back(x);
        v.push_back(y);
        lawn[i]={x,y};
    }
    v.push_back(0);
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    for(int i=1;i<=n;i++)
    {
        int x=get(lawn[i].first);
        int y=get(lawn[i].second);
        sum[x][y]+=1;
    }
    for(int i=1;i<v.size();i++)
        for(int j=1;j<v.size();j++)
            sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
    

    while(l<r)
    {
        int mid=l+r>>1;
        if(calc(mid)) r=mid;
        else l=mid+1;
    }
    printf("%d\n",r);
    return 0;
}

糖果传递

有n个小朋友坐成一圈,每人有a[i]个糖果。

每人只能给左右两人传递糖果。

每人每次传递一个糖果代价为1。

求使所有人获得均等糖果的最小代价。

输入格式
第一行输入一个正整数n,表示小朋友的个数。

接下来n行,每行一个整数a[i],表示第i个小朋友初始得到的糖果的颗数。

输出格式
输出一个整数,表示最小代价。

数据范围
1≤n≤1000000
输入样例:
4
1
2
5
4
输出样例:
4

这题就是七夕祭那题的一维版本给大家看一下证明:
算法竞赛进阶指南0x08 总结练习(中)_第1张图片
显然最后
d 1 = a 1 − x 1 + x 5 d_1=a_1-x_1+x_5 d1=a1x1+x5
d 2 = a 2 − x 2 + x 1 d_2=a_2-x_2+x_1 d2=a2x2+x1
d 3 = a 3 − x 3 + x 2 d_3=a_3-x_3+x_2 d3=a3x3+x2
d 4 = a 4 − x 4 + x 3 d_4=a_4-x_4+x_3 d4=a4x4+x3
d 5 = a 5 − x 5 + x 4 d_5=a_5-x_5+x_4 d5=a5x5+x4

d 1 = d 2 = d 3 = d 4 = d 5 = d d_1=d_2=d_3=d_4=d_5=d d1=d2=d3=d4=d5=d
我们化简 x 5 x_5 x5
x 5 = d − a 1 + x 1 x_5=d-a_1+x_1 x5=da1+x1
x 4 = d − a 5 + x 5 = d − a 5 + ( d − a 1 + x 1 ) x_4=d-a_5+x_5=d-a_5+(d-a_1+x_1) x4=da5+x5=da5+(da1+x1)
等等,我们可以把所有x都用 x 1 x_1 x1表示出来
我们不妨暂时先设 x 1 = 0 x_1=0 x1=0那么我们最后代价就可以转化为货舱选址问题,如何选 x 1 x_1 x1决定了我们最后的代价,找个中位数作为 x 1 x_1 x1,我们就可以得到最优解

#include
#include
#include
#include

#define ll long long

using namespace std;

const int N=1000010;

int a[N],d[N];
int n;

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);

    scanf("%d",&n);
    ll sum=0;
    for(int i=1;i<=n;i++)
    {    
        scanf("%d",&a[i]);
        sum+=a[i];
    }
    sum/=n;
    for(int i=n;i>=2;i--)
        d[i]=sum+d[i+1]-a[i];
    d[1]=0;
    sort(d+1,d+n+1);
    int midv=d[(1+n)>>1];
    ll res=0;
    for(int i=1;i<=n;i++)
        res+=abs(d[i]-midv);
    printf("%lld\n",res);

    return 0;
}

士兵

格格兰郡的N名士兵随机散落在全郡各地。

格格兰郡中的位置由一对(x,y)整数坐标表示。

士兵可以进行移动,每次移动,一名士兵可以向上,向下,向左或向右移动一个单位(因此,他的x或y坐标也将加1或减1)。

现在希望通过移动士兵,使得所有士兵彼此相邻的处于同一条水平线内,即所有士兵的y坐标相同并且x坐标相邻。

请你计算满足要求的情况下,所有士兵的总移动次数最少是多少。

需注意,两个或多个士兵不能占据同一个位置。

输入格式
第一行输入整数N,代表士兵的数量。

接下来的N行,每行输入两个整数x和y,分别代表一个士兵所在位置的x坐标和y坐标,第i行即为第i个士兵的坐标(x[i],y[i])。

输出格式
输出一个整数,代表所有士兵的总移动次数的最小值。

数据范围
1≤N≤10000,
−10000≤x[i],y[i]≤10000
输入样例:
5
1 2
2 2
1 3
3 -2
3 3
输出样例:
8

看见这题我们首先就要知道这题的横纵坐标是可以分开计算的。
我们可以先假设每个士兵可以在同一格,那我们怎么办,这就是我们的货舱选址问题,我们直接求就可以了,但这题不允许我们在同一格上怎么办呢?
我们可以先按y轴排序,先用货舱选址问题把他们弄在同一行的代价先算出来。然后我们可以发现,我们只要固定第一个人的位置其他人的位置也确定下来了,还有就是我们可以发现他们的相对位置不变,所以我们给x轴的每个数减去一个等差数列就可以了,距离来看代码。。~ 。~

#include
#include
#include

using namespace std;

const int N=100010;

int x[N],y[N];
int n;

int get(int a[])
{
    sort(a+1,a+n+1);
    int midv=a[(1+n)>>1];
    int res=0;
    for(int i=1;i<=n;i++)
        res+=abs(a[i]-midv);
    return res;
}

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]);
    sort(x+1,x+n+1);
    for(int i=1;i<=n;i++)
        x[i]-=i-1;
    int res=get(x)+get(y);
    cout<<res<<endl;


    return 0;
}

你可能感兴趣的:(算法竞赛进阶指南)