插入类DP

news/2024/11/8 19:39:19/文章来源:https://www.cnblogs.com/empty-space/p/18535786

对于这类题目的特征:

1.排列性质
2.\(100\le n \le 5\times10^3\)
3.答案和排列的上升下降的突变点有关

套路:

按照某个从小到大的顺序插入,有了这个顺序就能算贡献或者方案数
贡献往往提前计算,存在每个元素有新开一段、合并两端、连在某一段后面的不同方案
有的允许了\(A\),\(W\),\(V\),\(M\) 形状不同的最终状态可能在前面是同一状态,每个状态是为后来的状态做准备的

新开一个段连在某个段的边上并非同一个状态,新开一个段,未来会在两端之间插入更大的数字来合并两个 段,连在一个段的边上,则未来就不会如此

关于端点:

1.如果固定了端点,往往是当 \(i\) 是左右端点时特判
2.如果没有固定左右端点,往往多加一维记录当前确定了几个端点(如果左右端点的转移/贡献相同),或者多加两维记录当前确定了左/右端点(如果左右端点的转移/贡献并不相同)

QY太强了,令我的总结旋转


按照结尾数字排名进行的插入类\(DP\)

A. 【Atcoder_DP】T-排列 (AI)

有一个长度为\(N\) 正整数排列,再给一个由 \(<\)\(>\) 组成的长为 \(N - 1\) 的字符串
对于任意满足 \(1 \le i \le N - 1\)的字符 \(s_i\) ,如果 \(s_i\)\(<\)\(P_i < P_{i+1}\) 、 如果 \(s_i\)\(>\)\(P_i > P_{i+1}\) 。求满足这样性质的排列 \(P\) 的方案数
\(N\le 3000\)

考虑 \(DP\) ,设 \(f[i][j]\) 表示对于前 \(i\) 个数字,最后一个数字在前 \(i\) 个数字里排名第 \(j\) 的方案数
显然可以列出递推式

\[s[i-1]=='<' : f[i][j]=\sum^{j-1}_{k=1}{f[i-1][k]}\\ s[i-1]=='>' : f[i][j]=\sum^{i-1}_{k=j}{f[i-1][k]} \]

用前缀和优化,就可以把时间复杂度优化到\(O(n^2)\)

#include"bits/stdc++.h"using namespace std;
constexpr int N=3015,P=1e9+7;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
//#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}inl int read(void)
{int x=0;short f=1;char ch=getchar();for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());return x*f;
}int f[N][N],sum[N][N];
int n;int ans;
char ch[N];int main(void)
{n=read();cin>>ch+1;for(int i=1;i<=n;i++)	sum[1][i]=i;for(int i=2;i<=n;i++){for(int j=1;j<=i;j++){if(ch[i-1]=='<')	f[i][j]=sum[i-1][j-1]%P;else	f[i][j]=(sum[i-1][i-1]-sum[i-1][j-1]+P)%P;sum[i][j]=(sum[i][j-1]+f[i][j])%P;}}printf("%d ",sum[n][n]);return 0;
}

B. F - Deforestation (AI)

\(n\) 个数:\(a_1,a_2,...,a_n\)

每次可以选择一个 \(i\) ,选择的代价是 \(a_{i-1}+a_i+a_{i+1}\),然后令 \(a_i=0\) ,求有多少种方案,使得 \(a_1,a_2,...,a_n\) 都变为 \(0\) 的总代价最小,特别的 \(a_0=a_{n+1}=0\) \(1\le n \le 4000\)\(1 \le a_i \le 1\times 10^9\)

考虑限制,对于 \(i\)\(i+1\)

  • 先拿 \(i\) 再拿 \(i+1\) ,收益是 \(a[i]+2 \times a[i+1]\)
  • 先拿 \(i+1\) 再拿 \(i\) , 收益是 \(a[i+1]+2 \times a[i]\)

显然,对于更大的那个,先拿是最优的,如果相等就随意, \(a_i\) 是原数组, \(pos_i\)\(i\) 号是第几个取

于是类似于 \(T1\)

