51nod 1601 完全图的最小生成树计数 Trie+kruskal

本文介绍了一种解决特定图论问题的方法:给定数组和完全图,边权由节点值通过XOR运算得出,目标是找到边权和最小的生成树及其方案数。通过构建字典树来高效统计和确定最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给定一个长度为n的数组a[1..n],有一幅完全图,满足(u,v)的边权为a[u] xor a[v]
求边权和最小的生成树,你需要输出边权和还有方案数对1e9+7取模的值。

由于边权是xor得到,容易想到用trie统计。。
按照当前最高位0/1将当前区间内的点分成两个部分s/t,那么答案肯定是s的最小生成树+t的最小生成树+s-t的最小边,s-t最小边用trie统计,最小生成树递归处理。
那么方案数的话就是每次那个连接两个块之间的最小边的数量,所以trie树统计一下节点个数就好。
字典树那个地方每次查询一个数,尽量使得当前位置相同就好,最后记得记录一下,每个位可能有多个点(多个数),会对方案造成贡献。
好像是经典套路?

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e5+5;
const int mo=1e9+7;
const int inf=0x3f3f3f3f;
int n,cnt,tot,a[N],s[N],t[N],fac[N];
typedef long long ll;
ll sum;
struct node
{
    int cnt,next[2];
}ch[N*31];
inline void clear()
{
    fo(i,0,tot)
        ch[i].next[0]=ch[i].next[1]=ch[i].cnt=0;
    tot=0;
}
inline int pow(int a,int b)
{
    int ret=1;
    while (b)
    {
        if (b&1)ret=1ll*ret*a%mo;
        a=1ll*a*a%mo;
        b>>=1;
    }
    return ret;
}
inline void ins(int x)
{
    int p=0;
    fd(i,30,0)
    {
        int y=(x>>i)&1;
        if (!ch[p].next[y])
            ch[p].next[y]=++tot;
        p=ch[p].next[y];
    }
    ch[p].cnt++;
}
inline pair<int,int> find(int x)
{
    int p=0,ans=0,y;
    fd(i,30,0)
    {
        y=(x>>i)&1;
        if (ch[p].next[y])p=ch[p].next[y],ans|=y<<i;
        else p=ch[p].next[y^1],ans|=(y^1)<<i;
    }
    return make_pair(ans^x,ch[p].cnt);
}
inline void solve(int l,int r,int dep)
{
    if (l>=r)return;
    if (dep<0)
    {
        if (r-l+1>=2)cnt=1ll*cnt*pow(r-l+1,r-l-1)%mo;
        return;
    }
    int cnt1=0,cnt2=0;
    fo(i,l,r)
        if ((a[i]>>dep)&1)s[cnt1++]=a[i];
        else t[cnt2++]=a[i];
    fo(i,0,cnt1-1)a[l+i]=s[i];
    fo(i,0,cnt2-1)a[l+cnt1+i]=t[i];
    clear();
    pair<int,int>tmp;
    int ans=inf,tot=0;
    fo(i,0,cnt2-1)ins(t[i]);
    fo(i,0,cnt1-1)
    {
        tmp=find(s[i]);
        if (tmp.first<ans)ans=tmp.first,tot=tmp.second;
        else if (tmp.first==ans)
            tot+=tmp.second;
    }
    if (sum!=inf&&tot)sum+=ans,cnt=1ll*tot*cnt%mo;
    solve(l,l+cnt1-1,dep-1);
    solve(l+cnt1,r,dep-1);
}
int main()
{

    scanf("%d",&n);
    fac[0]=cnt=1;
    fo(i,1,n)fac[i]=1ll*fac[i-1]*i%mo;
    fo(i,1,n)scanf("%d",&a[i]);
    solve(1,n,30);
    printf("%lld\n%d\n",sum,cnt);
}
### 关于51Nod 3100 上台阶问题的C++解法 #### 题目解析 该题目通常涉及斐波那契数列的应用。假设每次可以走一步或者两步,那么到达第 \( n \) 层台阶的方法总数等于到达第 \( n-1 \) 层和第 \( n-2 \) 层方法数之和。 此逻辑可以通过动态规划来解决,并且为了防止数值过大,需要对结果取模操作(如 \( \% 100003 \)[^1])。以下是基于上述思路的一个高效实现: ```cpp #include <iostream> using namespace std; const int MOD = 100003; long long f[100010]; int main() { int n; cin >> n; // 初始化前两项 f[0] = 1; // 到达第0层有1种方式(不移动) f[1] = 1; // 到达第1层只有1种方式 // 动态规划计算f[i] for (int i = 2; i <= n; ++i) { f[i] = (f[i - 1] + f[i - 2]) % MOD; } cout << f[n] << endl; return 0; } ``` 以上代码通过数组 `f` 存储每层台阶的结果,利用循环逐步填充至目标层数 \( n \),并最终输出结果。 --- #### 时间复杂度分析 由于仅需一次线性遍历即可完成所有状态转移,时间复杂度为 \( O(n) \)。空间复杂度同样为 \( O(n) \),但如果优化存储,则可进一步降低到 \( O(1) \): ```cpp #include <iostream> using namespace std; const int MOD = 100003; int main() { int n; cin >> n; long long prev2 = 1, prev1 = 1, current; if (n == 0 || n == 1) { cout << 1 << endl; return 0; } for (int i = 2; i <= n; ++i) { current = (prev1 + prev2) % MOD; prev2 = prev1; prev1 = current; } cout << prev1 << endl; return 0; } ``` 在此版本中,只保留最近两个状态变量 (`prev1`, `prev2`) 来更新当前值,从而节省内存开销。 --- #### 输入输出说明 输入部分接受单个整数 \( n \),表示台阶数量;程序会返回从地面走到第 \( n \) 层的不同路径数目,结果经过指定模运算处理以适应大范围数据需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值