6.8練習賽題解

A.IP 網絡

問題描述

可以用一個網絡地址和一個子網掩碼描述一個子網(即連續的 IP 地址範圍)。
其中子網掩碼包含 32 個二進制位,前 32-n 位爲 1,後 n 位爲 0,網絡地址的前 32-n 位任意,後 n 位爲 0(0<=n<=32) 。 所有前 32-n 位和網絡地址相同的 IP 都屬於此網絡。
例如,網地址爲 194.85.160.176(二進制爲 11000010 01010101 10100000 10110000), 子網掩碼爲 255.255.255.248(二進制爲 11111111 11111111 11111111 11111000),則該 子網的 IP 地址範圍是 194.85.160.176~194.85.160.183。
現在輸入一些網絡地址,請你求出包含所有地址的最小的網絡(即包含 IP 地址最少的網絡)。

輸入格式

第一行爲一個整數 m,
接下來的 m 行,每行一個 IP 地址,可能有多個相同的地址出現。

輸出格式

輸出兩行,表示包含所有輸入的 IP 地址的最小網絡,第一行是網絡地址,第二行是子網掩碼。

數據範圍

對於 100%的數據,m<=1000

題解:

位運算題,但是題目描述很是麻人呀。。。其實將每個輸入的IP地址的4個數字都存起來,而網地址的數字就是輸入的IP全部&起來。而子網掩碼就只需要計算所有IP地址從左至右完全相等的位數,計算出來的位數就是子網掩碼左邊1的個數,右邊全是0。最後注意要將網地址和子網掩碼&一下。

代碼:

#include<iostream>
#include<cstdio>
using namespace std;
int answang[5],nowwang[5],wang[1010][6],ansyan[5];
int read(){
    int x=0;
    char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+c-'0';
        c=getchar();
    }
    return x;
}
int main(){
    int k,i,j,n,p,sum,now1,now2,o;
    scanf("%d",&n);
    for(i=1;i<=4;i++)answang[i]=(1<<8)-1;
    for(i=1;i<=n;i++){
        for(j=1;j<=4;j++)wang[i][j]=read();//手動輸入,比較方便 
        for(j=1;j<=4;j++)answang[j]&=wang[i][j];//計算網地址 
    }
    bool flag=true;
    for(i=1;i<=4;i++)ansyan[i]=0;
    for(i=1;i<=4;i++){
        for(j=8;j>=1;j--){
            p=1<<(j-1);
            sum=p&wang[1][i];
            for(k=2;k<=n;k++){
                o=sum^(p&wang[k][i]);
                if(o==p){
                    now1=i;now2=j;flag=false;break;
                }
            }
            if(!flag)break;
        }
        if(!flag)break;
    }//計算IP地址從左至右相等的位數 
    if(n==1)for(i=1;i<=4;i++)ansyan[i]=255;
    else {
        for(i=1;i<=now1-1;i++)ansyan[i]=255;
        ansyan[now1]=(1<<8)-1-((1<<j)-1);
    }
    printf("%d.%d.%d.%d\n",ansyan[1]&answang[1],ansyan[2]&answang[2],ansyan[3]&answang[3],ansyan[4]&answang[4]);
    printf("%d.%d.%d.%d",ansyan[1],ansyan[2],ansyan[3],ansyan[4]);
    return 0;
}

B.排列

問題描述

將自然數 1 到 n 任意排列,然後在排列的每兩個數之間根據他們的大小關係插入“>”和“<”。 例如:對於 1..5 的一個排列:3 2 4 1 5,可得到:3 > 2 < 4 > 1 < 5,其中有兩個“>”和 2 個“<” 。 現在給出自然數 n,問在自然數 1..n 的所有排列中,有多少個排列恰好有 k 個“<”。 請你解答這個問題。

輸入格式

包含多組數據。第一行一個整數 T,表示有 T 組數據。
每組數據的佔一行,包含兩個整數 n 和 k,它們之間用一個空格分開。

輸出格式

共 T 行,每組數據輸出一行,每行一個整數,表示對應輸入的排列數,這個數如果很大,則需要 輸出 mod 1000000007 的結果。

數據範圍

對於 30%的數據:n<=10
對於 100%的數據:k

題解:

動歸遞推題。
狀態:f[i][j]表示1~i個數中有j個小於符號的方案總數。
邊界條件:按題目:i[1,1000] ,j[0,i1]
狀態轉移方程:
當我們計算1至i中有j個小於符號的狀態時,我們可以考慮在i-1個數中插入i這個數。
顯然,對於i-1個數中有j個小於符號的數列插入數字,我們可以在一個小於符號處插入該數,或是在最左邊插入該數,即總共f[i1][j](j+1)
或者,對於i-1個數中有j-1個小於符號的數列插入數字,我們可以在一個大於符號處插入該數,或是在最右邊插入該數,即總共f[i1][j1](ij)
故得出方程:f[i][j]=f[i1][j](j+1)+f[i1][j1](ij)

代碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;
const LL maxn=1000,mod=1000000007;
LL f[maxn+10][maxn+10];
int main(){
    LL i,j,t,n,k;
    memset(f,0,sizeof(f));
    f[0][0]=1;
    for(i=1;i<=maxn;i++)
        for(j=0;j<=i;j++)
            f[i][j]=(f[i-1][j]*(j+1)+f[i-1][j-1]*(i-j))%mod;//動歸預處理 
    scanf("%lld",&t);
    for(i=1;i<=t;i++){
        scanf("%lld%lld",&n,&k);
        printf("%lld\n",f[n][k]);
    }
    return 0;
}

C.可愛的猴子(可能吧

問題描述

樹上有n只猴子。它們編號爲 1 到n。1 號猴子用它的尾巴勾着樹枝。剩下的猴子都被其他的猴子用手抓着。每隻猴子的每隻手可以抓住另一隻猴子的尾巴。從0 時刻開始,每一秒都有一隻猴子鬆開它的一隻手。這會導致一些猴子掉到地上(它們在地上也能繼續鬆開它們的手,猴子落地的時間很短可以不計)。 你的任務是: 寫一個程序,從標準輸入讀入猴子間抓與被抓住的關係信息,和它們放開手的順 序,對於每一隻猴子算出它落地的時間,把結果輸出到標準輸出。

輸入格式

第一行有兩個正整數n和m。n是猴子的數量,m是我們觀察猴子的時間(單位爲秒)。
接下來n行是初始情 況的描述。第k+1 行有兩個整數表示第k個猴子抓住的猴子的編號,前一個數 代表左手抓的猴子的編號,後一個數是右手抓的猴子的編號。如果讀入的數爲-1 則代表猴子的手是空的。
接下來m行是對猴子觀察的結果。在這m行裏的第i行,有兩個整數。前一個是猴子的編號,後一個是它在時刻i−1 時鬆開的手的編 號(1-左手,2-右手)。

輸出格式

輸出n個整數,每行一個。第i行表示第i個猴子落地的時間,如果在觀察結束前猴子沒有落地,那麼輸出-1

數據範圍

1≤n≤200000,1≤m≤400000

題解:

並查集,有點像模擬。定義ans[i]表示i號猴子的落下最早時間。先將末狀態(即m-1時的狀態)處理出來。只要他的父親是1,則它的ans值爲inf(即-1),否則爲m-1。然後一步步模擬倒推,每次將該次鬆手的兩隻猴子連接,然後將被鬆手的猴子的ans值更新爲該次鬆手時間。顯然對於一隻猴子來說,他的ans值中所有祖先的ans值中的最小,這個在getfa操作中更新即可。

代碼:

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=400010,inf=1e9;
int fa[maxn],t[maxn],n,m,l[maxn],r[maxn],bian[maxn][3];bool hand[400010][3];
int getfa(int x){
    int k;
    if(fa[x]==x)return x;
    k=getfa(fa[x]);
    t[x]=min(t[fa[x]],t[x]);//該只猴子的ans值爲他所有父親的ans最小值 
    fa[x]=k;
    return k;
}
void merge(int x,int y,int o){
    int x1=getfa(x),y1=getfa(y);
    if(x1!=y1){
        if(y1==1){
            fa[x1]=1;t[x1]=o;//若父親爲1則將x與1連接 
        }
        else{
            fa[y1]=x1;t[y1]=o;
        }
    }
}
int main(){
    int i,j;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++){
        scanf("%d%d",&l[i],&r[i]);
        fa[i]=i;t[i]=inf;
    }
    for(i=1;i<=m;i++){
        scanf("%d%d",&bian[i][1],&bian[i][2]);
        hand[bian[i][1]][bian[i][2]]=true;
    }
    for(i=1;i<=n;i++){
        if(l[i]!=-1&&!hand[i][1])merge(i,l[i],inf);
        if(r[i]!=-1&&!hand[i][2])merge(i,r[i],inf);//處理出末狀態 
    }
    for(i=1;i<=n;i++)
        if(getfa(i)==1)t[i]=inf;
            else t[i]=m-1;//預處理答案 
    for(i=m;i>=1;i--){
        if(bian[i][2]==1&&l[bian[i][1]]!=-1)merge(bian[i][1],l[bian[i][1]],i-1);
        if(bian[i][2]==2&&r[bian[i][1]]!=-1)merge(bian[i][1],r[bian[i][1]],i-1);//模擬倒推 
    }
    for(i=1;i<=n;i++){
        j=getfa(i);//更新該點的答案 
        if(t[i]==inf)printf("-1\n");
        else printf("%d\n",t[i]);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章