这几道经典的题本不应再由本菜啰嗦,无奈手痒总想贴点代码~
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
首先将序列变成元素有两个属性(颜色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; }