• ZOJ3874 Permutation Graph 【分治NTT】


    题目链接

    ZOJ3874

    题意简述:
    在一个序列中,两点间如果有边,当且仅当两点为逆序对
    给定一个序列的联通情况,求方案数对(786433)取模

    题解

    自己弄了一个晚上终于弄出来了

    首先(yy)一下发现一个很重要的性质:

    联通块内的点编号必须是连续的

    证明:
    假设一个联通块编号不连续,设(a)(b)分别为联通块左侧和联通块右侧中的一个点,(x)(a)(b)之间不在该联通块内的点
    那么显然有(a > b)(a < x)(x < b)
    (a < x < b)的同时(a > b)
    不符
    故一个联通块内的编号必须连续
    证毕

    好了我们有了这样一个性质,那么假设他给我们的联通块不符合这个条件,就直接输出(0)【一定要记得,我就是一直挂在这个(sb)地方调了半天QAQ】

    然后如果符合条件,我们就要计算方案数了

    因为联通块已经被分成一段一段,所以任意两个联通块之间一定是递增的,互不干涉
    所以我们只需要计算出(f[i])表示(i)个点联通块的方案

    按套路,我们补集转化,并枚举第一个点所在联通块大小

    [f[n] = n! - sumlimits_{i = 1}^{n - 1} f[i](n - i)! ]

    (n!)是总方案,前(i)个点联通方案是(f[i]),按照性质,前(i)个点一定是前(i)小的点,与后面(n - i)个点没有任何关联,所以后面(n - i)个点可以任意排布

    这样我们就可以分治(NTT)(O(nlog^2n))的时间内预处理出(f[i])
    然后询问的时候根据乘法原理计算即可

    时间复杂度(O(nlog^2n + Tn))
    如果您常数比较大,就需要优化一下,比如循环展开大法好

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #include<map>
    #define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
    #define REP(i,n) for (register int i = 1; i <= (n); i++)
    #define mp(a,b) make_pair<int,int>(a,b)
    #define cls(s) memset(s,0,sizeof(s))
    #define cp pair<int,int>
    #define LL long long int
    #define res register
    using namespace std;
    const int maxn = 400005,maxm = 100005,INF = 1000000000;
    inline int read(){
    	int out = 0,flag = 1; char c = getchar();
    	while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
    	while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
    	return out * flag;
    }
    const int G = 10,P = 786433;
    int R[maxn],c[maxn],w[maxn];
    inline int qpow(int a,int b){
    	int re = 1;
    	for (; b; b >>= 1,a = 1ll * a * a % P)
    		if (b & 1) re = 1ll * re * a % P;
    	return re;
    }
    void NTT(int* a,int n,int f){
    	for (res int i = 0; i < n; i++) if (i < R[i]) swap(a[i],a[R[i]]);
    	for (res int i = 1; i < n; i <<= 1){
    		int gn = w[i];
    		for (res int j = 0; j < n; j += (i << 1)){
    			int g = 1,x,y;
    			for (res int k = 0; k < i; k++,g = 1ll * g * gn % P){
    				x = a[j + k],y = 1ll * g * a[j + k + i] % P;
    				a[j + k] = (x + y) % P,a[j + k + i] = ((x - y) % P + P) % P;
    			}
    		}
    	}
    	if (f == 1) return;
    	int nv = qpow(n,P - 2); reverse(a + 1,a + n);
    	for (res int i = 0; i < n; i++) a[i] = 1ll * a[i] * nv % P;
    }
    int fac[maxn],fv[maxn],N = 100000;
    int f[maxn],A[maxn],B[maxn];
    void solve(int l,int r){
    	if (l == r){f[l] = ((fac[l] - f[l]) % P + P) % P; return;}
    	int mid = l + r >> 1;
    	solve(l,mid);
    	int n,m,L = 0; n = mid - l;
    	for (res int i = 0; i <= n; i += 4){
    		A[i] = f[i + l]; A[i + 1] = f[i + l + 1];
    		A[i + 2] = f[i + l + 2]; A[i + 3] = f[i + l + 3];
    	}
    	n = r - l - 1;
    	for (res int i = 0; i <= n; i += 4){
    		B[i] = fac[i + 1]; B[i + 1] = fac[i + 2];
    		B[i + 2] = fac[i + 3]; B[i + 3] = fac[i + 4];
    	}
    	m = mid - (l << 1) + r - 1; n = 1;
    	while (n <= m) n <<= 1,L++;
    	for (res int i = 1; i < n; i++) R[i] = (R[i >> 1] >> 1) | ((i & 1) << (L - 1));
    	for (res int i = mid - l + 1; i < n; i += 4){
    		A[i] = A[i + 1] = A[i + 2] = A[i + 3] = 0;
    	}
    	for (res int i = r - l + 1; i < n; i += 4){
    		B[i] = B[i + 1] = B[i + 2] = B[i + 3] = 0;
    	}
    	NTT(A,n,1); NTT(B,n,1);
    	for (res int i = 0; i < n; i += 4){
    		A[i] = 1ll * A[i] * B[i] % P;
    		A[i + 1] = 1ll * A[i + 1] * B[i + 1] % P;
    		A[i + 2] = 1ll * A[i + 2] * B[i + 2] % P;
    		A[i + 3] = 1ll * A[i + 3] * B[i + 3] % P;
    	}
    	NTT(A,n,-1);
    	for (res int i = mid - l; i <= r - l - 1; i++)
    		f[i + l + 1] = (f[i + l + 1] + A[i]) % P;
    	solve(mid + 1,r);
    }
    void init(){
    	fac[0] = 1;
    	for (res int i = 1; i <= N; i++) fac[i] = 1ll * fac[i - 1] * i % P;
    	fv[N] = qpow(fac[N],P - 2); fv[0] = 1;
    	for (res int i = N - 1; i; i--)
    		fv[i] = 1ll * fv[i + 1] * (i + 1) % P;
    	for (res int i = 1; i < maxn; i <<= 1)
    		w[i] = qpow(G,(P - 1) / (i << 1));
    	f[0] = 1;
    	solve(1,N);
    	REP(i,N) f[i] = (f[i] + P) % P;
    	//REP(i,11) printf("%d ",f[i]); puts("");
    }
    int n,m,scc[maxn],vis[maxn];
    int main(){
    	init();
    	int T = read();
    	while (T--){
    		n = read(); m = read(); int ans = 1,x;
    		REP(i,m) vis[i] = false;
    		REP(i,m){
    			x = read();
    			REP(j,x) scc[read()] = i;
    			ans = 1ll * ans * f[x] % P;
    		}
    		int flag = true;
    		for (res int i = 1; i <= n; i++){
    			if (i > 1 && scc[i] != scc[i - 1] && vis[scc[i]]){
    				flag = false; break;
    			}
    			vis[scc[i]] = true;
    		}
    		flag ? printf("%d
    ",ans) : puts("0");
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    记录下首次开通流量主,开心开心
    微信小程序之本地缓存
    在使用ef的情况下,有Migrations文件,想要直接生成数据库
    CSS 设置圆角div和阴影效果
    小程序UI库(UI组件)
    没有找到可以构建的 NPM 包---小程序开发
    php 接口参数对象转数组方法
    tp5框架获取随机n条
    php图片上传base64接口上传
    php如何实现定时任务,php定时任务方法,最佳解决方案,php自动任务处理
  • 原文地址:https://www.cnblogs.com/Mychael/p/9113904.html
Copyright © 2020-2023  润新知