干嘛去了呢??……
文化课真是修身养性……
高二考高考那三科,又放假,才放了三天,回来上了一天课他们又放……
假期与我无关就是了……还是颓机房好
先来道简单的树形DP题:
题目
有点像没有上司的舞会,主要分析每个节点选与不选两种情况:
对于每个节点来说:
如果选(1):那么下面的节点可选可不选;
如果不选(0):那么下面的节点必选。
没了,就是一道两个状态搞定的题目,相关的题目可以复习状态机里面的股票买卖I~VI
#include
using namespace std;
const int N = 1510, M = 2 * N;
int n;
int h[N], e[M], nxt[M], idx;
void add(int a, int b)
{
e[++ idx] = b, nxt[idx] = h[a], h[a] = idx;
}
int dp[N][2];
void dfs(int now, int dad)
{
dp[now][1] = 1, dp[now][0] = 0;
for(int i = h[now]; i ; i = nxt[i])
{
int son = e[i];
if(son == dad) continue;
dfs(son, now);
dp[now][1] += min(dp[son][0], dp[son][1]);
dp[now][0] += dp[son][1];
}
}
int main(){
cin >> n;
for(int i = 1; i <= n; i ++)
{
int a; cin >> a;
int k; cin >> k;
for(int j = 1; j <= k ; j ++)
{
int b; cin >> b;
add(a, b); add(b, a);
}
}
dfs(1, 1);
cout << min(dp[1][0], dp[1][1]);
return 0;
}
题目
做了两个小时,不爽。
问题就在分析被son看着时候的价值,有一个巧妙之处可以省去一大堆功夫,可惜一开始我想不到。
#include
using namespace std;
const int N = 1510, M = 2 * N;
int dp[N][3];
int n;
int h[N], e[N], nxt[N], idx, w[N];
void add(int a, int b)
{
e[++ idx] = b, nxt[idx] = h[a], h[a] = idx;
}
void dfs(int now)
{
//一开始写错位置了hhh
dp[now][1] = w[now];
for(int i = h[now]; i ; i = nxt[i])
{
int son = e[i];
dfs(son);
//now由dad看着,son没人看,怎么办?son可以自理或者由son的son看,
dp[now][0] += min(dp[son][1], dp[son][2]);
//now自律,
dp[now][1] += min(dp[son][0], min(dp[son][1], dp[son][2]));
}
/*
由son来看,自己想了两个小时,最后还是在y总的视频里学到了一个美妙的写法:
根据之前的分析,每次都要找到所有子树(除去枚举到的那个子树)的最小值,然后加上被枚举到的子树的自理价值
所有子树(除去枚举到的那个子树)的最小值 其实 就是dp[now][0] - min(dp[son][1], dp[son][2]),有意思吧?
*/
dp[now][2] = 1e6;
for(int j = h[now];j ; j = nxt[j])
{
int son = e[j];
dp[now][2] = min(dp[now][2], dp[son][1] + dp[now][0] - min(dp[son][1], dp[son][2]));
}
}
bool st[N];
int main()
{
cin >> n;
int a, k, m;
for(int i = 1; i <= n; i ++)
{
cin >> a;
cin >> w[a] >> m;
for(int j = 1; j <= m; j ++)
{
int b; cin >> b;
add(a, b);
st[b] = 1;
}
}
int root = 1;
while(st[root]) ++ root;
dfs(root);
cout << min(dp[root][1], dp[root][2]);
return 0;
}
七点半开始,九点结束……蒟蒻。
题目
#include
using namespace std;
const int N = 3010, M = 2 * N;
int dp[N][N];
int n, m, w[N];
int h[N], e[N], nxt[N], idx, v[N];
void add(int a, int b, int vv)
{
e[++ idx] = b, v[idx] = vv, nxt[idx] = h[a], h[a] = idx;
}
int sum[N], sum_son[N];
void dfs(int now)
{
// sum[now] = 1;
//如果搜到了叶子
if(w[now])
{
dp[now][1] = w[now];
sum_son[now] = 1;
return ;
}
//不是叶子就往下搜
//这个初始化……卡了半小时
dp[now][0] = 0;
for(int i = h[now]; i; i = nxt[i])
{
int son = e[i], vv = v[i];
dfs(son);
sum_son[now] += sum_son[son];
//留给树的体积
for(int j = sum_son[now]; j >= 0; j --)
{
//留给当前子树的体积
for(int k = min(j, sum_son[son]); k >= 0; k --)
{
dp[now][j] = max(dp[now][j], dp[son][k] + dp[now][j - k] - vv);
}
}
}
}
int main()
{
memset(dp, -0x3f, sizeof dp);
cin >> n >> m;
//枚举中转站i
for(int i = 1; i <= n - m; i ++)
{
int k; cin >> k;
for(int j = 1; j <= k; j ++)
{
int b, vv; cin >> b >> vv;
add(i, b, vv);
}
}
for(int i = n - m + 1; i <= n; i ++)
cin >> w[i];
dfs(1);
for(int i = sum_son[1]; i >= 0; i --)
{
if(dp[1][i] >= 0)
{
cout << i;
return 0;
}
}
// for(int i = 1; i <= n; i ++){
// for(int j = 1; j <= 3; j ++)
// printf("%d ", dp[i][j]);
// puts("");
// printf("%d ", sum_son[i]);
// }
// cout << "0";
return 0;
}
题目链接(ACwing)
题目链接(洛谷luogu)
想了两天,主要难点(对我)有如下:
一、转移方程到底是怎么来的?
首先我们定义两个数组:f[n]
和g[n]
;
f[u]
代表从根节点到点u所经过的路径上的合法括号数;
g[n]
代表从点u起往前数,所有的合法括号数,但是不能中断。
什么意思呢?
比如:()()(())
中,
g[1] = 0;g[2] = 1;g[3] = 0;g[4] = 2;g[5] = 0;g[6] = 0;g[7] = 1;g[8] = 3;
f[1] = 0;f[2] = 1;f[3] = 1;f[4] = 3;f[5] = 3;f[6] = 3;f[7] = 4;f[8] = 7;
看下图;
想一想:我们每次加入一个)
,是不是会让对应的(
所连接的合法的所有括号都延长一个长度,使得这些合法的情况变成新的合法的情况?
图注:每条横线表示不同的
)
加入后形成的新的***合法括号串***,不同的颜色代表不同的合成情况。
其中,紫色虚线的部分是可以忽略的,因为:
(())
即“括号里面套其它括号”这种情况下,将最右边的)
加入后,
- 只有外层的
()
会与前面相连的所有 合法括号串 组成新的 合法括号串;- 括号里面的任何情况都不会对产生新的***合法括号串***有帮助。
可以看到,对于每次新加入的
)
,它会使得对应的(
前面所有相连不间断的 合法括号串 都延长一个长度,形成新的合法括号串。
那么如果要加入一个)
,每次***合法括号串***都会在前一个括号的情况下,加上***前面所有相连不间断的合法括号串***的数量
也就是:f[u] = f[u - 1] + g[u];
而g[u] = g[对应左括号的前面紧挨着的右括号] + 1
是显然的
#include
using namespace std;
typedef long long ll;
const int N = 500010;
//dp为从根到当前u的所有合法括号串数。g为往前推不间断的合法括号串数。
ll dp[N], g[N];
int n, m, w[N];
char str[N];
int stck[N], top;
int p[N];
int h[N], e[N], nxt[N], idx, v[N];
void add(int a, int b)
{
e[++ idx] = b, nxt[idx] = h[a], h[a] = idx;
}
void dfs(int now)
{
//左括号的时候直接加入,dp的值将被继承。g不用管。
if(str[now] == '(')
{
stck[++ top] = now;
dp[now] = dp[p[now]];
for(int i = h[now]; i ; i = nxt[i]) dfs(e[i]);
-- top;
}
else
{
//如果还有可以匹配的左括号
if(top)
{
//取出栈顶的左括号
int t = stck[top --];
g[now] = g[p[t]] + 1;
dp[now] = g[now] + dp[p[now]];
for(int i = h[now]; i ; i = nxt[i]) dfs(e[i]);
stck[++ top] = t;
}
//没有可以匹配的左括号;dp的值将被继承。g不用管。
else
{
dp[now] = dp[p[now]];
for(int i = h[now]; i ; i = nxt[i]) dfs(e[i]);
}
}
}
int main()
{
cin >> n;
scanf("%s", str + 1);
for(int son = 2; son <= n; son ++)
{
cin >> p[son];
add(p[son], son);
}
dfs(1);
ll res = 0;
for(int i = 1; i <= n; i ++)
{
res ^= (i * dp[i]);
// printf("%d : %d\n", i, dp[i]);
}
cout << res;
return 0;
}