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 个“<”。 请你解答这个问题。

扫描二维码关注公众号,回复: 1564992 查看本文章

输入格式

包含多组数据。第一行一个整数 T,表示有 T 组数据。
每组数据的占一行,包含两个整数 n 和 k,它们之间用一个空格分开。

输出格式

共 T 行,每组数据输出一行,每行一个整数,表示对应输入的排列数,这个数如果很大,则需要 输出 mod 1000000007 的结果。

数据范围

对于 30%的数据:n<=10
对于 100%的数据:k

题解:

动归递推题。
状态:f[i][j]表示1~i个数中有j个小于符号的方案总数。
边界条件:按题目: i [ 1 , 1000 ] , j [ 0 , i 1 ]
状态转移方程:
当我们计算1至i中有j个小于符号的状态时,我们可以考虑在i-1个数中插入i这个数。
显然,对于i-1个数中有j个小于符号的数列插入数字,我们可以在一个小于符号处插入该数,或是在最左边插入该数,即总共 f [ i 1 ] [ j ] ( j + 1 )
或者,对于i-1个数中有j-1个小于符号的数列插入数字,我们可以在一个大于符号处插入该数,或是在最右边插入该数,即总共 f [ i 1 ] [ j 1 ] ( i j )
故得出方程: f [ i ] [ j ] = f [ i 1 ] [ j ] ( j + 1 ) + f [ i 1 ] [ j 1 ] ( i j )

代码:

#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;
}

猜你喜欢

转载自blog.csdn.net/qq_42013837/article/details/80656025