几道黑书上的简单DP题

这几道经典的题本不应再由本菜啰嗦,无奈手痒总想贴点代码~

POJ1141 括号的匹配

dp[i][j]表示从i到j使括号匹配完整的最少需要添加的括号,有

dp[i][i]=1;

dp[i][j]=min(dp[i][k],[k+1][j]);

当s[i]=='(',s[j]==')'或者s[i]=='[',s[j]==']'时,dp[i][j]=min(dp[i][j],dp[i+1][j-1]);

记录每个dp[i][i]添加的部分是什么,添的位置在前还是在后,在dp的过程中,记录dp的路径,dp后沿路径得解;

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

const int NN=250;

int n,dp[NN][NN],r[NN][NN];
char s[NN],ad[NN];

void dfs(int i,int j)
{
    if (i>j) return;
    if (i==j)
    {
        if (r[i][i]==-1) putchar(s[i]),putchar(ad[i]);
        else             putchar(ad[i]),putchar(s[i]);
        return;
    }

    if (r[i][j]==0)  { putchar(s[i]); dfs(i+1,j-1); putchar(s[j]); }
    else             { dfs(i,r[i][j]); dfs(r[i][j]+1,j); }
}

int main()
{
    int l,i,j,k,tmp1,tmp2;

    s[0]='0';
    gets(s+1);
    n=strlen(s)-1;
    memset(dp,0,sizeof(dp));
    for (i=1; i<=n; i++)
    {
        dp[i][i]=1;
        if (s[i]=='(') { ad[i]=')'; r[i][i]=-1; }
        if (s[i]=='[') { ad[i]=']'; r[i][i]=-1; }
        if (s[i]==')') { ad[i]='('; r[i][i]=-2; }
        if (s[i]==']') { ad[i]='['; r[i][i]=-2; }
    }
    for (l=1; l<n; l++)
      for (i=1; i<=n-l; i++)
      {
          j=i+l;
          tmp1=n+1;
          for (k=i; k<j; k++)
              if (dp[i][k]+dp[k+1][j]<tmp1)
              {
                  tmp1=dp[i][k]+dp[k+1][j];
                  tmp2=k;
              }
          if ((s[i]=='(' && s[j]==')' || s[i]=='[' && s[j]==']') && dp[i+1][j-1]<tmp1)//少打了个括号,WA了几次
          {
              tmp1=dp[i+1][j-1];
              tmp2=0;
          }
          dp[i][j]=tmp1; r[i][j]=tmp2;
      }

    dfs(1,n);
    printf("\n");
    return 0;
}

POJ2288 状态压缩入门,不解释不代表此题不经典不好,就是太经典的状态压缩了

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

typedef long long LL;

int n,m;
LL dp[2][1<<12];

inline bool ok(int x,int y)
{
    int i,j,k;
    for (k=1; k<=m; k++)
    {
        i=x&1; j=y&1;
        x>>=1; y>>=1;
        if (i && j) return false;
        if (!i && !j)
        {
            if (k<m && !(x&1) && !(y&1))
            {
                x>>=1;
                y>>=1;
                k++;
            }
            else return false;
        }
    }
    return true;
}

int main()
{
    int i,j,t,c,lim;

    while (scanf("%d%d",&n,&m),n|m)
    {
        if ((n&1) && (m&1)) { printf("0\n"); continue; }
        lim=(1<<m)-1;
        memset(dp[0],0,sizeof(dp[0]));
        dp[0][0]=1;
        for (t=1; t<=n; t++)
        {
            c=t&1;
            memset(dp[c],0,sizeof(dp[c]));
            dp[c][0]=dp[c^1][lim];
            for (i=0; i<lim; i++) if (dp[c^1][i])
              for (j=0; j<=lim; j++) if (ok(i,j))
                  dp[c][j]+=dp[c^1][i];
        }
        printf("%I64d\n",dp[c][0]);
    }
    return 0;
}
POJ1112 对反图的连通分量染色,两组分组背包,其实也不是太好想到

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

const int NN=110;

int n,bn,flag,mp[NN][NN],color[NN],belong[NN],cnt[NN][2];
int ans[NN][NN],id[NN][NN],dp[NN][NN];

void dfs(int u,int c)
{
    color[u]=c;
    belong[u]=bn;
    cnt[bn][c]++;
    for (int i=1; i<=n; i++)
    {
        if (!mp[u][i]) continue;
        if (color[i]==-1) dfs(i,c^1);
        else if (color[i]==c) { flag=0; return; }
    }
}

