会把所有这个专题所有题目的大致题解写上来,看情况放代码。
专题链接:https://vjudge.net/contest/68966#overview
题解按照解决人数降序排序
B - Ignatius and the Princess IV
不知道这个水题是怎么混进这个专题的。
I - 最少拦截系统
做过两遍的题目了。其实这个问题的本质上是个LIS,因为每一个上升的点又要对应一个单独的拦截系统。(这题数据好像很水)
A - Max Sum Plus Plus
最容易考虑到的状态方程:dp[i][j]:在拿a[j]的情况下,前j个元素分成i组的答案(最大和)
那么转移可以分为两种:和前面的分成一组、自创一组
所以就有:dp[i][j] = max(dp[i][j-1], dp[i-1][k]), 其中k : [i-1, j-1](就是拿一个最大的方案嘛)
但是这样时空复杂度都过不去
考虑优化:注意到dp[i][?] 只和dp[i][?]和dp[i-1][?]有关,所以我们可以把第一维优化掉,两个中选一个最优的,相当于做成了一个滚动数组
关于时间复杂度的优化:其实在枚举k的时候我们想得到的只是最大值,不需要具体位置,所以可以直接用一个数组来保存前一个前一个状态中最大的就可以直接在下一个要更新的时候来用了
Code:
int a[maxn];
int dp[maxn], pre[maxn];
int main()
{
int m, n;
while(~scanf("%d %d", &m, &n)){
rep(i, 1, n) scanf("%d", a + i);
fill(pre + 1, pre + n + 1, 0);
fill(dp + 1, dp + n + 1, 0);
int Max;
rep(i, 1, m){
Max = -inf;
rep(j, i, n){
dp[j] = max(dp[j-1], pre[j-1]) + a[j];
pre[j-1] = Max;
Max = max(Max, dp[j]);
}
}
printf("%d\n", Max);
}
return 0;
}
G - 免费馅饼
camp上Claris讲过一个类似的,不过那个好像难多了。这个题化一维为二维,纵轴表示时间,那么我们从上往下开始dp,每次取最大的,加到答案中就行了。(其实转换成平面以后就是和普通的dp了)
Rep(i, m, 0){
rep(j, 0, 10){
dp[i][j] += max(max(dp[i+1][j+1], dp[i+1][j]), dp[i+1][j-1]);
}
}
E - Super Jumping! Jumping! Jumping!
为什么这个题过的人反而少一些。。瞎搞一下转移就好了
C - Monkey and Banana
注意到其实给出的一种类型的长方体,所有的翻转情况有6种。n是30的,最多也就180种,按照长宽从小到大排个序,就变成了LIS问题了,直接n^2 DP即可。(或者按照题意,从大到小排,做“最长下降子序列”都是一样的)
F - Piggy-Bank
原题,完全背包做一下就好了
L - Common Subsequence
裸的LCS
H - Tickets
一个很简单的dp,瞎搞一下就行了。
N - Longest Ordered Subsequence
裸的LIS,我感觉我刷了一个假专题。
J - FatMouse's Speed
变形LIS,加个pre数组递归输出答案即可。
D - Doing Homework
一个简单的状压dp,转移方程也非常好想,遍历所有的位置,如果能从上一个状态转移就尝试,输出要稍微动一点脑子。
O - Treats for the Cows
这是一道区间dp,百度的题解。
dp[i][j]表示区间[i, j]的最大值,那么dp[1][n]就是答案。初始化比较好想dp[i][i] = a[i]。转移也就显而易见了,显然dp[i][j]只能由dp[i+1][j]和dp[i][j-1]转移而来,取个max就行了:dp[i][j] = max(dp[i+1][j] + a[i] * (n + i - j), dp[i][j-1] + a[i] * (n + i - j));
代码也非常短:
int a[maxn];
int dp[maxn][maxn];
int main()
{
int n; scanf("%d", &n);
rep(i, 1, n) scanf("%d", a + i), dp[i][i] = a[i];
Rep(i, n, 1){
rep(j, i, n){
dp[i][j] = max(dp[i+1][j] + a[i] * (n + i - j), dp[i][j-1] + a[j] * (n + i - j));
}
}
printf("%d\n", dp[1][n]);
return 0;
}
P - FatMouse and Cheese
这题用记忆化搜索就直接写了。没有数据范围我就直接写了个1000,代码也很好写很好理解
int dfs(int x, int y){
if(dp[x][y]) return dp[x][y];
int Max = 0;
rep(i, 0, 3){
rep(j, 1, k){
int tx = x + j * dir[i][0], ty = y + j * dir[i][1];
if(tx < 1 || tx > n || ty < 1 || ty > n) continue;
if(G[tx][ty] > G[x][y]) Max = max(Max, dfs(tx, ty));
}
}
return dp[x][y] = Max + G[x][y];
}
int main()
{
while(~scanf("%d %d", &n, &k) && n != -1){
memset(dp, 0, sizeof(dp));
rep(i, 1, n) rep(j, 1, n) scanf("%d", &G[i][j]);
printf("%d\n", dfs(1, 1));
}
return 0;
}
R - Milking Time
排个序直接dp就行了,这个题比较坑的一点是以作者的意思区间是左闭右开的,但是题面没有说清楚。
M - Help Jimmy
开始一直想着从上到下dp,写了好久发现各种情况很不好判,甚至还要用到树状数组什么的。百度了一下题解,看到别人从下往上dp的那一刻我是崩溃的。。太傻了,反过来dp就很好很好写了。
Q - Phalanx
很明显的二维DP,用dp[i][j]表示以(i, j)这个点作为左下角的矩阵中,对称子矩阵最大的数目。转移也就很显而易见了:用dt表示往上、右两个方向延伸最大的对称长度,那么
if:dt > dp[i-1][j+1],dp[i][j] = dp[i-1][j+1] + 1;
else: dp[i][j] = dt;
然后一直取max就好了,复杂度n ^3,不知道有没有更优秀的解法。
S - Making the Grade
老是觉得这题做过,但是好像以前写不出来dp呀哈哈。
如果a[i]的数据是个2e3这题就很水了,但是a[i]是1e9。我想着n给个2e3不就是让我们来n^2的吗,a[i]搞这么大应该是有一些规律。最后没想出来还是百度抬了一手,结论就是:一个位置后来变成的数,一定就是原序列里面的某个数。找到一个粗略的证明,转自这里:
记原来的数从小到大排序后分别是a1 a2 a3⋯ana1 a2 a3⋯an 修改后从左到右分别是b1 b2 b3⋯bnb1 b2 b3⋯bn. 为了方便描述,在数轴上标出这些点,称为关键点。
假设存在as<bi<=bi+1<=⋯<=bj<as+1as<bi<=bi+1<=⋯<=bj<as+1
情况一:如果这些b都相等,那么把这些b都改成asas或者as+1as+1 肯定会有一种更优。
情况二:如果不全相等,那么肯定存在 bp bp+1 bp+2⋯bqbp bp+1 bp+2⋯bq,他们的值相等,那么把他们移到左边的关键点或者右边的关键点,肯定有一种会更加优. 不断这样移动,最后就变成情况一了。
综上至少存在一种最优方案,最终的所有数都是原来的某个数。
然后就变成了一个n ^2的很水的DP(题目说单调增单调减(都是非严格)都可以,那么翻转序列做两遍就可以了)
int dp[maxn][maxn];
int a[maxn], b[maxn];
int n;
int main()
{
scanf("%d", &n);
rep(i, 1, n) scanf("%d", a + i), b[i] = a[i];
sort(b + 1, b + n + 1);
//part 1
rep(i, 1, n){
int t = inf;
rep(j, 1, n){
t = min(t, dp[i-1][j]);
dp[i][j] = t + abs(b[j] - a[i]);
}
}
int ans1 = inf;
rep(i, 1, n) ans1 = min(ans1, dp[n][i]);
//part 2
memset(dp, 0, sizeof(dp));
reverse(a + 1, a + n + 1);
rep(i, 1, n){
int t = inf;
rep(j, 1, n){
t = min(t, dp[i-1][j]);
dp[i][j] = t + abs(b[j] - a[i]);
}
}
int ans2 = inf;
rep(i, 1, n) ans2 = min(ans2, dp[n][i]);
printf("%d\n", min(ans1, ans2));
return 0;
}
K - Jury Compromise
这题对我有点难,属于完全不知道怎么DP的那种。
题解:把选人的问题转换成背包问题,即:n件物品,体积是1,价值是add[i](用add[i]表示辩护与指控之和,sub[i]表示之差),背包容量为m,那么问题的一个分解就是求辩护与指控和的最大值。dp[i]表示背包容量为i,和的最大值。
接下来加入问题的另一个分解:求辩护与指控差的最小值。用dp[i][j]表示:背包容量为i,在辩护与指控之差为j的情况下,和的最大值。那么答案就是dp[m][min(j)].显然第二维的取值范围是[-20 * m, 20 * m],是可能为负数的,所以我们 把它加上一个Fix = 20 *m,变成[0, 2 * Fix], 得到答案的时候再处理掉就好了。
考虑转移和初始化。
转移就比较好想了,dp[j][k] = max(dp[j][k], dp[j-1][k-sub[i]] + add[i])
初始化:因为存在不合法的情况,先把dp数组初始化为-1,dp[0][Fix] = 0
一些判断:不合法的式子就没必要继续dp下去了,所以dp[j-1][k-sub[i]] + add[i] == -1就continue; k < 0就conitnue;k - sub[i] > 2 * Fix 直接break;
路径储存以及寻找答案:用vector<int> path[25][805]储存路径,更新的时候先复制过去再压进去新的就好了。
寻找答案的时候找到第一个合法的dp[m][pos],那么最小差就是(dp[m][pos] + pos - Fix) / 2, 最大和就是(dp[m][pos] - pos + Fix) / 2,然后按照格式要求输出路径即可。
int sub[205], add[205];
int dp[25][805];
vector<int> path[25][805];
int n, m;
void init(){
memset(dp, -1, sizeof(dp));
rep(i, 0, m) rep(j, 0, 800) path[i][j].clear();
}
int main()
{
int Case = 0;
while(~scanf("%d %d", &n, &m) && n){
printf("Jury #%d\n", ++Case);
rep(i, 1, n){
int p, d; scanf("%d %d", &p, &d);
sub[i] = p - d;
add[i] = p + d;
}
init();
int Fix = 20 * m;
dp[0][Fix] = 0;
rep(i, 1, n){
Rep(j, m, 1){
rep(k, sub[i], Fix * 2){
if(dp[j-1][k-sub[i]] == -1) continue;
if(k < 0) continue;
if(k - sub[i] > 2 * Fix) break;
if(dp[j][k] < dp[j-1][k-sub[i]] + add[i]){
dp[j][k] = dp[j-1][k-sub[i]] + add[i];
path[j][k] = path[j-1][k-sub[i]];
path[j][k].pb(i);
}
}
}
}
int pos = 0;
while(dp[m][Fix-pos] == -1 && dp[m][Fix+pos] == -1) pos++;
if(dp[m][Fix-pos] > dp[m][Fix+pos]) pos = Fix - pos;
else pos = Fix + pos;
int ans1 = (dp[m][pos] + pos - Fix) / 2, ans2 = (dp[m][pos] - pos + Fix) / 2;
printf("Best jury has value %d for prosecution and value %d for defence:\n", ans1, ans2);
rep(i, 0, m - 1) printf(" %d", path[m][pos][i]);
puts("\n");
}
return 0;
}