例题9-1
本题目指标函数的变量为时间和站的编号,指标函数为函数值为在T时刻到达n站的等待时间。有三种状态转移的方法,一种为等待1分钟,一种为搭乘右边的车,一种为搭乘左边的车,要求得d[i][j]。那么就要求得搭乘左边的车d[i+t[j-1]][j-1]和d[i+t[j]][j+1]的最优值。如此就将这个问题进行了分解。初始状态就为d[T][n]=0;最终要求得是d[0][1].
代码如下:
#include
using namespace std;
const int INF=60000;
const int maxn = 50+10;
const int maxt = 200+10;
int main()
{
//freopen("datain.txt","r",stdin);
//freopen("dataout.txt","w",stdout);
int kase = 0,n;
while(cin>>n&&n)
{
int T,tmp;
cin>>T;
int dp[maxt][maxn],t[maxn],has_train[maxt][maxn][2];
memset(has_train,0,sizeof(has_train));
for (int i=1;i>t[i];
cin>>tmp;
for(int i=0;i>now;
if(now<=T)
has_train[now][1][0]=1;
for (int j=1;j>tmp;
for(int i=0;i>now;
if(now<=T)
has_train[now][n][1]=1;
for (int j=n;j>1;j--)
{
if(now+t[j-1]<=T)
{
now+=t[j-1];
has_train[now][j-1][1]=1;
}
else break;
}
}
//填表法
for (int i = 1;i<=n-1;i++) dp[T][i] = INF;//T时刻其他车站为无穷大
dp[T][n] = 0;//起点为在T时刻n车站等待时间,终点为在0时刻1车站的等待时间
//这样就初始化了起点。在dp这张表里面就等于填了一行
for(int i=T-1;i>=0;i--)//填写行
for(int j=1;j<=n;j++)//填写列
{//填写列的策略是:3种方式
dp[i][j]=dp[i+1][j]+1;
// 表示在i时刻需要等到1分钟才能到达i+1时刻状态
if(j1&&has_train[i][j][1]&&i+t[j-1]<=T)
dp[i][j]=min(dp[i][j],dp[i+t[j-1]][j-1]);
//dp[i+t[j]][j+1] 表示从 dp[i+t[j]][j+1] 到dp[T][n]的最小等待时间 。
//表示搭乘往左的火车
}
cout<<"Case Number "<<++kase<<": ";
if(dp[0][1]>=INF) cout <<"impossible"<
例题9-2
本题目是典型的在转化为DAG图中的动态规划,采用记忆化搜索方式。本题的技巧在于对状态的存储。以立方体编号和使用的高的序号作为存储。另外,在输入的时候,通过对3个长度进行排序,简化了后面动态规划的选择比较。
//这道题的代码参看了书中的代码仓库
//算法:DAG上的最长路,状态为(idx, k),即当前顶面为立方体idx,其中第k条边(排序后)为高
#include
#include
#include
using namespace std;
#define REP(i,n) for(int i = 0; i < (n); i++)
const int maxn = 30 + 5;
int n, blocks[maxn][3], d[maxn][3];
void get_dimensions(int* v, int b, int dim) {
int idx = 0;
REP(i,3) if(i != dim) v[idx++] = blocks[b][i];
}
int dp(int i, int j) {
int& ans = d[i][j];
if(ans > 0) return ans;//记忆化搜索
ans = 0;
int v[2], v2[2];
get_dimensions(v, i, j);
//取长和宽记录在V中
REP(a,n) REP(b,3) {
get_dimensions(v2, a, b);
if(v2[0] < v[0] && v2[1] < v[1]) ans = max(ans, dp(a,b));
//满足条件的可以进行,状态的转移。
}
ans += blocks[i][j];
//当没有条件进行状态转移的时候,说明已经是最后一个立方体了。
//那么加上ans进行返回。
return ans;
}
int main() {
int kase = 0;
while(scanf("%d", &n) == 1 && n) {
REP(i,n) {
REP(j,3) scanf("%d", &blocks[i][j]);
sort(blocks[i], blocks[i]+3);
//点睛之笔,使得后来的比较非常方面
}
memset(d, 0, sizeof(d));//d是全置0
int ans = 0;
REP(i,n) REP(j,3) ans = max(ans, dp(i,j));
//在未知起点的情况下,遍历所有起点,能够得到的最大值,选择出来。
printf("Case %d: maximum height = %d\n", ++kase, ans);
}
return 0;
}
例题9-3
通过书中的分析,设定合适的状态,并且有明确边界,本题使用递推法,可以参照例题9-1的框架。要注意的点是顶点有1000个,要申明成全局变量才能保证空间的合理分配。
#include
using namespace std;
const int maxn = 1000+10;
double d[maxn][maxn];
double dist(int x1,int y1,int x2,int y2)
{
return sqrt(double(abs((x1-x2))*abs((x1-x2))+abs((y1-y2))*abs((y1-y2))));
}
int main()
{
//freopen("datain.txt","r",stdin);
//freopen("dataout.txt","w",stdout);
int n;
while(cin>>n&&n)
{
int x[maxn],y[maxn];
for (int i=1;i<=n;i++) cin>>x[i]>>y[i];
for (int j=1;j1;i--)
for(int j=1;jj)
{
d[i][j]=min(d[i+1][j]+dist(x[i],y[i],x[i+1],y[i+1]),d[i+1][i]+dist(x[j],y[j],x[i+1],y[i+1]));
}
}
printf("%.2lf\n",d[2][1]+dist(x[1],y[1],x[2],y[2]));
}
return 0;
}
例题9-4
本题目要注意记录和输出路径的方式,另外一个是对一些特殊输入的处理比如只有1行或者1列。初值很重要。
#include
using namespace std;
const int maxn = 100+10;
const int INF = 60000;
int main()
{
//freopen("datain.txt","r",stdin);
//freopen("dataout.txt","w",stdout);
int r,c;
while(cin>>r>>c&&r)
{
int d[maxn][maxn],a[maxn][maxn],next[maxn][maxn];
//输入
int ans=INF,first=0;
for(int i=0;i>a[i][j];
//递推
for(int i=c-1;i>=0;i--) //i是列,j是行
for(int j=0;j
例题9-5
本题目是采用0-1背包的模型,两个优化的参数,那么在优化过程中,就要记录这两个参数。具体看代码
#include
using namespace std;
const long long maxn=50+10;
int d[maxn][180*maxn+678];
long long st[maxn][180*maxn+678];
int main()
{
//freopen("datain.txt","r",stdin);
//freopen("dataout.txt","w",stdout);
int n,rnd=1;
cin>>n;
while(n--)
{
int num,songs[maxn];
long long t;
cin>>num>>t;
memset(d,0,sizeof(d));
memset(st,0,sizeof(st));
for (int i=1;i<=num;i++)
{
cin>>songs[i];
}
for (int i=num;i>=1;i--)
for(int j=0;j<=t-1;j++)
{
d[i][j]= (i==num? 0: d[i+1][j]);
st[i][j]=(i==num? 0: st[i+1][j]);
if(j>=songs[i])
{
if(d[i][j]st[i][j])
{
st[i][j]=st[i+1][j-songs[i]]+songs[i];
}
}
}
cout<<"Case "<
例题9-6
本题目实际上,是从几种灯泡中,选取出最优的购买方案。注意对书中状态转移方程的理解。
#include
using namespace std;
const int maxn=10000;
const int INF = 0x3f3f3f3f;
struct lamp
{
int v,k,c,l;
bool operator < (const lamp &lamp1) const
{
return v>n&&n)
{
lamp lamps[maxn];
int d[maxn],s[maxn];
for(int i=1;i<=n;i++)
{
cin>>lamps[i].v>>lamps[i].k>>lamps[i].c>>lamps[i].l;
}
sort(lamps,lamps+n+1);
for (int i = 1; i <= n; i++)
{
s[i] = s[i-1]+lamps[i].l;
}
s[0]=0;
d[0]=0;//表示灯泡1-i的最小开销
for (int i=1;i<=n;i++)
{
d[i]=INF;
for (int j=0;j<=i;j++)
{
d[i]=min(d[i],d[j]+(s[i]-s[j])*lamps[i].c+lamps[i].k);
}
}
cout<
例题9-7
第一段代码TLE超时,采用动态规划,时间复杂度过高。
#include
using namespace std;
const int maxn=10000;
bool isP(int i,int j,string s)
{
int pre=i;
int pos=j;
bool flag=true;
while(pre>n;
while(n--)
{
int d[maxn];
string s;
cin>>s;
int len=s.length();
d[0]=0;
for (int i=1;i<=len;i++)
{
d[i]=d[i-1]+1;
for (int j=1;j<=i;j++)
{
if(isP(j-1,i-1,s))
d[i]=min(d[i],d[j-1]+1);
}
}
cout<
第二段:
对代码使用C语言进行改写。AC。
#include
using namespace std;
const int maxn=10000;
bool isP(int i,int j,char s[])
{
while(i
例题9-8
本题目通过书中的分析,并且参考了点击打开链接的博客中的写法,状态方程都可以列出,主要是对C(i,j)的表示和,和对C(i,j)的动态更新。尤其是要注意的C(i,j)的更新与之后的是选择(i+1,j)还是(i,j+1)没有关系,只和前面选择(i-1,j)还是(i,j-1)有关系。这样就能够编写出通过C(i,j)的动态更新过程。
#include
using namespace std;
//本代码参考了
//https://www.cnblogs.com/zyb993963526/p/6364069.html
const int maxn = 5000 + 5;
const int INF = 100000;
char p[maxn], q[maxn];
int sp[26], sq[26], ep[26], eq[26];
int d[maxn][maxn], c[maxn][maxn];
int main()
{
//freopen("datain.txt", "r", stdin);
int T, n, m;
scanf("%d", &T);
while (T--)
{
scanf("%s%s", p + 1, q + 1);
//cout << p + 1 << " " << q + 1 << endl;
n = strlen(p + 1);
m = strlen(q + 1);
//将字母转化成数字
for (int i = 1; i <= n; i++) p[i] -= 'A';
for (int i = 1; i <= m; i++) q[i] -= 'A';
//预处理
for (int i = 0; i < 26; i++)
{
sp[i] = sq[i] = INF;
ep[i] = eq[i] = 0;
}
//预处理,计算出序列1中每个字符的开始位置和结束位置
for (int i = 1; i <= n; i++)
{
if(sp[p[i]]==INF) sp[p[i]]=i;
ep[p[i]] = i;
}
//预处理序列2
for (int i = 1; i <= m; i++)
{
if(sq[q[i]]==INF) sq[q[i]]= i;
eq[q[i]] = i;
}
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
if (!i && !j) continue;
int v1 = INF, v2 = INF;
if (i) v1 = d[i-1][j] + c[i-1][j]; //从p中取颜色
if (j) v2 = d[i][j - 1] + c[i][j - 1]; //从q中取颜色
d[i][j] = min(v1, v2);
//更新c数组
if (i&&d[i][j]==v1)
{
c[i][j] = c[i - 1][j];
if (sp[p[i]] == i && sq[p[i]] > j) c[i][j]++;
if (ep[p[i]] == i && eq[p[i]] <= j) c[i][j]--;
}
else if (j)
{
c[i][j] = c[i][j - 1];
if (sq[q[j]] == j && sp[q[j]] > i) c[i][j]++;
if (eq[q[j]] == j && ep[q[j]] <= i) c[i][j]--;
}
}
}
cout << d[n][m] << endl;
}
return 0;
}
例题9-9
本题目按照书中的状态转移,采用记忆化搜索比较简单,但是要注意的将ans赋值INF的方式,OJ会WA。具体原因暂时不清楚,猜测可能是代码中的INF设置还是不够大导致的,所以最终采用其他形式,然后定义个vis数组来辅助进行记忆化搜索。补充:将INF设置为 const int INF=0x3f3f3f3f后AC。
#include
using namespace std;
const int maxn=50+5;
//const int INF=0xffffffff;
int a[maxn],d[maxn][maxn],vis[maxn][maxn];
int computed(int i,int j)
{
if(i+1>=j) return 0;
if(vis[i][j]) return d[i][j];
vis[i][j]=1;
int &ans=d[i][j];
//ans =INF uDebug全对,但是提交不会AC
ans = computed(i,i+1)+computed(i+1,j)+a[j]-a[i];
for(int k=i+2;k>len>>n&&len)
{
for (int i=0;i>a[k];
}
computed(0,n+1);
cout<<"The minimum cutting is "<
例题9-10
本题书才用书中的代码,采用递推的方式求解,采用递归的方式输出。
#include
using namespace std;
const int maxn=100+10;
int n,d[maxn][maxn];
string S;
bool match (char a,char b)
{
if(a=='(')
if(b==')') return true;
if(a=='[')
if(b==']') return true;
return false;
}
void dp()//使用递推,确定计算顺序和边界很重要。
{
for(int i=0;i=0;i--)//起点从第n-2个字符开始
for (int j=i+1;jj) return ;
if(i==j)
{
if(S[i]=='('||S[i]==')') printf("()");
else printf("[]");
return ;
}
int ans = d[i][j];
//满足规则1的递归输出
if(match(S[i],S[j])&&ans==d[i+1][j-1])
{
printf("%c",S[i]);print(i+1,j-1);printf("%c",S[j]);
return ;
}
for(int k=i;k>m;
getchar();
while(m--)
{
getline(cin,S);
getline(cin,S);
n=S.length();
dp();
print(0, n-1);
printf("\n");
if(m!=0)
printf("\n");
}
return 0;
}
例题9-11
本题目写出状态转移方程后,理清递推的顺序,一定要注意检查不是对角线这种情况下,不能进行计算。详细的分析可以看书。
#include
using namespace std;
const int maxn = 50+10;
const int INF=0x3f3f3f3f;
int x[maxn],y[maxn],m;
double d[maxn][maxn];
double area(int a,int b,int c)//已经构成三角形的顶点,求三角形的面积
{
double s=fabs((double)(1.0/2)*(x[a]*y[b]+x[b]*y[c]+x[c]*y[a]-x[a]*y[c]-x[b]*y[a]-x[c]*y[b]));
return s;
}
bool check(int a,int b,int c)//参考链接为https://www.cnblogs.com/Konjakmoyu/p/4905563.html的函数
//检查是否能够连接成对角线
{
int i;
for(i=0;i<=m-1;i++)
{
if(i==a||i==b||i==c) continue;
double d=area(a,b,i)+area(a,c,i)+area(b,c,i)-area(a,b,c);
if(d<0) d=-d;
if(d<=0.01) return false;
}
return true;
}
int main()
{
//freopen("datain.txt","r",stdin);
int n;
cin>>n;
while(n--)
{
cin>>m;
for(int i=0;i>x[i]>>y[i];
//确定边界
for (int i=0;i=0;i--)
{
d[i][j]=INF;
double ans;
for(int k=i+1;k0.1)
{
if(check(i,j,k))
{
ans=max(area(i,j,k),d[i][k]);
ans=max(ans,d[k][j]);
d[i][j]=min(d[i][j],ans);
}
}
}
}
printf("%.1lf\n",d[0][m-1]);
}
return 0;
}
例题9-12
本题目的状态比较简单,因为是树上的动态规划,使用了vector来进行辅助,采用了递归的方式,进行求解,通过排序和循环来选择最小的几个的值。写法值得借鉴。
#include
using namespace std;
const int maxn=100000+10;
int N,T;
vector sons[maxn];
int dp(int u)
{
if(sons[u].empty()) return 1;
int k=sons[u].size();
vector d;
for(int i=0;i>N>>T&&T)
{
for(int i=0;i>j;
sons[j].push_back(i);
}
cout<
例题9-13
本题目按照书中的思路,和9-12例题的代码结合,唯一注意的时,每次计算完之后,vector和map需要进行清空。
#include
using namespace std;
const int maxn=200+10;
map n2id;
vector sons[maxn];
int d[maxn][3];
bool f[maxn][3];
void dp(int u)
{
if(sons[u].empty())
{
d[u][0]=0;d[u][1]=1;
return ;
//0表示不唯一,1表示唯一
}
int k=sons[u].size();
for (int i=0;id[sons[u][i]][1])
{
d[u][0]+=d[sons[u][i]][0];
if(!f[sons[u][i]][0]) f[u][0]=0;
}
else
{
d[u][0]+=d[sons[u][i]][1];
if(d[sons[u][i]][0]==d[sons[u][i]][1]||!f[sons[u][i]][1]) f[u][0]=0;
}
}
d[u][1]++;
}
int main()
{
//freopen("datain.txt","r",stdin);
//freopen("dataout.txt","w",stdout);
int n;
while(cin>>n&&n)
{
memset(d,0,sizeof(d));
memset(f,1,sizeof(f));
int id = 0;
string boss,s1,s2;
cin>>boss;
n2id[boss] = id++;
for(int i=1;i>s1>>s2;
if(!n2id.count(s1))
n2id[s1]=id++;
if(!n2id.count(s2))
n2id[s2]=id++;
sons[n2id[s2]].push_back(n2id[s1]);
}
dp(0);
if(d[0][1]==d[0][0])
cout<d[0][0])
{
cout<
例题9-15
本题目中,采用位运算的与、或、异或来处理状态转化后的集合变化。代码采用的是代码仓库中的代码,我稍加了注释,更利于阅读。
//状态为d(i,s1,s2)表示考虑前i个人,
//只有1个人教的S1中的科目,至少两个人教S2种的科目的最小花费。
//状态转移方程为 d(i,s1,s2)=min{d(i+1,s1',s2')+c[i],d(i+1,s1,s2)},
//前面的状态表示招聘一人,s1',s2'表示招聘一人后s1和s2的新值。
// UVa10817 Headmaster's Headache
// Rujia Liu
#include
#include
#include
#include
using namespace std;
const int maxn = 100 + 20 + 5;
const int maxs = 8;
const int INF = 1000000000;
int m, n, s, c[maxn], st[maxn], d[maxn][1<= 0) return ans;
ans = INF;//初值为INF
// 不选
if(i >= m) ans = dp(i+1, s0, s1, s2);
// 选,m0表示 i个人能够教没人教的课的集合
//m1表示 i个人能够教一门的课的集合
int m0 = st[i] & s0, m1 = st[i] & s1;
s0 ^= m0; //更新s0
s1 = (s1 ^ m1) | m0;//更新s1,
s2 |= m1;
ans = min(ans, c[i] + dp(i+1, s0, s1, s2));
return ans;
}
int main() {
int x;
string line;
while(getline(cin, line)) {
stringstream ss(line);
ss >> s >> m >> n;
if(s == 0) break;
for(int i = 0; i < m+n; i++) {
getline(cin, line);
stringstream ss(line);
ss >> c[i];
st[i] = 0;
//通过|=的方式将所能教的课程加入集合
while(ss >> x) st[i] |= (1 << (x-1));
//通过getline来读取后,这样可以判断一行的末尾就为结束
}
memset(d, -1, sizeof(d));
cout << dp(0, (1<
例题9-16
本题提供了代码仓库中源码,具体地址点击打开链接,自己并没有看懂。还需要学习。
// UVa1252 Twenty Questions
// Rujia Liu
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 128;
const int maxm = 11;
int kase, n, m;
char objects[maxn][maxm + 100];
int vis[1<= 1 && cnt[s2][a] >= 1) {
int need = max(dp(s2, a2), // the object has feature k
dp(s2, a)) + 1; // the object doesn't have feature k
ans = min(ans, need);
}
}
return ans;
}
void init() {
for(int s = 0; s < (1<