欢迎使用皮肤 Geek|

Tsawke

园龄:2年7个月粉丝:6关注:2

Tsawke 的十二月模拟赛 题解

Tsawke 的十二月模拟赛 题解

God does not play dice with the universe

原题是 LG-P3779 [SDOI2017] 龙与地下城

关于自适应辛普森 Simpson - 辛普森法 学习笔记

题面

给定一个 m 面的骰子,等概率产出 0,1,2,,m1,投 n 次,求投出来的数之和在区间 [A,B] 的概率。

Solution

首先介绍一点前置知识:

正态分布:图形不再赘述,唯一需要注意的就是随机变量 X 的正态分布只需要它的期望 μ 和方差 σ2 即可描述,记作 N(μ,σ2),不难发现这恰好对应着题干。

概率密度函数:依然考虑一个随机变量 X,若其为离散的那么显然可以简单的求出任意点的概率。但若其为连续型的,那么一个点的概率在极限意义下为 0,然然而查询一段区间的时候显然不为 0,所以我们便引入了概率密度函数来描述这个概率,对于随机变量 X 的概率密度函数 f(x),需要满足 f(x) 在区间内的积分等于 X 落在该区间的概率。

然后有个结论:正态分布的概率密度函数为:

f(x)=12πσ2e(xμ)22σ2

关于这个东西的证明。。。完全不是人看的,似乎只能强行记下来这个公式。。。如果你一定要看一下证明,网上倒是也有一个 正态分布推导过程

然后还有就是 C++ 库里自带了个 erferfc,大概求的是误差函数的积分和互补误差函数之类的,(我不会),有兴趣可以看看。

然后所以如果我们能够证明本题这玩意是正态分布的,那么就直接对这个 f(x) 做自适应辛普森,求一下积分就行了。

独立同分布:首先独立比较好理解,就是两个随机变量之间无影响,和高中数学里面的独立事件差不多。然后同分布就是指一些随机变量服从相同的分布。

Tips:概率论中 E(X) 表示期望,D(X) 表示方差。

中心极限定理:对于 n 个独立同分布(如本题中的相同骰子)的随机变量 X1,X2,,Xn,若 E(Xi)=μ,D(Xi)=σ2,令:

Yn=i=1nXinμnσ2

n 足够大,则我们认为 YnN(0,1)

然后还有一个常用推论,当然首先我们需要知道正态分布的一点运算规则,即:

XN(a,b)cXN(ca,c2b),从期望和方差的意义不难理解。

XN(a,b)X+cN(a+c,b),同理不难得出。

所以我们可以将刚才的中心极限定理式子转化为:

i=1nXiN(nμ,nσ2)

也就是说,本题里求的这些骰子的点数和,实际上就是 n 个独立同分布的和,所以一定服从 N(nμ,nσ2),用我们刚才写的正态分布概率密度函数带进去这个期望和方差然后求个积分即可。

然后发现这东西套个自适应辛普森就可以在 O(玄学) 的复杂度完成。

但是我们不难发现这个东西还有点问题,就是中心极限定理需要一个前提,n 足够大,对于一些 n=1 之类的数据点用这个就显然寄了,所以我们要考虑一些数据点分治的做法。

显然对于 n 较小的数据,我们可以考虑多项式,多项式 i 次方项的系数为骰子值为 i 的概率,显然当 n=1 时,假设骰子面数为 m,不难想到多项式为 1mxm1+1mxm2++1mx1+1mx0。然后很容易想到对于其它的 n 结果就是这个多项式的 n 次方,我们只需要用 FFT 优化一下然后在结果里求出指数在 [A,B] 之间的系数和即可,这东西可以用多项式快速幂优化(这个实际上不算是多项式快速幂,因为最终多项式总长度较小,所以在正常 FFT 时写个复数的快速幂就行),我们可以分析一下,显然多项式初始项数最多为 m,所以时间复杂度大概是 O(nmlognm),常数不小,然后 nm4e6 级别的,总之 nm1e5 应该不成问题,而且因为我们的中心极限定理一般要求 n30 就可以了,所以这个理论上就可以过了。

Tips:仅用多项式快速幂期望得分 60~70。

upd:上述过程就可以通过本题了,需要注意的一个问题是不要忘记在自适应辛普森的过程中限制层数,后文是我最开始写这道题时的因为没有限制层数的一些误区与另一种类似的方法,仅供参考。