\[a[i-1]>a[i]\space:\space f[i][j]=\sum_{k=1}^{j-1}{f[i-1][k]}\\ a[i-1]<a[i]\space:\space f[i][j]=\sum_{k=j}^{i-1}{f[i-1][k]}\\ a[i-1]=a[i]\space:\space f[i][j]=\sum_{k=1}^{i-1}{f[i-1][k]} \]

#include"bits/stdc++.h"using namespace std;
constexpr int N=5015,P=1e9+7;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
//#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}inl int read(void)
{int x=0;short f=1;char ch=getchar();for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());return x*f;
}int f[N][N],sum[N][N];
int n,a[N];int main(void)
{n = read();for (int i = 1;i <= n;i ++)	a[i] = read();f[1][1] = 1;for (int i = 1;i <= n;i ++)	sum[1][i] = 1;for (int i = 2; i <= n;i ++){for (int j = 1;j <= i;j ++){if (a[i] < a[i - 1])	f[i][j] = sum[i - 1][j - 1];	elseif (a[i] > a[i - 1])	f[i][j] = (sum[i - 1][i - 1] - sum[i - 1][j - 1] + P) % P;	elseif (a[i] == a[i - 1])	f[i][j] = sum[i - 1][i - 1];sum[i][j] = (sum[i][j - 1] + f[i][j]) % P;}}printf ("%d ",sum[n][n]);return 0;
}

C. kangaroo

袋鼠,有 \(n\) 个草丛排成一排,编号从 \(1\)\(n\) ,有一个袋鼠,从 \(s\) 出发,每次挑一步到一个其他的草丛,经过每个草丛恰好一次,最终到达 \(t\) ,显然他会跳跃 \(n-1\) 次,未来不被人类发现,袋鼠每次跳跃的方向必须与前一次不同
问从 \(s\)\(t\) 的方案数 \(\mod 10^9+7\)的结果
两个路线不同,当且仅当草丛被访问的顺序不同,保证至少有一种方案初始时可以往任意方向跳
\(2\le n \le 2\times 10^3\)\(1 \le s,t \le n\)

\(f[i][j]\) 表示前 \(i\) 个数字,分成了 \(j\) 段,规定每段都是 一个元素 或者 低高低 或者 地高低高低 \(...\)
对于处理 \(f[i][j]\) 时,第 \(i\) 个数字对于前 \(i-1\) 个数字都是高的,所以他可以用来合并两个段,或者新开一段

\(f[i][j]=f[i-1][j+1]\times j+f[i-1][j-1]\times j\)

考虑需要处理 \(s,t\) 的关系

\[i=s \|\| i=t \space : \space f[i][j]=f[i-1][j-1]+f[i-1][j]\\ i≠s \&\& i≠t \space : \space f[i][j]=f[i-1][j+1]\times j+f[i-1][j-1]\times(j-(i>s)-(i>t)) \]

\(answer = f[n][1]\)

#include"bits/stdc++.h"using namespace std;
constexpr int N=2e3+15,P=1e9+7;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}inl int read(void)
{int x=0;short f=1;char ch=getchar();for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());return x*f;
}int n,s,t;
ll f[N][N];int main(void)
{n = read(),s = read(),t = read();f[1][1] = 1;for(int i = 2;i <= n;i ++){for(int j = 1;j <= i;j ++){if(i != s && i != t)	f[i][j] = (j * f[i - 1][j + 1] % P + (j - (i > s) - (i > t)) * f[i - 1][j - 1] % P) % P;else	f[i][j] = f[i - 1][j - 1] + f[i - 1][j];}}printf("%lld ",f[n][1]);return 0;
}

D.CF704B Ant Man

\(n\) 个元素,第 \(i\) 个元素有五个参数 \(x_i,a_i,b_i,c_i,d_i\) ,需要求出一个从 \(1\)\(n\) 的排列 \(p\) ,满足 \(p_1=s,p_n=e\) ,同时最小化这个排列的权值,定义一个排列的权值为 \(\sum_{i=1}^{n-1}{f(p_i.p_{i+1})}\) , 其中 \(f(i,j)\) 的值有两种情况:

  • \(i>j\) , 则 \(f(i,j) = x_i - x_j+c_i+b_j\)
  • \(i<j\) , 则 \(f(i,j) = x_j - x_i + d_i + a_j\)

