「动态规划」练习

0x5E「动态规划」练习

5E01 乌龟棋
考虑如何表示当前状态,d[x,i,j,k,l]表示目前在x位置,四种牌的使用张数分别为i,j,k,l时的最大分数。发现时间空间复杂度过高,因此考虑优化。
仔细观察后发现,只要知道四种牌的使用张数就能够算出当前位置x=i+2j+3k+4*l+1。因此修改状态定义如下:
状态表示:d[i,j,k,l]表示四种牌的使用张数分别为i,j,k,l时的最大分数
转移方程:
若\;i>0,d[i,j,k,l]=\max\left\{d[i,j,k,l],d[i-1,j,k,l]+a[i+2*j+3*k+4*l+1]\right\}
其他牌同理
边界:d[0,0,0,0]=a[1]
目标:d[cnt1,cnt2,cnt3,cnt4],其中\;cnt1,cnt2,cnt3,cnt4\;是四张牌的初始张数
时间复杂度:O(40^{4})
代码如下:(ps:为了简化代码,使用了引用表示d[i,j,k,l])

/*

*/
#define method_1
#ifdef method_1
/*

*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=350+5;
const int maxm=40+5;
const int INF=0x3f3f3f3f;
int d[maxm][maxm][maxm][maxm]={0},cnt1=0,cnt2=0,cnt3=0,cnt4=0,n,m,a[maxn],x;
int main() {
    ios::sync_with_stdio(false);
    //freopen("乌龟棋.in","r",stdin);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=m;i++){
        cin>>x;
        if(x==1) cnt1++;
        if(x==2) cnt2++;
        if(x==3) cnt3++;
        if(x==4) cnt4++;
    }
    d[0][0][0][0]=a[1];
    for(int i=0;i<=cnt1;i++){
        for(int j=0;j<=cnt2;j++){
            for(int k=0;k<=cnt3;k++){
                for(int l=0;l<=cnt4;l++){
                    int &temp=d[i][j][k][l];
                    int v=a[i+2*j+3*k+4*l+1];
                    if(i) temp=max(temp,d[i-1][j][k][l]+v);
                    if(j) temp=max(temp,d[i][j-1][k][l]+v);
                    if(k) temp=max(temp,d[i][j][k-1][l]+v);
                    if(l) temp=max(temp,d[i][j][k][l-1]+v);
                }
            }
        }
    }
    cout<<d[cnt1][cnt2][cnt3][cnt4];
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

5E02 花店橱窗
考虑状态设计,由于每种花要依次放置,所以以当前放置的花为阶段,以花瓶和花为状态建立状态表示。
状态表示:d[i,j]表示当前放到第i种花,到达第j个花瓶的最大美观度
转移方程:
对于第j个花瓶,有两种决策:放花和不放花
不放花的情况
d[i,j]=\max_{i+1 \leq j\leq v}\left\{d[i,j],d[i,j-1]\right\}
放花的情况
d[i,j]=\max_{i-1 \leq k\leq j-1}\left\{d[i,j],d[i-1,k]+a[i,j]\right\}
边界:d[0,i]=0,其中\;1\leq i \leq v。其他\;d\;值均为-INF(答案可能是负数)
目标:d[f,v]
时间复杂度:O(f*v^{2})
题目还要求记录方案,因此我使用了一个pre1[i,j]和pre2[i,j]记录d[i,j]取到最大值时的上一个状态d[x,y]的两个下标x和y。最后递归输出即可。
代码如下:(ps:和LCIS类似,这题的状态转移方程也有一样的特点,考虑每次随着j的增加 k的范围相对最外层循环只增不减(范围是[i-1,j-1]) 而且每次只增加一个单位。因此可以用变量记录d[i-1][j-1]的取到最优值时j-1的值,从而优化dp。几种方法的具体过程详见代码)

/*

*/
#define method_2
#ifdef method_1
/*
无方案 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=100+5;
const int INF=0x3f3f3f3f;
int d[maxn][maxn],a[maxn][maxn],f,v;
int main() {
    ios::sync_with_stdio(false);
    //freopen("花店橱窗.in","r",stdin);
    cin>>f>>v;
    for(int i=1;i<=f;i++) for(int j=1;j<=v;j++) cin>>a[i][j];
    memset(d,-INF,sizeof(d)); //注意答案可能是负数 所以要初始化为-INF 而不是0 
    for(int i=0;i<=v;i++) d[0][i]=0;
    for(int i=1;i<=f;i++){ //为了保证放花的顺序 这里第一重循环是循环花数 即阶段 每个阶段放一束花 
        for(int j=i;j<=v;j++){
            if(j-1>=i) d[i][j]=max(d[i][j-1],d[i][j]);
            for(int k=i-1;k<=j-1;k++) d[i][j]=max(d[i][j],d[i-1][k]+a[i][j]);
        }
    }
    cout<<d[f][v];
    return 0;
}
#endif
#ifdef method_2
/*
带方案
考虑每次随着j的增加 k的范围相对最外层循环只增不减(范围是[i-1,j-1]) 而且每次只增加一个单位。因此可以用变量记录d[i-1][j-1]的取到最优值时j-1的值,从而优化dp 
双重循环 优化dp 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=100+5;
const int INF=0x3f3f3f3f;
int d[maxn][maxn],a[maxn][maxn],pre1[maxn][maxn],pre2[maxn][maxn],f,v;
void print(int f,int v){
    if(pre1[f][v]==0) return;
    print(pre1[f][v],pre2[f][v]);
    cout<<pre2[f][v]<<" ";
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("花店橱窗.in","r",stdin);
    cin>>f>>v;
    for(int i=1;i<=f;i++) for(int j=1;j<=v;j++) cin>>a[i][j];
    memset(d,-INF,sizeof(d)); //注意答案可能是负数 所以要初始化为-INF 而不是0 
    for(int i=0;i<=v;i++) d[0][i]=0;
    for(int i=1;i<=f;i++){
        int k=i-1;
        for(int j=i;j<=v;j++){
            d[i][j]=d[i-1][k]+a[i][j]; 
            pre1[i][j]=i-1;
            pre2[i][j]=k;
            if(d[i-1][j]>d[i-1][k]){
                k=j;
            }
        }
    } 
    int last=0;
    for(int i=f;i<=v;i++){
        if(d[f][i]>d[f][last]){ //method_2不能保证d[f][v]就是最优解 需要循环找一下 目前原因未知 
            last=i;
        }
    }
    cout<<d[f][last]<<endl;
    print(f,last);
    cout<<last;
    return 0;
}
#endif
#ifdef method_3
/*
带方案
三重循环 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=100+5;
const int INF=0x3f3f3f3f;
int d[maxn][maxn],a[maxn][maxn],pre1[maxn][maxn],pre2[maxn][maxn],f,v;
void print(int f,int v){
    if(pre1[f][v]==0) return;
    print(pre1[f][v],pre2[f][v]);
    cout<<pre2[f][v]<<" ";
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("花店橱窗.in","r",stdin);
    cin>>f>>v;
    for(int i=1;i<=f;i++) for(int j=1;j<=v;j++) cin>>a[i][j];
    memset(d,-INF,sizeof(d)); //注意答案可能是负数 所以要初始化为-INF 而不是0 
    for(int i=0;i<=v;i++) d[0][i]=0;
    for(int i=1;i<=f;i++){
        for(int j=i;j<=v;j++){
            if(j-1>=i){
                if(d[i][j]<d[i][j-1]){
                    d[i][j]=d[i][j-1];
                    pre1[i][j]=i;
                    pre2[i][j]=j-1;
                }
            }
            for(int k=i-1;k<=j-1;k++){
                if(d[i][j]<d[i-1][k]+a[i][j]){
                    d[i][j]=d[i-1][k]+a[i][j];
                    pre1[i][j]=i-1;
                    pre2[i][j]=k;
                }
            }
        }
    }
    cout<<d[f][v]<<endl;
    int last;
    for(int i=f;i<=v;i++){
        if(d[f][i]==d[f][v]){
            last=i;
            break;
        }
    }
    print(f,last);
    cout<<last;
    return 0;
}
#endif

POJ1952 Buy Low Buy Lower
LIS的裸题,第一问的状态转移方程不再赘述,这里讲一下第二问怎么求方案。设[1...i]的最长递减子序列的长度为d[i],对应方案数为f[i],则:
若a[j]>a[i]\;且\;d[i]==d[j]+1,则 f[i]+=f[j] 其中\;0 \leq j \leq i-1
但需要注意的是题目中不同方案的定义:两个相同的子序列即使选出的位置不一样,也属于一样的子序列,所以j需要倒序循环,具体原理和反例见代码

/*

*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=5000+5;
const int INF=0x3f3f3f3f;
int n,a[maxn],d[maxn],f[maxn],ans,ans1;
int main() {
    ios::sync_with_stdio(false);
    //freopen("Buy Low Buy Lower.in","r",stdin);
    cin>>n;
    for(int i=1; i<=n; i++) cin>>a[i];
    f[0]=1;
    a[0]=INF;
    for(int i=1; i<=n; i++) {
        d[i]=1;
        for(int j=0; j<i; j++) {
            if(a[j]>a[i]) d[i]=max(d[i],d[j]+1);
        }
        /*
        //不能这么写 反例
        5
        3 2 3 1 1 
        需要输出 3 1
        而不是3 2 
        因为两个最长递减子序列都是3 2 1 所以只能算成一个 
        for(int j=0;j<i;j++){
            if(a[j]>a[i]&&d[i]==d[j]+1) f[i]+=f[j];
        }
        */
        ans=max(ans,d[i]);
    }
    for(int i=1; i<=n; i++) {
        for(int j=i-1; j>=0; j--) { //倒序循环  不能够顺序进行 反例在上面 
            if(a[j]>a[i]&&d[i]==d[j]+1) f[i]+=f[j];
            if(a[i]==a[j]) break; //相同就break 
        }
        cout<<f[i]<<" "; 
    }
    cout<<endl;
    for(int i=1; i<=n; i++) {
//      cout<<f[i]<<" ";
        if(d[i]==ans) {
            ans1+=f[i];
//          cout<<i<<" "<<f[i]<<endl;
        }
    }
