猜排列 题解

推荐在 cnblogs 上阅读

猜排列 题解

差 eps 步想到正解。

题意描述

m m m 个长为 n n n 序列 a 1 , … , a n a_1,\dots,a_n a1,,an,还有 m m m 个长为 n n n 序列 b 1 , … , b n b_1,\dots,b_n b1,,bn

其中 b i b_i bi 是由 a i a_i ai 通过排列 p p p 置换得到的, b p i = a i b_{p_i}=a_i bpi=ai

现在又要求出排列 p p p 会有多种可能,模 998244353 998244353 998244353

Solution

首先手玩样例,发现如果这 m m m a a a 的某个位置和 b b b 的某个位置都是相等的,可以确保一定有一种可能的 p p p 排列使他们就是通过置换得到的。

有点像二分图,连个边再说。

发现有些位置是可以同时连不同的位置的(那当然了,不然答案就是唯一的,输出 1 1 1 就好了),这时发现,对面也可以向我们连边。而无论哪个样例,似乎连出来的二分图都是一个部的每个点都连向了另一个部的每个点。

那这个子图贡献的就是子图所有点 ÷ 2 \div 2 ÷2 的数量的阶乘。(就全排列嘛,除以二是因为二分图上下两部的点的数量是对称的)

考场就想到这里,发现如果要找子图的话是 O ( n 2 m ) O(n^2m) O(n2m) 的。含泪 80pts。

差那一步呢?

我们发现,同一位上的 m m m 次可以压成哈希。这样的话如果本来就是同一子图,那哈希值相等。

也不用建图了,因为可用 map 统计点数,不需要把图真正建出来去跑。

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int N = 6e5 + 5, P = 998244353, base = 19491001, P0 = 20090327, P1 = 20070923;

int T, n, m;
int fac[N], inv[N], cnt[N];
bool vis[N];
unsigned int a[N][2], b[N][2];
int su, en[N], lt[N], hd[N];
map<pair<unsigned int, unsigned int>, unsigned int> mp1, mp2;
map<pair<unsigned int, unsigned int>, bool> Vis;

void add(int u, int v) { en[++su] = v, lt[su] = hd[u], hd[u] = su; }

void init() {
    fac[0] = 1;
    for (int i = 1; i <= N - 5; i++) fac[i] = fac[i - 1] * i % P;
}

inline void solve() {
    memset(a, 0, sizeof a);
    memset(b, 0, sizeof b);
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= m; i++) {
        int x;
        for (int j = 1; j <= n; j++) {
            scanf("%lld", &x);
            a[j][0] = (1ll * a[j][0] * base + x) % P0;
            a[j][1] = (1ll * a[j][1] * base + x) % P1;
        }
        for (int j = 1; j <= n; j++) {
            scanf("%lld", &x);
            b[j][0] = (1ll * b[j][0] * base + x) % P0;
            b[j][1] = (1ll * b[j][1] * base + x) % P1;
        }
    }
    mp1.clear(), mp2.clear();
    Vis.clear();
    for (int i = 1; i <= n; i++) mp1[{ a[i][0], a[i][1] }]++, mp2[{ b[i][0], b[i][1] }]++;
    int ans = 1;
    for (int i = 1; i <= n; i++) {
        if (!Vis[{ a[i][0], a[i][1] }]) {
            Vis[{ a[i][0], a[i][1] }] = 1;
            if (mp1[{ a[i][0], a[i][1] }] != mp2[{ a[i][0], a[i][1] }])
                ans = 0;
            else
                ans = ans * fac[mp1[{ a[i][0], a[i][1] }]] % P;
        }
    }
    printf("%lld\n", ans);
}

signed main() {
    // freopen("interact.in","r",stdin);
    // freopen("interact.out","w",stdout);
    scanf("%lld", &T);
    init();
    while (T--) solve();
    return 0;
}
  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值