本来准备了很多poj的专题想做,结果没想到poj最近坏了。。。没办法,就先把这周的题给做了,这周的的确不难,都是比较基本的dp,做着做着就完了。我估计懂的人应该不会有什么难度,不懂的可能会比较费劲,所以就先把这周的题解写了吧。
我还是建议看书系统地学习,如果只是从题目里学习某种思想的话,一定要全面地搜索网上的资料,如果只是懂个模板,那你基本和不懂没区别。
什么是dp,一种递推的思想,更加高效的搜索,也可以认为是一种由方向性的记忆化搜索,反正思维很重要就是了,找到递推关系,那么一切问题就迎刃而解了。
初学dp并不是很容易理解,要花一些时间。
A
很基本的dp,因为到每一个点只能是从上面两个(有时候是一个)走过来,所以一层一层找,每次看看哪边的的值更大,就选择哪边的值,加上当前点的权值即可。
# include <cstdio>
# include <cstring>
# include <algorithm>
using namespace std;
const int MAX_N = 100;
int A[MAX_N][MAX_N];
int dp[MAX_N][MAX_N];
int N, T;
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d", &N);
int i, j;
for(i = 0 ; i < N ; i++)
{
for(j = 0 ; j <= i ; j++)
{
scanf("%d", &A[i][j]);
}
}
memset(dp , 0 , sizeof(dp));
dp[0][0] = A[0][0];
for(i = 1 ; i < N ; i++)
{
for(j = 0 ; j <= i ; j++)
{
if(j)
dp[i][j] = max(dp[i][j] , A[i][j] + dp[i - 1][j - 1]);
if(j < i)
dp[i][j] = max(dp[i][j] , A[i][j] + dp[i - 1][j]);
}
}
int ans = 0;
for(i = 0 ; i < N ; i++)
ans = max(ans , dp[N - 1][i]);
printf("%d\n", ans);
}
return 0;
}
B
背包模板题,结果因为忘了细节wa了几次,一个很经典的问题,有很多很炫技的做法,我用的这个是比较朴素的。
考虑前i种情况重量不超过j的情况,那么只有两种情况可以到达这个状态,只拿了前i-1种,或者拿了i种,这样他的最大价值就只能从这两种情况下查找了。
# include <cstdio>
# include <cstring>
# include <algorithm>
using namespace std;
const int MAX_N = 2000;
int dp[MAX_N + 1][MAX_N + 1];
int T, N, V;
int Va[MAX_N], W[MAX_N];
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d %d", &N, &V);
int i, j;
for(i = 0 ; i < N ; i++)
scanf("%d", &Va[i]);
for(i = 0 ; i < N ; i++)
scanf("%d", &W[i]);
memset(dp , 0 , sizeof(dp));
for(i = 1 ; i <= N ; i++)
{
for(j = 0 ; j < W[i - 1] ; j++)
dp[i][j] = dp[i - 1][j];
for(j = W[i - 1] ; j <= V ; j++)
{
dp[i][j] = max(dp[i - 1][j] , dp[i - 1][j - W[i - 1]] + Va[i - 1]);
}
}
printf("%d\n", dp[N][V]);
}
return 0;
}
C
读题稍稍有点复杂,说的是找到一条能量最小的从上往下的路,每次向下只能最多向左右移动一格,也是一个dp,因为到达ij时的最优解只看是从上面三个点出发的,所以找一找就是了,另外因为n最大只有100,所以直接每个点存路径是没有问题的,这样时间空间复杂度都是n立方(当然用滚动数组会节约内存,不过没必要)。
# include <cstdio>
# include <cstring>
# include <vector>
# include <algorithm>
using namespace std;
const int MAX_N = 100;
int A[MAX_N][MAX_N];
int d[MAX_N][MAX_N];
vector<int> p[MAX_N][MAX_N];
int T, N, M;
int main()
{
scanf("%d", &T);
int tt = 1;
while(T--)
{
scanf("%d %d", &N, &M);
int i, j;
for(i = 0 ; i < N ; i++)
{
for(j = 0 ; j < M ; j++)
{
scanf("%d", &A[i][j]);
p[i][j].clear();
}
}
memset(d , 0x3f , sizeof(d));
for(i = 0 ; i < M ; i++)
{
d[0][i] = A[0][i];
p[0][i].push_back(i);
}
for(i = 1 ; i < N ; i++)
{
for(j = 0 ; j < M ; j++)
{
if(j && d[i][j] >= d[i - 1][j - 1] + A[i][j])
{
d[i][j] = d[i - 1][j - 1] + A[i][j];
p[i][j] = p[i - 1][j - 1];
}
if(d[i][j] >= d[i - 1][j] + A[i][j])
{
d[i][j] = d[i - 1][j] + A[i][j];
p[i][j] = p[i - 1][j];
}
if(j < M - 1 && d[i][j] >= d[i - 1][j + 1] + A[i][j])
{
d[i][j] = d[i - 1][j + 1] + A[i][j];
p[i][j] = p[i - 1][j + 1];
}
p[i][j].push_back(j);
}
}
int ans = 1e8;
int nn;
for(i = 0 ; i < M ; i++)
{
if(ans >= d[N - 1][i])
{
ans = d[N - 1][i];
nn = i;
}
}
printf("Case %d\n", tt++);
int pp = p[N - 1][nn].size();
for(i = 0 ; i < pp ; i++)
printf("%d%s", p[N - 1][nn][i] + 1 , (i == pp - 1) ? "\n" : " ");
}
return 0;
}
D
和刚才那道题差不多,每次考察前一个时间的三个可以达到这里的状态的最大值即可。另外这道题t好像超限了,有点坑。
# include <cstdio>
# include <cstring>
# include <algorithm>
using namespace std;
const int MAX_N = 11;
const int MAX_T = 1e5 + 100;
int box[MAX_T][MAX_N];
int N;
int dp[MAX_T][MAX_N];
int main()
{
while(1)
{
scanf("%d", &N);
if(!N)
break;
int i, j;
memset(box , 0 , sizeof(box));
memset(dp , 0 , sizeof(dp));
int x, t, mt = 0;
for(i = 0 ; i < N ; i++)
{
scanf("%d %d", &x, &t);
box[t][x]++;
mt = max(mt , t);
}
dp[1][4] = box[1][4];
dp[1][5] = box[1][5];
dp[1][6] = box[1][6];
for(i = 2 ; i <= mt ; i++)
{
for(j = 0 ; j <= 10 ; j++)
{
dp[i][j] = dp[i - 1][j];
if(j < 10)
dp[i][j] = max(dp[i][j] , dp[i - 1][j + 1]);
if(j)
dp[i][j] = max(dp[i][j] , dp[i - 1][j - 1]);
dp[i][j] += box[i][j];
}
}
int ans = 0;
for(i = 0 ; i <= 10 ; i++)
ans = max(ans , dp[mt][i]);
printf("%d\n", ans);
}
return 0;
}
E
模板题,详情参考挑战程序设计初级篇dp最长公共子串即可,我直接套的当时的代码。
看挑战如果不把所有出现的代码都敲一遍,不把所有能交的例题都交一遍,不把每道题的思想都理解,等于没看,所以看的时候如果达不到这个标准,建议重看。
虽然进行了一些修改,但是这个代码依然充满了我过去落后的风格
# include <stdio.h>
# include <string.h>
const int MAX_N = 1001;
int n, m;
char t[MAX_N], s[MAX_N];
int dp[MAX_N][MAX_N];
int max(int a , int b)
{
if(a > b)
return a;
return b;
}
void solve()
{
memset(dp , 0 , sizeof(dp));
for(int i = 0 ; i < n ; i++)
{
for(int j = 0 ; j < m ; j++)
{
if(s[i] == t[j])
{
dp[i + 1][j + 1] = dp[i][j] + 1;
}
else
{
dp[i + 1][j + 1] = max(dp[i][j + 1] , dp[i +1][j]);
}
}
}
printf("%d\n",dp[n][m]);
}
int main()
{
while(~scanf("%s %s", t, s))
{
n = strlen(s);
m = strlen(t);
solve();
}
return 0;
}
F
其实和亦或没什么关系,因为f的关系本来就是一个递推的关系,所以先用dp把每一种情况下的f值求出来,然后再用相似的方法求范围内的最大值。
另外说一下这道题的空间,我这个数组开的大约是5千万,通常是会mle的,但是这道题给的内存范围是正常题目的4倍左右,所以是够的。
# include <cstdio>
# include <cstring>
# include <algorithm>
using namespace std;
const int MAX_N = 5e3;
int A[MAX_N][MAX_N];
int dp[MAX_N][MAX_N];
int N, Q;
int main()
{
scanf("%d", &N);
int i, j;
for(i = 0 ; i < N ; i++)
scanf("%d", &A[i][i]);
for(i = 1 ; i < N ; i++)
{
for(j = 0 ; j + i < N ; j++)
{
A[j][j + i] = A[j][j + i - 1] ^ A[j + 1][j + i];
}
}
for(i = 0 ; i < N ; i++)
dp[i][i] = A[i][i];
for(i = 1 ; i < N ; i++)
{
for(j = 0 ; j + i < N ; j++)
{
dp[j][j + i] = max(A[j][j + i] , max(dp[j + 1][j + i] , dp[j][j + i - 1]));
}
}
scanf("%d", &Q);
int t, s;
for(i = 0 ; i < Q ; i++)
{
scanf("%d %d", &t, &s);
printf("%d\n", dp[t - 1][s - 1]);
}
return 0;
}
G
当时没看到连续,所以被坑了,花了很长时间思考如何把n方的算法优化成nlogn的,不过我还真有一些想法,依靠某些数据结构是可以成功的,不过可能还需要研究一下。
由于发现这道题这么水之后实在懒得写了,所以这个是某个人写的。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 200000;
int a[maxn+5];//1串的桶,表示以i为结尾的最长LCIS的长度
int b[maxn+5];//2串的桶
int main()
{
int T;
scanf("%d", &T);
while (T--) {
int n, m,t;
scanf("%d%d", &n, &m);
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
for (int i = 0; i < n; i++)
{
scanf("%d", &t);
a[t] = a[t-1] + 1;
}
for (int i = 0; i < m; i++)
{
scanf("%d", &t);
b[t] = b[t-1] + 1;
}
int ans = -1;
int mmin;
for (int i = 0; i <maxn; i++) {
mmin=min(a[i], b[i]);
ans = max(ans, mmin);
}
printf("%d\n", ans);
}
return 0;
}
H
记得之前在一个比赛里看到过相似的题目,只不过那道题是两个人分别走一个对角线,难度肯定是大多了,这道题只要基本的dp即可,把这道题视为是走两条路线时最小的值,所以每次枚举一下,复杂度是n方,总共n复杂度的路程,所以最后n立
这里写代码片
方来查找,完全够用。
# include <cstdio>
# include <cstring>
# include <algorithm>
using namespace std;
const int MAX_N = 10;
int dp[MAX_N * MAX_N][MAX_N * MAX_N];
int maz[MAX_N][MAX_N];
int dx1[4] = {-1 , 0 , -1 , 0}, dy1[4] = {0 , -1 , 0 , -1}, dx2[4] = {-1 , -1 , 0 , 0}, dy2[4] = {0 , 0 , -1 , -1};
int N;
int main()
{
while(~scanf("%d", &N))
{
memset(dp , 0 , sizeof(dp));
memset(maz , 0 , sizeof(maz));
int i, j, k, u;
while(1)
{
int x, y, v;
scanf("%d %d %d", &x, &y, &v);
if(!x && !y && !v)
break;
maz[x - 1][y - 1] = v;
}
dp[0][0] = maz[0][0];
for(i = 1 ; i < 2 * N ; i++)
{
int siz = min(i , N - 1);
for(j = max(0 , i - N + 1) ; j <= siz ; j++)
{
for(k = j ; k <= siz ; k++)
{
int x1 = j, y1 = i - j , x2 = k, y2 = i - k;
for(u = 0 ; u < 4 ; u++)
{
int nx1 = x1 + dx1[u], ny1 = y1 + dy1[u], nx2 = x2 + dx2[u], ny2 = y2 + dy2[u];
if(nx1 >= 0 && ny1 >= 0 && nx2 >= 0 && ny2 >= 0)
{
dp[x1 * N + y1][x2 * N + y2] = max(dp[x1 * N + y1][x2 * N + y2] , dp[nx1 * N + ny1][nx2 * N + ny2]);
}
}
if(j == k)
dp[x1 * N + y1][x2 * N + y2] += maz[x1][y1];
else
dp[x1 * N + y1][x2 * N + y2] += maz[x1][y1] + maz[x2][y2];
}
}
}
printf("%d\n", dp[N * N - 1][N * N - 1]);
}
return 0;
}
I
这道题还算有点难度,不过重点并不是dp,而是找圈比较烦,dp的话只要把26个字母在一个点的最长数量存起来即可,然后用记忆化搜索来找最长的,可以认为是一个树形dp,然而找圈真的很烦,我直接用了tarjan加自己找自环的方法,看不懂tarjan就自行百度吧。
# include <cstdio>
# include <cstring>
# include <algorithm>
# include <set>
using namespace std;
const int MAX_N = 3e5;
typedef pair<int , int> P;
struct node
{
int to;
int next;
};
node edge[MAX_N + 1];
int edg, num, top;
char s[MAX_N + 1];
int head[MAX_N];
bool f;
int dfn[MAX_N], low[MAX_N];
int ss[MAX_N];
bool used[MAX_N];
int dp[MAX_N][26];
int N, M;
int ans;
inline void ad(int from , int to)
{
edge[edg].to = to;
edge[edg].next = head[from];
head[from] = edg++;
}
void dfs(int x)
{
if(f)
return;
dfn[x] = low[x] = ++num;
used[x] = 1;
ss[++top] = x;
int i, j;
for(i = head[x] ; i ; i = edge[i].next)
{
int t = edge[i].to;
if(!dfn[t])
{
dfs(t);
low[x] = min(low[x] , low[t]);
}
else if(used[t])
low[x] = min(low[x] , dfn[t]);
for(j = 'a' ; j <= 'z' ; j++)
{
dp[x][j - 'a'] = max(dp[x][j - 'a'] , dp[t][j - 'a']);
}
}
if(dfn[x] == low[x])
{
used[x] = 0;
if(ss[top] != x)
f = 1;
top--;
}
dp[x][s[x] - 'a']++;
for(i = 0 ; i < 26 ; i++)
{
//printf("%d %c %d\n", x, 'a' + i, dp[x][i]);
ans = max(dp[x][i] , ans);
}
}
int main()
{
scanf("%d %d\n", &N, &M);
gets(s);
edg = 1;
int i;
int t, s;
set<P> st;
for(i = 0 ; i < M ; i++)
{
scanf("%d %d", &t, &s);
if(t-- == s--)
{
f = 1;
break;
}
if(st.find(P(s , t)) == st.end())
{
ad(s , t);
st.insert(P(s , t));
}
}
if(f)
{
puts("-1");
return 0;
}
for(i = 0 ; i < N ; i++)
{
if(f)
break;
if(!dfn[i])
{
dfs(i);
}
}
if(f)
puts("-1");
else
printf("%d\n", ans);
return 0;
}
2018、6、6
之前poj挂了,之后这三体又挂上去了。
I
第三周的原题,记忆化dfs,之前我的确用过dp的做法,但是要比dfs慢一些。从上往下和从下往上是没有区别的,代码一个字都不用改。
# include <cstdio>
# include <cstring>
# include <algorithm>
using namespace std;
const int MAX_N = 1e3;
int dx[4] = {1 , 0 , -1 , 0}, dy[4] = {0 , 1 , 0 , -1};
int d[MAX_N][MAX_N];
int h[MAX_N][MAX_N];
int N, M, T;
int dfs(int x , int y)
{
if(d[x][y])
return d[x][y];
d[x][y] = 1;
int i;
for(i = 0 ; i < 4 ; i++)
{
int nx = dx[i] + x, ny = dy[i] + y;
if(nx >= 0 && nx < N && ny >= 0 && ny < M && h[nx][ny] > h[x][y])
d[x][y] = max(d[x][y] , 1 + dfs(nx , ny));
}
return d[x][y];
}
int main()
{
scanf("%d %d", &N, &M);
int i, j;
for(i = 0 ; i < N ; i++)
for(j = 0 ; j < M ; j++)
scanf("%d", &h[i][j]);
int ans = 0;
for(i = 0 ; i < N ; i++)
{
for(j = 0 ; j < M ; j++)
{
if(!d[i][j])
dfs(i , j);
ans = max(d[i][j] , ans);
}
}
printf("%d\n", ans);
return 0;
}
J
挑战程序设计初级篇习题集题目,一道很简单的区间dp,首先把所有结束时间加R,N也加上R,R就可以去掉了(题目说了所有开始时间都小于N),然后把区间按照开始顺序进行dp。每个时间dp一次,先更新一次之前的最大值,若这里是某区间开始,那么对结束时间最大值进行更新。
# include <stdio.h>
# include <algorithm>
using namespace std;
struct tim
{
int from, to, cost;
};
const int MAX_M = 1000;
int dp[MAX_M + 1];
int en[MAX_M + 1];
tim mil[MAX_M];
int N, M, R;
bool com(tim a , tim b)
{
return a.to < b.to;
}
int max(int a , int b)
{
return (a > b) ? a : b;
}
void solve()
{
fill(dp , dp + M + 1 , 0);
sort(mil , mil + M , com);
sort(en , en + M + 1);
int i;
for(i = 0 ; i < M ; i++)
{
int op = lower_bound(en , en + M + 1 , mil[i].from) - en;
if(en[op] != mil[i].from)
op--;
dp[i + 1] = max(dp[i] , dp[op] + mil[i].cost);
}
printf("%d\n", dp[M]);
}
int main()
{
scanf("%d %d %d", &N, &M, &R);
int i;
for(i = 0 ; i < M ; i++)
{
scanf("%d %d %d", &mil[i].from, &mil[i].to, &mil[i].cost);
mil[i].to += R;
en[i + 1] = mil[i].to;
}
en[0] = 0;
solve();
return 0;
}
H
挑战程序设计初级篇习题集题目,读题比较费劲,就说说怎样求单调不增路径把(方向同理),首先把所有高度排序,可以发现即使移动了,花费最小的时候肯定高度都是之前出现过的,所以用一个离散化,然后用dpij表示把第i个变成第j高的时候的最小花费,然后每次j升高更新之前的最小花费,因为高度单增,所以最小花费不用每次都一个一个找,直接更新即可(否则复杂度变为n立方),再加上变成这么高的花费,这样可以在n方完成。
# include <stdio.h>
# include <math.h>
# include <algorithm>
using namespace std;
# define min(A , B) (A < B) ? A : B;
typedef pair<int , int> P;
const int MAX_N = 2000;
const int INF = 100000000;
int dp[MAX_N + 1][MAX_N + 1], fr[MAX_N + 1];
P nu[MAX_N], fan[MAX_N];
int N;
bool com(P a , P b)
{
return a.first < b.first;
}
void solve()
{
//fill(fr , fr + N , INF);
//fill(ho , ho + N , INF);
int i, j, sum;
for(i = N - 2 ; i >= 0 ; i--)
{
for(j = 0 ; j < N ; j++)
{
dp[i][j] = fr[j] + abs(nu[j].first - fan[i].first);
fr[j] = dp[i][j];
if(j > 0)
fr[j] = min(fr[j - 1] , dp[i][j]);
}
}
sum = fr[N - 1];
fill(fr , fr + N , 0);
for(i = 1 ; i < N ; i++)
{
for(j = 0 ; j < N ; j++)
{
dp[i][j] = fr[j] + abs(nu[j].first - fan[i].first);
fr[j] = dp[i][j];
if(j > 0)
fr[j] = min(fr[j - 1] , dp[i][j]);
}
}
sum = min(sum , fr[N - 1]);
printf("%d\n", sum);
}
int main()
{
scanf("%d", &N);
int i;
for(i = 0 ; i < N ; i++)
{
scanf("%d", &nu[i].first);
nu[i].second = i;
}
sort(nu , nu + N , com);
for(i = 0 ; i < N ; i++)
{
fan[nu[i].second].first = nu[i].first;
fan[nu[i].second].second = i;
}
solve();
return 0;
}