//  cout<<endl;
    cout<<ans<<" "<<ans1;
    return 0;
}

POJ1934 Trip
求LCS的过程不再赘述,这里讲一下怎么求方案。
预处理出如下数组
d[i][j]表示s1[1...i]和s2[1...j]的LCS
f[i][j]表示s1[1...i]中的j+'a'最后一次出现的位置
g[i][j]表示s2[1...i]中的j+'a'最后一次出现的位置
然后从最后的状态向前dfs,每次尝试是否选取当前位的字母即可。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*

*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=100+5;
const int maxm=1000+5;
const int maxl=26+5;
const int INF=0x3f3f3f3f;
int n,m,d[maxn][maxn],f[maxn][maxl],g[maxn][maxl],tot=0;
//d[i][j]表示s1[1...i]和s2[1...j]的LCS
//f[i][j]表示s1[1...i]中的j+'a'最后一次出现的位置
//g[i][j]表示s2[1...i]中的j+'a'最后一次出现的位置
string s1,s2,s[maxm];
void dfs(int len1,int len2,int len,string now) {
    if(!len) {
        s[++tot]=now;
        return;
    }
    if(!len1||!len2) return;
    for(int i=0; i<=25; i++) {
        int pos1=f[len1][i],pos2=g[len2][i];
        if(d[pos1][pos2]!=len) continue;
        dfs(pos1-1,pos2-1,len-1,char(i+'a')+now);//注意不能写成dfs(pos1,pos2,len-1,now+char(i+'a'));
    }
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("Trip.in","r",stdin);
    cin>>s1>>s2;
    s1=' '+s1,s2=' '+s2;
    n=s1.length()-1,m=s2.length()-1;
//  for(int i=1;i<=n;i++) cout<<s1[i];
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++) {
            if(s1[i]==s2[j]) d[i][j]=max(d[i][j],d[i-1][j-1]+1);
            else d[i][j]=max(d[i][j-1],d[i-1][j]);
        }
    }
