CF 468E

题意

给定一个 NN 的矩阵 A 。对于A中的 K 个位置(xi,yi),给定其权值 Vi 。对于其他未被确定的位置,其权值为1。现在希望你求出
σNi=1Ai,σ(i) ,其中 σ 1N 的排列。

数据范围

N105,K50

题解

首先,原题中给了一个wiki的链接,https://en.wikipedia.org/wiki/Permanent,从这个链接中我们可以找到一条公式(我也不会证,但是本题的基础)
perm(A+B)=s,tperm(aij)is,jtperm(bij)is¯,jt¯

接着,我们将原矩阵视为一个二分图,那么一个给定的位置 (xi,yi) 相当于在二分图中连了一条权值为 Vi 的边,并且,由上边的公式,我们可以推的应该要这样计算答案:
K 中选出t条边 e1,e2et ,并且满足 x1x2xt,y1y2y3yt ,那么
Ans=(nt)!vei

Ft vei ,其中 e 为大小为t的边集,那么我们可以分别对每个二分图中的联通块进行求解。

设当前联通块的点数为 n ,那么由于他是一个二分图,所以某一边最多只有n2个点。
一种比较简单的方法就是设 Gi,S 表示当前做到了 X 集合中第i个位置, Y 集合中被选的状态为S的乘积总和,那么对于每次转移,我们直接枚举 i 选择了那条边即可,时间复杂度为O(m2n2)=O(n22n2)

但由于题目中 n502=100 ,所以只有这个算法是不够的。

再考虑另外一种做法,对于一个联通块,设边数为 m ,我们先求出其生成树。

对于生成树上的匹配,我们可以设Fi,j表示对于 i 的子树,选了j个匹配的乘积的和,这个可以 O(N2) 地求出。

对于不在生成树上的边,我们可以直接暴力枚举每条边是否选,复杂度为 O(2mn+1) ,所以总的复杂度就是 O(2mn+1n2)

当我们在处理一个联通块时,我们根据情况选择这两种算法,那么复杂度就是
O(min(2n2,2mn+1)n2)

又因为 m,n 是同阶的,所以可以求出最终复杂度就是 O(2m3n2)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>

using namespace std;

const int MAXN = 305,Mo = int(1e9) + 7;

vector<int> Lk[MAXN],E[MAXN];
map<int,int> Hash;
bool Walk[MAXN];
int Els[MAXN][3],Pos[MAXN];
int T[MAXN],Ref[MAXN],Col[MAXN],H[MAXN],Block[MAXN][3],W[MAXN][MAXN],Self[MAXN],Edge,N,K,cnt,c,tot;

void Link(int u,int v)
{
    Lk[u].push_back(v),Lk[v].push_back(u);
}

void Extract(int S)
{
    static int Pre[MAXN];
    for(int i = 1;i <= cnt;i ++) Pre[i] = 0,E[i].clear(),Self[i] = 0;
    c = 0;
    tot = 0;
    Edge = 0;
    static int Q[MAXN];
    Q[1] = S;
    Ref[S] = (c = 1);
    Pos[1] = S;
    Walk[S] = 1;
    Col[S] = 0;
    for(int fi = 1,en = 1;fi <= en;fi ++)
    {
        int u = Q[fi];
        for(int i = 0;i < Lk[u].size();i ++)
        {
            if (!Walk[Lk[u][i]])
            {
                Self[u] ++;
                Edge ++;
                Ref[Lk[u][i]] = ++ c;
                Pre[Lk[u][i]] = u;
                Pos[c] = Lk[u][i];
                Walk[Lk[u][i]] = 1;
                Col[Lk[u][i]] = (Col[u] ^ 1);
                E[Ref[u]].push_back(Ref[Lk[u][i]]);
                Q[++ en] = Lk[u][i];
            } else
                if (Lk[u][i] > u && Lk[u][i] != Pre[u])
                    Self[u] ++,Edge ++,Els[++ tot][0] = Ref[u],Els[tot][1] = Ref[Lk[u][i]],Els[tot][2] = W[u][Lk[u][i]];
        }
    }
}

