bnuoj 52305 Around the World dfs+排列组合

本文介绍了一种计算从指定起点遍历所有边的方案数的算法。针对大规模图(最多10万节点),使用静态链表存储图结构,并通过深度优先搜索计算方案数。文章详细解释了递推公式的含义及实现过程,并提供了完整的C++代码。

题意

n个城市,联通图,给出n-1条信息,表示城市A和城市B之间有2*C条无向路相连通。需求出从城市1开始,遍历所有边的方案数。

数据范围:
2 ≤ n ≤ 1e5
路的总数量不超过2*1e6

样例输入:
3
1 2 1
2 3 1
表示3个城市,1-2有2条路,2-3有三条路

题解

由于点数为1e5,静态链表方式存图。
求方案数的过程为深搜。
对于当前节点u来说,它有若干个字节点v1、v2、、、
ans[u]= ( ∑(num[u][vi]) )! * ∏( ( 2*num[u][vi] )! ) / ∏ ( num[u][vi] !) * ∏Lucas(out[vi] + num[u][vi] -1,num[u][vi]-1) * ∏ans[vi];
ps: num[u][vi]表示u->vi有2*num[u][vi]条边

表示这个递推公式让人根本不想看。。T_T

其实要做的只有两件事,遍历当前子节点的方案数,将子树与子树的遍历进行穿插的方案数。

先将每个子树看作一个节点,当前节点u有子节点v1、v2、、、vk,遍历当前子节点的方案数为ans[u] = out(u) ! /∏ ( num[u][vi] !) , out(u) = ∑(num[u][vi]), 为u的出度的一半。

同时,需考虑u-vi之间,边与边的不同:
ans[u] = ans[u] * ∏( ( 2*num[u][vi] )! )

其次,要将u-vi的进出穿插进vi子树本身的遍历中,同插空法,在out[vi]个小球中,插入num[u][vi]-1个隔板,将其分成num[u][vi]份,每一份>=0.
ans[u] = ans[u] * ∏Lucas(num[vi] + num[u][vi] -1,num[u][vi]-1)

最后,将子树的遍历方案数乘进来
ans[u] = ans[u] * ∏ans[vi];

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
typedef long long ll;
#define mod 1000000007
#define p 1000000007
#define LL ll
const ll MAX_N = 1e5+10;

struct EDGE
{
    ll b;
    ll num;
    ll pre;
}edge[MAX_N * 2];

ll point[MAX_N];
ll num[MAX_N];

void add_edge(ll a,ll b,ll c,ll i)
{
    edge[i].b = b;
    edge[i].num = c;
    edge[i].pre = point[a];
    point[a] = i;
    num[a] = (num[a] + c)%mod;
}

ll half(ll x)
{
    ll ans = 1LL;
    for(ll t = 2*x; t>x; t--)
    {
        ans = (ans*t)%mod;
    }
    return ans;
}

ll full(ll x)
{
    ll ans = 1LL;
    for(ll t = x; t>0; t--)
    {
        ans = (ans*t)%mod;
    }
    return ans;
}


LL quick_mod(LL a, LL b)  
{  
    LL ans = 1;  
    a %= p;  
    while(b)  
    {  
        if(b & 1)  
        {  
            ans = ans * a % p;  
            b--;  
        }  
        b >>= 1;  
        a = a * a % p;  
    }  
    return ans;  
}  

LL C(LL n, LL m)  
{  
    if(m > n) return 0;  
    LL ans = 1;  
    for(int i=1; i<=m; i++)  
    {  
        LL a = (n + i - m) % p;  
        LL b = i % p;  
        ans = ans * (a * quick_mod(b, p-2) % p) % p;  
    }  
    return ans;  
}  

LL Lucas(LL n, LL m)  
{  
    if(m == 0) return 1;  
    return C(n % p, m % p) * Lucas(n / p, m / p) % p;  
}  


ll dfs(ll x, ll last, ll dec)
{
    num[x] -= dec;
    ll ans = 0LL;
    ll add = 0LL;
    ll mul = 1LL;
    for(ll t = point[x];t!=-1;t = edge[t].pre)
    {
        ll v = edge[t].b;
        if(v ==  last) continue;
        add = (add + edge[t].num) % mod;
        mul = (mul * half(edge[t].num)) % mod;
    }
    add = full(add);
    ans = (add * mul)%mod;

    for(ll t = point[x];t!=-1;t = edge[t].pre)
    {
        ll v = edge[t].b;
        if(v ==  last) continue;
        ans = (ans * dfs(v, x, edge[t].num)) % mod;
        ll tans = 0LL;
        tans = (tans + Lucas( edge[t].num + num[v]-1, edge[t].num-1)) % mod;
        ans = (ans * tans) % mod;
    }
    return ans;
}

int main()
{
    ll n;
    while(scanf("%lld",&n)!=EOF)
    {

        memset(point,-1,sizeof(point));
        memset(num,0,sizeof(num));

        for(int i=1;i<n;i++)
        {
            ll a,b,c;
            scanf("%lld%lld%lld",&a,&b,&c);
            add_edge(a,b,c,i);
            add_edge(b,a,c,i+n-1);
        }

        ll ans = dfs(1,-1,0);;
        printf("%lld\n",ans);
    }
    return 0;
}

其中lucas的模版来源于ACdreamers的博客组合数取模

内容概要:本文详细记录了对一个Android ARM64静态ELF文件中字符串加密机制的逆向分析过程。该ELF文件的所有字符串均被加密,无法通过常规strings命令或IDA直接识别。作者通过分析发现,加密字符串存储在.rodata段,其解密所需信息(包括密文地址、长度和16位密钥)保存在.data.rel.ro段的40字节描述符中。核心解密函数sub_10F408采用自反的双pass流密码算法,结合固定密钥KEY_TERM(由.data段24字节数据计算得出),实现字节级非线性、位置与长度相关的加密。文章还复现了完整的Python解密脚本,并揭示了该保护机制的本质为代码混淆而非强加密,最终成功批量解密全部956条字符串,暴露程序真实行为,如shell命令模板、设备标识篡改、网络重置等操作。此外,文中还提及未启用的自定义壳框架及其反dump设计。; 适合人群:具备逆向工程基础的安全研究人员、二进制分析人员及对ELF保护技术感兴趣的开发者。; 使用场景及目标:①学习ELF二进制中字符串加密的典型实现方式与逆向突破口;②掌握从结构识别、函数追踪到算法还原的完整逆向流程;③理解“绑定二进制”的完整性校验设计及其局限性;④实践编写IDAPython脚本自动化提取与解密敏感数据。; 阅读建议:此资源以实战案例驱动,不仅展示技术细节,更强调逆向思维与验证方法,建议读者结合IDA调试环境,逐步跟随文中步骤进行动态分析与算法验证,深入理解每一步的推理依据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值