\(n\le 5\times 10^3,s≠e,1\le x_1 < x_2 < ... < x_n \le 10^9,1\le a_i,b_i,c_i,d_i \le 10^9\)

\(f[i][j]\) 表示前 \(i\) 个数字,有 \(j\) 段的最小花费

\(f[0][1]=0\)

普通的情况
\(f[i][j]=f[i-1][j-1]-x[i]\times 2+b[i]+d[i]\) 作为新开一段,这一段必然是在未来作为高低高出现的,所以他对答案的贡献是 \(-2\times x[i]+b[i]+d[i]\)

\(f[i][j]=f[i-1][j+1]+x[i]\times 2+a[i]+c[i]\) 合并两个段,这个点最为低高低出现,对答案的贡献是\(2\times x[i]+c[i]+a[i]\)

\(f[i][j]=f[i-1][j]+b[i]+c[i]\) 往某个段前面放,作为高中低出现,对答案的贡献是 \(b[i]+c[i]\)

\(f[i][j]=f[i-1][j]+a[i]+d[i]\) 往某个段后面放,作为低中高出现,对答案的贡献是 \(a[i]+d[i]\)

对于起点 \(s\)\(f[i][j]=f[i-1][j]+x[i]+c[i]\) 连接在某个段前面,于是排列高低起手,\(s\) 的贡献是 \(x[i]+c[i]\)
新开一个段,\(f[i][j]=f[i-1][j-1]-x[i]+d[i]\),于是最终排列低高起手, \(s\) 的贡献是 \(-x[i]+d[i]\)

对于终点 \(t\)\(f[i][j]=f[i-1][j]+x[i]+a[i]\) ,于是最终排列低高结尾,\(t\) 的贡献是\(x[i]+a[i]\)
新开一个段, \(f[i][j]=f[i-1][j-1]-x[i]+b[i]\), 于是最终排列高低结尾, \(t\) 的贡献是 \(-x[i]+b[i]\)

考虑起点和终点固定下来,对转移有什么影响\(:\)
想要新开一段时,当 \(i>s\&\&i>t\) 时,此时 \(s,t\) 做起点终点,只有一段时不能新开一段
想往某个段的前面放,要么 \(s\) 没出现,要么 \(s\) 出现了并且段数大于 \(1\) 才能往前放
想往某个段的后面放,要么 \(t\) 没出现,要么 \(t\) 出现了,并且段数大于 \(1\) 才能往后放

\(answer=f[n][1]\)

#include"bits/stdc++.h"using namespace std;
constexpr int N=5e3+15;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}inl int read(void)
{int x=0;short f=1;char ch=getchar();for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());return x*f;
}ll f[N][N];
int n,s,e;int x[N],a[N],b[N],c[N],d[N];inl void cmin(ll &a,ll b){return a=min(a,b),void();} int main(void)
{n=read(),s=read(),e=read();for(int i=1;i<=n;i++)	x[i]=read();for(int i=1;i<=n;i++)	a[i]=read();for(int i=1;i<=n;i++)	b[i]=read();for(int i=1;i<=n;i++)	c[i]=read();for(int i=1;i<=n;i++)	d[i]=read();memset(f,0x3f,sizeof f);f[0][0]=0;for(int i=1;i<=n;i++){for(int j=1;j<=i;j++){if(i==e)	cmin(f[i][j],min(f[i-1][j]+x[i]+a[i],f[i-1][j-1]-x[i]+b[i]));	elseif(i==s)	cmin(f[i][j],min(f[i-1][j-1]-x[i]+d[i],f[i-1][j]+x[i]+c[i]));	else{if(j>(i>s)+(i>e))	cmin(f[i][j],f[i-1][j-1]-2*x[i]+b[i]+d[i]);if(j>1||i<s)	cmin(f[i][j],f[i-1][j]+b[i]+c[i]);if(j>1||i<e)	cmin(f[i][j],f[i-1][j]+a[i]+d[i]);cmin(f[i][j],f[i-1][j+1]+2*x[i]+a[i]+c[i]);}}}printf("%lld ",f[n][1]);return 0;
}

