CF 468E

本文介绍了一种解决特定矩阵永久项问题的有效算法,通过构建二分图并结合图论中的概念来解决大规模数据集的问题。针对不同规模的输入数据采用不同的优化策略,确保算法的高效运行。

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

题意

给定一个 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;
}
<think>好的,用户现在在问Codeforces problem734E的解决方案或解释。我需要先回忆一下这道题的具体内容。题目编号734E对应的是Codeforces上的一个题目,通常这类题目会有具体的名称和问题描述。不过根据用户提供的引用内容,之前的引用似乎提到了其他问题,比如742B,涉及位运算和异或操作。但用户现在明确询问的是734E,所以需要转向这个题号的内容。首先,我需要确认734E的题目内容。Codeforces的题目734E的标题通常是“Antonand Tree”,这题涉及树的结构和图论中的连通性处理。题目大意是给定一棵树,每个节点有黑色或白色,每次操作可以翻转一个颜色相同的连通区域的颜色,问最少需要多少次操作让整棵树变成同一种颜色。 接下来,我需要分析这个问题。解决这类问题通常需要找到树中的最长路径,也就是直径。因为每次操作可以翻转整个同色连通区域,所以关键在于如何将相邻的相同颜色节点合并,形成更大的连通块。然后,将问题转化为处理合并后的树的直径,所需的最小操作次数通常是直径长度的一半,向上取整。用户可能需要具体的解决步骤,比如如何通过两次BFS或DFS找到树的直径,或者如何预处理颜色相同的连通块。此外,可能还需要解释为什么直径决定了最小操作次数,以及具体的实现方法,比如构建缩点后的树,然后计算其直径。在回答时,需要结构清晰,分步骤解释。可能还需要提供代码示例,比如用Python或C++实现的解法。同时,根据用户之前的引用,他们可能对位运算或其他算法感兴趣,但734E主要涉及树和图论,需要调整回答内容。 用户可能还会关心时间复杂度的分析,或者如何优化算法。例如,两次BFS的时间复杂度是线性的,适合处理大范围的树结构。此外,需要验证思路的正确性,确保每一步的逻辑正确,比如合并同色节点后的树结构是否确实将问题转化为求直径的问题。 最后,生成相关问题,比如如何找到树的直径,如何处理连通块,或者类似的问题推荐,帮助用户进一步巩固相关知识。</think>### Codeforces 734E Anton and Tree 问题解析 **题目概述** 给定一棵$n$个节点的树,每个节点为黑色或白色。每次操作可以选择一个颜色相同的连通区域,翻转其颜色(黑→白,白→黑)。求使整棵树变为单一颜色的最小操作次数[^4]。 --- ### 关键思路 1. **连通块合并** 将相邻同色节点视为一个超级节点(缩点),形成新的树结构。此时新树中相邻节点颜色必然不同。 **示例**:原树为`黑-黑-白-白-黑`,合并后变为`黑-白-黑`。 2. **树直径与操作次数** 新树的直径决定了最小操作次数。设直径为$d$,则答案为$\lfloor \frac{d+1}{2} \rfloor$。 **原理**:每次操作可消除直径端点的一层颜色差异,最终在$\lfloor d/2 \rfloor$步内完成[^1]。 --- ### 算法步骤 1. **构建缩点后的树** - 使用DFS/BFS遍历原树,合并同色连通块。 - 记录合并后的边(仅连接不同颜色块)。 2. **求树的直径** - **两次BFS法**: - 第一次BFS找到最远节点$u$ - 第二次BFS从$u$出发找到最远节点$v$,路径$u→v$即为直径。 --- ### 代码实现(Python) ```python import sys from collections import deque def main(): sys.setrecursionlimit(1 << 25) n = int(sys.stdin.readline()) color = list(map(int, sys.stdin.readline().split())) adj = [[] for _ in range(n)] for _ in range(n-1): u, v = map(int, sys.stdin.readline().split()) adj[u-1].append(v-1) adj[v-1].append(u-1) # 构建缩点后的边 new_edges = [[] for _ in range(n)] visited = [False] * n def dfs(u, parent): visited[u] = True for v in adj[u]: if v == parent: continue if color[u] == color[v]: dfs(v, u) else: new_edges[u].append(v) dfs(0, -1) # BFS找最远节点 def bfs(start): q = deque() dist = [-1] * n dist[start] = 0 q.append(start) max_dist = 0 far_node = start while q: u = q.popleft() for v in new_edges[u]: if dist[v] == -1: dist[v] = dist[u] + 1 if dist[v] > max_dist: max_dist = dist[v] far_node = v q.append(v) return far_node, max_dist u, _ = bfs(0) v, d = bfs(u) print((d + 1) // 2) if __name__ == "__main__": main() ``` --- ### 复杂度分析 - **时间**:$O(n)$,两次BFS和DFS均为线性时间。 - **空间**:$O(n)$,存储邻接表和距离数组。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值