如果不在自适应辛普森中限制层数,那么会有精度问题,原因除此之外还可能因拟合 N(0,1) 的概率密度函数会比拟合 N(nμ,nσ2) 精度更高一点,可能因为 nμnσ2 的值域范围太大了,再加上自适应辛普森本来精度就很玄学,所以会导致最终答案精度爆炸。

总之还可以考虑另一个方法,即中心极限定理的初始式子:

Yn=i=1nXinμnσ2

不难发现我们知道了限定的 i=1nXi 的范围,也就可以带进式子里直接推出 Yn 的范围,然后用自适应辛普森跑一下 N(0,1) 的概率密度函数,因为 YnN(0,1),所以求出对应范围之后直接求 N(0,1) 的概率密度函数在新范围里的积分即为答案。不过这样会发现依然是错误的如果在自适应辛普森中限制层数那么就没有问题了。

检查发现,对于正态分布中,在角落可能很小,从而导致 [l,mid],[mid,r],[l,r] 都很小,从而直接返回 0,可以感性理解一下,所以可能会导致拟合的误差过大,于是考虑每次求范围 [A,B] 的时候分别拟合 [0,A][0,B],然后用 [0,B] 的值减去 [0,A] 的,这样是等效的,且会更多的引入较大的值使得精度更高,改成此方法后即使不限制层数也可以通过本题。

Tips:代码中注释部分即为后半部分的实现方式。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define comp complex < ld >
#define DFT (true)
#define IDFT (false)
#define EPS (ld)(1e-10)

template < typename T = int >
inline T read(void);

int N, M;

comp comp_qpow(comp a, ll b){
    comp ret(1.0, 0.0), mul(a);
    while(b){
        if(b & 1)ret = ret * mul;
        b >>= 1;
        mul = mul * mul;
    }return ret;
}

class Polynomial{
private:
public:
    int len;
    comp A[2100000];
    comp Omega(int n, int k, bool pat){
        if(pat == DFT)return comp(cos(2 * PI * k / n), sin(2 * PI * k / n));
        return conj(comp(cos(2 * PI * k / n), sin(2 * PI * k / n)));
    }
    void Reverse(comp* pol){
        int pos[len + 10];
        memset(pos, 0, sizeof pos);
        for(int i = 0; i < len; ++i){
            pos[i] = pos[i >> 1] >> 1;
            if(i & 1)pos[i] |= len >> 1;
        }
        for(int i = 0; i < len; ++i)if(i < pos[i])swap(pol[i], pol[pos[i]]);
    }
    void FFT(comp* pol, int len, bool pat){
        Reverse(pol);
        for(int siz = 2; siz <= len; siz <<= 1)
            for(comp* p = pol; p != pol + len; p += siz){
                int mid = siz >> 1;
                for(int i = 0; i < mid; ++i){
                    auto tmp = Omega(siz, i, pat) * p[i + mid];
                    p[i + mid] = p[i] - tmp, p[i] = p[i] + tmp;
                }
            }
        if(pat == IDFT)
            for(int i = 0; i <= len; ++i)
            A[i].real(A[i].real() / (ld)len), A[i].imag(A[i].imag() / (ld)len);
    }
    void MakeFFT(void){
        FFT(A, len, DFT);
        for(int i = 0; i < len; ++i)A[i] = comp_qpow(A[i], N);
        FFT(A, len, IDFT);
    }
}poly;

ld mu, sigma2;

ld f(ld x){
    return exp(-(x - mu) * (x - mu) / 2.0 / sigma2) / sqrt(2.0 * PI * sigma2);
}
ld Simpson(ld a, ld b){
    return (b - a) * (f(a) + f(b) + 4 * f((a + b) / 2.0)) / 6.0;
}
ld Adaptive(ld l, ld r, ld cur, ld eps = 1e-6, ll dep = 1){
    ld mid = (l + r) / 2.0;
    ld lval = Simpson(l, mid), rval = Simpson(mid, r);
    if(dep >= 10 && fabs(lval + rval - cur) <= eps * 15.0)return lval + rval + (lval + rval - cur) / 15.0;
    return Adaptive(l, mid, lval, eps / 2.0, dep + 1) + Adaptive(mid, r, rval, eps / 2.0, dep + 1);
}