E. 「JOI Open 2016」摩天大楼

\(N\) 个整数, \(a_1,a_2,a_3,...,a_N\),按照一定顺序排序,假设排列为 \(f_1,f_2,...,f_N\) ,要求: \(\|f_1-f_2\|+\|f_2-f_3\|+...\|f_{N-1}-f_N\|\le L\) ,求满足题意的排列的方案数 \((\mod 10^9+7)\)

为了规避掉绝对值,先排个序。

这题没有规定左右端点,考虑\(dp\)的时候加一维

\(f[i][j][sum][d]\)表示前 \(i\) 大的 \(a\),有 \(j\) 段,当前已经固定了 \(d\) 个端点(左右端点),和为 \(sum\)

\(a_i\) 插入的贡献看成是把之前的 \(j\) 段的每一段的左右端点都变为 \(a_i\) ,需要花费 \((2 \times j-d)\cdot\|f[a]-a[i-1]\|\),然后插入 \(a_i\) 时无需贡献,所以 \(a_i-a_{i-1}\)的贡献是和插入前的段数和端点数量相关的,有 \(j\) 段,\(d\) 个端点时,贡献为 \((a_i-a_{i-1})\cdot(2\times j-d)\)

考虑转移

\[设\space p \space 为\space 当前贡献,t \space 为所枚举到的\space f[i][j][k][d] \\ 新开一个:f[i+1][j+1][p][d]=f[i+1][j+1][p][d]+t*(j+1-d)\\ 放在某一段的左右(j\le 1):f[i+1][j][p][d]=f[i+1][j][p][d]+t*(2*j-d)\\ 合并两个(j\le 2):f[i+1][j-1][p][d]=f[i+1][j-1][p][d]+t*(j-1)\\ 让a_i新开一段(d < 2):f[i+1][j+1][p][d+1]=f[i+1][j+1][p][d+1]+t*(2-d)\\ 让a_i做端点(d < 2 \&\& j ≥ 1):f[i+1][j][p][d+1]=f[i+1][j][p][d+1]+t*(2-d)\\ \]

#include"bits/stdc++.h"using namespace std;
constexpr int N=115,L=1015,P=1e9+7;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
inl void add_(ll &a,ll b,int p=P){return a=(a+b>=p?a+b-p:a+b),void();}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}inl int read(void)
{int x=0;short f=1;char ch=getchar();for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());return x*f;
}ll f[N][N][L][3];int a[N],n,l;int main(void)
{n=read(),l=read();for(int i=1;i<=n;i++)	a[i]=read();sort(a+1,a+1+n);f[0][0][0][0]=1;for(int i=0;i<n;i++){for(int j=0;j<=i;j++){for(int k=0;k<=l;k++){for(short d=0;d<3;d++){ll p=k+(a[i+1]-a[i])*(2*j-d),t=f[i][j][k][d];if(!t||p>l)	continue;add_(f[i+1][j+1][p][d],t*(j+1-d)%P);if(j>=2)	add_(f[i+1][j-1][p][d],t*(j-1)%P);if(j>=1)	add_(f[i+1][j][p][d],t*(2*j-d)%P);if(d<2){add_(f[i+1][j+1][p][d+1],t*(2-d));if(j>=1)	add_(f[i+1][j][p][d+1],t*(2-d)%P);}}}}}ll ans=0;for(int i=0;i<=l;i++)	add_(ans,f[n][1][i][2]);printf("%lld",n==1?1:ans);return 0;
}

P2612 [ZJOI2012] 波浪

随机生成一个排列

排列的价值定义为相邻两个数差的绝对值累加

问价值大于等于 \(M\) 的概率

\(L = \| a_2-a_1 \|+\|a_3-a_2\|...\)

与上题类似,现在有 \(n\) 个空位,从小到大以此放入 $1 $ ~ $ n$ 个数

