
日记
上午
今天不用考试,所以是个补专题的好日子。
P4363 [九省联考 2018] 一双木棋 chess
我们先来看看一组我搓的样例:
这是初始状态,红色的格子表示下一步能选什么。
这是选了一个的状态
这是选了两个的状态
我们可以发现,在题目的限制下,黑色的轮廓线总是向上或者向右,如初始状态下就是 上上上上右右右右
,第二种就是 上上上右上右右右
。这能发现什么,这不就能状压了吗?
我们设 表示状态为 时的答案。其中: 在二进制下的第 位为 1
表示这个轮廓线为上。那么初始状态为 ((1 << n) - 1) << m
。那么如何转移呢?先暂且设下一个状态为 ,当前坐标为 ,那么有:
然后重要的就是怎么求 。我们看:这个点能取,也就是说他左上的轮廓线是 上右
。到状态里就是 ...01...
。那怎么判断呢?很简单,只需要按位与 0b11
。我们枚举到第 位,当 (sta >> i) & 0b11 == 1
时,就说明满足条件了,我们就可以进行转移, 就是 sta ^ (0b11 << i)
,就是将那两位取反,变成 右上
。而结束状态就是 (1 << m) - 1
。但是我们怎么判断当前是该该菲菲取还是该牛牛取呢?一种是写判断小函数,但是我太懒也太蒻了,写不来。于是我写的记搜,把 该谁取 放在形参里传下去就可以了。
核心代码:
123456789101112131415161718192021
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优化到了 。
核心代码:
123456789101112131415161718192021222324
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:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
#include<iostream>
#include<algorithm>
#include<queue>
#define int long long
using 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;
}