BZOJ4498 魔法的碰撞
【题目】
原题地址
数轴上有
L
L
L个整点,分别是
0
…
L
−
1
0\dots L-1
0…L−1,现在有
n
n
n个人,每个人有一个安全范围
r
i
r_i
ri,表示在它两边距离
<
r
i
<r_i
<ri的点上不能放人。问将所有人放在整点上的方案数。
L
≤
1
0
5
,
n
,
r
i
≤
40
L\leq 10^5,n,r_i\leq 40
L≤105,n,ri≤40
【解题思路】
一道十分玄学的
DP
\text{DP}
DP题,确实是道好题。
我们首先考虑暴力,枚举一个放的排列,那么令
w
=
∑
i
=
1
n
−
1
max
(
r
i
,
r
i
+
1
)
w=\sum_{i=1}^{n-1}\max(r_i,r_{i+1})
w=∑i=1n−1max(ri,ri+1),贡献就是
(
L
−
w
n
)
L-w\choose n
(nL−w)。实际上这个
w
w
w就是安全范围所产生的不可以选择的空位数。
现在不能枚举排列,而 ∑ r i \sum r_i ∑ri很小我们考虑对于每个 w w w算出有多少种方案,然后再乘组合数得到贡献。
首先将人按 r i r_i ri从大到小排序,那么考虑将这些人一个个放到数轴上。
我们考虑这样一个 DP \text{DP} DP状态 f i , j , k f_{i,j,k} fi,j,k表示当前放到第 i i i个人,有 j j j个空位可以放, w w w值现在为 k k k。
这个空位是什么意思呢?假设一个人左边不再会放人,我们就说他左边没有空位,否则有,右边亦然。由于是从大到小进行放置,那么只有当这个人左右还会放人的时候,这个人的 r i r_i ri才会产生贡献。
那么现在我们就有三种转移:
- 左右都不再放人,减少一个空位,不产生贡献
- 只有一边放人,空位数不变,产生 d i d_i di的贡献
- 左右两边都要放人,空位数 + 1 +1 +1,产生 2 d i 2d_i 2di的贡献
转移方程分别对应( f i , j , k f_{i,j,k} fi,j,k加上):
- f i − 1 , j + 1 , k × ( j + 1 ) f_{i-1,j+1,k}\times (j+1) fi−1,j+1,k×(j+1)
- f i − 1 , j , k − r i × 2 j f_{i-1,j,k-r_i}\times 2 j fi−1,j,k−ri×2j
- f i − 1 , j − 1 , k − 2 r i × ( j − 1 ) f_{i-1,j-1,k-2r_i}\times (j-1) fi−1,j−1,k−2ri×(j−1)
具体意义显然,实现上略有差距。
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10,M=42,P=1e4+10,mod=1e9+7;
int L,n,ans,sum;
int fac[N],ifac[N],r[M],f[M][M][P];
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
int qpow(int x,int y)
{
int ret=1;
for(;y;y>>=1,x=(ll)x*x%mod) if(y&1) ret=(ll)ret*x%mod;
return ret;
}
int C(int x,int y)
{
if(x<y) return 0;
return (ll)fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}
void up(int &x,int y){x+=y;if(x>=mod)x-=mod;}
bool cmp(int x,int y){return x>y;}
int main()
{
#ifndef ONLINE_JUDGE
freopen("BZOJ4498.in","r",stdin);
freopen("BZOJ4498.out","w",stdout);
#endif
fac[0]=ifac[0]=1;
for(int i=1;i<N;++i) fac[i]=(ll)fac[i-1]*i%mod,ifac[i]=qpow(fac[i],mod-2);
int T=read();
while(T--)
{
L=read();n=read();
//cerr<<T<<" "<<L<<" "<<n<<endl;
for(int i=1;i<=n;++i) r[i]=read()-1;
sort(r+1,r+n+1,cmp);
memset(f,0,sizeof(f));f[0][1][0]=1;sum=0;
for(int i=1;i<=n;++i)
{
sum+=r[i];
for(int j=0;j<=i+1;++j) for(int k=0;k<=2*sum;++k)
{
up(f[i][j][k],(ll)f[i-1][j+1][k]*(j+1)%mod);
if(k>=r[i]) up(f[i][j][k],(ll)f[i-1][j][k-r[i]]*j*2%mod);
if(j && k>=2*r[i]) up(f[i][j][k],(ll)f[i-1][j-1][k-2*r[i]]*(j-1)%mod);
}
}
ans=0;
for(int i=0;i<=2*sum;++i) up(ans,(ll)f[n][0][i]*C(L-i,n)%mod);
printf("%d\n",ans);
}
return 0;
}
TC12996 TaroCheckers
【题目】
原题地址
给定一个
n
×
m
n\times m
n×m的格子,你需要在上面放
2
n
2n
2n个棋子,需要满足:
- 第 i i i行的前 l i l_i li个和后 r i r_i ri个格子中分别有且只有一个棋子
- 每一列只能放一个棋子。
求合法方案数,答案对 1 e 9 + 7 1e9+7 1e9+7取模。
n ≤ 50 , m ≤ 200 , l i + r i ≤ m n\leq 50,m\leq 200,l_i+r_i\leq m n≤50,m≤200,li+ri≤m
【解题思路】
这又是一个神奇的
DP
\text{DP}
DP。
一个思路是我们从左往右考虑是否放棋子:
由于所有 l i l_i li在一开始都可以放,因此实际上我们只需要考虑分配了多少个列给 l i l_i li,但当遇到一个边界 l i l_i li时,我们可以分配(且必须分配)之前任意一列可用的给这一行。
当我们遇到一个 r i r_i ri时,相当于新加入一行,我们之后可以任意分配列给当前可分配的 r i r_i ri行。
这就是左边的行匹配列,右边用列匹配行。(意会一下)
那么我们设 f i , j , k f_{i,j,k} fi,j,k表示当前到第 i i i列,还有 j j j个未分配的列,有 k k k个未分配的 r i r_i ri且当前可以分配。
转移时枚举当前列放在哪个 r i r_i ri内(或者不放),若有 l i l_i li在当前列结束我们考虑用哪一个剩余的列分配给它即可。
复杂度 O ( n 2 m ) O(n^2m) O(n2m)
【参考代码】
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int mod=1e9+7,N=205;
int n,m,cnt,p1[N],p2[N],a[N<<2];
ll fac[N],C[N][N],f[N][N][N];
vector<int>le,ri;
class TaroCheckers
{
public:
int getNumber(vector<int>,vector<int>,int);
};
void up(ll &x,ll y){x+=y;x%=mod;}
ll upm(ll x){return x>=mod?x-mod:x;}
void init()
{
fac[0]=1;for(int i=1;i<N;++i)fac[i]=fac[i-1]*i%mod;
for(int i=0;i<N;++i)
{
C[i][0]=C[i][i]=1;
for(int j=1;j<i;++j) C[i][j]=upm(C[i-1][j-1]+C[i-1][j]);
}
}
int TaroCheckers::getNumber(vector<int>L,vector<int>R,int m)
{
n=L.size();init();
memset(p1,0,sizeof(p1));memset(p2,0,sizeof(p2));memset(f,0,sizeof(f));
for(int i=0;i<n;++i) p1[L[i]]++,p2[m-R[i]+1]++;
f[0][0][0]=1;int sum=n;
for(int i=0;i<m;++i)
{
int x=p1[i+1],y=p2[i+1];sum+=y;
for(int j=0;j<=i;++j)
{
for(int k=0;k<=n;++k) if(f[i][j][k])
{
if(j+1>=x) up(f[i+1][j-x+1][k+y],f[i][j][k]*C[j+1][x]%mod*fac[x]%mod);
if(j>=x) up(f[i+1][j-x][k+y],f[i][j][k]*C[j][x]%mod*fac[x]%mod*(n-sum+1)%mod);
if(j>=x && k+y>0) up(f[i+1][j-x][k+y-1],f[i][j][k]*C[j][x]%mod*fac[x]%mod*(k+y)%mod);
}
}
sum-=x;
}
return f[m][0][0];
}
/*int main()
{
freopen("TC12996.in","r",stdin);
freopen("TC12996.out","w",stdout);
while(~scanf("%d",&a[cnt++]));--cnt;
for(int i=0;i<cnt/2;++i) le.pb(a[i]);
for(int i=cnt/2;i<cnt-1;++i) ri.pb(a[i]);
printf("%d\n",getNumber(le,ri,a[cnt-1]));
}*/
【总结】
两道十分不错的
DP
\text{DP}
DP题,反正我模拟的时候都没想到。
尤其是问题的转化以及不要想岔
TC的数据交互方式不是传统型,巨坑。