void Dp()
{
    int c = 0,tag = 0;
    static int All[MAXN],Siz[2];
    memset(Siz,0,sizeof Siz);
    for(int i = 1;i <= cnt;i ++)
        if (Ref[i]) Siz[Col[i]] ++;
    if (Siz[0] < Siz[1]) 
    {
        for(int i = 1;i <= cnt;i ++)
            if (Ref[i] && Col[i] == 0) All[i] = (c ++);
        tag = 1;
    } else
    {
        tag = 0;
        for(int i = 1;i <= cnt;i ++)
            if (Ref[i] && Col[i]) All[i] = (c ++);
    }
    static int G[2][1 << 21];
    c = (1 << c);
    for(int i = 0;i < c;i ++) G[1][i] = 0;
    G[1][0] = 1;
    int cr = 0;
    for(int i = 1;i <= cnt;i ++)
        if (Ref[i] && Col[i] == tag)
        {
            cr ^= 1;
            for(int j = 0;j < c;j ++) G[cr ^ 1][j] = G[cr][j];
            for(int j = 0;j < c;j ++)
                if (G[cr][j])
                {
                    for(int k = 0;k < Lk[i].size();k ++)
                    {
                        int v = Lk[i][k];
                        if (j & (1 << All[v])) continue;
                        int nj = (j | (1 << All[v]));
                        G[cr ^ 1][nj] = (G[cr ^ 1][nj] + G[cr][j] * 1ll * W[i][v] % Mo) % Mo;
                    }
                }
        }
    cr ^= 1;
    memset(H,0,sizeof H);
    for(int j = 0;j < c;j ++)
    {
        int v = 0;
        for(int tmp = j;tmp;tmp >>= 1) v += (tmp & 1);
        H[v] = (H[v] + G[cr][j]) % Mo;
    }
}

bool Ch[MAXN];
int G[MAXN][MAXN],F[MAXN][MAXN],Siz[MAXN],Max[MAXN];

void Dfs(int Now)
{
    Siz[Now] = Edge;
    for(int i = 0;i < E[Now].size();i ++)
        Dfs(E[Now][i]);
    for(int i = 0;i <= Siz[Now];i ++) F[Now][i] = G[Now][i] = 0;
    F[Now][0] = 1;
    for(int i = 0,cr = 0;i < E[Now].size();i ++)
    {
        int v = E[Now][i];
        for(int j = Siz[Now];j + 1;j --)
            if (F[Now][j])
                for(int k = 1;k <= Max[v] && k + j <= Edge;k ++)
                    if ((G[v][k] + F[v][k]))
                        F[Now][j + k] = (F[Now][j + k] + F[Now][j] * 1ll * ((G[v][k] + F[v][k]) % Mo)) % Mo;
    }
    if (!Ch[Now])
    {
        static int Cur[MAXN],Suf[MAXN][MAXN],Pre[MAXN];
        Suf[0][0] = 1;
        int ch = 1;
        for(int i = E[Now].size() - 1,cr = 0;i + 1;i --,ch ++)
        {
            int v = E[Now][i];
            for(int j = 0;j <= Siz[Now];j ++) Suf[ch][j] = Suf[ch - 1][j];
            for(int j = Siz[Now];j + 1;j --)
                if (Suf[ch][j]) 
                    for(int p = 1;p <= Max[v] && p + j <= Edge;p ++)
                        if ((F[v][p] + G[v][p]))
                            Suf[ch][j + p] = (Suf[ch][j + p] + Suf[ch][j] * 1ll * ((F[v][p] + G[v][p]) % Mo) % Mo) % Mo;
        }
        for(int i = 0;i <= Edge;i ++) Pre[i] = 0;
        Pre[0] = 1;
        ch --;
        for(int i = 0,cr = 0;i < E[Now].size();i ++,ch --)
        {
            int v = E[Now][i];
            if (!Ch[v])
            {
                static int Bak[MAXN];
                for(int p = 0;p <= Siz[Now];p ++) Bak[p] = Pre[p];
                for(int p = Siz[Now];p + 1;p --)
                    if (Bak[p])
                        for(int q = 1;q <= Max[v] && q + p <= Siz[Now];q ++)
                            if (F[v][q])
                                Bak[p + q] = (Bak[p + q] + Bak[p] * 1ll * F[v][q]) % Mo;
                for(int p = 0;p <= Siz[Now];p ++)
                    if (Bak[p])
                        for(int q = 0;p + q + 1 <= Siz[Now];q ++)
                            if (Suf[ch - 1][q])
                                G[Now][p + q + 1] = (G[Now][p + q + 1] + Bak[p] * 1ll * Suf[ch - 1][q] % Mo * W[Pos[Now]][Pos[v]] % Mo) % Mo;
            }
            for(int p = Siz[Now];p + 1;p --)
                if (Pre[p])
                    for(int q = 1;q <= Max[v] && q + p <= Siz[Now];q ++)
                        if ((F[v][q] + G[v][q]))
                            Pre[p + q] = (Pre[p + q] + Pre[p] * 1ll * (F[v][q] + G[v][q]) % Mo) % Mo;
            cr += Siz[v];
        }
    }
    Max[Now] = 0;
    for(int i = 0;i <= Edge;i ++) if (F[Now][i] + G[Now][i]) Max[Now] = i;
}