//  cout<<d[n][m];
    for(int i=1; i<=n; i++) {
        for(int j=0; j<=25; j++) {
            if(s1[i]==j+'a') f[i][j]=i;
            else f[i][j]=f[i-1][j];
        }
    }
    for(int i=1; i<=m; i++) {
        for(int j=0; j<=25; j++) {
            if(s2[i]==j+'a') g[i][j]=i;
            else g[i][j]=g[i-1][j];
        }
    }
    dfs(n,m,d[n][m],"");
    sort(s+1,s+tot+1);
    for(int i=1; i<=tot; i++) cout<<s[i]<<endl;
    return 0;
}
#endif

POJ1722 Substract
假设只有两个数a,b,则只可能产生a-b
假设只有三个数a,b,c,则可能产生a-b-c和a-b+c
假设只有四个数a,b,c,则可能产生a-b-c+d、a-b+c-d、a-b-c-d、a-b+c+d
以此类推,第一个数前一定是'+'号,第二个数前一定是'-'号,其他的数字都可以通过一定的顺序改变前面的符号。
因此,问题等价于求在n个数前面使用加减号使得结果为t的方案,其中第一个数前一定是'+'号,第二个数前一定是'-'号。
因此做出如下状态定义:
状态表示:d[i,j]表示前i个数计算后达到j时,第i个数前的符号。d[i,j]=1表示'+'号,d[i,j]=-1表示'-'号,d[i,j]=0表示未更新或无法达到。
转移方程:
对于第i个数字,有两种决策:前面放'+'号或者'-'号
若d[i-1,j]!=0,d[i,j+a[i]]=1,d[i,j-a[i]=-1
边界:d[1,a[1]]=1,d[2,a[1]-a[2]]=-1
目标:d[n,t]
时间复杂度:O(n*t)
这题还有两个注意的地方:
1 由于j可能为负数,所以要做坐标平移
2 考虑输出方案,因为有SPJ,所以可以构造一组合法的解:先去处理所有的加号,相当于把所有的加号都挪去了原来a3的位置,这样最后处理减号的时候,减a2就可以负负得正了。然后处理减号,把所有的减号都挪到了原来a2的位置,给a1来减。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
https://www.cnblogs.com/wyboooo/p/9775701.html
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=100+5;
const int base=10100+5;
const int INF=0x3f3f3f3f;
int n,t,a[maxn],d[maxn][base*2+200],ans[maxn];
int main() {
    ios::sync_with_stdio(false);
    //freopen("Substract.in","r",stdin);
    cin>>n>>t;
    t+=base;
    for(int i=1;i<=n;i++) cin>>a[i];
    d[1][a[1]+base]=1;
    d[2][a[1]-a[2]+base]=-1;
    for(int i=3;i<=n;i++){
        for(int j=-10000+base;j<=10000+base;j++){
            if(d[i-1][j]){
                d[i][j+a[i]]=1,d[i][j-a[i]]=-1;
            }
        }
    }
    for(int i=n;i>=2;i--){
        ans[i]=d[i][t];
        if(ans[i]==-1){
            t+=a[i];
        }
        if(ans[i]==1){
            t-=a[i];
        }
    }
    int cnt=0;
    for(int i=2;i<=n;i++){ //先处理加号(这里减一次 后面的循环再减一次 就成了加号) 
        if(ans[i]==1){
            cout<<i-cnt-1<<endl;
            cnt++;
        }
    }
    for(int i=2;i<=n;i++){
        if(ans[i]==-1){
            cout<<1<<endl;
        }
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ1187 陨石的秘密
题解链接
https://blog.csdn.net/flying_stones_sure/article/details/7954114
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
https://blog.csdn.net/flying_stones_sure/article/details/7954114
dp核心:为了防止一个序列被重复计数,用http://contest-hunter.org:83/contest/0x50%E3%80%8C%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E3%80%8D%E4%BE%8B%E9%A2%98/5302%20%E9%87%91%E5%AD%97%E5%A1%94相同的方法
即每次枚举括号的时候 依次枚举最靠左边外面的括号是()[]还是{}这样在记忆化搜索的时候就不会有重复或遗漏
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxl=15+5;
const int maxd=30+5;
const int mod=11380;
const int INF=0x3f3f3f3f;
int l1,l2,l3,d,f[maxl][maxl][maxl][maxd];
int dfs(int l1,int l2,int l3,int d) {
    if((!l1)&&(!l2)&&(!l3)) {
        return f[l1][l2][l3][d]=1;
    }
    if(!d) {
        return f[l1][l2][l3][d]=0;
    }
    if(f[l1][l2][l3][d]) {
        return f[l1][l2][l3][d];
    }
    int res=0;
    for(int i=0; i<=l3; i++) {
        if(i) res=(res+dfs(0,0,i-1,d-1)*dfs(l1,l2,l3-i,d)%mod)%mod;
        for(int j=0; j<=l2; j++) {
            if(j) res=(res+dfs(0,j-1,i,d-1)*dfs(l1,l2-j,l3-i,d)%mod)%mod;
            for(int k=0; k<=l1; k++) {
                if(k) res=(res+dfs(k-1,j,i,d-1)*dfs(l1-k,l2-j,l3-i,d)%mod)%mod;
            }
        }
    }
    return f[l1][l2][l3][d]=res;
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("陨石的秘密.in","r",stdin);
    cin>>l1>>l2>>l3>>d;
    if(d) cout<<(dfs(l1,l2,l3,d)-dfs(l1,l2,l3,d-1)+mod)%mod;
    else cout<<dfs(l1,l2,l3,d)%mod;
    //不能写成 else cout<<0; 因为dfs(0,0,0,0)=1 
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

5E07 划分大理石
多重背包,但由于只是判断可行性,所以直接照搬POJ1742 Coins就行了。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
可行性dp多重背包 不需要二进制拆分或者单调队列优化
方法参见coin 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=6+5;
const int maxm=200000+5;
const int INF=0x3f3f3f3f;
int a[maxn],f[maxm],used[maxm];
int main() {
    ios::sync_with_stdio(false);
//  freopen("划分大理石.in","r",stdin);
    while(1) {
        int flag=0,sum=0;
        for(int i=1; i<=6; i++) {
            cin>>a[i];
            sum+=a[i]*i; 
            if(a[i]!=0) flag=1;
        }
        if(!flag) break;
        if(sum&1){ //注意这里要特判 
            cout<<"Can't"<<endl;
            continue;
        }
        sum/=2;
        memset(f,0,sizeof(f));
        f[0]=1;
        for(int i=1;i<=6;i++){
            memset(used,0,sizeof(used));
            for(int j=i;j<=sum;j++){
                if(!f[j]&&f[j-i]&&used[j-i]<a[i]){
                    f[j]=1,used[j]=used[j-i]+1;
                }
            }
        }
        if(f[sum]) cout<<"Can"<<endl;
        else cout<<"Can't"<<endl;
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ2176 Folding
区间DP,讨论两种决策,分割区间和将区间压缩,分别转移即可,具体到实现上,可以借助一个struct来记录[i...j]的最短长度len和对应方案s。利用sprintf和strcpy,strcat转移即可。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*

*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=100+5;
const int INF=0x3f3f3f3f;
int n;
string s;
struct node{
    char s[maxn]; //记录方案 
    int len;
}f[maxn][maxn];
int main() {
    ios::sync_with_stdio(false);
    //freopen("Folding.in","r",stdin);
    cin>>s,s=' '+s,n=s.length();
    for(int i=1;i<=n;i++){
        f[i][i].len=1,f[i][i].s[0]=s[i];
    }
    for(int len=2;len<=n;len++){
        for(int i=1;i<=n-len+1;i++){
            int j=i+len-1;
            f[i][j].len=INF;
            for(int k=1;k<=len/2;k++){ //贪心原则 让压缩后的子串(除去数字和括号)尽量短 因此从小到大枚举压缩长度
                if(len%k) continue;
                int l=i,r=i+k;
                while(r<=j&&s[l++]==s[r]) r++;
                if(r>j){ //可以完全填充[i,j] 
                    sprintf(f[i][j].s,"%d",len/k);
                    strcat(f[i][j].s,"(");
                    strcat(f[i][j].s,f[i][i+k-1].s);
                    strcat(f[i][j].s,")");
                    f[i][j].len=strlen(f[i][j].s);
                    break;//贪心原则 让压缩后的子串(除去数字和括号)尽量短 
                }
            }
            for(int k=i;k<j;k++){
                if(f[i][j].len>f[i][k].len+f[k+1][j].len){
                    f[i][j].len=f[i][k].len+f[k+1][j].len;
                    strcpy(f[i][j].s,f[i][k].s);
                    strcat(f[i][j].s,f[k+1][j].s);
                }
            }
        }
    }
    cout<<f[1][n].s;
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

5E09 能量项链
区间DP,具体细节详见代码。
代码如下

/*

*/
#define method_1
#ifdef method_1
/*

*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=200+5;
const int INF=0x3f3f3f3f;
int n,a[maxn],ans=0,d[maxn][maxn];
int main() {
    ios::sync_with_stdio(false);
    //freopen("能量项链.in","r",stdin);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i],a[i+n]=a[i];
    for(int len=2;len<=n;len++){
        for(int i=1;i<=2*n-len+1;i++){
            int j=len+i-1;
            for(int k=i;k<j;k++){
                d[i][j]=max(d[i][j],d[i][k]+d[k+1][j]+a[i]*a[k+1]*a[j+1]);  
                //a[i]*a[k+1]*a[j+1]的解释
                /*
                a[]={0,3,4,5,6,7,8} i=1,j=5,k=2 a[i]=3,a[k]=4,a[k+1]=5,a[j]=7
                则此时a分为两段[3,4]和[5,6,7,8]
                相当于两端珠子(3,4)(4,5)和(5,6)(6,7)(7,8)
                分别合并后两端珠子变成(3,5)和(5,8) 
                合并后的珠子为(3,8) 这一次合并放出的能量是3*5*8=a[1]*a[3]*a[6]=a[i]*a[k+1]*a[j+1] 
                */ 
            }
        }
    }
    for(int i=1;i<=n+1;i++){
        ans=max(ans,d[i][i+n-1]);
    }
    cout<<ans;
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ1191 棋盘分割
化简均方差公式
方差\;^{2}\;=\;\frac{1}{n}\sum_{i=1}^{矩形块数\;n}(x_{i}-\hat{x}^{2})\;=\;\frac{1}{n}\sum_{i=1}^{矩形块数\;n}(x_{i}^{2})-\hat{x}^{2}
因此,我们发现,\hat{x}^{2}为定值,所以只考虑计算\sum_{i=1}^{矩形块数\;n}(x_{i}^{2})其中x_{i}为第i个矩形的元素和
因此用一个五维dp数组进行递推即可,代码里有博客链接,那里有关于状态定义,状态转移方程和初值的详解。
代码如下

/*
注意题目要求 ——使各矩形棋盘总分的均方差最小
公式中xi为第i块矩形棋盘的总分 而不是某个矩形的第i个元素!
因此在dp的时候 dp初态是矩形各个元素的和的平方 而不是平方和!
https://www.cnblogs.com/scau20110726/archive/2013/02/27/2936050.html
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=15+2;
const int maxl=8+2;
const int INF=0x3f3f3f3f;
double d[maxn][maxl][maxl][maxl][maxl],s[maxl][maxl];
int n;
double cal(int i,int j,int k,int l) {
    double ans=s[k][l]-s[k][j-1]-s[i-1][l]+s[i-1][j-1];
    return ans*ans;
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("棋盘分割.in","r",stdin);
    scanf("%d",&n);
    for(int i=1; i<=8; i++) {
        for(int j=1; j<=8; j++) {
            scanf("%lf",&s[i][j]);
            s[i][j]=s[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];
        }
    }
    for(int m=1; m<=n; m++)
        for(int i=1; i<=8; i++) {
            for(int j=1; j<=8; j++) {
                for(int k=i; k<=8; k++) {
                    for(int l=j; l<=8; l++) {
                        if(m==1) d[1][i][j][k][l]=cal(i,j,k,l);
                        else d[m][i][j][k][l]=1<<30;
                    }
                }
            }
        }
//  printf("%lf\n",cal(1,2,1,2));
    for(int m=2; m<=n; m++) {
        for(int i=1; i<=8; i++) {
            for(int j=1; j<=8; j++) {
                for(int k=i; k<=8; k++) {
                    for(int l=j; l<=8; l++) {
                        double &temp=d[m][i][j][k][l];
                        for(int x1=i; x1<k; x1++) { //注意x1的范围  
                            temp=min(temp,d[m-1][i][j][x1][l]+cal(x1+1,j,k,l));
                            temp=min(temp,d[m-1][x1+1][j][k][l]+cal(i,j,x1,l));
                            //由于存在顺序问题 所以要选一个作为不会再切的矩形 
                            //如果这里只有一行 会wa 
                        }
                        for(int x2=j; x2<l; x2++) {
                            temp=min(temp,d[m-1][i][j][k][x2]+cal(i,x2+1,k,l));
                            temp=min(temp,d[m-1][i][x2+1][k][l]+cal(i,j,k,x2));
                        }
                    }
                }
            }
        }
    }
    printf("%.3lf",sqrt(d[n][1][1][8][8]/n-(s[8][8]/n)*(s[8][8]/n)));
    return 0;
}

POJ1390 Blocks
首先用一个结构体,来记录一个个方块组的长度和颜色。
显然单个的一个方块组可以视为一个长度为1的区间,可以作为区间dp的边界。
之后考虑状态定义:
如果只是用状态d[i,j]来描述消去方块i到方块j获得的分数是无法形成递推关系的。 因为在这个时候对于最右边的大块有两种选择,一是直接消去,二是将其与左边某个大块 合并删除。而对于选择二来说,删去未必是最有方案,也许还应该与左边的某方块合并后 消去。所以只有两个参数是无法准确描述状态并形成递推关系的。
因此附加一个参数来维护状态:
d[l,r,len]表示消去方块组l至r,同时r右边与r同色的方块长度(也称之为个数)为len。
此时递推关系:
直接消去r:d[l,r,len]=d[l,r-1,0]+(b[r].l+len)*(b[r].l+len)
将j与左边某个方块k合并:d[l,r,len]=d[k+1,r-1,0]+d[l,k,b[r].l+len]
代码如下

/*

*/
#define method_1
#ifdef method_1
/*
https://blog.csdn.net/qq_29169749/article/details/52202149
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=200+5;
const int INF=0x3f3f3f3f;
int d[maxn][maxn][maxn],n,a[maxn],t,kase=0,cnt;
struct Segment{
    int c,l;
}b[maxn];
int dfs(int l,int r,int len){
    if(l>r) return 0;
    if(l==r) return (b[l].l+len)*(b[l].l+len);
    if(d[l][r][len]!=-1) return d[l][r][len];
    int ans=dfs(l,r-1,0)+(b[r].l+len)*(b[r].l+len);
    for(int k=l;k<r;k++){
        if(b[r].c==b[k].c){
            ans=max(ans,dfs(k+1,r-1,0)+dfs(l,k,b[r].l+len));
        }
    }
    return d[l][r][len]=ans;
} 
int main() {
    ios::sync_with_stdio(false);
    //freopen("Blocks.in","r",stdin);
    cin>>t;
    while(t--){
        cout<<"Case "<<++kase<<": ";
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i];
        memset(d,-1,sizeof(d));
        cnt=1;
        for(int i=1;i<=n;i++){
            if(a[i]==a[i-1]){
                b[cnt].l++;
            }
            else{
                b[++cnt].c=a[i];
                b[cnt].l=1;
            }
        }
        cout<<dfs(1,cnt,0)<<endl;
    }
    return 0;
}
#endif
#ifdef method_2
/*

*/

#endif
#ifdef method_3
/*

*/

#endif

POJ1463 Strategic game
很基础的树形dp,每个节点有两个状态,跑一次dfs即可。
但是需要注意的是,这里要求的是看守所有的“边”。也就是说,每条边至少有一个端点存在守卫,因此每个点状态只可能是如下两个状态之一:该节点放置守卫来守卫这个点到其子节点的边,或者该节点不放置守卫,但靠在所有子节点放置守卫来守卫这个点到其子节点的边。
但如果是要守卫树上的所有的“点”,那么就不能做这样简单的状态定义了。因为每个点可以有三种状态:被父亲守卫,被自己守卫,被儿子守卫。具体转移方程参见vijos 1144小胖守皇宫。
本题代码如下

/*

*/
#define method_1
#ifdef method_1
/*
和vijos 1144不同 那道题目要求守卫所有点 因此每个点有三种守卫情况:被自己守卫 被儿子守卫 被父亲守卫 所以状态分三种
换句话说 就是会出现某些边不会被覆盖的情况 反例见method_2 
https://www.cnblogs.com/linda-fcj/p/7206257.html

但是这道题目要求的是 守卫所有边 每条边有两个端点 因此只有两种状态:被父端点守卫 被子端点守卫
因此设f[x][0]表示守卫以x节点为根的子树 x节点不放置士兵的最小代价
f[x][1]表示守卫以x节点为根的子树 x节点放置士兵的最小代价
转移一下就行了
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=1500+5;
const int INF=0x3f3f3f3f;
int n,f[maxn][2],deg[maxn];
vector<int>son[maxn];
void dfs(int x) { //flag=0 靠自己保护 flag=1 靠儿子保护
    f[x][0]=0,f[x][1]=1;
    for(int i=0; i<son[x].size(); i++) {
        int y=son[x][i];
        dfs(y);
        f[x][0]+=f[y][1];
        f[x][1]+=min(f[y][0],f[y][1]);
    }
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("Strategic game.in","r",stdin);
    while(~scanf("%d",&n)) {
        memset(deg,0,sizeof(deg));
        for(int i=0; i<=n-1; i++) son[i].clear();
        int now,num,temp;
        for(int i=0; i<=n-1; i++) {
            scanf("%d:(%d)",&now,&num);
            for(int j=0; j<=num-1; j++) {
                scanf("%d",&temp);
                son[now].push_back(temp);
                deg[temp]++;
//              son[temp].push_back(now);
            }
        }
        for(int i=0; i<=n-1; i++) {
            if(!deg[i]) {
                dfs(i);
                cout<<min(f[i][0],f[i][1])<<endl;
                break;
            }
        }
    }
    return 0;
}
#endif
#ifdef method_2
/*
魔改后的vijos 1144的代码 只过了两个点
反例如下 
6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 10 0
6 5 0
应该选择3 4 5 6四个点 得到答案24
但是这个程序会输出25
原因在于 最后方案下 1->2的边两端都没有被覆盖
但是这时 所有顶点都是被覆盖到的 
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=1500+5;
const int INF=0x3f3f3f3f;
int n,f[maxn][2],deg[maxn],a[maxn];
vector<int>son[maxn];
void dfs(int x) { //flag=0 靠自己保护 flag=1 靠儿子保护
    f[x][0]=0,f[x][1]=a[x];
    for(int i=0; i<son[x].size(); i++) {
        int y=son[x][i];
        dfs(y);
        f[x][0]+=f[y][1];
        f[x][1]+=min(f[y][0],f[y][1]);
    }
}
int main() {
    ios::sync_with_stdio(false);
    //freopen("Strategic game.in","r",stdin);
    scanf("%d",&n);
    int pos,now,num,temp;
    for(int i=1; i<=n; i++) {
        scanf("%d",&pos);
        scanf("%d%d",&a[pos],&num);
        for(int j=0; j<=num-1; j++) {
            scanf("%d",&temp);
            son[pos].push_back(temp);
            deg[temp]++;
//          son[temp].push_back(now);
        }
    }
    for(int i=1; i<=n; i++) {
        if(!deg[i]) {
            dfs(i);
            cout<<min(f[i][0],f[i][1])<<endl;
            break;
        }
    }

    return 0;
}
#endif
#ifdef method_3
/*

*/

#endif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • 专业考题类型管理运行工作负责人一般作业考题内容选项A选项B选项C选项D选项E选项F正确答案 变电单选GYSZ本规程...
    小白兔去钓鱼阅读 8,990评论 0 13
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,342评论 0 2
  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,657评论 0 89
  • 树形动态规划,顾名思义就是树+DP,先分别回顾一下基本内容吧:动态规划:问题可以分解成若干相互联系的阶段,在每一个...
    Mr_chong阅读 1,486评论 0 2
  • 动态规划(Dynamic Programming) 本文包括: 动态规划定义 状态转移方程 动态规划算法步骤 最长...
    廖少少阅读 3,282评论 0 18