zoukankan      html  css  js  c++  java
  • 【2020五校联考NOIP #2】矩阵

    咕咕咕到现在~
    题面传送门

    题意:
    给出一个 (n imes n) 的矩阵 (A)。要你求有多少个 (n imes n) 的矩阵 (B) 满足:

    • 每一行都是 (1)(n) 的排列。
    • 对于任意 (1leq ilt n)(1leq jleq n)(B_{i,j} eq B_{i+1,j})
    • 定义 (f(A)) 为矩阵 (A) 从上到下,从左到右拼接而成的序列,即 (f(A)_{(i-1) imes n+j}=A_{i,j}),那么必须有 (f(B)) 的字典序小于 (f(A)) 的字典序。
      答案对 (998244353) 取模。

    我们考虑条件三。“(f(B)) 的字典序小于 (f(A)) 的字典序”意味着我们可以枚举第一个 (f(A)_i<f(B)_i) 的位置然后统计答案。
    假设第一个不相等的位置为 (i)(j) 列。那么矩阵 (B) 的前 (i-1) 行上的数,以及第 (i) 行的前 (j-1) 个数,都已经确定下来了,我们的任务就是填好剩下来的 (n^2-(i-1) imes n-(j-1)) 个数。
    不难注意到,对于后 (n-i) 行每行的方案数是固定的,即 (f_n),其中 (f_i) 表示长度为 (i) 的满足 (p_j eq j) 的排列个数,也就是一个简单的错排数问题。
    接下来考虑如何计算填好第 (i) 行剩余的 (n-j+1) 个数的方案数,我们可以枚举第 (j) 个数是什么。那么我们需把这一行剩下来的 (n-j) 个数塞入这 (n-j) 个位置。再,假设 (A_{i-1,j+1},A_{i-1,j+2},dots,A_{i-1,n}) 分别记作 (x_1,x_2,dots,x_{n-j}),第 (i) 行剩余的数分别记作 (y_1,y_2,dots,y_{n-j}),要求有多少个 (1)(n-j) 的排列 (p) 满足 (x_i eq y_{p_i})
    其实方案数与 (x_i,y_i) 具体是什么数不重要,我们关心的只是有多少个数在 (x,y) 中同时出现,也就是官方题解中的“有限制的位置”与“无限制的位置”。
    然后就可以 (dp) 了,设 (dp_{i,j}) 为有 (i) 个有限制的位置和 (j) 个无限制的位置的方案数。
    (j=0),显然 (dp_{i,j}=f_i),直接上公式 (f_i=sumlimits_{j=0}^iinom{i}{j} imes(-1)^{i-j})
    (j eq 0),那么可以用类似组合数递推的方式。枚举最后一个无限制的位置上填的数,如果填了一个无限制的数,有 (j) 种方案,无限制的数变为 (j-1),即 (j imes dp_{i,j-1});如果填了一个有限制的数,有 (i) 种方案,无限制的位置还是 (j) 个,原来有限制的数变为无限制的数,即 (i imes dp_{i-1,j})。故 (dp_{i,j}=j imes dp_{i,j-1}+i imes dp_{i-1,j})
    这样做是 (n^3) 的。不过可以进一步优化:枚举这个位置填的是有限制的数还是没限制的数,用树状数组/线段树维护选法,乘上一个 (dp) 系数即可。

    #include <bits/stdc++.h>
    using namespace std;
    #define fz(i,a,b) for(int i=a;i<=b;i++)
    #define fd(i,a,b) for(int i=a;i>=b;i--)
    #define fill0(a) memset(a,0,sizeof(a))
    #define fill1(a) memset(a,-1,sizeof(a))
    #define fillbig(a) memset(a,63,sizeof(a))
    #define fi first
    #define se second
    #define int long long
    const int MOD=998244353;
    int n,a[2005][2005],f[2005],c[2005][2005],fac[2005],dp[2005][2005],b[2005][2005];
    bool vis1[2005],vis2[2005];
    int qpow(int x,int e){
    	int ans=1;
    	while(e){
    		if(e&1) ans=ans*x%MOD;
    		x=x*x%MOD;e>>=1;
    	}
    	return ans;
    }
    struct node{
    	int l,r,cnt[4],val;
    } s[2005<<2];
    inline void pushup(int k){
    	fz(i,0,3) s[k].cnt[i]=s[k<<1].cnt[i]+s[k<<1|1].cnt[i];
    }
    inline void build(int k,int l,int r){
    //	cout<<k<<" "<<l<<" "<<r<<endl;
    	s[k].l=l;s[k].r=r;fill0(s[k].cnt);s[k].val=0;s[k].cnt[0]=r-l+1;
    	if(l==r) return;
    	int mid=(l+r)>>1;
    	build(k<<1,l,mid);build(k<<1|1,mid+1,r);
    }
    inline void add(int k,int ind,int x){
    	if(s[k].l==s[k].r){
    		s[k].cnt[s[k].val]--;s[k].val+=x;s[k].cnt[s[k].val]++;
    		return;
    	}
    	int mid=(s[k].l+s[k].r)>>1;
    	if(ind<=mid) add(k<<1,ind,x);
    	else add(k<<1|1,ind,x);
    	pushup(k);
    }
    inline int query(int k,int l,int r,int x){
    	if(l>r||!l||!r) return 0;
    	if(l<=s[k].l&&s[k].r<=r) return s[k].cnt[x];
    	int mid=(s[k].l+s[k].r)>>1;
    	if(r<=mid) return query(k<<1,l,r,x);
    	else if(l>mid) return query(k<<1|1,l,r,x);
    	else return query(k<<1,l,mid,x)+query(k<<1|1,mid+1,r,x);
    }
    signed main(){
    	scanf("%d",&n);
    	fz(i,0,n){
    		c[i][0]=1;
    		fz(j,1,i) c[i][j]=(c[i-1][j]+c[i-1][j-1])%MOD;
    	}
    	fac[0]=1;fz(i,1,n) fac[i]=fac[i-1]*i%MOD;
    	fz(i,0,n){
    		fz(j,0,i){
    			if((j&1)==(i&1)) f[i]=(f[i]+c[i][j]*fac[j]%MOD)%MOD;
    			else f[i]=(f[i]-c[i][j]*fac[j]%MOD+MOD)%MOD;
    		}
    //		cout<<i<<" "<<f[i]<<endl;
    	}
    	for(int i=0;i<=n;i++){
    		dp[i][0]=f[i];
    		for(int j=1;i+j<=n;j++){
    			dp[i][j]=(dp[i][j-1]*j%MOD+dp[i-1][j]*i%MOD)%MOD;
    //			printf("%d %d %d
    ",i,j,dp[i][j]); 
    		}
    	}
    	fz(i,1,n) fz(j,1,n) scanf("%d",&a[i][j]);
    	fz(i,1,n-1){
    		fill0(vis1);fill0(vis2);
    		int both=0;
    		fd(j,n,1){
    			vis1[a[i][j]]=1;if(vis2[a[i][j]]) both++;
    			vis2[a[i+1][j]]=1;if(vis1[a[i+1][j]]) both++;
    			b[i][j]=both;
    		}
    //		fz(j,1,n) cout<<b[i][j]<<" ";puts("");
    	}
    	int ans=0;
    	fz(i,1,n){
    		build(1,1,n);int sum=0;
    		fd(j,n,1){
    //			cout<<i<<" "<<j<<endl;
    			add(1,a[i][j],1);if(i!=1) add(1,a[i-1][j],2);
    			int cnt1=query(1,1,a[i][j]-1,1)-((a[i-1][j]<a[i][j])?(query(1,a[i-1][j],a[i-1][j],1)):0);
    			int cnt2=query(1,1,a[i][j]-1,3)-((a[i-1][j]<a[i][j])?(query(1,a[i-1][j],a[i-1][j],3)):0);
    			int flg=query(1,a[i][j],a[i][j],2)+query(1,a[i][j],a[i][j],3);
    			sum=(sum+cnt2*dp[b[i-1][j+1]-1+flg][n-j-(b[i-1][j+1]-1+flg)]%MOD+cnt1*dp[b[i-1][j+1]+flg][n-j-(b[i-1][j+1]+flg)]%MOD)%MOD;
    //			cout<<i<<" "<<j<<" "<<cnt1<<" "<<cnt2<<" "<<b[i-1][j]<<endl;
    		}
    		ans=(ans+sum*qpow(f[n],n-i)%MOD)%MOD;
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    控制视图函数 接受 post or get 的访问方法的 写法
    带进度条的重定向
    装饰器@property,让调用类里面的函数 和 调用类里面的变量一样写法。 含 类中@property与@xxx.setter的方法介绍。
    filter() 函数 判断数组里面的 数据 是否符合 要求,符合就加入数组
    套件的使用追加
    linux下几个常用软件
    PHP探针
    Yii2 用户登录
    Datatables JQuery插件
    收藏的几个经典Flash
  • 原文地址:https://www.cnblogs.com/ET2006/p/13746766.html
Copyright © 2011-2022 走看看