上午
今天不用考试,所以是个补专题的好日子。
P4363 [九省联考 2018] 一双木棋 chess
我们先来看看一组我搓的样例:
这是初始状态,红色的格子表示下一步能选什么。
这是选了一个的状态
这是选了两个的状态
我们可以发现,在题目的限制下,黑色的轮廓线总是向上或者向右,如初始状态下就是 上上上上右右右右,第二种就是 上上上右上右右右 。这能发现什么,这不就能状压了吗?
我们设 表示状态为 时的答案。其中: 在二进制下的第 位为 1 表示这个轮廓线为上。那么初始状态为 ((1 << n) - 1) << m 。那么如何转移呢?先暂且设下一个状态为 ,当前坐标为 ,那么有:
然后重要的就是怎么求 。我们看:这个点能取,也就是说他左上的轮廓线是 上右 。到状态里就是 ...01... 。那怎么判断呢?很简单,只需要按位与 0b11 。我们枚举到第 位,当 (sta >> i) & 0b11 == 1 时,就说明满足条件了,我们就可以进行转移, 就是 sta ^ (0b11 << i) ,就是将那两位取反,变成 右上 。而结束状态就是 (1 << m) - 1 。但是我们怎么判断当前是该该菲菲取还是该牛牛取呢?一种是写判断小函数,但是我太懒也太蒻了,写不来。于是我写的记搜,把 该谁取 放在形参里传下去就可以了。
核心代码:
int dfs(int sta,int wh)//wh就是记录该谁取{ if (dp[sta] != inf) return dp[sta]; dp[sta] = wh ? -inf : inf; int x = n,y = 0; for (int i = 0; i < n + m - 1; i++) { if ((1 << i) & sta)//这个if就是记录当前坐标的 x --; else y ++; if (((sta >> i) & 0b11) != 1)//如果不可以转移 continue; if (wh)//题解里说的转移 dp[sta] = max(dp[sta],dfs(sta ^ (0b11 << i),wh ^ 1) + a[x][y]); else dp[sta] = min(dp[sta],dfs(sta ^ (0b11 << i),wh ^ 1) - b[x][y]); } return dp[sta];}UVA12983 The Battle of Chibi
solution
我们先想朴素的状态和转移:设 表示以 结尾的长度为 的子序列个数。那么有 。但是我们发现:枚举状态就有 的,而转移又有 ,合起来就是 ,这明显过不了啊。
我们考虑优化:我们发现 都是由 转移而来的,所以最外层循环可以是 。然后我们可以想到前缀和:以 为下标,记录前 个数中 的 的前缀和,这个可以用树状数组。但是 很大,要离散化。那么这样,我们成功的把dp优化到了 。
核心代码:
for (int i = 1; i <= n; i++){ cin >> a[i]; apr[i] = a[i];}sort(apr + 1,apr + n + 1);len = unique(apr + 1,apr + n + 1) - apr - 1;memset(dp,0,sizeof dp);for (int i = 1; i <= n; i++) dp[i][1] = 1;for (int i = 1; i <= n; i++) a[i] = bound(a[i]);//bound用来求a[i]在apr[i]里的下标for (int j = 2; j <= m; j++){ memset(tree,0,sizeof tree); for (int i = 1; i <= n; i++) { dp[i][j] = que(a[i] - 1);//查询前缀和 upd(a[i],dp[i][j - 1]);//记录dp[k][j - 1]的前缀和 }}int ans = 0;for (int i = 1; i <= n; i++) ans = (ans + dp[i][m]) % mod;下午
P10978 Fence
solution
我们先来想朴素转移:设 表示第 个工人刷到第 块木板的最大收益。那么有三种情况:
- 不需要第 个工人刷,答案为
- 第 个工人不刷第 块木板,答案为
- 第 个工人刷第 块木板,那么我们就枚举上个人所刷的区间的右端点 ,但是保证 ,也就是下一块木板能被刷到。答案即为
但是,我们发现这样的时间复杂度根本不可以,枚举状态是 的,转移还有一个 ,合起来就是 。这不行啊。
我们考虑优化。我们发现复杂度的瓶颈主要是第三种情况的转移,注意到第三种情况可以变成这个样子: 。但是,对于每一个 来说, 是不变的,那么我们就只用考虑最大的 。我们发现这个东西可以用单调队列维护。
code:
#include<iostream>#include<algorithm>#include<queue>#define int long longusing namespace std;int n,k;int dp[105][16005];struct Nahida{int l,p,s;}a[105];bool operator<(const Nahida &x,const Nahida &y){return x.s < y.s;}signed main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> k; for (int i = 1; i <= k; i++) cin >> a[i].l >> a[i].p >> a[i].s; sort(a + 1,a + k + 1); for (int i = 1; i <= k; i++) { deque<int>q; //单调队列里记录的是使dp[i - 1][k] - p[i] * k最大的k //上面的k不是题目里说的有k个人,是上文dp转移时枚举的k。不要搞混了
q.push_back(max(a[i].s - a[i].l,0ll)); //类似dp初始化,第一个k就是这个人能刷到的左端点 for (int j = 1; j <= n; j++) { dp[i][j] = max(dp[i - 1][j],dp[i][j - 1]); if (j >= a[i].l + a[i].s) continue;//如果j不在粉刷范围内 while (!q.empty() && q.front() + a[i].l < j) q.pop_front();//排除不在粉刷范围内的k if (j < a[i].s) { int tmp = dp[i - 1][j] - j * a[i].p;//将dp[i - 1][j] - p[i] * j插入,这里的j就相当于上面注释里的k while (!q.empty() && dp[i - 1][q.back()] - q.back() * a[i].p < tmp) q.pop_back(); q.push_back(j); } else dp[i][j] = max(dp[i][j],dp[i - 1][q.front()] + a[i].p * (j - q.front()));//进行转移 } } cout << dp[k][n]; return 0;}