void get_ans(int i,int k)
{   if (i==0) return;
    ans[i][id[i][k]]=1;
    ans[i][id[i][k]^1]=0;
    get_ans(i-1,k-cnt[i][id[i][k]]);
}

int main()
{
    int i,j,k;

    scanf("%d",&n);
    memset(mp,1,sizeof(mp));

    for (i=1; i<=n; i++)
    {
        mp[i][i]=0;
        while (scanf("%d",&j),j)
            mp[i][j]=0;
    }

    for (i=1; i<=n; i++)
      for (j=1; j<=n; j++) if (mp[i][j])
         mp[j][i]=1;

    bn=0; flag=1;
    memset(color,-1,sizeof(color));
    memset(cnt,0,sizeof(cnt));

    for (i=1; i<=n && flag; i++) if (color[i]==-1) bn++,dfs(i,0);

    if (!flag) { puts("No solution"); return 0; }

    memset(dp,0,sizeof(dp));
    dp[0][0]=1;
    for (i=1; i<=bn; i++)
      for (j=0; j<=1; j++)
      {
          for (k=(n+1)/2; k>=cnt[i][j]; k--) if (dp[i-1][k-cnt[i][j]])
          {
              dp[i][k]=1;
              id[i][k]=j;
          }
      }
    for (k=(n+1)/2; k; k--) if (dp[bn][k]) break;

    get_ans(bn,k);

    printf("%d",k);
    for (i=1; i<=n; i++) if (ans[belong[i]][color[i]]) printf(" %d",i);
    printf("\n%d",n-k);
    for (i=1; i<=n; i++) if (!ans[belong[i]][color[i]]) printf(" %d",i);
    printf("\n");

    return 0;
}
POJ1848 树形DP

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;

const int NN=110;
const int INF=16843009;

int n,ans=0,flag=0;
int  dp[NN][3];
bool vis[NN]={0};
vector<int> adj[NN];

void dfs(int u)
{
    int tmp,i;
    int cnt0=0;
    int cnt1=INF;
    int cnt2=INF;
    int cnt3=INF;

    vis[u]=true;
    for (i=0; i<adj[u].size(); i++)
    {
        int v=adj[u][i];
        if (vis[v]) continue;

        dfs(v);

        cnt0+=dp[v][0];
        tmp=min(dp[v][1],dp[v][2])-dp[v][0];
        if (tmp<cnt1)
        {
            cnt2=cnt1;
            cnt1=tmp;
        }
        else if (tmp<cnt2) cnt2=tmp;
        if (dp[v][2]-dp[v][0]<cnt3) cnt3=dp[v][2]-dp[v][0];
    }

    if (cnt0<dp[u][1])             dp[u][1]=cnt0;
    if (cnt0+cnt1<dp[u][2])        dp[u][2]=cnt0+cnt1;
    if (cnt0+cnt1+cnt2<dp[u][0])   dp[u][0]=cnt0+cnt1+cnt2+1;
    if (cnt0+cnt3<dp[u][0])        dp[u][0]=cnt0+cnt3+1;

}

int main()
{
    int i,u,v;

    scanf("%d",&n);
    for (i=1; i<n; i++)
    {
        scanf("%d%d",&u,&v);
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    memset(dp,1,sizeof(dp));
    dfs(1);
    if (dp[1][0]>n) printf("-1\n");
    else            printf("%d\n",dp[1][0]);

    return 0;
}
ZOJ1234 简单线性DP(滚动数组)

将原始数据的筷子长按从长到短存后,

记dp[i][j][0](i>=3*j)为前i个筷子分为j组的最小badness和,不使用第i个筷子;dp[i][j][1](i>=3*j)为前i个筷子分为j组的最小badness和,使用第i个筷子。

有 dp[i][j][0]=min{ dp[i-1][0],dp[i-1][1] }; dp[i][j][1]=min{ dp[i-2][0],dp[i-2][j-1][1] }+(a[i-1]-a[i])^2;

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

int a[5005],dp[3][1002][2];

int main()
{
    int n,m,i,j,c,k1,k2,T;
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d",&m,&n);
        m+=8;
        for (i=n; i; i--) scanf("%d",&a[i]);

        memset(dp,0x7f,sizeof(dp));
        dp[0][1][1]=(a[2]-a[3])*(a[2]-a[3]);
        for (i=0; i<=2; i++) dp[0][0][0]=dp[i][0][1]=0;

        for (i=4; i<=n; i++)
        {
            c=i%3; k1=(c+2)%3; k2=(c+1)%3;
            for (j=1; j<=i/3 && j<=m; j++)
            {
                dp[c][j][0]=min(dp[k1][j][0],dp[k1][j][1]);
                dp[c][j][1]=min(dp[k2][j-1][0],dp[k2][j-1][1])+(a[i-1]-a[i])*(a[i-1]-a[i]);
            }
        }
        printf("%d\n",min(dp[c][m][0],dp[c][m][1]));
    }
    return 0;
}
POJ1947 树形DP+背包