设计 \(f[l][i][j][k]\) 为放了 \(i\) 个数,形成 \(j\) 个连通块,价值为 \(k\) 且有 $l $ 个边界放了数的方案

转移类似上一题,注意得开 \(\_\_float 128\),代码用了两种 , 小测试点快一些

#include"bits/stdc++.h"using namespace std;
constexpr int N=5015;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ld __float128
//#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}inl int read(void)
{int x=0;short f=1;char ch=getchar();for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());return x*f;
}
int n,m,K;
template <class T>
void Print(T ans)
{int num[100];num[0]=0;ans=ans*10;for(int i=1;i<K;i++){num[i]=(int)ans;ans=(ans-num[i])*10;}num[K]=(int)(ans+0.5);for(int i=K;i>=1;i--)	if(num[i]>=10)	num[i]-=10,num[i-1]++;printf("%d.",num[0]);for(int i=1;i<=K;i++)	printf("%d",num[i]);puts("");
}
int l;
ld ans;
template <class T>
void solve(T f[][115][3][N])
{f[0][0][0][0]=1;for(int i=0;i<n;i++){int cur=i%2;for(int j=0;j<=i+1;j++)	for(int op=0;op<=2;op++)	for(int s=0;s<=(n*n)/2;s++)	f[!cur][j][op][s]=0;for(int j=(i!=0);j<=i;j++)	for(int op=0;op<=2;op++)	for(int s=0,ts=2*j-op;ts<=(n*n)/2;s++,ts++){if(!f[cur][j][op][s])	continue;ld t=f[cur][j][op][s];f[!cur][j+1][op][ts]=f[!cur][j+1][op][ts]+t*(j+1-op);f[!cur][j][op][ts]+=t*(2*j-op);if(j)	f[!cur][j-1][op][ts]+=t*(j-1);if(op!=2)f[!cur][j+1][op+1][ts]+=t*(2-op),f[!cur][j][op+1][ts]+=t*(2-op);}}for(int i=l;i<=5000;i++)	ans=(ans+f[n%2][1][2][i]);for(int i=1;i<=n;i++)	ans=ans/i;Print(ans);
}
ld f1[2][115][3][N];
double f2[2][115][3][N];
int main(void)
{n=read(),l=read(),K=read();if(n==1)	Print(1),exit(0);if(n<=8)	solve(f2);else	solve(f1);return 0;
}

CF1515E Phoenix and Computers

给定 \(N\) 台电脑,起初每台电脑都是关闭的
现在你可以随意打开电脑,但如果第 \(i−1\)、第 \(i+1\) 台电脑是开启的,则第 \(i\) 台电脑也会自动开启,而你无法手动开启它
问你有多少种打开电脑的方法,使得最后所有电脑都是开着的

\(f[i][j]\)表示有\(i\)个电脑,组成\(j\)个连续已开启的段

转移有 新建段,段扩增,段合并 共五种情况

#include"bits/stdc++.h"using namespace std;
constexpr int N=415;
#define inl inline
#define regi register
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ll long long//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl void add_(ll &a,ll b,int p=P){return a=(a+b>=p?a+b-p:a+b),void();}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}inl int read(void)
{int x=0;short f=1;char ch=getchar();for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());return x*f;
}int n,P;
ll f[N][N];int main(void)
{n=read(),P=read(),f[0][0]=1;for(int i=1;i<=n;i++)	for(int j=1;j<=i;j++){f[i][j]+=f[i-1][j]*2*j;if(i>=2)	f[i][j]+=f[i-2][j]*2*j,f[i][j]+=f[i-2][j+1]*2*j;if(i>=3)	f[i][j]+=f[i-3][j+1]*j;(f[i][j]+=f[i-1][j-1]*j)%=P;}printf("%lld",f[n][1]);return 0;
}

