DP练习3-0320(VJ)

比赛地址

T1(棋盘分割)

二维区间DP。

但是似乎从爆搜开始会好想一点——记忆化搜索(这样写会T)。用循环的方式写就好了。

\(f[x_1][y_1][x_2][y_2][k]\) 表示在 \((x_1,y_1)\)\((x_2,y_2)\) 这个矩形被分为 \(k\) 块时的最小均方差。具体计算看代码啦。

${\color{skyblue}{Code}}$
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stdio.h>
using namespace std;
#define ff f[x1][y1][x2][y2][k]

const int INF=0x3f3f3f3f;
int n;
int sum[10][10];
double f[10][10][10][10][20],ave;

double cal(int x1,int y1,int x2,int y2){
	double s=sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1]-ave;
	return s*s/n;
}

int main(){
	
	cin>>n;
	for(int i=1;i<=8;++i)
		for(int j=1;j<=8;++j){
			cin>>sum[i][j];
			sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
		}
	
	for(int i=0;i<=9;++i)
		for(int j=0;j<=9;++j)
			for(int k=0;k<=9;++k)
				for(int q=0;q<=9;++q)
					for(int p=0;p<=16;++p)
						f[i][j][k][q][p]=INF;
	ave=1.0*sum[8][8]/n;
	
	for(int k=1;k<=n;++k){
		for(int x1=1;x1<=8;++x1)
			for(int y1=1;y1<=8;++y1)
				for(int x2=1;x2<=8;++x2)
					for(int y2=1;y2<=8;++y2){
						if(k==1){ff=cal(x1,y1,x2,y2);continue;}
						//从x切 
						for(int i=x1;i<x2;++i){
							ff=min(ff,f[x1][y1][i][y2][k-1]+cal(i+1,y1,x2,y2));
							ff=min(ff,f[i+1][y1][x2][y2][k-1]+cal(x1,y1,i,y2));
						}
						//从y切 
						for(int i=y1;i<y2;++i){
							ff=min(ff,f[x1][y1][x2][i][k-1]+cal(x1,i+1,x2,y2));
							ff=min(ff,f[x1][i+1][x2][y2][k-1]+cal(x1,y1,x2,i));
						}
					}
	}
	
	printf("%.3lf",sqrt(f[1][1][8][8][n]));
	
	return 0;
}

T2(Tree with Maximum Cost)

还根DP。

先随便找个点搜一遍,记录下以每个节点为根的子树的 权值的和(\(sum[x]\)及要求的价值的总和(\(tmp[x]\)(含根)。

再以同一个点开始搜,令 \(f[i]\) 表示以当前节点为根的树的答案是多少。开始搜的这个点 \(f[x]=tmp[x]\)

画一画就可以知道,\(f[y]=f[x]+sum[1]-2 * sum[y]\) ,其中 \(y\)\(x\) 的孩子。

${\color{skyblue}{Code}}$
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;

#define int long long

const int N=2e5+5;
int f[N],sum[N],tmp[N],a[N],co;
int n;
int h[N],ver[N*2],nxt[N*2];

void add(int x,int y){
	nxt[++co]=h[x],h[x]=co,ver[co]=y;
}

int dfs(int x,int fa,int dis){
	int s=0;sum[x]=a[x];
	for(int i=h[x];i;i=nxt[i]){
		int y=ver[i];
		if(y==fa)	continue;
		dfs(y,x,dis+1);
		sum[x]+=sum[y],tmp[x]+=tmp[y];
	}
	return tmp[x]+=dis*a[x];
}

void dp(int x,int fa){
	for(int i=h[x];i;i=nxt[i]){
		int y=ver[i];
		if(y==fa)	continue;
		f[y]=f[x]+sum[1]-2*sum[y];
		dp(y,x);
	}
}

signed main(){
	
	cin>>n;
	for(int i=1;i<=n;++i)	cin>>a[i];
	for(int i=1;i<n;++i){
		int u,v;cin>>u>>v;
		add(u,v),add(v,u);
	}
	
	f[1]=dfs(1,0,0);
	dp(1,0);
	
	int ans=-1;
	for(int i=1;i<=n;++i)	ans=max(ans,f[i]);
	printf("%lld",ans);
	
	return 0;
} 

T3(涂抹果酱)

状压DP(严格上我用的并不是)

先用 DFS 预处理出每排可能的状态(用 10 进制,如: 12323 ,最多48种)。

\(f[i][j]\) 表示第 \(i\) 排状态为 \(j\) 为编号的状态 的方案数。

以确定的那排为分界线,分成两半部分转移(从 \((k-1)-1\)\((k+1)-n\)),转移时写个 check 函数判断上下是否为一样即可。

${\color{skyblue}{Code}}$
#include<bits/stdc++.h>
using namespace std;

const int p=1e6;
int f[10005][50],tmp[250],cnt;
int n,m;

void dfs(int x,int sum){
	if(x>m){
		tmp[++cnt]=sum;
		return;
	}
	for(int i=1;i<=3;++i)
		if(sum%10!=i)
			dfs(x+1,sum*10+i);
}

bool ch(int x,int y){
	while(x){
		if(x%10==y%10)	return 0;
		x/=10,y/=10;
	}
	return 1;
}

int main(){
	
	int k,sum=0;
	cin>>n>>m>>k;
	for(int i=1;i<=m;++i){
		int x;cin>>x;
		sum=sum*10+x;
	}
	dfs(1,0);
	int tt=0;
	for(int i=1;i<=cnt;++i)
		if(tmp[i]==sum)	tt=i;
	f[k][tt]=1;
	
	for(int i=k-1;i>=1;--i)
		for(int j=1;j<=cnt;++j)
			for(int q=1;q<=cnt;++q){
				if(!ch(tmp[j],tmp[q]))	continue;
				f[i][j]+=f[i+1][q],f[i][j]%=p;
			}
	
	for(int i=k+1;i<=n;++i)
		for(int j=1;j<=cnt;++j)
			for(int q=1;q<=cnt;++q){
				if(!ch(tmp[j],tmp[q]))	continue;
				f[i][j]+=f[i-1][q],f[i][j]%=p;
			}
	
	long long sum1=0,sum2=0;
	for(int i=1;i<=cnt;++i)
		sum1+=f[1][i],sum2+=f[n][i],sum%=p,sum2%=p;
	sum1=(1ll*sum1*sum2)%p;
	printf("%lld",sum1);
	
	return 0;
}
posted @ 2022-03-22 17:48  _yolanda  阅读(42)  评论(0编辑  收藏  举报