算法競賽進階指南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<iostream>
#include<cstring>
#include<algorithm>

#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<iostream>
#include<cstring>
#include<algorithm>
#include<vector>

#define pii pair<int,int>

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

這題就是七夕祭那題的一維版本給大家看一下證明:
在這裏插入圖片描述
顯然最後
d1=a1x1+x5d_1=a_1-x_1+x_5
d2=a2x2+x1d_2=a_2-x_2+x_1
d3=a3x3+x2d_3=a_3-x_3+x_2
d4=a4x4+x3d_4=a_4-x_4+x_3
d5=a5x5+x4d_5=a_5-x_5+x_4

d1=d2=d3=d4=d5=dd_1=d_2=d_3=d_4=d_5=d
我們化簡x5x_5
x5=da1+x1x_5=d-a_1+x_1
x4=da5+x5=da5+(da1+x1)x_4=d-a_5+x_5=d-a_5+(d-a_1+x_1)
等等,我們可以把所有x都用x1x_1表示出來
我們不妨暫時先設x1=0x_1=0那麼我們最後代價就可以轉化爲貨艙選址問題,如何選x1x_1決定了我們最後的代價,找個中位數作爲x1x_1,我們就可以得到最優解

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>

#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<iostream>
#include<cstring>
#include<algorithm>

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;
}
發佈了29 篇原創文章 · 獲贊 38 · 訪問量 2698
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章