牛客小白月赛#55

牛客小白月赛#55

A.至至子的等差中项

  • 题意

    • 给等差数列的a,b,以b为等差中项,输出c
  • 题解

    • 等差中项的性质
  • 代码

#include <iostream>

using namespace std;

int main() {
    int a,b;
    cin>>a>>b;
    cout<<2*b-a<<'\n';
    
    return 0;
}

B.至至子的按位与

  • 题意

    • 给a,b,求一个尽可能大的c(1<c<2^63),使得a&c=b&c
  • 题解

    • 位运算是每一位独立的运算,所以一位位看规律就行。因为c尽可能大,所以尽量选1

    • abc可选c终选
      000,11
      0100
      1000
      110,11

      可以取巧,先让c中63位全为1,由表格得ab相同时c选1,不同时c选0,所以直接c-a^b

  • 代码

#include <iostream>

using namespace std;

int main() {
    long long a,b;
    cin>>a>>b;
    
    long long c=(1ll<<63)-1ll;//这样写似乎不会溢出
    cout<<(c-(a^b))<<'\n';
    
    return 0;
}

C.至至子的斐波那契

  • 题意

    • 询问t次,每次询问给出x,找到斐波纳切中最接近x的下标(x<10^18)
  • 题解

    • 有结论易得,斐波纳切的88项已经超过10^18,对于每个询问要查找的范围较小,所以直接暴力即可
  • 代码

#include <iostream>

using namespace std;
long long f[110];

void solve() {
    long long x,ans;
    cin>>x;
    for(int i=1;i<=100;i++) 
        if(f[i]==x) {cout<<i<<'\n'; return ;}//恰好相等直接输出
        else if(f[i]>x) {ans=i;break;}//否则找到第一个比x大的数
    cout<<((f[ans]-x < x-f[ans-1]) ? ans:ans-1)<<'\n';//比较与前一个数列哪个更近,输出
}

int main() {
    f[1]=f[2]=1;
    for(int i=3;i<105;i++) f[i]=f[i-1]+f[i-2];//预处理出要查找的范围内的数列
    
    int t;
    cin>>t;
    while(t--) solve();
    
    return 0;
}

D.至至子的鸿门宴

  • 题意

    • 给n堆石子,满足1<=a1<a2<a3<…<an
    • 两人游戏,每人每轮只能拿一个石子,并且要保证上式始终成立,当不能拿的时候游戏结束
    • 至至子先手,问给定的n堆石子是谁赢
  • 题解

    • 必败态为1,2,3…n,并且拿哪一堆对于结果没有影响,所以只需要看给定的石堆离必败态有多少个石子,奇数先手必胜,否则必败
  • 代码

#include<iostream>

using namespace std;

int main() {
    int n;
    cin>>n;
    long long sum=0,x;
    for(int i=1;i<=n;i++) {
        cin>>x;
        sum+=x-i;
    }
    cout<<(sum&1 ? "ZZZ":"SSZ")<<'\n';
    return 0;
}

E.至至子的长链剖分

  • 题意

    • 给定一颗有n个点(1~n)的树,只给出每个点的高度,请你构造出符合要求的树
  • 题解

    • 用容器Pos[h]存储高度为h的节点编号是哪些
    • 根节点只有一个,所以高度最高的节点只有1个,否则无法构造
    • 高度从0到最高的h都要有点,并且高度低的点数量不能小于高度高的,否则无法构造
    • 构造方法:从底向顶链式一一对应的连边(pos[i,j]–pos[i+1,j]),若i+1高度点不够则全挂在i+1的最后一个点上
  • 代码

#include <iostream>
#include <vector>

using namespace std;

void solve() {
    int n;
    cin>>n;
    vector<vector<int>> pos(n);
    int h=0;//注意初始化,最高的高度
    for(int i=1;i<=n;i++) {
        int t; cin>>t;
        h=max(h,t);
        pos[t].push_back(i);//高度为t的编号加上一个i
    }
    
    if(pos[h].size()>1) {cout<<"-1\n"; return ;}//根只一个
    for(int i=0;i<h;i++)
        if(pos[i].size()<pos[i+1].size()) {//底的点数量不得小于高的点数量
            cout<<"-1\n";
            return ;
        }
    
    cout<<pos[h][0]<<'\n';
    for(int i=0;i<h;i++)
        for(int j=0;j<pos[i].size();j++)//链式构造
            cout<<pos[i][j]<<' '<<pos[i+1][min(j,int(pos[i+1].size()-1))]<<'\n';
}

