CF804F Fake bullions 题解

博客详细解析了CF804F Fake bullions问题,分为两个部分。Part 1中,利用gcd和Lucas定理判断条件,并通过竞赛图的缩点与拓扑排序简化计算。Part 2中,求出每个点的最大值和最小值,通过枚举集合B的最小值,结合偏序关系,计算可能的组合数,以解决这道二合一的算法题目。

CF804F Fake bullions

说实话这题根据套路应该很容易做出来,但是感觉想要通过思维做出来 … \dots

一个二合一的题目。

P a r t   1 \tt Part\ 1 Part 1

首先考虑题目给定的限制,如果 u → v u \to v uv 而且 u u u 的第 i i i 个人有金条,那么 v v v 中第 j j j 个人有假金条当且仅当 S u x + i = S v y + j S_ux + i = S_vy + j Sux+i=Svy+j 根据 L u c a s \tt Lucas Lucas 定理可以得到符合条件当且仅当 gcd ⁡ ( S u , S v ) ∣ j − i \gcd(S_u, S_v) | j - i gcd(Su,Sv)ji

我们考虑这个的传递性,也就是 gcd ⁡ ( path ( u , v ) ) ∣ j − i \gcd(\text{path}(u, v)) | j - i gcd(path(u,v))ji。对于竞赛图我们不妨考虑缩点之后再还原。

因为竞赛图缩点之后的特殊性质,每个点都向后面的所有点连边,所以可以直接根据拓扑序列进行计算。

对于一个点因为同余的性质可以得到 a n s i = c t b e l i × S i gcd ⁡ ( b e l i ) ans_i = \dfrac{ct_{bel_i}\times S_i}{\gcd(bel_i)} ansi=gcd(beli)ctbeli×Si


P a r t   2 \tt Part\ 2 Part 2

之后我们可以求出每个点的最大值和最小值称其为 m x i , m n i mx_i, mn_i mxi,mni

对于一个集合 B B B 最可能合法的情况就是集合 B B B 中全部是 m x mx mx,外面全部是 m n mn mn 所以我们考虑将集合 B B B 中全部取 m x mx mx 来考虑。

对于整个集合 A , B A, B A,B 最明显的限制就是最小值,那么我们不妨考虑枚举 B B B 中的最小值不妨设其为 i i i 。之后找出肯定在 A A A 中的元素,也就是 m n j > m x i mn_j > mx_i mnj>mxi

还有可以选择的元素也就是 m n j ≤ m x i mn_j \le mx_i mnjmxi 而且 m x j > m x i , m x j = m x i  and  i < j mx_j > mx_i, mx_j = mx_i \text{ and } i < j mxj>mxi,mxj=mxi and i<j 本质上就是定义一个偏序关系。

设前面那个是 c t 1 ct1 ct1 后面那个是 c t 2 ct2 ct2

我们直接暴力枚举一下将几个后面的放到前面,之后进行组合数即可。

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

//#define Fread
#define Getmod

#ifdef Fread
char buf[1 << 21], *iS, *iT;
#define gc() (iS == iT ? (iT = (iS = buf) + fread (buf, 1, 1 << 21, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
#define getchar gc
#endif // Fread

//template <typename T>
//void r1(T &x) {
//	x = 0;
//	char c(getchar());
//	int f(1);
//	for(; c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
//	for(; '0' <= c && c <= '9';c = getchar()) x = (x * 10) + (c ^ 48);
//	x *= f;
//}
//
//template <typename T,typename... Args> inline void r1(T& t, Args&... args) {
//    r1(t);  r1(args...);
//}

#ifdef Getmod
const int mod  = 1e9 + 7;
template <int mod>
struct typemod {
    int z;
    typemod(int a = 0) : z(a) {}
    inline int inc(int a,int b) const {return a += b - mod, a + ((a >> 31) & mod);}
    inline int dec(int a,int b) const {return a -= b, a + ((a >> 31) & mod);}
    inline int mul(int a,int b) const {return 1ll * a * b % mod;}
    typemod<mod> operator + (const typemod<mod> &x) const {return typemod(inc(z, x.z));}
    typemod<mod> operator - (const typemod<mod> &x) const {return typemod(dec(z, x.z));}
    typemod<mod> operator * (const typemod<mod> &x) const {return typemod(mul(z, x.z));}
    typemod<mod>& operator += (const typemod<mod> &x) {*this = *this + x; return *this;}
    typemod<mod>& operator -= (const typemod<mod> &x) {*this = *this - x; return *this;}
    typemod<mod>& operator *= (const typemod<mod> &x) {*this = *this * x; return *this;}
    int operator == (const typemod<mod> &x) const {return x.z == z;}
    int operator != (const typemod<mod> &x) const {return x.z != z;}
};
typedef typemod<mod> Tm;
#endif


