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;
}