题目大意
n*n的网格里有障碍或空位,你要往里面放棋子。只能在空位里放棋子,每个空位至多放1个。
如果两个棋子位于同一行或同一列,且它们之间没有障碍,那么就会产生1的费用。
若干询问如果放k个棋子最小费用是多少。
网络流
把障碍与障碍之间抽出来叫段。
那么自然有横段和竖段。
同一个段内如果有t个棋子,产生t*(t-1)/2的费用。
于是可以根据这个建费用流图。
可以动态加边来优化。
#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=100+10;
int f[2][maxn*maxn+5000][maxn],pri[20],num[20];
int i,j,k,l,t,n,m,L,now,down,up,ndown,nup,sg,cnt,ans,top,xx,yy;
void fj(int m){
int i,t=floor(sqrt(m)),k=m;
fo(i,2,t)
if (k%i==0){
pri[++top]=i;
while (k%i==0) k/=i;
}
}
int qsm(int x,int y){
if (!y) return 1;
int t=qsm(x,y/2);
t=(ll)t*t%m;
if (y%2) t=(ll)t*x%m;
return t;
}
void exgcd(int a,int b){
if (!b){
xx=1;yy=0;
return;
}
exgcd(b,a%b);
int x,y;
x=yy;
y=xx-(a/b)*yy;
xx=x;yy=y;
}
int getny(int x){
exgcd(x,m);
xx%=m;
(xx+=m)%=m;
return xx;
}
int C(int t){
int i,j,k,l=1,r=1;
fo(i,1,top) num[i]=0;
fo(i,t+1,t+n){
k=i;
fo(j,1,top){
while (k%pri[j]==0){
num[j]++;
k/=pri[j];
}
}
l=(ll)l*k%m;
}
fo(i,1,n){
k=i;
fo(j,1,top){
while (k%pri[j]==0){
num[j]--;
k/=pri[j];
}
}
r=(ll)r*k%m;
}
l=(ll)l*getny(r)%m;
fo(i,1,top) l=(ll)l*qsm(pri[i],num[i])%m;
return l;
}
int main(){
freopen("tower.in","r",stdin);freopen("tower.out","w",stdout);
scanf("%d%d%d",&n,&L,&m);
fj(m);
f[0][0][2]=1;
now=0;
sg=n;
cnt=0;
fd(i,n+1,3){
if (i<=n) ndown=down+i;
nup=(ndown+i-1)*2;
fo(j,ndown,nup)
fo(k,0,min(n-i,i-2)+2)
f[1-now][j][k]=0;
fo(j,down,up)
fo(k,0,min(n+1-i,i-1)+2)
if (f[now][j][k]){
t=(k-2)*(k-3);
t+=(k-2)*2;
t%=m;
if (k>1) (f[1-now][j][k-1]+=(ll)f[now][j][k]*t%m)%=m;
t=(k-2)*2;
t+=2;
(f[1-now][j+i-1][k]+=(ll)f[now][j][k]*t%m)%=m;
(f[1-now][j+2*(i-1)][k+1]+=f[now][j][k])%=m;
}
down=ndown;
up=nup;
/*cnt++;
if (cnt==2) cnt=0,sg--;*/
now=1-now;
}
fo(j,down,up){
t=L-j-1;
if (t<0) break;
(ans+=(ll)f[now][j][2]*C(t)%m)%=m;
}
printf("%d\n",ans);
}