关于我想了很久才想出这题咋做这档事 - 3
#0.0 目录
目录中以下题目皆可点击查看原题
#1.0 P1107 [BJWC2008]雷涛的小猫
其实最开始是要找模拟题做...结果却做了这道题...
#1.1 简单思考
最初读题,便很容易的能看出这是一道dp(话说为什么带着模拟的标签)。
我们将这一棵棵树拼在一起,可以看成是一个方格图,题目就又变成了类似方格取数的样子,状态很好设计,\(f_{i,j}\) 表示在走到格子 \((i,j)\) 时能得到的最大柿子数,那么根据题意,可以迅速地写出转移方程:
不过要注意一点,当 \(k=j\) 时,\(d = 1\)。
#1.2 记忆化搜索
虽然,我们已经得到了转移方程式,但是,我们仍旧先从最直接的想法出发:搜索 + 记忆化。很好实现,利用 vis[i][j]
判断该格子是否搜索过。
/*P1107 DFS + 记忆化 50pts*/
inline int dfs(int x,int y){
if (vis[x][y]) //正常的记忆化
return f[x][y];
vis[x][y] = true;
if (x > h) //注意边界
return m[x][y];
f[x][y] = m[x][y];
for (int i = 1;i <= n;i ++){
if (i == y) //注意特殊情况
f[x][y] = max(f[x][y],m[x][y] + dfs(x + 1,i));
else
f[x][y] = max(f[x][y],m[x][y] + dfs(x + d,i));
}
return f[x][y];
}
int main(){
scanf("%d%d%d",&n,&h,&d);
for (int i = 1;i <= n;i ++){
scanf("%d",&num[i]);
for (int j = 1;j <= num[i];j ++){
int x;
scanf("%d",&x);
m[h - x + 1][i] ++; //注意,树的高度是从下向上递增,但我这里(脑抽)转换成的图行号自上而下递增
}
}
int ans = -INF;
for (int i = 1;i <= n;i ++) //n个点都有可能开始,不能一次解决
ans = max(ans,dfs(1,i));
printf("%d\n",ans);
return 0;
}
交上去,不出所料,只有一半的分
#1.3 转为简单递推
有了上面的记忆化,转写为递推就不难了,这里要注意边界的处理
/*P1107 N^3 dp 50pts*/
for (int i = h;i >= 1;i --)
for (int j = 1;j <= n;j ++)
f[i][j] = m[i][j] + f[i + 1][j];
for (int i = h;i >= d + 1;i --)
for (int j = 1;j <= n;j ++)
for (int k = 1;k <= n;k ++)
if (k != j)
f[i - d][k] = max(f[i - d][k],f[i][j] + m[i - d][k]);
else
f[i - 1][k] = max(f[i - 1][k],f[i][k] + m[i - 1][k]);
for (int i = h;i >= 1;i --)
for (int j = 1;j <= n;j ++)
f[i][j] = max(f[i][j],m[i][j] + f[i + 1][j]);
int ans = -INF;
for (int i = 1;i <= n;i ++)
ans = max(ans,f[1][i]);
简单一算,时间复杂度是 \(O(n^3)\),显然是不能接受的,果不其然,又是 50pts
#1.4 优化!优化!
#1.4.1 寻找冗杂循环
看上面的代码,不难发现,在
for (int k = 1;k <= n;k ++)
if (k != j)
f[i - d][k] = max(f[i - d][k],f[i][j] + m[i - d][k]);
else
f[i - 1][k] = max(f[i - 1][k],f[i][k] + m[i - 1][k]);
这第三层循环中,\(k\) 每增长 \(1\) ,就又有一个 \(f_{i-d,k}\) 加入了可以选择的范围,但我们最终需要的只有一个,不过是第 \(i-d\) 行的最大的 \(f_{i-d,j}\),那么我们完全可以每一次维护一个 \(g_i\) ,表示第 \(i\) 行最大的 \(f\) 是多少,只需加一行代码,省去了一个循环,血赚!
#1.4.2 主要代码
/*P1107 N^2 dp 100pts*/
for (int i = h;i >= 1;i --){
for (int j = 1;j <= n;j ++)
f[i][j] = m[i][j] + f[i + 1][j];
if (i >= d + 1)
for (int j = 1;j <= n;j ++)
f[i][j] = max(f[i][j],g[i + d] + m[i][j]);
for (int j = 1;j <= n;j ++)
g[i] = max(g[i],f[i][j]);
}
int ans = -INF;
for (int i = 1;i <= n;i ++)
ans = max(ans,f[1][i]);
#1.4.3 Q&A
为啥还少了两个二层循环?还少了对于列标的特判?
因为这些是我脑抽将转移方式想的复杂了。
先看对于列标的特判,这里无非是 \(f_{i-1,k}\) 和 \(f_{i-d,k}\) 的区别,而且很显然,假如最终的选择是 \(f_{i-d,k}\) ,就意味着 \(f_{i-d,k} > f_{i-1,k}\),但是,\(f_{i-1,k}\) 是从哪里转移来的呢?在他的转移路线上,一定是有 \(f_{i-d,k}\) 这个选择的,那么就有以下两种情况:
- \(f_{i-1,k}\) 是由 \(f_{i-d,k}\) 转移而来,那么根据转移方程,一定有 \(f_{i-1,k} \geqslant f_{i-d,k}\), 与假设矛盾
- \(f_{i-1,k}\) 不是由 \(f_{i-d,k}\) 转移而来,那么 \(f_{i-1,k}\) 一定是由一个 \(f_{i-1-d,l}\) 转移而来,就一定有 \(f_{i-d-1,l} > f_{i-d,k}\),且 \(f_{i-1,k} \geqslant f_{i-1-d,k}\),显然就有 \(f_{i-1,k}>f_{i-d,k}\),与假设矛盾
综上,\(f_{i-d,k}\) 一定不会被选。
那两个二层循环,实际是放到了与主要转移同一个循环里,及时更新,效果是相同的。
#2.0 P1083 [NOIP2012 提高组] 借教室
#2.1 简单思考
读题,我们可以这样理解题意:“有一个有 \(n\) 个数的数列 \(\{r_i\}\),有 \(m\) 个操作,每个操作将 \(r_{s_j}\sim r_{t_j}\) 这个区间里的数减去 \(d_i\),找到第一个使数列中出现负数的操作。”打眼一看,区间操作维护区间最小值,用什么?线段树!!!但是线段树码量太大了,所以这里选择另一种方式 ——差分
#2.1.1 神器——差分
首先——差分是什么?
回答这个问题,我们先来说一个熟悉的家伙——前缀和(这里只讲一维的)。
前缀和大家都知道,就是数列前 \(i\) 项的和,有这样的递推式:
sum[i] = sum[i - 1] + a[i];
差分则是前 \(i\) 项相邻两项的差,那给定差分数组去递推求原数组便是这样的:
a[i] = a[i - 1] + diff[i];
由此,我们便能得到以下两点:
-
给 \(diff_i\) 加上一个数 \(x\) ,相当于给 \(a_k(k\geqslant i)\) 都加上了 \(x.\)
-
再在 \(diff_{j+1}\) 处减去一个数 \(x\),相当于给 \(a_k(i \leqslant k \leqslant j)\) 都加上了 \(x\).
具体例子这里不再多讲,各位可以自行尝试。
#2.1.1 差分的使用
以上两点可以很方便的用在这里:
对于每一个操作,对原数列的差分数组做如下操作:
diff[s[i]] += d[i];
diff[t[i] + 1] -= d[i];
就相当于改变了该数列的数值。
#2.2 二分
我们看,根据上面的思路,是不是枚举每一个操作,找到第一个使数列出现负数的操作。
那么,对于操作 \(p\) ,有以下两种情况:
- 操作 \(1 \sim p\) 不会使该数列产生负数,那么显然操作 \(1\sim p-1\) 一定也不会,不需考虑了
- 操作 \(1\sim p\) 中有一个操作会使数列产生负数,那么操作 \(p + 1\sim m\) 不需考虑了,因为第一个使数列出现负数的操作已经出现
可以看到,这满足一个性质——局部舍弃性,故我们可以使用二分来优化时间复杂度。
#2.3 码代码
/*P1083 二分 100pts*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define INF 0x3fffffff
#define N 2000110
#define ll long long
#define mset(l,x) memset(l,x,sizeof(l));
#define mp(a,b) make_pair(a,b)
using namespace std;
int m,n;
int d[N],s[N],t[N];
int f[N],r[N],need[N];
inline bool check(int x){
mset(f,0);
for (int i = 1;i <= x;i ++){
f[s[i]] += d[i];
f[t[i] + 1] -= d[i];
}
for (int i = 1;i <= n;i ++){
need[i] = need[i - 1] + f[i];
if (need[i] > r[i])
return false;
}
return true;
}
int main(){
scanf("%d%d",&n,&m);
for (int i = 1;i <= n;i ++)
scanf("%d",&r[i]);
for (int i = 1;i <= m;i ++)
scanf("%d%d%d",&d[i],&s[i],&t[i]);
int l = 1,r = m,ans = 0;
if (check(m)){
printf("0");
return 0;
}
while (l <= r){
int mid = (l + r) >> 1;
if (check(mid)){
ans = mid;
l = mid + 1;
}
else
r = mid - 1;
}
printf("-1\n%d",l);
return 0;
}
#2.4 另一种实现——线段树
线段树来实现这题十分直接,直接暴力维护区间最小值即可,不再多讲,见代码:
/*P1083 线段树 100pts*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
#define N 1001000
#define INF 0x3fffffff
#define ll long long
#define mset(l,x) memset(l,x,sizeof(l))
using namespace std;
int cnt,n,m;
ll a[N],minn[N * 4],add[N * 4];
inline void pushup(int k){
minn[k] = min(minn[k << 1],minn[k << 1 | 1]);
}
inline void build(int k,int l,int r){
if (l == r){
minn[k] = a[l];
return;
}
int mid = l + r >> 1;
build(k << 1,l,mid);
build(k << 1 | 1,mid + 1,r);
pushup(k);
}
inline void Add(int k,int l,int r,ll v){
add[k] += v;
minn[k] += v;
}
inline void pushdown(int k,int l,int r,int mid){
if (!add[k])
return;
Add(k << 1,l,mid,add[k]);
Add(k << 1 | 1,mid + 1,r,add[k]);
add[k] = 0;
}
inline ll query(int k,int l,int r,int x,int y){
cnt = max(cnt,k);
if (l >= x && r <= y)
return minn[k];
int mid = l + r >> 1;
ll res = INF;
pushdown(k,l,r,mid);
if (x <= mid)
res = min(res,query(k << 1,l,mid,x,y));
if (y > mid)
res = min(res,query(k << 1 | 1,mid + 1,r,x,y));
return res;
}
inline void modify(int k,int l,int r,int x,int y,ll v){
if (l >= x && r <= y){
Add(k,l,r,v);
return;
}
int mid = l + r >> 1;
pushdown(k,l,r,mid);
if (x <= mid)
modify(k << 1,l,mid,x,y,v);
if (y > mid)
modify(k << 1 | 1,mid + 1,r,x,y,v);
pushup(k);
}
int main(){
scanf("%d%d",&n,&m);
for (int i = 1;i <= n;i ++)
scanf("%lld",&a[i]);
build(1,1,n);
for (int i = 1;i <= m;i ++){
int s,t,x;
scanf("%d%d%d",&x,&s,&t);
modify(1,1,n,s,t,-x);
if (query(1,1,n,s,t) < 0){
printf("-1\n%d",i);
return 0;
}
}
printf("0");
return 0;
}
#3.0 P3128 [USACO15DEC]Max Flow P
本题可以使用树上差分解决,而且并不难,但是才疏学浅的我(因为不会)使用了轻重链剖分实现(调试到死),不想多讲QwQ
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define INF 0x3fffffff
#define ll long long
#define mset(l,x) memset(l,x,sizeof(l))
#define mp(a,b) make_pair(a,b)
using namespace std;
const int N = 5e4+1e4;
struct Edge{ //建树所需结构体
int u;
int v;
int next;
};
Edge e[N << 1];
struct Node{ //线段树所需结构体
int l,r;
int ls,rs;
int maxn;
int lazy;
};
Node p[N << 2];
int n,m,cnt;
int head[N],top[N],id[N],rk[N];
int d[N],size[N],son[N],f[N];
inline void add_e(int u,int v){ //加边
e[cnt].u = u;
e[cnt].v = v;
e[cnt].next = head[u];
head[u] = cnt ++;
}
inline void dfs1(int x,int fa,int depth){ //树剖第一个dfs,确定重儿子与父节点
f[x] = fa;
d[x] = depth;
size[x] = 1;
for (int i = head[x];i != -1;i = e[i].next){
int v = e[i].v;
if (v == fa)
continue;
dfs1(v,x,depth + 1);
size[x] += size[v];
if (size[v] > size[son[x]])
son[x] = v;
}
}
inline void dfs2(int x,int t){ //树剖第二个dfs,确定重链链首
top[x] = t;
id[x] = cnt;
rk[cnt ++] = x;
if (!son[x])
return;
dfs2(son[x],t);
for (int i = head[x];i != -1;i = e[i].next){
int v= e[i].v;
if (v != f[x] && v != son[x])
dfs2(v,v);
}
}
inline int len(int k){ //
return p[k].r - p[k].l + 1;
}
inline void pushup(int k){
p[k].maxn = max(p[p[k].ls].maxn,p[p[k].rs].maxn);
}
inline void add(int k,int x){
p[k].maxn += x;
p[k].lazy += x;
}
inline void pushdown(int k){ //基本的下放懒标记
if (p[k].lazy){
int ls = p[k].ls,rs = p[k].rs;
add(ls,p[k].lazy);
add(rs,p[k].lazy);
p[k].lazy = 0;
}
}
inline void build(int k,int l,int r){ //基本的建树
if (l == r){
p[k].maxn = 0;
p[k].l = p[k].r = l;
return;
}
int mid = (l + r) >> 1;
p[k].ls = cnt ++;
p[k].rs = cnt ++;
build(p[k].ls,l,mid);
build(p[k].rs,mid + 1,r);
p[k].l = p[p[k].ls].l;
p[k].r = p[p[k].rs].r;
pushup(k);
}
inline void modify(int k,int l,int r,int x){ //基本的区间修改
if (l <= p[k].l && p[k].r <= r){
add(k,x);
return;
}
pushdown(k);
int mid = (p[k].l + p[k].r) >> 1;
if (mid >= l)
modify(p[k].ls,l,r,x);
if (mid < r)
modify(p[k].rs,l,r,x);
pushup(k);
}
inline int query(int k, int l,int r){ //基本的区间查询
if (l <= p[k].l && p[k].r <= r)
return p[k].maxn;
pushdown(k);
int mid = (p[k].l + p[k].r) >> 1;
int ans = -INF;
if (mid >= l)
ans = max(ans,query(p[k].ls,l,r));
if (mid < r)
ans = max(ans,query(p[k].rs,l,r));
return ans;
}
inline void update(int x,int y){ //树剖求LCA,并沿途修改点值
while (top[x] != top[y]){
if (d[top[x]] < d[top[y]])
swap(x,y);
modify(0,id[top[x]],id[x],1);
x = f[top[x]];
}
if (id[x] > id[y])
swap(x,y);
modify(0,id[x],id[y],1);
}
int main(){
mset(head,-1);
scanf("%d%d",&n,&m);
for (int i = 1;i < n;i ++){
int u,v;
scanf("%d%d",&u,&v);
add_e(u,v);
add_e(v,u);
}
cnt = 1;
dfs1(1,0,1);
dfs2(1,1);
cnt = 0;
build(cnt ++,1,n);
while (m --){
int a,b;
scanf("%d%d",&a,&b);
update(a,b);
}
printf("%d",query(0,1,n));
return 0;
}
#4.0 P1126 机器人搬重物
打眼一看,搜索题。这题思维含量并不多,但情况是真不少,不过根据题意还是不难做出的。
要注意以下两点:
- 给的地图是描述格子的,我们走的是左上角的格点(但我怎么数都是右下角的格点)
- 机器人是有宽度的球,所以不能走边界
#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
#define N 210
#define INF 0x3fffffff
#define mset(l,x) memset(l,x,sizeof(l))
#define mp(a,b) make_pair(a,b)
using namespace std;
struct Robot{
int d;
int x,y;
int t;
};
int n,m;
int M[N][N],s[2],e[2];
bool vis[N][N][4];
int dir[3][4][2] = {
{{-1,0},{0,-1},{1,0},{0,1}},
{{-2,0},{0,-2},{2,0},{0,2}},
{{-3,0},{0,-3},{3,0},{0,3}}};
queue <Robot> q;
int main(){
scanf("%d%d",&n,&m);
Robot b;
for (int i = 1,j;i <= n;i ++)
for (j = 1;j <= m;j ++){
scanf("%d",&M[i][j]);
if (M[i][j])
M[i - 1][j - 1] = M[i - 1][j] = M[i][j - 1] = true;
}
scanf("%d%d%d%d",&b.x,&b.y,&e[0],&e[1]);
char c;
cin >> c;
if (c == 'N')
b.d = 0;
else if (c == 'W')
b.d = 1;
else if (c == 'S')
b.d = 2;
else b.d = 3;
b.t = 0; //BFS
q.push(b);
while (!q.empty()){
Robot now = q.front();
q.pop();
if (now.x == e[0] && now.y == e[1]){
printf("%d",now.t);
return 0;
}
if (vis[now.x][now.y][now.d])
continue;
vis[now.x][now.y][now.d] = true;
Robot newr;
for (int i = 0;i < 3;i ++){
int cx = now.x + dir[i][now.d][0];
int cy = now.y + dir[i][now.d][1];
if (cx >= 1 && cx < n && cy >= 1 && cy < m && !M[cx][cy]){
bool f = false;
for (int j = 0;j <= i;j ++)
if (M[now.x + dir[j][now.d][0]][now.y + dir[j][now.d][1]])
f = true;
if (f)
continue;
newr.d = now.d;
newr.t = now.t + 1;
newr.x = cx;
newr.y = cy;
q.push(newr);
}
}
newr.x = now.x;
newr.y = now.y;
newr.t = now.t + 1;
int dn = (now.d + 1) % 4;
newr.d = dn;
q.push(newr);
dn = now.d - 1;
if (dn == -1)
dn = 3;
newr.d = dn;
q.push(newr);
}
printf("-1");
return 0;
}
更新日志及说明
更新
- 初次完成编辑 - \(2021.2.2\)