JZOJ6930. 【2020.12.26冬令营模拟】quarantine

Description

  • 有一个 m m m n n n列的图,第 i i i列有连向 i + 1 i+1 i+1列的有向边,有 q q q次操作:
    • 修改 i i i j j j行向 i + 1 i+1 i+1 k k k行的边的状态。
    • 对于 l , r l,r l,r,给出 l , r l,r l,r列的起点和终点的状态,求最少删除多少个点可以使得不存在一个起点能够到达任意一个终点。
  • m ≤ 24 , n , q ≤ 8292 m\le24,n,q\le8292 m24,n,q8292

Solution

  • 暴力可以最小割。
  • 考虑最小割=最大流,而每一个点只能割一次,因此求的实际上是最大不相交路径个数。
  • 考虑LGV定理,对于两两的路径数建一个矩阵,求它的行列式就是带符号的起点终点两两匹配且不相交的路径个数。由于它是带符号的,因此我们需要给每一条边随机权值,在对一个 1 e 9 1e9 1e9级别的数取模意义下,刚好消成0的概率是 1 m o \frac{1}{mo} mo1,(不要用 u n s i g n e d   i n t unsigned\ int unsigned int,容易出锅).
  • 问题是我们怎么求最大的匹配数 c c c呢?注意到如果存在一个 c ∗ c c*c cc子矩阵行列式不为0,而 ( c + 1 ) ∗ ( c + 1 ) (c+1)*(c+1) (c+1)(c+1)的所有子矩阵都为0,那么 c c c实际上就是矩阵的秩,因此我们只需要求矩阵的秩就好了。
  • 用线段树、矩阵乘法维护路径数,需要注意取模和常数。
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<ctime>
#define maxn 8205
#define maxm 25
#define ll long long
#define mo 9999991
using namespace std;

int n,m,q,i,j,k,a[maxn][maxm][maxm],d1[maxm],d2[maxm];

int rd(){return (rand()*rand()+rand())%(mo-1)+1;}

struct matrix{ll a[maxm][maxm];} t[maxn*4],S;
matrix operator*(matrix A,matrix B){
	matrix C; memset(C.a,0,sizeof(C.a));
	for(int i=0;i<m;i++) for(int j=0;j<m;j++) for(int k=0;k<m;k++)
		C.a[i][k]+=A.a[i][j]*B.a[j][k];
	for(int i=0;i<m;i++) for(int j=0;j<m;j++) C.a[i][j]=C.a[i][j]%mo;
	return C;
}

void maketree(int x,int l,int r){
	if (l==r){
		for(int i=0;i<m;i++) for(int j=0;j<m;j++) if (a[l][i][j])
			t[x].a[i][j]=rd(); else t[x].a[i][j]=0;
		return;
	}
	int mid=(l+r)>>1;
	maketree(x<<1,l,mid),maketree(x<<1^1,mid+1,r);
	t[x]=t[x<<1]*t[x<<1^1];
}

void change(int x,int l,int r,int p){
	if (l==r){
		for(int i=0;i<m;i++) for(int j=0;j<m;j++) if (a[l][i][j])
			t[x].a[i][j]=rd(); else t[x].a[i][j]=0;
		return;
	}
	int mid=(l+r)>>1;
	if (p<=mid) change(x<<1,l,mid,p); else change(x<<1^1,mid+1,r,p);
	t[x]=t[x<<1]*t[x<<1^1];
}

void find(int x,int l,int r,int L,int R){
	if (l>R||r<L) return;
	if (L<=l&&r<=R) {
		S=S*t[x];
		return;
	}
	int mid=(l+r)>>1;
	find(x<<1,l,mid,L,R),find(x<<1^1,mid+1,r,L,R);
}

ll A[maxm][maxm];
ll ksm(ll x,ll y){
	ll s=1;
	for(;y;y/=2,x=x*x%mo) if (y&1)
		s=s*x%mo;
	return s;
}
int getrk(int n,int m){
	static ll tmp[maxm][maxm];
	if (n>m){
		for(i=0;i<n;i++) for(j=0;j<m;j++)
			tmp[j][i]=A[i][j];
		memcpy(A,tmp,sizeof(A));
		swap(n,m);
	}
	int c=0;
	for(i=0;i<m&&c<n;i++){
		for(j=c;j<n;j++) if (A[j][i]) break;
		if (j==n) continue;
		for(k=0;k<m;k++) swap(A[c][k],A[j][k]);
		ll inv=ksm(A[c][i],mo-2);
		for(j=0;j<m;j++) A[c][j]=A[c][j]*inv%mo;
		for(j=0;j<n;j++) if (c!=j) for(k=m-1;k>=i;k--)
			(A[j][k]-=A[j][i]*A[c][k])%=mo;
		c++;
	}
	return c;
}

int main(){
	freopen("quarantine.in","r",stdin);
	freopen("quarantine.out","w",stdout);
//	freopen("ceshi.out","w",stdout);
	srand(time(0));
	scanf("%d%d%d",&n,&m,&q); char ch=getchar();
	for(i=1;i<n;i++) for(j=0;j<m;j++) {
		while (ch!='0'&&ch!='1') ch=getchar();
		for(k=0;k<m;k++) a[i][j][k]=ch-'0',ch=getchar();
	}
	maketree(1,1,n-1);
	while (q--){
		ch=getchar();while (ch!='M'&&ch!='T') ch=getchar();
		if (ch=='T'){
			scanf("%d%d%d",&i,&j,&k),j--,k--;
			a[i][j][k]^=1,change(1,1,n-1,i);
		} else {
			int l,r; scanf("%d%d",&l,&r);
			memset(S.a,0,sizeof(S.a));
			for(i=0;i<m;i++) S.a[i][i]=1;
			find(1,1,n-1,l,r-1);
			ch=getchar(); while (ch!='0'&&ch!='1') ch=getchar();
			d1[0]=d2[0]=0;
			for(i=0;i<m;i++) {
				if (ch-'0') d1[++d1[0]]=i;
				ch=getchar();
			}
			ch=getchar();
			for(i=0;i<m;i++) {
				if (ch-'0') d2[++d2[0]]=i;
				ch=getchar();
			} 
			memset(A,0,sizeof(A));
			for(i=1;i<=d1[0];i++) for(j=1;j<=d2[0];j++)
				A[i-1][j-1]=S.a[d1[i]][d2[j]];
			printf("%d\n",getrk(d1[0],d2[0]));
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值