1. 程式人生 > >Codeforces Round #541 (Div. 2) 題解

Codeforces Round #541 (Div. 2) 題解

pso 很多 spa rop center blank .com noi 二維

Codeforces Round #541 (Div. 2)

題目鏈接:https://codeforces.com/contest/1131

A. Sea Battle

題意:

給出兩個矩形的寬和高,滿足第一個矩形的左上頂點為(0,0),右下頂點為(w1,-h1);第二個矩形的坐下頂點為(0,0),右上頂點為(w2,h2)。然後求圍繞這兩個矩形一圈的格子個數為多少。

題解:

利用二維前綴和的思路推導一下公式就行了,公式也有很多吧==我當時就是大概模擬了一下。。

代碼如下:

技術分享圖片
#include <bits/stdc++.h>
using namespace std;
typedef 
long long ll; ll w1,h1,w2,h2; int main(){ ios::sync_with_stdio(false);cin.tie(0); cin>>w1>>h1>>w2>>h2; ll ans = 0; ans+=(h1+h2+2)*(max(w1,w2)+2); ll now=min(w1,w2); ll tmp=max(w1,w2)-now; if(w1<w2) ans-=h1*tmp; else ans-=h2*tmp; ans
-=(w1*h1+w2*h2); cout<<ans; return 0; }
View Code

B. Draw!

題意:

給出n次兩個人的比分,每次給出xi,yi代表兩個人的比分,然後問這兩個人最多平局的多少次。

題解:

假設這兩個人在比分為p的時候平局,對於xi,yi,xi+1,yi+1來說,肯定滿足max(xi,yi)<=p<=min(xi+1,yi+1)的,然後就借用這個式子來算。註意一下xi==yi這種特殊情況,可能會被計算多次,處理一下就好了。

原諒我代碼對不上上面的題解。。懶得改了,我感覺我的做題思路還沒有抽象化,上面一題也是,基本都是采用模擬的方法來做的,沒有很好地進行數學建模。

代碼如下:

技術分享圖片
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 10005;
int n;
int a[N],x[N],y[N];
int main(){
    ios::sync_with_stdio(false);cin.tie(0);
    cin>>n;
    ll ans = 1;
    for(int i=1;i<=n;i++) cin>>x[i]>>y[i];
    ans+=min(x[1],y[1]);
    for(int i=2;i<=n;i++){
        if(x[i]>y[i]){
            int dx=x[i]-x[i-1],dy=y[i]-y[i-1];
            if(y[i]<x[i-1]) continue ;
            if(y[i-1]<x[i-1]) ans+=(y[i]-x[i-1]+1);
            else if(y[i-1]>x[i-1]) ans+=dy+1;
            else ans+=min(dx,dy);
        }else{
            int dx=x[i]-x[i-1],dy=y[i]-y[i-1];
            if(x[i]<y[i-1]) continue ;
            if(x[i-1]<y[i-1]) ans+=(x[i]-y[i-1]+1);
            else if(x[i-1]>y[i-1]) ans+=dx+1;
            else ans+=min(dx,dy);
        }
    }
    cout<<ans;
}
View Code

C. Birthday

題意:

n個人,每個人都有對應的身高ai,然後這n個人站成一個圈,問怎樣的站法,能夠使相鄰兩個人的身高差最小,最後任意輸出一種方法即可。

題解:

考慮貪心的做法(為啥這題的數據為100啊...),先對所有的數排個序,然後從前往後依次放奇數位置的人,從後往前依次放偶數位置的人。

題解的證明用到了哈密頓回路,我們這種做法得到的答案為max(ai+2-ai),然後只需要證明不可能存在max(ai+1-ai)這樣的答案就行了,證明這個在直觀上面還是比較好理解的,很容易得證。

代碼如下:

技術分享圖片
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 105;
int a[N],ans[N];
int n;
int main(){
    ios::sync_with_stdio(false);cin.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    sort(a+1,a+n+1);
    int fir=1,last=n;
    for(int i=1;i<=n;i++){
        if(i&1){
            ans[fir++]=a[i];
        }else ans[last--]=a[i];
    }
    for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
    return 0;
}
View Code

D. Gourmet choice

題意:

給出一個n*m的符號矩陣,分別對應第n個數和第m個數的大小關系,然後讓你根據這個矩陣構造這n+m個數,如果得不到就輸出"No"。

題解:

其實根據大小關系很容易想到拓撲排序,小的向大的連邊,然後如果無環就存在一種情況。

