1. 程式人生 > >10-3國慶節第六場模擬賽題解

10-3國慶節第六場模擬賽題解

oid == sca -a 圖片 不能 多少 空間 fclose

T1 炮 (cannon)

Description

Makik 曾經沈迷於打麻將,熱衷於點炮的他近日終於開始愛上了中國象棋。面對一個n×m的棋盤,他不禁陷入了思考:在這張棋盤上擺“炮”,並且任意兩個“炮”之間不會互相攻擊的方案數究竟有多少呢?

說明:兩枚炮可以互相攻擊,當且僅當它們處在同一行或同一列上且恰好間隔一枚棋子,即“炮打隔山”。

由於 Makik 記不住太大的數字,所以請告訴他答案對 999983 取模的結果。

Input

輸入文件包含一行兩個整數 n,m,分別表示棋盤的長度和寬度。

Output

輸出一行一個整數,表示方案數對 999983999983 取模的結果。

XJBDP

依稀記得是洛谷月賽的一道原題,不過並沒有做當時。

剛開始以為是個數學題,想了想發現是DP。但是狀態不會設啊。。。

感謝題解:

\(f[i][j][k]\)表示前i行有j列放置1個炮,有k列放置2個炮。

根據分析問題可以知道,在同一列或者同一列都是不可以放超過兩個炮的,因為三個炮就會開始互相攻擊了。

所以每一行每一列只有三種可能,要麽不放,要麽放一個,要麽放兩個。

這是一個非常有用的信息,可以讓我們開始寫狀態轉移。

使用推表法。

很容易想出如果i+1可以通過什麽都不放直接有i推出,這是第i+1行不放炮的情況。

f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%mod;

可以由\(f[i][j][k]\)推出\(f[i+1][j+1][k]\)

的取值,意思就是前i行中如果存在一列什麽都沒有放,那麽就可以在這一列上放一個,這樣就會使j加1,這是i+1行放一個炮的情況。

if(m-j-k>=1)f[i+1][j+1][k]=(f[i+1][j+1][k]+f[i][j][k]*(m-j-k))%mod;

還可以由\(f[i][j][k]\)推出\(f[i+1][j-1][k+1]\)的情況,意思就是如果前i行中存在只放1個炮的列,我們就可以在這一列上面再放一個炮讓它變成放兩個棋子的,這也是i+1行放一個炮的情況。

if(j>=1)f[i+1][j-1][k+1]=(f[i+1][j-1][k+1]+f[i][j][k]*j)%mod;

上面的三種情況針對的是第i+1行放0個或者1個炮的情況。

下面還有三種情況是第i+1行放2個炮的情況。

那麽首先,可以從\(f[i][j][k]\)推出\(f[i+1][j-2][k+2]\),意思是我們可以找到兩個已經放了一個炮的列,再在這兩列上各自放上一個炮,那麽就會使有一個炮的列數減2,有兩個炮的列數加2.

if(j>=2)f[i+1][j-2][k+2]=(f[i+1][j-2][k+2]+f[i][j][k]*C(j))%mod;C(j)=C(j,2)

還可以從\(f[i][j][k]\)推出\(f[i+1][j+2][k]\)意思是可以找到兩個一個炮都沒放的列,然後再在這兩列再放上一個炮

if((m-j-k)>=2)f[i+1][j+2][k]=(f[i+1][j+2][k]+f[i][j][k]*C(m - j - k)) % mod;

還可以綜合上面兩種,第i+1行上新放的兩個炮一種一個

if((m-k-j)>=1&&j>=1)f[i+1][j][k+1]=(f[i+1][j][k+1]+f[i][j][k]*(m-k-j)*j)%mod;

哦對了,最後答案就是\(\sum_{j=0;j<=n}\sum_{k=0,k+j<=n}f[n][j][k]\)

至此,問題得以解決。

code

