T1
题解
将行与列分开考虑,每两个#之间属于一个连通块。
对于每个连通块建立节点,如果只从连通块中选取一个点,那么不会产生相互攻击的棋子。选取第2个点的时候会产生1的贡献,选取第三个点的时候会产生2的贡献。。。。
行列都是如此,那么S->行所代表的连通块,列代表的连通块->T。对于每个连通块连size条边,每条边的容量为1,费用依次递增。
对于每个不是#的位置,一定属于两个连通块,在两个连通块之间连容量为1,费用为0的边,保证每个点只会被选择一次。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#define N 500003
#define inf 1000000000
using namespace std;
int tot,point[N],v[N],nxt[N],remain[N],c[N],dis[N],can[N],last[N];
int belong[53][53],belong1[53][53],mp[53][53],size[N];
int n,m,ans,cnt,S,T,TT,ck[N];
void add(int x,int y,int z,int k)
{
tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z; c[tot]=k;
tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0; c[tot]=-k;
//cout<<x<<" "<<y<<" "<<z<<" "<<k<<endl;
}
int addflow(int s,int t)
{
int ans=inf; int now=t;
while (now!=s) {
ans=min(ans,remain[last[now]]);
now=v[last[now]^1];
}
now=t;
while (now!=s) {
remain[last[now]]-=ans;
remain[last[now]^1]+=ans;
now=v[last[now]^1];
}
return ans;
}
int spfa(int s,int t)
{
for (int i=1;i<=t;i++) dis[i]=inf,can[i]=0;
dis[s]=0; queue<int> p; p.push(s); can[s]=1;
while (!p.empty()){
int now=p.front(); p.pop();
can[now]=0;
for (int i=point[now];i!=-1;i=nxt[i])
if (remain[i]&&dis[v[i]]>dis[now]+c[i]){
dis[v[i]]=dis[now]+c[i];
last[v[i]]=i;
if (!can[v[i]]) {
can[v[i]]=1;
p.push(v[i]);
}
}
}
int flow=addflow(s,t);
ans+=dis[t];
}
int main()
{
freopen("chess.in","r",stdin);
freopen("chess.out","w",stdout);
scanf("%d",&n); int sum=0;
tot=-1;
memset(point,-1,sizeof(point));
for (int i=1;i<=n;i++) {
char s[53]; scanf("%s",s+1);
for (int j=1;j<=n;j++)
if (s[j]=='#') mp[i][j]=1;
else sum++;
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++){
if (mp[i][j]) continue;
if (j==1||mp[i][j-1]) ++cnt;
belong[i][j]=cnt;
size[cnt]++;
}
int k=cnt;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++){
if (mp[j][i]) continue;
if (j==1||mp[j-1][i]) ++cnt;
belong1[j][i]=cnt;
size[cnt]++;
}
S=cnt+1; T=S+1; TT=T+1;
for (int i=1;i<=k;i++) {
add(S,i,1,0);
for (int j=1;j<size[i];j++) add(S,i,1,j);
}
for (int i=k+1;i<=cnt;i++) {
add(i,T,1,0);
for (int j=1;j<size[i];j++) add(i,T,1,j);
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (!mp[i][j]) add(belong[i][j],belong1[i][j],1,0);
for (int i=1;i<=sum;i++){
add(T,TT,1,0);
spfa(S,TT);
ck[i]=ans;
}
scanf("%d",&m);
for (int i=1;i<=m;i++){
int x; scanf("%d",&x);
printf("%d\n",ck[x]);
}
}
T2
题解
考虑最暴力的做法,n!枚举所有排列,对于每种排列在没有多余空隙的情况下占的空隙数为
∑n−1i=1max(ai,ai+1)
对于每种排列我们求出占的空隙数x,那么剩下的l-1-x个空隙可以任意插入到n+1个空隙中。
实际上就是插板发l-1-x个空隙,分到n+1个区间中,允许区间为空,答案为
C(l−x+n−1,n)
算法的瓶颈在于n!的排列。
如果我们能求出空隙数为x的排列数,那么就可以直接利用组合数计算了。
考虑从小到大依次插入
f[i][j][k]
表示插入到第i个数,j个能插入的位置,占的空隙数为k的方案数。注意能放数的位置不包括两边,所以初始值是
dp[1][0][0]=1
因为是从小到大插入的,所以如果插入到两个已经插入的数之间且不再从这个数两侧插入数,那么对于空隙的贡献就是插入的数*2,能插入的位置-1
如果插入一个位置,只保证插入位置的一侧不能再插入,那么对于空隙的贡献就是插入的数,能插入的位置不变。
如果插入一个位置,两侧都可以再插入数(再插入的数一定比当前数大),那么对于空隙的贡献就是0,能插入的位置+1.
两头的位置单独考虑一下。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 101
#define LL long long
using namespace std;
int n,l;
int dp[N][N][N*N],p;
int cnt,prime[N],c[N],tmp[N],pd[N],sum[N];
void init()
{
for (int i=2;i<=n;i++){
if (!pd[i]) prime[++cnt]=i;
for (int j=1;j<=cnt;j++){
if (prime[j]*i>n) break;
pd[prime[j]*i]=1;
if (i%prime[j]==0) break;
}
}
}
LL C(int K)
{
LL ans=1;
for (int i=1;i<=cnt;i++) tmp[i]=0;
for (int i=K-n+1;i<=K;i++){
int x=i;
for (int j=1;j<=cnt;j++)
while (tmp[j]<c[j]&&x%prime[j]==0)
x/=prime[j],tmp[j]++;
ans=(LL)ans*x%p;
}
return ans;
}
int main()
{
freopen("tower.in","r",stdin);
freopen("tower.out","w",stdout);
scanf("%d%d%I64d",&n,&l,&p);
init();
for (int i=1;i<=n;i++) {
int x=i;
for(int j=1;j<=cnt;j++)
while (x%prime[j]==0) c[j]++,x/=prime[j];
}
for (int i=1;i<=n;i++) sum[i]=sum[i-1]+2*i;
dp[1][0][0]=1;
for (int i=1;i<n;i++) {
for (int j=1;j<=min(i-1,n-i);j++)
for (int k=0;k<=sum[i];k++){
dp[i+1][j-1][k+(i+1)*2]=(dp[i+1][j-1+0][k+(i+1)*2]+(LL)j*dp[i][j][k]%p)%p;
dp[i+1][j][k+(i+1)]=(dp[i+1][j][k+(i+1)]+(LL)2*j*dp[i][j][k]%p)%p;
dp[i+1][j+1][k]=(dp[i+1][j+1][k]+(LL)j*dp[i][j][k]%p)%p;
}
for (int j=0;j<=min(i-1,n-i);j++)
for (int k=0;k<=sum[i];k++){
dp[i+1][j][k+(i+1)]=(dp[i+1][j][k+(i+1)]+(LL)2*dp[i][j][k]%p)%p;
dp[i+1][j+1][k]=(dp[i+1][j+1][k]+(LL)2*dp[i][j][k]%p)%p;
}
}
LL ans=0;
for (int i=0;i<=sum[n];i++){
LL t=C(l-i+n-1);
ans=(ans+(LL)t*dp[n][0][i]%p)%p;
}
printf("%I64d\n",ans);
}