题目链接:[Problem - D2 - Codeforces](https://codeforces.com/contest/1733/problem/D2)
dfs实现动态规划
题意:给定两个01串,要求经过操作使得第一个串等于第二个串。可以选择两个位置翻转,如果两个位置距离为1,那么翻转的代价为x,距离大于1则代价为y。求最小代价。
#include<bits/stdc++.h>
using namespace std;
vector<int>pos;
long long dp[5005][5005];
long long n,x,y;
long long get(long long l,long long r)
{
if(l+1==r)return min(2*y,x);//如果l,r相邻,我们可以通过两次y操作达到修改相邻的效果,所以我们需比较一下2*y和x的大小,选择最优的
else return min(y,x*(r-l));//同理,对于l,r不相邻,我们也可以通过x(r-l)次x操作达到修改l和r的效果。
}
long long dfs(long long l,long long r)
{
if(l>=r)return 0;
if(dp[l][r]!=-1)return dp[l][r];//剪枝
long long res=1e18;
res=min(res,dfs(l+1,r-1)+get(pos[l],pos[r]));
res=min(res,dfs(l,r-2)+get(pos[r-1],pos[r]));
res=min(res,dfs(l+2,r)+get(pos[l],pos[l+1]));
return dp[l][r]=res;
}
void solve()
{
cin>>n>>x>>y;
string a,b;
cin>>a>>b;
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
dp[i][j]=-1;
pos.clear();
for(int i=0;i<n;i++)if(a[i]!=b[i])pos.push_back(i);
if(pos.size()%2==1)//不同的个数为奇数个,我们肯定是无法使他们相同的,因为每次都是修改2个
{
cout<<-1<<endl;
return ;
}
if(pos.size()==0)
{
cout<<0<<endl;
return ;
}
if(pos.size()==2)//两个相邻的情况
{
if(pos[0]+1==pos[1])cout<<min(2*y,x)<<endl;
else cout<<min(y,x*(pos[1]-pos[0]))<<endl;
return ;
}
if(y<=x)
{
cout<<pos.size()/2*y<<endl;
return;
}
else
{
cout<<dfs(0,pos.size()-1)<<endl;
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--)
{
solve();
}
system("pause");
return 0;
}
思路:对于x>=y的情况比较简单,只需尽量全部选择y即是最优。麻烦的是当x<y的时候我们的贪心策略就失效了。
别人的题解:
容易发现,相距越远的两个数,用x的花费越大,而用y的花费不变;因此如果我们要用y,就会选择最远的两个数用
用dp [ i ] [ j ] 表示解决i~j所需的最小花费,接着我们考虑dp [ i ] [ j ] 从什么地方转移过来
可以发现,每次操作只有三种情况
1.**改变开头的两个位置**,即从dp[ l+2 ] [ r ]转移到dp[ l ] [ r ]
2.**改变结尾的两个位置**,即从dp[ l ] [ r-2 ]转移到dp[ l ] [ r ]
3.**改变开头和结尾各一个位置**,即从dp[ l+1 ] [ r-1 ]转移到dp[ l ] [ r ]
由此,我们得出了状态转移方程,可以直接用记忆化搜索(比较方便)
题解链接https://zhuanlan.zhihu.com/p/566175996 https://zhuanlan.zhihu.com/p/566141853
题目链接:https://www.luogu.com.cn/problem/P2014?contestId=90483
#include<bits/stdc++.h>
using namespace std;
long long head[500],ver[3000],nex[3000],w[3000],f[500][500];
long long cnt=0,n=0,m=0;
void add(int from,int to,int val)
{
ver[++cnt]=to;
nex[cnt]=head[from];
w[cnt]=val;
head[from]=cnt;
}
void dfs(int now,int fa)
{
for(int i=head[now];i;i=nex[i])
{
int to=ver[i];
if(to==fa)continue;
dfs(to,now);//递归到子节点
for(int j=m;j>=0;j--)
{
for(int k=0;k<j;k++)
{
f[now][j]=max(f[now][j],f[now][k]+f[to][j-k-1]+w[i]);
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int x,y;
cin>>x>>y;
add(x,i,y);
}
dfs(0,-1);
cout<<f[0][m]<<endl;
return 0;
}
典型的树上背包,每个节点可以有很多的子节点,我们可以先让我们当前要处理的节点与他其中一个节点进行01背包,然后将处理好的当前节点再去和其他节点进行01背包,相当于将当前处理好的节点和已经被01背包过的子节点看作一个整体。
for(int j=m;j>=0;j--)
{
for(int k=0;k<j;k++)
{
f[now][j]=max(f[now][j],f[now][k]+f[to][j-k-1]+w[i]);
//f[now][k]是从0到j的,是以前的状态,我们在求f[now][k]要用到,所以外面那个for循环要倒着进行。
}
}
在进行01背包的时候,最外面的for循环应倒着循环,这是因为我们再背包的时候要用到以前的状态来推出当前的状态,倒着循环就可以避免因改变f数组的值而对当前需要推的节点造成影响
树形dp:
// }
for(int i=head[now];i;i=nex[i])
{
int to=ver[i];
if(to==fa)continue;
dfs(to,now);
//某一节点的某一状态等于它的子节点的其余状态另外两种状态相加,然后再与其余节点相乘即可算出答案
f[now][1]=(f[now][1]*((f[to][2]+f[to][3])%mod))%mod;
f[now][2]=(f[now][2]*((f[to][1]+f[to][3])%mod))%mod;
f[now][3]=(f[now][3]*((f[to][2]+f[to][1])%mod))%mod;
}
}
int main()
{
cin>>n>>k;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);
}
for(int i=1;i<=k;i++)
{
int x,c;
cin>>x>>c;
f[x][c]=1;
}
for(int i=1;i<=n;i++)
{
if(f[i][1]==0&&f[i][2]==0&&f[i][3]==0)
{
f[i][1]=1;
f[i][2]=1;
f[i][3]=1;
}
}
dfs(1,0);
cout<<(f[1][1]+f[1][2]+f[1][3])%mod;
return 0;
}
思路:此题我们需将除已染色的节点的三个状态都赋值成1,已染色的节点染色的状态赋值成一其余的状态赋值成0,
换根dp:
链接题目:https://www.luogu.com.cn/problem/P3047
题意:给你一棵 n个点的树,点带权,对于每个节点求出距离它不超过 k 的所有节点权值和 mi。
#include<bits/stdc++.h>
using namespace std;
int n,k,head[100009],ver[100009*2],nex[100009*2],c[100009],f[100009][21],d[100009][21];
int cnt=0;
void add(int from,int to)
{
ver[++cnt]=to;
nex[cnt]=head[from];
head[from]=cnt;
}
void dfs1(int now,int fa)
{
for(int i=0;i<=k;i++)f[now][i]=c[now];
for(int i=head[now];i;i=nex[i])
{
int to=ver[i];
if(to==fa)continue;
dfs1(to,now);
for(int i=1;i<=k;i++)
f[now][i]+=f[to][i-1];
}
}
void dfs2(int now,int fa)
{
// d[now][1]+=f[fa][0];
// for(int i=2;i<=k;i++)
// {
// d[now][i]+=(d[fa][i-1]-f[now][i-2]);
// }//由父亲节点把当前的节点的d[now][k]推出,但是不知道为什么错了
for(int i=head[now];i;i=nex[i])
{
int to=ver[i];
if(to==fa)continue;
d[to][1]+=f[now][0];//k=1要单独考虑,因为下面的for循环当k=1数组下标会小于0
for(int i=2;i<=k;i++)
{
d[to][i]+=d[now][i-1]-f[to][i-2];
}//有当前节点已经做好,并由当前节点,将其儿子节点全部推出
dfs2(to,now);
}
}
int main()
{
cin>>n>>k;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++)cin>>c[i];
dfs1(1,-1);
for(int i=1;i<=n;i++)
{
for(int j=0;j<=k;j++)
d[i][j]=f[i][j];
}
dfs2(1,-1);
for(int i=1;i<=n;i++)
{
cout<<d[i][k]<<endl;
}
return 0;
}
一题比较好象的换根dp。换根dp就是先进行一次dfs将一个节点的答案算出,并由此节点通过与个节点答案的关系,并进行第二次dfs将算出节点的值推出其他还没算出节点的值。此题就是先将一号节点的答案通过第一次dfs算出,然后再通过节点间的关系转移过去就行了,主要是当k=1的时候要注意数组下标会小于0,所以要单独考虑。
题目链接:https://www.luogu.com.cn/problem/CF219D
#include<bits/stdc++.h>
using namespace std;
int head[200009],ver[200009*2],nex[200009*2],w[200009*2];
int cnt=0,f[200009];
void add(int from,int to,int val)
{
ver[++cnt]=to;
nex[cnt]=head[from];
w[cnt]=val;
head[from]=cnt;
}
void dfs(int now,int fa)
{
for(int i=head[now];i;i=nex[i])
{
int to=ver[i];
if(to==fa)continue;
dfs(to,now);
f[now]+=(f[to]+w[i]);
}
}
void dfs1(int now,int fa)
{
for(int i=head[now];i;i=nex[i])
{
int to=ver[i];
if(to==fa)continue;
if(w[i]==1)
{
f[to]=f[now]-1;
}
else
{
f[to]=f[now]+1;
}
dfs1(to,now);
}
}
int main()
{
int n;
cin>>n;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
add(x,y,0);
add(y,x,1);
}
dfs(1,-1);
dfs1(1,-1);
int minn=1e9;
for(int i=1;i<=n;i++)
{
if(minn>f[i])minn=f[i];
}
cout<<minn<<endl;
for(int i=1;i<=n;i++)
{
if(f[i]==minn)cout<<i<<" ";
}
return 0;
}
思路:对于这题,我们在建树的时候可以将指向我的边的权值设为1,由我指向别的节点的边设为0,这用链式前向星很容易实现。然后我们只需找出到个点路程总和最近的节点即可。这题也是用换根来实现的。
if(w[i]==1)//改变是由子节点指向当前节点的
{
f[to]=f[now]-1;//所以该子节点到个点距离和只需由当前节点减一,即将这条边翻转一下即可得到。
}
else //改变是由当前节点指向子节点的
{
f[to]=f[now]+1;
}
一般的换根都是由当前节点推他的子节点,这样不容易出错,由当前节点的父亲节点把当前节点推出,不仅代码不好写,思路也有点乱,以后换根还是由当前节点将他的子节点推出吧。