P7967 [COCI2021-2022#2] Magneti

给定 \(n\) 个磁铁和 \(l\) 个空位,其中相邻空位之间的距离为 \(1\),每个空位可放置一个磁铁。所有 \(n\) 个磁铁都必须被放置。每个磁铁可以吸引距离小于 \(r_i\) 的其它磁铁。

求所有磁铁互不吸引的方案总数对 \(10^9+7\) 取模的结果。

\(f[i][j][s]\),表示用了前\(i\)个,\(j\)段,段的长度之和为\(s\) 方案数

\[放在某一段的边上:(s>=r[i])f[i][j][s]+=f[i-1][j][s-r[i]]*j*2\\ 新开一段(s>=1):f[i][j][s]+=f[i-1][j-1][s-1]*j\\ 合并两段(s>=2*r[i]-1):f[i][j][s]+=f[i-1][j+1][s 2*r[i]+1]*j\]

最后还需要组合数一下。对于\(f[n][1][i]\),需要给\(n\)个磁铁的前后中间共\(n+1\)个“盒子”放置\(l-i\)空格,可以为空

转化为\(n+1\)个盒子,\(l-i+n+1\)个空格,不可以为空,抓化成\(l-i+n\)个间隙,赛\(n\)个挡板

\(ans=\sum{f[n][1][i]}*C(n+l-i,n)\)

#include"bits/stdc++.h"using namespace std;
constexpr int N=2e5+15,P=1e9+7;
#define inl inline
#define regi register int
#define PII pair<int,int>
#define mkp(x,y) make_pair(x,y)
#define ll long long
//char buf_[1<<20],*_now=buf_,*_end=buf_;
//#define getchar() (_now==_end&&(_end=(_now=buf_)+fread(buf_,1,1<<20,stdin),_now==_end)?EOF:*_now++)
//namespace IO{void Unbind(){std::ios::sync_with_stdio(false);std::cin.tie(0);}}
//inl int add_(int a,int b,int p=P){return a+b>=p?a+b-p:a+b;}
//inl int sub_(int a,int b,int p=P){return a-b<0?a-b+p:a-b;}
//inl int mul_(int a,int b,int p=P){return 1ll*a*b%p;}inl int read(void)
{int x=0;short f=1;char ch=getchar();for(;ch<'0'||ch>'9';ch=getchar()) f=ch=='-'?-1:f;for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());return x*f;
}ll fac[N],inv[N];
ll qpow(ll a,ll b){ll res=1;a%=P;while(b){if(b&1)res=res*a%P;a=a*a%P;b>>=1;}return res;}
void init(int n_)
{fac[0]=inv[0]=1;for(regi i=1;i<=n_;i++)	fac[i]=fac[i-1]*i%P;inv[n_]=qpow(fac[n_],P-2);for(regi i=n_-1;i>=1;i--)	inv[i]=inv[i+1]*(i+1)%P;
}
ll C(ll n,ll m){return fac[n]*inv[n-m]%P*inv[m]%P;}
ll n,l,r[N],ans;
ll f[65][65][10015];
int main(void)
{
//	freopen("magnet.in","r",stdin),freopen("magnet.out","w",stdout);n=read(),l=read();init(20000);for(int i=1;i<=n;i++)	r[i]=read();sort(r+1,r+1+n);f[0][0][0]=1;for(int i=1;i<=n;i++){for(int j=1;j<=i;j++){for(int s=0;s<=l;s++){if(s>=r[i])	(f[i][j][s]+=f[i-1][j][s-r[i]]*j*2)%=P;if(s>=1)	(f[i][j][s]+=f[i-1][j-1][s-1]*j)%=P;if(s>=2*r[i]-1)	(f[i][j][s]+=f[i-1][j+1][s-2*r[i]+1]*j)%=P;}}}for(int i=0;i<=l;i++)	ans=(ans+(f[n][1][i]*C(l+n-i,n))%P)%P;printf("%lld",ans);return 0;
}

再附上我们强大的\(QY\)学长的[\(cf\)账号](zzuqy - Codeforces)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/828981.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

进销存系统是什么?企业如何用好进销存系统?

很多老板都知道,面对激烈的竞争环境,进销存管理已经成为企业运营中不可或缺的一部分。无论是商品的采购、销售,还是库存管理,进销存系统的有效运作都直接影响着企业的资金流动、客户满意度以及市场竞争力。因此,如何高效地应用进销存管理系统,不仅关乎日常运营的顺畅,更…

