一类特殊的 dp 模型--zhengjun
这类问题大概长这样:
求一个排列 \(p_{1\sim n}\),最小(大)化如下值:
\[\sum\limits_{i=1}^{n-1}f(p_i,p_{i+1})\\
f(i,j)=
	\left\{
		\begin{array}{**lr**}
		g(i)+h(j),i<j\\
		h(i)+g(j),i>j
		\end{array}
	\right.
\]
那么就可以用如下方法 \(O(n^2)\) 解决:
- 
从小到大向序列中插入元素; 
- 
记 \(f_{i,j,0/1,0/1}\) 表示,前 \(i\) 个数插入后,分成了 \(j\) 段,首尾分别是否确定 的最小(大)值。 这里的分段表示后面大的数不能插在一段中间 
- 
对 \(i\) 插入位置分类讨论: - 插入首位:
- 在首位新开一个段;
- 在第一个段最前面插入。
 
- 插入末位:
- 在末位新开一个段;
- 在最后一个段后面插入。
 
- 插入中间:
- 在任意位置(可以在开头或末位)插入一段
需要讨论首位末位是否确定判断是否可以插入开头和末位 
- 插入【不是第一个段的段】的开头;
- 插入【不是最后一个段的段】的末尾;
- 插入两个段的中间并合并两个段。
 
- 在任意位置(可以在开头或末位)插入一段
 
- 插入首位:
- 
然后就做完了,改成计数也是没问题的。 
例题
CF704B Ant Man
板题,直接上即可。
甚至帮你决定了要不要插入首位和末位。
代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=5e3+10;
const ll INF=1e18;
int n,st,ed,x[N],a[N],b[N],c[N],d[N];
ll f[N][N];
void chkmin(ll &x,ll y){
	if(x>y)x=y;
}
int main(){
	freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	scanf("%d%d%d",&n,&st,&ed);
	for(int i=1;i<=n;i++)scanf("%d",&x[i]);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)scanf("%d",&b[i]);
	for(int i=1;i<=n;i++)scanf("%d",&c[i]);
	for(int i=1;i<=n;i++)scanf("%d",&d[i]);
	memset(f,0x3f,sizeof f);
	f[0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(j>(i>st)+(i>ed))chkmin(f[i][j],f[i-1][j-1]+b[i]-x[i]+d[i]-x[i]);
			if(j>(i>st)&&i!=ed)chkmin(f[i][j],f[i-1][j]+c[i]+b[i]);
			if(j>(i>ed)&&i!=st)chkmin(f[i][j],f[i-1][j]+a[i]+d[i]);
			if(i!=st&&i!=ed)chkmin(f[i][j],f[i-1][j+1]+x[i]+c[i]+x[i]+a[i]);
		}
	}
	cout<<f[n][1]-(b[st]-x[st])-(d[ed]-x[ed]);
	return 0;
}
P2612 [ZJOI2012] 波浪
概率->计数,需要数据分治。
\(K\le 8\) 时可用 long double,否则用 __float128。
由于这题的特殊性,\(g(i)=h(i)\),所以不需要记录首尾分别是否确定。
只要记录首尾确定了几个即可。
代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
int n,m,k;
namespace Solve1{
	const int N=1e2+10,M=N*N,zero=M/2;
	long double f[2][3][N][M];
	void solve(){
		f[0][0][0][zero]=1;
		for(int i=1,now=1,las=0;i<=n;i++,swap(now,las)){
			memset(f[now],0,sizeof f[now]);
			for(int x=0;x<3;x++){
				for(int j=0;j<=n;j++){
					for(int k=0;k<M;k++){
						if(f[las][x][j][k]==0)continue;
						if(j>1&&k+i*2<M)f[now][x][j-1][k+i*2]+=f[las][x][j][k]*(j-1)/i;
						if(k-i*2>=0)f[now][x][j+1][k-i*2]+=f[las][x][j][k]*(j+1-x)/i;
						if(j)f[now][x][j][k]+=f[las][x][j][k]*(j*2-x)/i;
						if(x<2){
							if(j&&k+i<M)f[now][x+1][j][k+i]+=f[las][x][j][k]*(2-x)/i;
							if(k-i>=0)f[now][x+1][j+1][k-i]+=f[las][x][j][k]*(2-x)/i;
						}
					}
				}
			}
		}
		long double ans=0;
		for(int i=m;zero+i<M;i++)ans+=f[n&1][2][1][zero+i];
		printf("%.*Lf",k,ans);
	}
}
namespace Solve2{
	const int N=50+10,M=N*N,zero=M/2;
	__float128 f[2][3][N][M];
	void solve(){
		f[0][0][0][zero]=1;
		for(int i=1,now=1,las=0;i<=n;i++,swap(now,las)){
			memset(f[now],0,sizeof f[now]);
			for(int x=0;x<3;x++){
				for(int j=0;j<=n;j++){
					for(int k=0;k<M;k++){
						if(f[las][x][j][k]==0)continue;
						if(j>1&&k+i*2<M)f[now][x][j-1][k+i*2]+=f[las][x][j][k]*(j-1)/i;
						if(k-i*2>=0)f[now][x][j+1][k-i*2]+=f[las][x][j][k]*(j+1-x)/i;
						if(j)f[now][x][j][k]+=f[las][x][j][k]*(j*2-x)/i;
						if(x<2){
							if(j&&k+i<M)f[now][x+1][j][k+i]+=f[las][x][j][k]*(2-x)/i;
							if(k-i>=0)f[now][x+1][j+1][k-i]+=f[las][x][j][k]*(2-x)/i;
						}
					}
				}
			}
		}
		__float128 ans=0;
		for(int i=m;zero+i<M;i++)ans+=f[n&1][2][1][zero+i];
		int d=(int)ans;
		printf("%d.",d);
		ans-=d;
		for(int d;k--;){
			d=ans*10+(k?0:0.5);
			printf("%d",d);
			ans=ans*10-d;
		}
	}
}
int main(){
	freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n>>m>>k;
	if(k<=8)Solve1::solve();
	else Solve2::solve();
	return 0;
}
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号