但是這裏有等於符號,在本題中,等於的那些數本質上是相等的,也就是說擁有相同的拓撲序,那我們用並查集縮點處理即可。

然後跑拓撲排序,途中標記答案,最後根據輸出就行了。

註意一下這種情況:就是如果一些點都屬於一個集合中,但是有些點的關系存在不等關系,這就矛盾了,要判斷一下。

代碼如下:

技術分享圖片
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1005;
int n,m,cnt,tot;
char mp[N][N];
int f[N<<1],num[N<<1],head[N<<1],in[N<<1],ans[N<<1];
struct Edge{
    int u,v,next;
}e[N*N<<1];
int find(int x){
    return f[x]==x?f[x]:f[x]=find(f[x]);
}
void Union(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx==fy) return ;
    f[fx]=fy;
}
void adde(int u,int v){
    e[tot].v=v;e[tot].next=head[u];head[u]=tot++;
}
int topsort(){
    queue <int> q;
    int tot=0;
    for(int i=1;i<=cnt;i++) if(!in[i]) q.push(i),tot++,ans[i]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i!=-1;i=e[i].next){
            int v=e[i].v;
            if(--in[v]==0){
                ans[v]=ans[u]+1;
                q.push(v);
                tot++;
            }
        }
    }
    return tot==cnt;
}
int main(){
    scanf("%d%d",&n,&m);
    int flag=0;
    for(int i=1;i<=n+m;i++) f[i]=i;
    for(int i=1;i<=n;i++){
        scanf("%s",mp[i]+1);
        for(int j=1;j<=m;j++){
            if(mp[i][j]===) Union(i,j+n);
            else flag=1;
        }
    }
    for(int i=1;i<=n+m;i++){
        int f=find(i);
        if(!num[f]) num[f]=++cnt;
    }
    if(cnt==1 && flag){
        cout<<"No";
        return 0;
    }
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            int u=num[find(i)],v=num[find(j+n)];
            if(u==v&&mp[i][j]!==){
                cout<<"No";
                return 0;
            }
            if(mp[i][j]==<) adde(u,v),in[v]++;
            else if(mp[i][j]==>) adde(v,u),in[u]++;
        }
    }
    int f=topsort();
    if(!f) cout<<"No";
    else{
        puts("Yes");
        for(int i=1;i<=n+m;i++){
            int fx=find(i);
            cout<<ans[num[fx]]<<" ";
            if(i==n) cout<<\n;
        }
    }
    return 0;
}
View Code

F. Asya And Kittens

題意:

有n個數亂序排列,每個數都互不相等,並且它們中間有n-1個隔板將它們分離。現在每次會抽出兩個數之中的隔板,進行n-1次操作,最終n個數之中沒有隔板,最後輸出這n個數排列的一種情況。註意題目中給出的兩個數,不一定是相鄰的,但它們一定在相鄰的兩個塊中。

題解:

對於兩個相鄰的塊,我們直接合並就行了,往左或者右合並都行,因為反正它們都會成為一個塊。這裏用並查集的話就要啟發式合並,減少空間的消耗,具體點就是將size小的集合往size大的集合合並,這樣可以節約總空間,並且可以證明空間復雜度不超過O(nlogn);如果不這樣合並,空間復雜度上界可能為O(n^2)。

當然這裏有個黑科技rope,用這個來儲存十分節約空間,對於這個題怎麽合並都行,並且單次插入、刪除等操作都是O(logn)的復雜度,常數稍微大了點,但是已經十分優秀了。這個底層是用平衡樹來搞的,似乎可以來代替塊狀鏈表= =反正就是個類似於鏈表的東西。

代碼如下:

技術分享圖片
#include <bits/stdc++.h>
#include <ext/rope>
using namespace std;
using namespace __gnu_cxx;
typedef long long ll;
const int N = 150005;
int n;
int f[N];
rope <int> a[N];
int find(int x){
    return f[x]==x ? f[x] : f[x]=find(f[x]);
}
int main(){
    ios::sync_with_stdio(false);cin.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) f[i]=i,a[i]=i;
    for(int i=1;i<n;i++){
        int x,y;
        cin>>x>>y;
        int fx=find(x),fy=find(y);
        if(fx==fy) continue ;
        f[fx]=fy;
        a[fy]+=a[fx];
    }
    for(int i=1;i<=n;i++){
        int f=find(i);
        if(f==i){
            for(int j=0;j<a[f].size();j++){
                cout<<a[f][j]<<" ";
            }
            break ;
        }
    }
    return 0;
}
View Code

Codeforces Round #541 (Div. 2) 題解