【linux内核】 BCC Tools命令汇总

什么是BCC Tools 直接通过BPF指令编写BPF 程序是非常繁琐的事情,尤其对于运维人员来说,花70%的时间来编写一个BPF程序来实现一个检查点,不如花70%的时间来熟悉别人已经写好的BPF程序,并且把别人的BPF程序有效的串联起来形成自己分析问题的套路,等有了闲暇时间再去尝试突破…

20222311 2024-2025-1 《网络与系统攻防技术》实验四实验报告

1. 实验内容 1.1恶意代码文件类型标识、脱壳与字符串提取 对提供的rada恶意代码样本,进行文件类型识别,脱壳与字符串提取,以获得rada恶意代码的编写作者,具体操作如下: (1)使用文件格式和类型识别工具,给出rada恶意代码样本的文件格式、运行平台和加壳工具; (2)使用…

进销存系统怎么实施?进销存的实施流程解析

很多老板都知道,企业的日常运营中,进销存管理是至关重要的核心环节之一。它涉及到企业的采购、库存、销售等多个方面,直接影响企业的资金流动、库存控制及客户满意度。因此,如何有效实施进销存管理系统,已成为许多企业提升运营效率、降低成本、优化资源配置的重要任务。 随…

解决centos7.9镜像问题

解决方案 https://wiki.bafangwy.com/doc/719/ 安装chrome(https://www.cnblogs.com/zxqblogrecord/p/13171516.html)时出现以下问题: failure: repodata/repomd.xml from centos7: [Errno 256] No more mirrors to try. http://mirror.centos.org/centos/7/os/x86_64/repodat…

【IDEA】IntelliJ IDEA远程开发:释放本地设备,打破资源与环境的限制

IDEA的远程开发 (Remote Development) 是指在不依赖于本地计算资源的情况下,通过连接远程服务器或虚拟机来进行代码编写、调试和构建的开发模式。这种方式主要通过 JetBrains Gateway 和 JetBrains Projector 提供支持,可以在本地 IDE 界面与远程环境紧密协作,帮助开发者解决…

有哪些免费的轻量级在线 CRM 系统?6款CRM系统盘点

对于许多中小型企业和初创公司而言,如何选择一款既能满足日常需求又具性价比的CRM系统很重要。通常来说,系统的成本是企业选型过程中不可忽视的因素,但在这么多的CRM系统中,各类CRM系统的功能与价格对比真的很容易让人眼花缭乱。 本文将盘6款值得关注的免费轻量级在线CRM系…

[极客大挑战 2019]PHP 1

[极客大挑战 2019]PHP 1 打开文件发现提示信息“备份文件”,查看源码并未发现其他有效信息采用dirsearch爆破目录,找到www.zip文件解压zip,发现是源码泄露提交flag,发现不正确,看到index.php源码中有Get传参,然后反序列化函数unserialize(),判断为反序列化漏洞;查看class…

【IDEA】告别繁琐反编译:IDEA中轻松反编译与修改Jar包

本文带你了解 JarEditor 插件的强大功能,它让你无语再用其他第三方工具,也无需解压 JAR 文件就能直接在IDEA中编辑其中的类和资源文件。还能用它轻松添加、删除或重命名 JAR 包里的文件。即使遇到混淆过的 JAR,JarEditor 也能通过 Javassist 工具搞定。 支持的功能 JarEdito…

[极客大挑战 2019]BuyFlag 1

[极客大挑战 2019]BuyFlag 1 打开实例发现pay.php页面,有提示信息打开源码发现password post提交逻辑burpsuite抓包传参,传入money和password参数,这里password是==弱比较,所以加个字符就可以绕过 password=404a&money=100000000回显发现并没有变化 注意到学生需要CUIT(…

写什么代码来清空缓冲区

在 C++ 中,清空输入缓冲区的方法主要用于清除 std::cin 输入流中的残留字符,避免它们影响后续的输入操作。下面介绍几种常用的办法。 1. cin.ignore() 方法 cin.ignore() 是最常用的清空缓冲区方法,可以忽略输入流中的若干字符。cin.ignore(numeric_limits<streamsize>…