int main() {
    cin.tie(0)->sync_with_stdio(false);
    
    int T;
    cin>>T;
    while(T--) solve();
    
    return 0;
}

F.至至子的公司排队

  • 题意

    • n家公司,每个公司给定上下级关系,1为boss
    • 问n家公司中拓扑序的数量
  • 题解

    • 对于一家公司求拓扑序数量时:用树形dp求解

      f[u]:以u为根的子树的拓扑数
      sz[u]:以u为根的子树大小(节点数量)
      
      例如:
      将u的两颗子树v1,v2合并求f[u]:合并后的f[u]=f[v1]*f[v2]*C(v1+v2,v1)
      组合数解释:子树所有节点sz[v1]+sz[v2]进行排序,子树v1内部的拓扑数为f[v1],v2的拓扑数为f[v2],当两树节点排列时不相互插入,即前sz[v1]个数全是子树v1的节点,后sz[v2]个数全是子树v2的节点时,这种情况下sz[v1]+sz[v2]进行排序会有f[v1]*f[v2]个拓扑数,但v1,v2两子树中的节点为互不分高低的,所以两子树可以随意的互相插入,只需保持各子树内部保持拓扑序即可,所以两子树插入的方法有C(sz[v1]+sz[v2],sz[v1]),即保持sz[v1]+sz[v2]个位置中sz[v1]个数是拓扑序
      
    • 拓展到n家公司同理,只需把所有公司都看成一颗连在虚根上的子树即可,拓展后的计算公式为

    f [ u ] = ( ∏ v ∈ s o n ( u ) f [ v ] ) ⋅ ( s z [ u ] − 1 ) ! ∏ v ∈ s o m ( u ) s z [ v ] ! f [ u ] = ( s z [ u ] − 1 ) ! ⋅ ∏ v ∈ s o n ( u ) f [ v ] s z [ v ] ! f[u]=(\prod_{v\in son(u)}f[v])\cdot \frac{(sz[u]-1)!}{\prod_{v\in som(u)}sz[v]!}\\ f[u]=(sz[u]-1)!\cdot\prod_{v\in son(u)}\frac{f[v]}{sz[v]!} f[u]=(vson(u)f[v])vsom(u)sz[v]!(sz[u]1)!f[u]=(sz[u]1)!vson(u)sz[v]!f[v]

  • 代码

#include <iostream>

using namespace std;
#define int long long
const int N=1e5+10,mod=1e9+7;

int h[N],e[N],ne[N],idx;//建树
int f[N],sz[N];//dp数组,size数组
int fac[N],rev[N];//排列及其逆元数组

int qp(int a,int b) {//快速幂
    int res=1;
    while(b) {
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}

int C(int n,int m) {//求组合数
    return fac[n]*rev[m]%mod*rev[n-m]%mod;
}

void add(int a,int b) {//加边
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void dfs(int u) {//dfs树形dp
    f[u]=1,sz[u]=1;
    for(int i=h[u];~i;i=ne[i]) {
        int v=e[i];
        dfs(v);
        sz[u]+=sz[v];
        f[u]=f[u]*f[v]%mod*C(sz[u]-1,sz[v])%mod;
    }
}

int dp(int n) {//返回每一个公司的拓扑数
    idx=0;
    for(int i=1;i<=n;i++) h[i]=-1;//初始化
    for(int i=2;i<=n;i++) {//输入边
        int x;
        cin>>x;
        add(x,i);
    }
    
    dfs(1);//dfs计算
    return f[1];//f[1]即为此公司的拓扑数
}

signed main() {
    cin.tie(0)->sync_with_stdio(false);
    fac[0]=1;//预处理排列数数组
    for(int i=1;i<N;i++) fac[i]=fac[i-1]*i%mod;
    rev[N-1]=qp(fac[N-1],mod-2);
    for(int i=N-1;i>0;i--) rev[i-1]=rev[i]*i%mod;
    
    int n,m,sum=0,res=1;
    cin>>n;//n个公司,看做n个子树
    while(n--) {
        cin>>m;
        sum+=m;
        res=res*dp(m)%mod*C(sum,m)%mod;//按公式计算答案
    }
    cout<<res<<'\n';

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值