[luogu P3813] [FJOI2017] 矩阵填数 解题报告 (容斥原理)
题目链接:
https://www.luogu.org/problemnew/show/P3813
题目:
给定一个 h*w的矩阵,矩阵的行编号从上到下依次为 1..h,列编号从左到右依次1..w。
在这个矩阵中你需要在每个格子中填入 1..m中的某个数。
给这个矩阵填数的时候有一些限制,给定 n 个该矩阵的子矩阵,以及该子矩阵的最大值 v,要求你所填的方案满足该子矩阵的最大值为 v。
现在,你的任务是求出有多少种填数的方案满足 n 个限制。
两种方案是不一样的当且仅当两个方案至少存在一个格子上有不同的数。由于答案可能很大,你只需要输出答案 mod 1,000,000,007
题解:
对于每个格,能填的最⼤值是 $min(m,v_i)$,$v_i$ 为覆盖到该点的所有⼩矩阵的预设答案,这就是总⽅案数。
考虑容斥原理,奇减偶加。总方案数-一个不合法的方案数+两个不合法的方案数...
离散化后 $2^n$ 枚举⼦集,然后对于选中的矩阵为 $min(v_i−1)$,即强制让选中的⼦矩阵的最⼤值⼩于预设的答案(总方案里一个矩阵里所有的元素都小于等于这个矩阵的v)
这⼀步由于离散化的原因,可以直接暴⼒ for 遍历所有在⼦ 矩阵内的位置。 复杂度:$O(2^n n^3)$
#include<algorithm> #include<cstring> #include<cstdio> #include<iostream> using namespace std; typedef long long ll; const int N=500; const ll mo=1e9+7; int h,w,m,n,vx,vy,vp; int ma[N][5],dx[N],dy[N],mv[N],a[N][N],mp[N][N]; ll vv[N],tong[N]; inline int read(){ char ch=getchar();int s=0,f=1; while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return s*f; } ll qpow(ll a,ll b){ ll re=1; for (;b;b>>=1,a=a*a%mo) if (b&1) re=re*a%mo; return re; } int main() { int T=read(); while (T--) { h=read();w=read();m=read();n=read(); vx=vy=vp=0; dx[++vx]=1;dx[++vx]=h+1; dy[++vy]=1;dy[++vy]=w+1; vv[++vp]=m; for (int i=1;i<=n;i++) { ma[i][1]=read();ma[i][2]=read();ma[i][3]=read();ma[i][4]=read();mv[i]=read(); dx[++vx]=ma[i][1];dx[++vx]=ma[i][3]+1; dy[++vy]=ma[i][2];dy[++vy]=ma[i][4]+1; vv[++vp]=mv[i];vv[++vp]=mv[i]-1; } sort(dx+1,dx+1+vx); sort(dy+1,dy+1+vy); sort(vv+1,vv+1+vp); vx=unique(dx+1,dx+1+vx)-dx-1; vy=unique(dy+1,dy+1+vy)-dy-1; vp=unique(vv+1,vv+1+vp)-vv-1; for (int i=1;i<vx;i++)//<号不是<=号,因为最后一个是无效的位置 for (int j=1;j<vy;j++) a[i][j]=(dx[i+1]-dx[i])*(dy[j+1]-dy[j]); for (int i=1;i<=n;i++) { ma[i][1]=lower_bound(dx+1,dx+1+vx,ma[i][1])-dx; ma[i][3]=lower_bound(dx+1,dx+1+vx,ma[i][3]+1)-dx; ma[i][2]=lower_bound(dy+1,dy+1+vy,ma[i][2])-dy; ma[i][4]=lower_bound(dy+1,dy+1+vy,ma[i][4]+1)-dy; mv[i]=lower_bound(vv+1,vv+1+vp,mv[i])-vv; } ll ans=0; for (int S=0;S<(1<<n);S++) { for (int i=1;i<vx;i++) for (int j=1;j<vy;j++) mp[i][j]=vp; ll s=1; for (int i=0;i<n;i++) { int v=mv[i+1]; if (S>>i&1) v--,s=-s; for (int j=ma[i+1][1];j<ma[i+1][3];j++) for (int k=ma[i+1][2];k<ma[i+1][4];k++) mp[j][k]=min(mp[j][k],v); } for (int i=1;i<=vp;i++) tong[i]=0; for (int i=1;i<vx;i++) for (int j=1;j<vy;j++) tong[mp[i][j]]+=a[i][j]; for (int i=1;i<=vp;i++) s=s*qpow(vv[i],tong[i])%mo; ans=(ans+s)%mo; } ans=(ans%mo+mo)%mo; printf("%lld\n",ans); } return 0; }
星星之火,终将成燎原之势