//#define int long long
const int maxn = 5e3 + 5;
const int maxm = 2e6 + 5;
const int N = 2e6;
int n, A, B;
vector<int> vc[maxn];
void add(int u,int v) {
    vc[u].push_back(v);
}

Tm fac[maxm], inv[maxm];
char s[maxm];

Tm ksm(Tm x,int mi) {
    Tm res(1);
    while(mi) {
        if(mi & 1) res *= x;
        mi >>= 1;
        x *= x;
    }
    return res;
}

Tm C(int a,int b) {
    if(a < b) return 0;
    if(a < 0 || b < 0) return 0;
    return fac[a] * inv[b] * inv[a - b];
}

string g[maxn];
int Ln[maxn];
typedef long long ll;
ll mx[maxn], mn[maxn];

int dfn[maxn], st[maxn], ed, low[maxn];
int vis[maxn], tot(0);
int bel[maxn], Siz(0);
int gc[maxn];
vector<int> f[maxn];

int gcd(int a,int b) {
    return b == 0 ? a : gcd(b, a % b);
}

void tarjan(int p) {
    st[++ ed] = p, dfn[p] = low[p] = ++ tot; vis[p] = 1;
    for(int to : vc[p]) {
        if(!dfn[to]) tarjan(to), low[p] = min(low[p], low[to]);
        else if(vis[to]) low[p] = min(low[p], dfn[to]);
    }
    if(low[p] == dfn[p]) {
        bel[p] = ++ Siz; gc[Siz] = Ln[p];
        vis[p] = 0;
        int y;
        while((y = st[ed --]) != p) {
            bel[y] = Siz; gc[Siz] = gcd(gc[Siz], Ln[y]);
            vis[y] = 0;
        }
    }
}


signed main() {
//    freopen("S.in", "r", stdin);
//    freopen("S.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);
    int i, j;
    fac[0] = 1;
    for(i = 1; i <= N; ++ i) fac[i] = fac[i - 1] * i;
    inv[N] = ksm(fac[N], mod - 2);
    for(i = N - 1; ~ i; -- i) inv[i] = inv[i + 1] * (i + 1);

//    r1(n, A, B);
    cin >> n >> A >> B;
    for(i = 1; i <= n; ++ i) {
        cin >> (s + 1);
        for(j = 1; j <= n; ++ j) if(s[j] == '1') add(i, j);
    }
    for(i = 1; i <= n; ++ i) {
        cin >> Ln[i] >> g[i];
        for(j = 0; j < Ln[i]; ++ j) g[i][j] ^= 48, mn[i] += g[i][j];
    }
    for(i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);
//    for(i = 1; i <= Siz; ++ i) f[i].resize(max(gc[i - 1], gc[i]) + 1);
    for(i = 1; i <= Siz; ++ i) f[i].resize(gc[i] + 1);
    for(i = 1; i <= n; ++ i) for(j = 0; j < Ln[i]; ++ j) {
        f[bel[i]][j % gc[bel[i]]] |= g[i][j];
    }
//    puts("ZZZZ");
    vector<int> ct(Siz + 2, 0);
    for(i = Siz; i > 1; -- i) {
        int gc1 = gcd(gc[i], gc[i - 1]);
        for(int j = 0; j < gc[i]; ++ j) f[i - 1][j % gc1] |= f[i][j], ct[i] += f[i][j];
    }
    for(i = 0; i < gc[1];  ++ i) ct[1] += f[1][i];
    for(i = 1; i <= n; ++ i) mx[i] = 1ll * ct[bel[i]] * Ln[i] / gc[bel[i]];
    Tm res(0);
    for(i = 1; i <= n; ++ i) {
        int ct1(0), ct2(0);
        for(j = 1; j <= n; ++ j) if(i != j) {
            if(mn[j] > mx[i]) ++ ct1; // 一定选
            else if(mx[j] > mx[i] || (mx[j] == mx[i] && j < i)) ++ ct2; // 可能能选
        }
        if(ct1 > A - 1) continue;
        for(j = min(B, min(A - 1 - ct1, ct2)); j + ct1 + 1 >= B; -- j)
            res += C(ct2, j) * C(ct1, B - j - 1);
    }
    cout << res.z << endl;
//    printf("%d\n", res.z);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值