void Tree()
{
    memset(H,0,sizeof H);
    for(int i = 0;i < (1 << tot);i ++)
    {
        int cr = 1,ch = 0,f = 0;
        for(int j = 1;j <= c;j ++) Ch[j] = 0;
        for(int j = 0;j < tot;j ++)
            if (i & (1 << j))
            {
                if (Ch[Els[j + 1][0]] || Ch[Els[j + 1][1]]) {f = 1;break;}
                Ch[Els[j + 1][0]] = Ch[Els[j + 1][1]] = 1;
                cr = cr * 1ll * Els[j + 1][2] % Mo;
                ch ++;
            }
        if (f) continue;
        Dfs(1);
        for(int i = 0;i <= Edge - ch;i ++)
            H[i + ch] = (H[i + ch] + cr * 1ll * ((F[1][i] + G[1][i]) % Mo) % Mo) % Mo;
    }
}


void Work(int S)
{
    Extract(S);
    if (c / 2 <= Edge - c) Dp(); else
        Tree();
    for(int i = K;i;i --)
        for(int j = 1;j <= i;j ++)
            T[i] = (T[i] + T[i - j] * 1ll * H[j] % Mo) % Mo;
    memset(Ref,0,sizeof Ref);
}

int main()
{
    static int Fac[100005];
    //freopen("data.in","r",stdin),freopen("data.out","w",stdout);
    scanf("%d%d", &N, &K);
    for(int i = 1;i <= K;i ++) scanf("%d%d%d", &Block[i][0], &Block[i][1], &Block[i][2]);
    for(int i = 1;i <= K;i ++)
    { 
        if (!Hash[Block[i][0]]) Hash[Block[i][0]] = ++ cnt;
        Block[i][0] = Hash[Block[i][0]];
    }
    Hash.clear();
    for(int i = 1;i <= K;i ++) if (!Hash[Block[i][1]]) Hash[Block[i][1]] = ++ cnt;
    for(int i = 1;i <= K;i ++)
    {
        Block[i][1] = Hash[Block[i][1]];
        Link(Block[i][0],Block[i][1]);
        W[Block[i][0]][Block[i][1]] = W[Block[i][1]][Block[i][0]] = (Block[i][2] - 1 + Mo) % Mo;
    }
    T[0] = 1;
    for(int i = 1;i <= cnt;i ++)
        if (!Walk[i])
            Work(i);
    Fac[0] = 1;
    for(int i = 1;i <= N;i ++) Fac[i] = Fac[i - 1] * 1ll * i % Mo;
    int ans = 0;
    for(int i = 0;i <= K;i ++)
        ans = (ans + Fac[N - i] * 1ll * T[i] % Mo) % Mo;
    printf("%d\n", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值