int main(){
    int T = read();
    while(T--){
        M = read(), N = read();
        if(N * M <= (int)1e5){
            memset(poly.A, 0, sizeof poly.A);
            for(int i = 0; i <= M - 1; ++i)
                poly.A[i].real((ld)1.0 / (ld)M), poly.A[i].imag(0.0);
            poly.len = 1;
            while(poly.len <= N * M)poly.len <<= 1;
            poly.MakeFFT();
            for(int i = 1; i <= 10; ++i){
                int A = read(), B = read();
                ld ans(0.0);
                for(int j = A; j <= B; ++j)ans += poly.A[j].real();
                printf("%.10Lf\n", ans);
            }
        }else{
            // mu = 0.0, sigma2 = 1.0;
            // ld real_mu = (ld)(M - 1) / 2.0;
			// ld real_sig = ((ld)M * M - 1.0) / 12.0;
            // for(int i = 1; i <= 10; ++i){
            //     int A = read(), B = read();
            //     ld L = (ld)((ld)A - N * real_mu) / sqrt(N * real_sig);
            //     ld R = (ld)((ld)B - N * real_mu) / sqrt(N * real_sig);
            //     printf("%.8Lf\n", Adaptive((ld)L, (ld)R, Simpson(L, R)));
            //     // printf("%.8Lf\n", Adaptive((ld)0, (ld)R, Simpson(0, R)) - Adaptive((ld)0, (ld)L, Simpson(0, L)));
            // }

            mu = (ld)N * (ld)(M - 1) / 2.0;
            sigma2 = (ld)N * (ld)((ll)M * M - 1) / 12.0;
            for(int i = 1; i <= 10; ++i){
                int A = read(), B = read();
                printf("%.8Lf\n", Adaptive((ld)A, (ld)B, Simpson(A, B)));
            }
        }
    }
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

真正的OIer从不女装

原题 LG-P5500 [LnOI2019]真正的OIer从不女装

显然一次反转和多次反转等效,即对应题目背景。同时亦等效为将查询区间的某个前缀拼在其最后,换句话说每一次反转操作代表着转换为任意一个循环同构的序列,同时这也印证了一次反转与多次反转等效。所以线段树维护区间最长相同子串,左端点的子串,右端点的子串,左右端点颜色,区间长度等,支持区间修改区间查询和初始化,然后询问的时候判一下 k 是不是 0 即可,随便写写就过了,很好写很好调。

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define MAXN (210000)

template< typename T = int >
inline T read(void);

int N, M;
int a[MAXN];

struct Node{
    int mx;
    int mxl, mxr;
    int corl, corr;
    int siz;
    friend const Node operator + (const Node &l, const Node &r){
        Node ret;
        ret.mx = max({l.mx, r.mx, l.corr == r.corl ? l.mxr + r.mxl : 0});
        ret.mxl = max({l.mxl, l.mxl == l.siz && l.corr == r.corl ? l.mxl + r.mxl : 0});
        ret.mxr = max({r.mxr, r.mxr == r.siz && r.corl == l.corr ? r.mxr + l.mxr : 0});
        ret.corl = l.corl, ret.corr = r.corr;
        ret.siz = l.siz + r.siz;
        return ret;
    }
};

class SegTree{
private:
    Node tr[MAXN << 2];
    int lz[MAXN << 2];
    #define LS (p << 1)
    #define RS (LS | 1)
    #define MID ((gl + gr) >> 1)
    #define SIZ(l, r) (r - l + 1)
public:
    void Pushup(int p, int gl, int gr){tr[p] = tr[LS] + tr[RS];}
    void Pushdown(int p){
        tr[LS].mx = tr[LS].mxl = tr[LS].mxr = tr[LS].siz;
        tr[RS].mx = tr[RS].mxl = tr[RS].mxr = tr[RS].siz;
        tr[LS].corl = tr[LS].corr = lz[p];
        tr[RS].corl = tr[RS].corr = lz[p];
        lz[LS] = lz[RS] = lz[p];
        lz[p] = false;
    }
    void Build(int p = 1, int gl = 1, int gr = N){
        if(gl == gr)return tr[p].mx = tr[p].mxl = tr[p].mxr = tr[p].siz = 1, tr[p].corl = tr[p].corr = a[gl = gr], void();
        Build(LS, gl, MID), Build(RS, MID + 1, gr);
        Pushup(p, gl, gr);
    }
    void Assign(int l, int r, int v, int p = 1, int gl = 1, int gr = N){
        if(l <= gl && gr <= r){
            tr[p].mx = tr[p].mxl = tr[p].mxr = tr[p].siz;
            tr[p].corl = tr[p].corr = v;
            if(lz[p])Pushdown(p);
            lz[p] = v;
            return;
        }
        if(lz[p])Pushdown(p);
        if(l <= MID)Assign(l, r, v, LS, gl, MID);
        if(MID + 1 <= r)Assign(l, r, v, RS, MID + 1, gr);
        Pushup(p, gl, gr);
    }
    Node Query(int l, int r, int p = 1, int gl = 1, int gr = N){
        if(l <= gl && gr <= r)return tr[p];
        if(lz[p])Pushdown(p);
        return
            l <= MID && r >= MID + 1
                ? Query(l, r, LS, gl, MID) + Query(l, r, RS, MID + 1, gr)
                :(
                    l <= MID
                        ? Query(l, r, LS, gl, MID)
                        : Query(l, r, RS, MID + 1, gr)
                );
    }
}st;

int main(){
    N = read(), M = read();
    for(int i = 1; i <= N; ++i)a[i] = read();
    st.Build();
    while(M--){
        char c = getchar(); while(c != 'Q' && c != 'R')c = getchar();
        if(c == 'R'){
            int l = read(), r = read(), v = read();
            st.Assign(l, r, v);
        }else{
            int l = read(), r = read(), k = read();
            auto ans = st.Query(l, r);
            printf("%d\n", k && ans.corl == ans.corr ? max(min(ans.siz, ans.mxl + ans.mxr), ans.mx) : ans.mx);
        }
    }
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

简单数据结构

原题 LG-P5350 序列

emm 感觉没什么好说的,就是无脑写个 ODT 即可,唯一需要注意的就是维护 ODT 的时候需要注意尽量保证序列里元素连续,也就是复制的时候先复制再删除,否则会有奇怪的未知错误。

然后如果你非要写可持久化平衡树我也不拦你。

#define _USE_MATH_DEFINES
#include <bits/extc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;
using namespace __gnu_pbds;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define MOD (ll)(1e9 + 7)

template< typename T = int >
inline T read(void);

int N, M;
ll ans[310000];

struct Node{
    int l, r;
    mutable ll val;
    friend const bool operator < (const Node &a, const Node &b){return a.l < b.l;}
};
class ODT{
private:
    #define SIZ(it) (it->r - it->l + 1)
    set < Node > tr;
public:
    auto Insert(Node x){return tr.insert(x);}
    auto Split(int p){
        auto it = tr.lower_bound(Node{p});
        if(it != tr.end() && it->l == p)return it;
        --it;
        int l = it->l, r = it->r;
        ll v = it->val;
        tr.erase(it);
        Insert(Node{l, p - 1, v});
        return Insert(Node{p, r, v}).first;
    }
    void Assign(int l, int r, ll val){
        auto itR = Split(r + 1), itL = Split(l);
        tr.erase(itL, itR);
        Insert(Node{l, r, val});
    }
    void Modify(int l, int r, ll val){
        auto itR = Split(r + 1), itL = Split(l);
        for(auto it = itL; it != itR; ++it)it->val = (it->val + val) % MOD;
    }
    ll Query(int l, int r){
        ll ret(0);
        auto itR = Split(r + 1), itL = Split(l);
        for(auto it = itL; it != itR; ++it)ret = (ret + SIZ(it) * it->val % MOD) % MOD;
        return ret;
    }
    void Copy(int l1, int r1, int l2, int r2){
        auto itR = Split(r1 + 1), itL = Split(l1);
        basic_string < Node > tmp;
        for(auto it = itL; it != itR; ++it)tmp += Node{it->l + (l2 - l1), it->r + (l2 - l1), it->val};
        itR = Split(r2 + 1), itL = Split(l2);
        tr.erase(itL, itR);
        for(auto nod : tmp)Insert(nod);
    }
    void Swap(int l1, int r1, int l2, int r2){
        basic_string < Node > S, T;
        auto itR = Split(r1 + 1), itL = Split(l1);
        for(auto it = itL; it != itR; ++it)S += Node{it->l + (l2 - l1), it->r + (l2 - l1), it->val};
        tr.erase(itL, itR);
        itR = Split(r2 + 1), itL = Split(l2);
        for(auto it = itL; it != itR; ++it)T += Node{it->l - (l2 - l1), it->r - (l2 - l1), it->val};
        tr.erase(itL, itR);
        for(auto nod : S + T)Insert(nod);
    }
    void Reverse(int l, int r){
        basic_string < Node > vals;
        auto itR = Split(r + 1), itL = Split(l);
        for(auto it = itL; it != itR; ++it)vals += *it;
        tr.erase(itL, itR);
        for(auto nod : vals)Insert(Node{r - nod.r + l, r - nod.l + l, nod.val});
    }
    void SetAns(void){
        for(auto nod : tr)
            for(int i = nod.l; i <= nod.r; ++i)ans[i] = nod.val;
    }
}odt;

int main(){
    N = read(), M = read();
    for(int i = 1; i <= N; ++i)odt.Insert(Node{i, i, read()});
    while(M--){
        int opt = read();
        switch(opt){
            case 1:{int l = read(), r = read(); printf("%lld\n", odt.Query(l, r)); break;}
            case 2:{int l = read(), r = read(), val = read(); odt.Assign(l, r, val); break;}
            case 3:{int l = read(), r = read(), val = read(); odt.Modify(l, r, val); break;}
            case 4:{int l1 = read(), r1 = read(), l2 = read(), r2 = read(); odt.Copy(l1, r1, l2, r2); break;}
            case 5:{int l1 = read(), r1 = read(), l2 = read(), r2 = read(); odt.Swap(min(l1, l2), min(r1, r2), max(l1, l2), max(r1, r2)); break;}
            case 6:{int l = read(), r = read(); odt.Reverse(l, r); break;}
        }
    }
    odt.SetAns();
    for(int i = 1; i <= N; ++i)printf("%lld%c", ans[i], i == N ? '\n' : ' ');
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

什么???NP问题???

原题 LG-P4727 [HNOI2009]图的同构计数

题解在这里有 Group Theory - 浅析群论

首先称两个图同构,当且仅当两个图可以通过对顶点的重标号使得两图完全相同。

然后这个东西的答案是一个数列,OEIS 上的编号是 A000088

然后我们再次回到 Burnside定理,集合 X 即为 n 个顶点可能构成的 2(n2)=2n(n1)2 个简单无向图。置换群 G 即为 [1,n] 的全排列。

然后我们的难点依然在于求不动点的数量。也就是说对于一个置换 g,有多少图在经过置换 g 之后与原图同构。

下面将会有一个与上一题,即 Polya定理 模板,或者说群论的标准套路。我们可以考虑先将图认为是完全图,然后用两种颜色对边进行染色,两种颜色表示该边存在或不存在。于是此时我们就会发现对于某一个置换 g,仍然存在一个等价类中,所有边必须均存在或不存在。

举个例子,对于一个标号为 1,2,3 的三角形完全图,如果有置换 g=(2,3,1),那么此时不难发现三条边同属一个等价类,因为你要么使三条边均存在,要么均不存在,否则按照这个置换的意义,旋转一下三角形,就会使得图不同构。此时的 g 对应的不动点即为 21=2 个。

所以说上面我们说的这一对,就是标准的 Polya定理,即 |Xg|=2c(g)

所以现在关键就在于我们如何求这个 c(g)

首先我们会发现,对于一个置换 g,其是可以被分成多个不相交的部分的,用双行表示法举个例子:

(123456231546)=(123231)+(4554)+(66)

考虑若将置换 g 拆成 k 个循环,长度分别为 b1,b2,,bk。然后不难发现有两种边:

首先考虑对于两个点均在同一段循环内的边

然后我们考虑对于一种置换中的一个长度为 b 的循环,有一个结论,这样的循环共有 b2 个等价类。

我们可以将对应的点数排成一个正 n 边形,比如 n=6 的时候,如果有这样一个置换 g=(123456 234561),存在这样的图和边:

img

不难发现对于长度相同的边都是等价的,其必须同时染或不染,否则置换后将会不同构。而不难想到,由于正 b 边形存在对称性,所以线段的长度共有 b2 种,所以等价类也就共有这些种。

于是其贡献的等价类数量为:i=1kbi2

然后考虑在不同循环中的边

举个例子:

g=(123231)+(4554)+(66)

这个东西的图大概是这样的:

img

如对于长度为 b1,b2 的两个循环,那么两者之间共有 b1×b2 条边,所以在这两者之间的边,每次经过 lcm(b1,b2) 次置换之后就会回到原位,则等价类的大小,或者说环长为 lcm(b1,b2),那么此时等价类个数,也就是环种类数,即为 b1×b2lcm(b1,b2),也就是 gcd(b1,b2)

所以这些边的贡献的等价类个数即为:

i=1kj=1i1gcd(bi,bj)

所以最终等价类的个数即为:

c(g)=i=1kbi2+i=1kj=1i1gcd(bi,bj)

答案即为:

1n!gG2c(g)

不过 Gn! 的,复杂度肯定不行,尝试优化;

考虑发现很多置换会被重复计算,如:

(123231)+(4554)+(66)(11)+(2332)+(456645)

这样的 bk 都是 1,2,3,那么 c(g) 的值是相同的。

也就是说对于相同的 n 的拆分贡献是相同的,所以可以考虑枚举 n 的拆分。

考虑对于一种拆分,计算有多少种对应的置换,首先总共有 n!,但是对于每一段长度为 b 的循环,都需要使其从普通排列转换为圆排列,也就是先去掉重复的 b!,然后再乘上圆排列的 (b1)!,并且对于相同长度的多个循环,假设有 c 个,那么这里又会重复 c! 个,那么在去重后应该为:

n!bi(ci!)

然后这里再枚举每个 n 的拆分,答案即为:

1n!n!bi(ci!)2c(g)

然后化简一下即为:

2c(g)bi(ci!)

c(g)=i=1kbi2+i=1kj=1i1gcd(bi,bj)

这样便可以通过了。然后尝试分析一下复杂度:

枚举 n 的拆分是 A000041,然后对于拆分长度为 k 的求解为 O(k2) 的,最终应为 A296010,然后这个东西完全不知道怎么分析,总之最后的复杂度大约在 O(10n),虽然我也不知道是怎么分析出来的。。。

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define MOD (997)

template < typename T = int >
inline T read(void);

int N;
ll fact[110], inv[110], inv_d[110];
int cnt[110];
ll ans(0);
basic_string < int > cur;
unordered_set < int > exist;

ll qpow(ll a, ll b){
    ll ret(1), mul(a);
    while(b){
        if(b & 1)ret = ret * mul % MOD;
        b >>= 1;
        mul = mul * mul % MOD;
    }return ret;
}

void Init(void){
    for(int i = 1; i <= 100; ++i)inv_d[i] = qpow(i, MOD - 2);
    fact[0] = 1;
    for(int i = 1; i <= 100; ++i)fact[i] = fact[i - 1] * i % MOD;
    inv[100] = qpow(fact[100], MOD - 2);
    for(int i = 99; i >= 0; --i)inv[i] = inv[i + 1] * (i + 1) % MOD;
}

void dfs(int lft = N){
    if(!lft){
        ll C(0);
        for(auto i : cur)C += i >> 1;
        for(int i = 1; i <= (int)cur.size(); ++i)
            for(int j = 1; j <= i - 1; ++j)
                C += __gcd(cur.at(i - 1), cur.at(j - 1));
        ll ret = qpow(2, C);
        for(auto i : cur)(ret *= inv_d[i]) %= MOD;
        for(auto i : exist)(ret *= inv[cnt[i]]) %= MOD;
        (ans += ret) %= MOD;
        return;
    }
    for(int i = cur.empty() ? 1 : cur.back(); i <= lft; ++i){
        cur += i, ++cnt[i], exist.insert(i);
        dfs(lft - i);
        cur.pop_back();
        if(!--cnt[i])exist.erase(i);
    }
}

int main(){
    Init();
    N = read();
    dfs();
    printf("%lld\n", ans);
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

本文作者:Tsawke

本文链接:https://www.cnblogs.com/tsawke/p/17124359.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Tsawke  阅读(11)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起