这周找回节奏
早睡早起,晨跑,记录每日成果。注意劳逸结合
冲冲冲,加油
10.26 周一
P1280 尼克的任务
这道题想了3天,没有突破
然后就看了题解了。至少思考过两三天还是没有思路才可以看题解,严格遵守
看了题解焕然大悟
我思考的时候一直卡在一个点,就是当前这个任务的选择会影响后来的选择
也即是有后效性。动态规划要求无后效性,也就是当前的选择只与之前有关,不能影响未来的选择
我就一直卡在这,不知道怎么办
看了题解,逆向思维,倒着搜,就可以避免后效性
太秀了我天
这道题最大的收获就是逆向思维,有些题目反着想就可以突破
卡住时提醒自己反着想,逆向思维
还有一点是不一定是前i个任务,可以是前i段时间
然后看别人题解看关键卡住自己的点就行了,剩下的细节什么的自己写,不要全部照抄
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 1e4 + 10;
vector<int> a[MAXN];
int f[MAXN], n, k;
int main()
{
scanf("%d%d", &n, &k);
_for(i, 1, k)
{
int l, t;
scanf("%d%d", &l, &t);
a[l].push_back(t);
}
for(int i = n; i >= 1; i--)
{
if(!a[i].size()) f[i] = f[i+1] + 1;
else REP(j, 0, a[i].size()) f[i] = max(f[i], f[i+a[i][j]]);
}
printf("%d\n", f[1]);
return 0;
}
线性动规告一段落
开始刷区间dp
P1880 [NOI1995]石子合并
区间dp裸题
几个点注意一下
一 f[i][j]表示i到j最大价值,这个问题显然具有最优子结构。然后求f[i][j]的时候要枚举断点,为了保证前面的状态都已经推过,即从小推到大,第一层循环就要枚举区间长度
然后断点k断成f[i][k], f[k+1][j]。f[i][j] = max(f[i][k] + f[k+1][j]) + 合并价值(这里就是i到j的和) 这里注意区间长度从2开始,因为区间长度为1的价值为0(根据题意)
二 环怎么处理 再加一段,更新答案的时候区间为n就行
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 220;
int f1[MAXN][MAXN], f2[MAXN][MAXN], a[MAXN], s[MAXN], n;
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
scanf("%d", &a[i]);
a[i+n] = a[i];
}
_for(i, 1, 2 * n) s[i] = s[i-1] + a[i];
int ans1 = 1e9, ans2 = 0;
_for(len, 2, n)
_for(i, 1, 2 * n)
{
int j = i + len - 1;
if(j > 2 * n) break;
f1[i][j] = 1e9;
_for(k, i, j - 1)
{
f2[i][j] = max(f2[i][j], f2[i][k] + f2[k+1][j] + s[j] - s[i-1]);
f1[i][j] = min(f1[i][j], f1[i][k] + f1[k+1][j] + s[j] - s[i-1]);
}
if(len == n) ans2 = max(ans2, f2[i][j]), ans1 = min(ans1, f1[i][j]);
}
printf("%d\n%d", ans1, ans2);
return 0;
}
P3146 [USACO16OPEN]248 G
这道题和石子合并很像
注意几个点
一 注意i=j时区间就是一个点,这个时候没有断电。所以提前初始化,然后len从2开始
二 区间dp相当于可以枚举所有情况,这里想一下可以得出满足最优子结构(用反证法) 然后这里合并是有条件的,所以很多状态是不合法的,要注意
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 300;
int f[MAXN][MAXN], n;
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &f[i][i]);
int ans = 0;
_for(len, 2, n)
_for(i, 1, n)
{
int j = i + len - 1;
if(j > n) break;
f[i][j] = -1e9;
_for(k, i, j - 1)
if(f[i][k] == f[k+1][j])
f[i][j] = max(f[i][j], f[i][k] + 1);
ans = max(ans, f[i][j]);
}
printf("%d\n", ans);
return 0;
}
P1063 能量项链
和前两题差不多
区间dp有点死板??
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 210;
int f[MAXN][MAXN], a[MAXN], n;
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]), a[n+i] = a[i];
int ans = 0;
_for(len, 2, n)
_for(i, 1, 2 * n)
{
int j = i + len - 1;
if(j > 2 * n) break;
_for(k, i, j - 1)
{
int t = j == 2 * n ? 1 : j + 1;
f[i][j] = max(f[i][j], f[i][k] + f[k+1][j] + a[i] * a[k+1] * a[t]);
}
if(len == n) ans = max(ans, f[i][j]);
}
printf("%d\n", ans);
return 0;
}
区间dp先到这
这种区间合并的问题都可以用区间dp解
开始树形dp
P1352 没有上司的舞会
树形dp入门题
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 6e3 + 10;
vector<int> g[MAXN];
int f[MAXN][2], v[MAXN], in[MAXN], n;
void dfs(int x)
{
f[x][1] = v[x];
REP(i, 0, g[x].size())
{
int u = g[x][i]; dfs(u);
f[x][1] += f[u][0];
f[x][0] += max(f[u][0], f[u][1]);
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &v[i]);
_for(i, 1, n - 1)
{
int l, k; scanf("%d%d", &l, &k);
g[k].push_back(l); in[l]++;
}
_for(i, 1, n)
if(!in[i])
{
dfs(i);
printf("%d\n", max(f[i][1], f[i][0]));
break;
}
return 0;
}
一口气刚了5题
今天效率很高啊
上星期很忙写得少了,然后就浑身不舒服
今天时间比较多,重新开始写题还是很爽的
加油,继续干,一点一点稳步前进
10.27 周二
P2016 战略游戏
这道题昨天看错题了导致没做出来
认真审题。。。。。
注意这是无根树。
有根树边有方向,有父亲和儿子,无根树是双向边(搜索时用fa)没有父亲和儿子这个概念
这时我们随便找个点作根,变成有根树
同时注意树形dp时只考虑父亲和儿子,父亲的父亲不用理
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 2e3;
vector<int> g[MAXN];
int f[MAXN][2], n;
void dfs(int u, int fa)
{
f[u][1] = 1;
REP(i, 0, g[u].size())
{
int v = g[u][i];
if(v == fa) continue;
dfs(v, u);
f[u][1] += min(f[v][1], f[v][0]);
f[u][0] += f[v][1];
}
}
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int x, k, t; scanf("%d%d", &x, &k);
while(k--)
{
scanf("%d", &t);
g[x].push_back(t);
g[t].push_back(x);
}
}
dfs(1, -1);
printf("%d\n", min(f[1][1], f[1][0]));
return 0;
}
开始复习树上背包
P2014 [CTSC1997]选课
树上背包模板题
注意几点
一 这里没有父亲的,全部父亲为0节点,这样森林变成了一棵树,非常方便,同时这个0一定要选,m++就好
二 这其实就是再更新的时候用上分组背包,如果分组背包理解透彻,这个代码很容易理解的。我发现很多题目都可以转化为分组背包的模型,分组背包的应用范围比较广
这里就是把件数看作了费用,每个物品费用为1
#include<bits/stdc++.h>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 310;
int f[MAXN][MAXN], n, m;
vector<int> g[MAXN];
void dfs(int u)
{
REP(i, 0, g[u].size())
{
int v = g[u][i]; dfs(v);
for(int j = m; j >= 1; j--)
_for(k, 0, j - 1)
f[u][j] = max(f[u][j], f[u][j-k] + f[v][k]);
}
}
int main()
{
scanf("%d%d", &n, &m); m++;
_for(i, 1, n)
{
int x; scanf("%d%d", &x, &f[i][1]);
g[x].push_back(i);
}
dfs(0);
printf("%d\n", f[0][m]);
return 0;
}
实力恢复了不少,现在可以尝试一些蓝题了
P1273 有线电视网
蓝题也就这样哈哈
一开始想用f[u][j]表示当前钱数为j的时候的最大的观众
但我发现负数不好表示,而且题目也没给出钱总和的范围,数组范围都不知道是多少
这个时候说明这么设有问题
然后我休息一下,回来的时候灵光一闪
不一定要把答案作为值,可以把答案放到维度里面
也就是f[u][j]是表示j个观众时最多赚了多少钱,然后统计答案的时候检查合法性
这是逆向思维,很秀。设状态不一定要那么直接把最后答案设在状态的值上,可以
把答案设为一个维度,最后检查合法性
然后这里要注意初始化的问题。数组默认为0,因为有负数的存在,所以这个0是有意义的
应该先全部设为不合法,也就是负无穷。不过0个叶子时是为0
然后这里要统计一下子树叶子的个数,不然会超时,这个优化还是非常明显的
可以边搜边统计叶子个数
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int MAXN = 3e3 + 10;
struct node { int v, w; };
vector<node> g[MAXN];
int f[MAXN][MAXN], cnt[MAXN], n, m;
void dfs(int u)
{
REP(i, 0, g[u].size())
{
int v = g[u][i].v, w = g[u][i].w;
dfs(v); cnt[u] += cnt[v];
for(int j = cnt[u]; j >= 1; j--)
_for(k, 1, j)
f[u][j] = max(f[u][j], f[u][j-k] + f[v][k] - w);
}
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n - m)
{
int k, v, w;
scanf("%d", &k);
while(k--)
{
scanf("%d%d", &v, &w);
g[i].push_back(node{v, w});
}
}
_for(i, 1, n)
_for(j, 1, n)
f[i][j] = -1e9;
_for(i, n - m + 1, n)
{
int x; scanf("%d", &x);
f[i][1] = x; cnt[i] = 1;
}
dfs(1);
for(int i = m; i >= 0; i--)
if(f[1][i] >= 0)
{
printf("%d\n", i);
break;
}
return 0;
}
10.28 周三
最近有点飘
谦逊低调少炫耀装逼
今天一直在刚重建道路的,有几个点没想透,继续刚
明天应该能刚掉
然后就开始打洛谷比赛和刷cf,一直持续到寒假
10.29 周四
神tm还是不会写
太菜了,明天继续刚。
先放放,看看怎么用codefoces刷题打比赛
这周六下午打一波洛谷月赛