#include<iostream>
#include<cstdio>
using namespace std;
const int mod=9999973;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=(sum<<1)+(sum<<3)+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
int C(int _){
    return _*(_-1)/2;
}
int n,m;
long long ans;
long long f[111][111][111];
int main(){
    n=read();
    m=read();
    f[0][0][0]=1;//什麽東西都不放有1種方案
    for(int i=0;i<=n;i++){
        for(int j=0;j<=m;j++){
            for(int k=0;k+j<=m;k++){
                if(f[i][j][k]){             
                    f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%mod;
                    if(j>=1)f[i+1][j-1][k+1]=(f[i+1][j-1][k+1]+f[i][j][k]*j)%mod;
                    if(m-j-k>=1)f[i+1][j+1][k]=(f[i+1][j+1][k]+f[i][j][k]*(m-j-k))%mod;
                    if(j>=2)f[i+1][j-2][k+2]=(f[i+1][j-2][k+2]+f[i][j][k]*C(j))%mod;
                    if((m-j-k)>=2)f[i+1][j+2][k]=(f[i+1][j+2][k]+f[i][j][k]*C(m - j - k)) % mod;
                    if((m-k-j)>=1&&j>=1)f[i+1][j][k+1]=(f[i+1][j][k+1]+f[i][j][k] * (m - k -  j) * j) % mod;
                }
            }
        }
    }
    for(int i=0;i<=m;i++){
        for(int j=0;j+i<=m;j++){
            (ans+=f[n][i][j])%=mod;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

T2滾 (roll)

Description

玩膩了象棋之後,Makik 想出去看看風景。這次,他來到了 MS 山脈。MS 山脈共包含 n 座山峰,山峰從 11 到 nn 編號,有些山峰間還連接有道路。被這裏的景色深深吸引的 Makik 花重金請人把他擡到了1號山峰的頂端,他要從這裏開始旅程。

Makik 不喜歡爬山,但是喜歡下山,因為下山可以滾。當 Makik 在 i號山峰上時,如果 i 號山峰與 j 號山峰間有一條道路,並且 j 號山峰的高度不大於 i 號山峰的高度,那麽他就可以順勢沿這條路從 i 號山峰滾到 j 號山峰上。Makik 還有一個神奇的能力,可以沿著滾來的道路走回(而不是滾回)到曾經遊覽過的山峰上。

Makik 想要遊覽盡可能多的山峰,還想在此前提下使滾的距離盡可能短。請你說出,他最多能遊覽到多少山峰,此時最短需要滾多少路?

Input

輸入文件第一行包含兩個整數 n,m分別表述山峰的道路的數量。

接下來一行包含 \(n\) 個數,依次表示每一座山峰的高度。

之後 m 行,每行三個整數 x_i,y_i,z_i表示 xi 號和 yi 號兩座山峰間有一條長度為 zi 的道路。

Output

輸出一行兩個整數,分別表示 Makik 最多能遊覽到的山峰數目和他最短需要滾的距離。

原題鏈接:洛谷P2573 [SCOI2012]滑雪 https://www.luogu.org/problemnew/show/P2573

要註意一個問題,就是滾和走是不一樣的,題目中求得是滾的路程,所以向回走是對答案不產生貢獻的。

所以嘗試畫一下圖,可以發現,我們從一開始向外擴展,因為要保證到達的點最多,所以需要先擴展出所有可以從一到達的點,一個搜索就可以解決問題。

在這個基礎上,我們要求出最短的路程,這裏的路程就是我們擴展過程中的每一條邊的邊長。

試想一下,當我們擴展出這條邊之後,就一定是滾到那裏的,所以一定會對答案產生貢獻。

感覺上述太雜亂了,我需要重新說一下。

題目大意:從一開始,對於所有可以擴展到的點的最小生成樹。

不能直接求最小生成樹,因為有些點因為高度的限制無法到達,所以上一句說的是可以擴展到的所有的點。

保證從高到低,所以根據兩點之間的高度比較來建邊。(高點向低點建單向邊,同高度建無向邊)

做法步驟:輸入時按照高度進行建邊,先從1開始寬搜,標記每個可以到達的點,這個時候就可以統計出第一問的答案,之後可以求一下建出的邊的MST,又因為要保證到達的點最多,所以在對邊排序的時候要改變一下優先級。

在類似這種情況下,這三個點都是我們可以走到也應該走到的,但是如果對邊長進行排序的話,很顯然有兩條邊都連向了同一個點,這就不符合我們要求的最小樹形圖,如果按照高度排序,就可以很好的先把點都遍歷到,再去求最小的邊權

[在類似這種情況下,這三個點都是我們可以走到也應該走到的,但是如果對邊長進行排序的話,很顯然有兩條邊都連向了同一個點,這就不符合我們要求的最小樹形圖,如果按照高度排序,就可以很好的先把點都遍歷到,再去求最小的邊權](技術分享圖片

然後就很簡單了,難點就出在排序。

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int wx=2000017;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=(sum<<1)+(sum<<3)+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
int h[wx];
int n,m,num,tot,x,y,z,cnt;
int head[wx];
int f[wx];
long long ans;
int vis[wx];
struct e{
    int nxt,to,dis;
}edge[wx*2];
struct node{
    int l,r,d;
    friend bool operator < (const node& a,const node& b){
        if(h[a.r]==h[b.r])return a.d < b.d;
        return h[a.r] > h[b.r];
    }
}a[wx];
void add(int from,int to,int dis){
    edge[++num].nxt=head[from];
    edge[num].to=to;
    edge[num].dis=dis;
    head[from]=num;
}
queue<int > q;

void bfs(int s){
    q.push(s); vis[1] = 1;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].to; 
            a[++ tot].l = u; a[tot].r = v; a[tot].d = edge[i].dis;
            if(!vis[v]){
                vis[v]=1;
                 cnt++;q.push(v);
            }
        }
    }
}
int find(int x){
    if(x==f[x])return x;
    return f[x]=find(f[x]);
}
signed main(){
    freopen("roll.in" ,"r",stdin);
    freopen("roll.out","w",stdout);
    n=read();m=read();
    for(int i=1;i<=n;i++)h[i]=read();
    for(int j=1;j<=m;j++){
        x=read();y=read();z=read();
        if(h[x]==h[y]){
            add(x,y,z);add(y,x,z);
        } 
        else if(h[x]>h[y])add(x,y,z);
        else add(y,x,z);
    }
    bfs(1);
    for(int i=1;i<=n;i++)f[i]=i;
    sort(a+1,a+1+tot);
    for(int i=1;i<=tot;i++){
        if(find(a[i].l)!=find(a[i].r)){
            ans+=a[i].d;
            f[find(a[i].l)]=find(a[i].r);
        }
    }
    printf("%d %lld\n",cnt + 1,ans);
    return 0;
}

T3分裂 (split)

Description

Makik 滾得頭昏腦脹,一下子分裂成了 n 只小 makik。小 makik 們從左到右排成一排,看起來十分有趣。

你想調戲一下小 makik 們,於是準備了這樣的一個遊戲:先為每只小 makik 分配一個編號,之後進行許多次詢問。每一次詢問時,先指定一個區間,對於區間內的任意兩只小 makik,如果它們編號相同,則稱其中左側的小 makik 是一只壞 makik。之後,你要對區間內所有的壞 makik 中最靠右的一個施加懲罰。如果區間內沒有壞 makik,那麽你會感到有些無聊。

你想提前知道,對於每一次詢問,你將要懲罰第幾只小 makik。

Input

輸入文件第一行包含一個正整數 n,表示一共有 n 只小 makik。

接下來一行包含 n 個數 a_1, a_2, ..., a_n依次表示每只小 makik 被分配到的編號。

之後一行給出一個整數 q,表示詢問的數量。

下面 q 行,每行兩個整數 l,r表示這次詢問的區間為左起第 l 只到第 r 只小 makik。

Output

輸出 q 行,對應 q 次詢問。每次詢問輸出最靠右的壞 makik 排在第幾個位置。如果沒有壞 makik,輸出 “Boring”。

這道題的暴力應該是離線的,但是好氣,這道題的數據實在是太水了,各種暴力碾壓標算,eolv用生日悖論給我們證明了這道題的數據有多麽難處,然而我這個辣雞並沒有get到

感謝教練教育我們要通讀題目,讓我這場考試發現了最水的第三題,但是,就是因為它太水了,搞得我沒有像往常一樣看一看數據範圍再做題,直接敲了一個線段樹完事。本來美滋滋地以為自己T3絕對穩了,但是最後卻得20分。

忽然發現,\(a_i\)的範圍是1e9的,這心情。。。石樂誌石樂誌。。。身敗名裂身敗名裂。。。

這次教訓告訴我們,寫區間操作用類似線段樹的數據結構時一定要註意輸入數據的範圍,因為再記錄前驅時很容易想到開一個桶,然後就傻傻的爆空間。。。RE。。。所以一定要寫離散化。

忽然發現自己的離散化有點不透徹,復習一下:
離散化:

    for(int i=1;i<=n;i++){
        aa[i]=read();
        b[i] = aa[i];
    }
    sort(b+1,b+1+n);
    for(int i = 1; i <= n; ++ i) a[i] = lower_bound(b + 1, b + 1 + n, aa[i]) - b;

去重:

    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    int size=unique(a+1,a+1+n)-a-1;
    //debug()
    //for(int i=1;i<=size;i++)printf("%d ",a[i]);

再來看這道題:對於當前詢問的區間,如果一個位置他的前驅也在這個區間,那麽他的前驅的位置就會對答案產生影響,所以直接離散化,然後記錄一下每個位置的前驅,再把這個前驅放到線段樹中維護。每一次詢問直接輸出區間最大值即可。

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ls(o) o<<1
#define rs(o) o<<1|1
using namespace std;
const int wx=2000017;
inline int read(){
    int sum=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        sum=(sum<<1)+(sum<<3)+ch-'0';
        ch=getchar();
    }
    return sum*f;
}
struct val_tree{
    int l,r,ma;
    #define ma(o) t[o].ma
}t[wx*4];
int pre[wx],a[wx],last[wx],b[wx];
int n,m,x,y;
void up(int o){
    ma(o)=max(ma(ls(o)),ma(rs(o)));
}
void build(int o,int l,int r){
    t[o].l=l;t[o].r=r;
    if(l==r){ma(o)=pre[l];return;}
    int mid=t[o].l+t[o].r>>1;
    if(l<=mid)build(ls(o),l,mid);
    if(r>mid)build(rs(o),mid+1,r);
    up(o);
}
int query(int o,int l,int r){
    if(l<=t[o].l&&t[o].r<=r){
        return ma(o);
    }
    int mid=t[o].l+t[o].r>>1;
    int maxn=0;
    if(l<=mid)maxn=max(maxn,query(ls(o),l,r));
    if(r>mid)maxn=max(maxn,query(rs(o),l,r));
    return maxn;
}
int aa[wx];
int main(){
    freopen("split.in","r",stdin);
    freopen("split.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++){
        aa[i]=read();
        b[i] = aa[i];
    }
    sort(b+1,b+1+n);
    for(int i = 1; i <= n; ++ i) a[i] = lower_bound(b + 1, b + 1 + n, aa[i]) - b;
    for(int i=1;i<=n;i++){
        pre[i]=last[a[i]];
        last[a[i]]=i;
    }
    build(1,1,n);
    m=read();
    for(int i=1;i<=m;i++){
        x=read();y=read();
        int ans=query(1,x,y);
        if(ans<x)printf("Boring\n");
        else printf("%lld\n",ans);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
} 

10-3國慶節第六場模擬賽題解