HDU 6038 Function (强连通+计数)

思路:

看似是数学题。。其实是图论。
我们可以发现他给的函数的约束关系其实是一种递推推导。
对于样例
3 2
1 0 2
0 1
我们把a行的第i个(i从0开始)建一条从i到 ai 的边,那么对于这个样例我们得到了一个二元环和一个一元环,对b行进行相应的操作,我们得到了两个一元环。(我这里的二元环即代表由两个元素组成的环)

这个环是什么意思呢?她代表了一种相互递推的关系,只要在环中的任意一个元素被决定了,环中其余的元素也就都确定了。正因为有这样的性质,所以我们才建图来找出各种环的数量。

tarjan跑两遍分别求出a行中各种环的数量和b行中各种环的数量。
我在这里把这些信息分别存在 aa[n] bb[m] 中。其中 aa[i] 表示i元环的数量为 aa[i] 个。

然后对于每个非0的 aa[i] ,我们需要算出有多少 bb[i] 能够放进去。比如 aa[3]=1 bb[1]=1
bb[3]=1 那么我们应该得到的答案即: 1*bb[1] + 3*bb[3] (此处算的是每个aa[i]的取的种数,最后答案需要将这些相乘)

注意环的关系,只有当a中的环的元的个数是b中环的元的个数的整数倍时,我们才能将b中的环放到a中而不引起关系的崩溃。这里采用类似素数筛的方式,直接一遍扫出来。

//b中最多有m元环 sumb数组中保存了aa[i]对应的有多少种取法

for(int i = 1;i <= m;i++){
    if(bb[i] == 0) continue;
    for(int j = i;j <= n;j+=i){
         sumb[j] += i*bb[i];
    }
}

再之后,我们需要注意这是在考虑aa[i]为1的情况下的结果,如果aa[i]不为1而是为n,那么我们需要为这n种i元环分别选择一种,即 sumb[i]n
所以如下:

lli ans = 1;
for(lli i = 1;i <= n;i++){
    if(aa[i] != 0){//只有当此种环存在时才算
        ans *= qp(sumb[i],aa[i]);//quick power 快速幂 
        ans %= mod;
    }
}

ac代码:

#include <iostream>
#include <cstdio>
#include <queue>
#include <string.h>
#include <algorithm>
#define inf 0x3f3f3f3f
typedef long long int lli;
using namespace std;
const int maxn =  120000;
const int mod = 1e9+7;
struct edge{
    lli to,v,next;
}ed[maxn];lli cnte,head[maxn];
void ae(lli x,lli y){
    ed[++cnte].to = y;
    ed[cnte].next = head[x];
    head[x] = cnte;
}
lli aa[maxn],bb[maxn],sumb[maxn],dp[maxn];
lli dfn[maxn],low[maxn],vis[maxn],stak[maxn],cntc,cnts,index;
void dfs(int u,lli *ss){
    dfn[u]=low[u] = ++index;
    stak[cnts++]=u;
    vis[u]=1;
    for(int i = head[u];~i;i=ed[i].next){
        lli v = ed[i].to;
        if(!dfn[v]){
            dfs(v,ss);
            low[u] = min(low[u],low[v]);
        }
        else if(vis[v]){
            low[u] = min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u]){
        cntc++;lli v,cnt = 0;
        do{
            cnt++;
            v = stak[--cnts];
            vis[v] = 0;
        }while(v!=u);
        ss[cnt]++;
    }
}
lli n,m;
void tarjan(lli *ss,lli n){
    for(int i = 0;i < n;i++){
        if(!dfn[i]) dfs(i,ss);
    }
}
inline lli qp(lli a,lli x){
    if(a == 0) return 0;
    lli ans = 1;
    for(;x;x>>=1){
        if(x&1) ans = ans*a % mod;
        a = a*a % mod;
    }
    return ans % mod;
}
void ini(){
    memset(head,-1,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(vis,0,sizeof(vis));
    cnte = cntc = cnts = index = 0;
}
int main(){
    int cas = 0;
    while(~scanf("%I64d%I64d",&n,&m)){
        ini();
        memset(aa,0,sizeof(aa));
        memset(bb,0,sizeof(bb));
        memset(sumb,0,sizeof(sumb));
        cas++;
        int temp;
        for(int i = 0;i < n;i++){
            scanf("%d",&temp);
            ae(i,temp);
        }
        tarjan(aa,n);
        ini();
        for(int i = 0;i < m;i++){
            scanf("%d",&temp);
            ae(i,temp);
        }
        tarjan(bb,m);
        for(int i = 1;i <= m;i++){      //类似素数筛
            if(bb[i] == 0) continue;
            for(int j = i;j <= n;j+=i){
                sumb[j] += i*bb[i];
            }
        }
        lli ans = 1;
        for(lli i = 1;i <= n;i++){
            if(aa[i] != 0){
                ans *= qp(sumb[i],aa[i]);
                ans %= mod;
            }
        }
        printf("Case #%d: %lld\n",cas,ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值