#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
using namespace std;

const int NN=160;

vector<int> adj[NN];
bool vis[NN]={0};
bool mark[NN][NN]={0};
int n,p,dp[NN][NN];

void dfs(int u)
{
    vis[u]=true;

    int size=adj[u].size();

    dp[u][1]=size;
    mark[u][1]=true;

    for (int i=0; i!=size; i++)
    {
        int v=adj[u][i];
        if (vis[v]) continue;
        dfs(v);
        for (int j=n; j; j--)
            for (int k=1; k<j; k++) if (mark[v][k] && mark[u][j-k])
            {
                if (!mark[u][j])
                {
                    mark[u][j]=true;
                    dp[u][j]=dp[u][j-k]+dp[v][k]-2;
                }
                else if (dp[u][j]>dp[u][j-k]+dp[v][k]-2) dp[u][j]=dp[u][j-k]+dp[v][k]-2;
            }
    }
}

int main()
{
    int u,v;

    scanf("%d%d",&n,&p);
    for (int i=1; i<n; i++)
    {
        scanf("%d%d",&u,&v);
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    dfs(1);

    int ans=n;
    for (int i=1; i<=n; i++) if (mark[i][p] && dp[i][p]<ans) ans=dp[i][p];
    printf("%d\n",ans);

    return 0;
}
POJ1390 DP
题意:给出一个序列(序列长度l <= 200,序列元素值(原题为小方块的颜色)Ai <= l),每次可以从序列中选取子串Ai~j(要求字串中元素颜色一致),获得字串长度平方的价值,然后将字串从原序列中删除(序列剩下的元素合并,顺序不变)。重复取字串直至序列被取完,问能获取的最大价值。

首先将序列变成元素有两个属性(颜色a,个数c)的新序列。在做的过程中,原本设计了一个二维的状态,后来发现不好做。考虑到消除了一个字串后,可能使得相同颜色的元素合并到了一起,然而最后一起删除的字串在最原始的序列中的位置不一定是连续的,这里我说的连续的意思是,比如这个例子:{1 2 2 1 2 2 1},最好的做法是先取中间的1,再取4个2,最后一起取两边的2个1。最后一步所取的2个1在原序列中的中间还有一个1,也就是这两个1不连续。序列更复杂时,最优解中某一步所取的字串来自的位置更是各种情况都有,二维的状态(dp[i][j]表示新序列i到j取完所得价值)信息太少,转移时要就所取字串的位置来枚举所有的可能的组合,很是复杂。估计很多人思维和我一样。虽然也想了要加维,又不好从何入手。

#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <iostream>
using namespace std;

typedef pair<int, int> pii;

const int N = 210;

int n, a[N], c[N], last[N], prev[N], dp[N][N][N];

void init() {
	int x, y = -1, k = 0;

	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> x;
		if (x == y) c[k-1]++;
		else a[k]= x, c[k++] = 1;
		y = x;
	}
	n = k;

	memset(last, -1, sizeof(last));
	for (int i = 0; i < n; i++) {
		prev[i] = last[a[i]];
		last[a[i]] = i;
	}
	memset(dp, -1, sizeof(dp));
}

int DP(int i, int j, int k) {
	if (dp[i][j][k] != -1) return dp[i][j][k];
	if (i > j) return dp[i][j][k] = -10000000;

	if (i == j) return dp[i][j][k] = (c[i]+k) * (c[i]+k);

	int ret = DP(i, j-1, 0) + (c[j]+k) * (c[j]+k);
	for (int p = prev[j]; p >= i; p = prev[p]) {
		int tmp = DP(i, p, c[j]+k) + DP(p+1, j-1, 0);
		ret = max(ret, tmp);
	}
	return dp[i][j][k] = ret;
}

int main() {
	int cas;
	cin >> cas;
	for (int i = 0; i < cas; i++) {
		init();
		cout << "Case " << i+1 << ": " << DP(0, n-1, 0) << endl;
	}
	return 0;
}

待续。。。

你可能感兴趣的:(动态规划)