[每日随题12] BFS- 树上背包 - 动态开点线段树
整体概述
- 难度:1500 \(\rightarrow\) 2000 \(\rightarrow\) 2200
P3918 [国家集训队] 特技飞行
-
标签:BFS
-
前置知识:无
-
难度:黄 1500
题目描述:
输入格式:
输出格式:
样例输入:
2
1 2 3
4 5 6
7 8 9
1 5 2
4 6 3
7 8 9
样例输出:
0
3
解题思路:
-
本题如果从每一个起点开始 bfs 到终点,在 \(10^5\) 次询问下会超时。
-
我们发现 \(9!\) 仅为 \(362,880\),我们考虑从终点预处理出到所有可能情况的步数,最后直接 \(O(1)\) 查询。
-
我们用 \(string\) 来存矩阵,注意从终点出发需要逆时针旋转,跑一遍 \(bfs\) 即可。
完整代码
#include<bits/stdc++.h>
#define P(x,y) ((x)*3+(y))
using namespace std;
const int N = 9*8*7*6*5*4*3*2 + 5;
unordered_map<string,int> mp;
inline string ring(string str,int x,int y){
char tmp = str[P(x,y)];
str[P(x,y)] = str[P(x,y+1)], str[P(x,y+1)] = str[P(x+1,y+1)];
str[P(x+1,y+1)] = str[P(x+1,y)], str[P(x+1,y)] = tmp;
return str;
}
struct Node{string s;int step;}; queue<Node> qu;
int ans[N]; bool vis[N];
inline void bfs(string st){
qu.push({st,0}), mp[st] = mp.size()+1;
while(!qu.empty()){
Node cur = qu.front(); qu.pop();
string s = cur.s; int step = cur.step;
for(int x=0;x<=1;x++) for(int y=0;y<=1;y++){
string ns = ring(s,x,y);
if(mp.find(ns) == mp.end()){
mp[ns] = mp.size()+1;
ans[mp[ns]] = step+1;
qu.push({ns,step+1});
}
}
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
bfs("123456789");
int T; cin >> T;
while(T--){
string str = "";
for(char i=1,ch;i<=9;i++) cin >> ch, str += ch;
cout << ans[mp[str]] << '\n';
}
return 0;
}
P4516 [JSOI2018] 潜入行动
-
标签:树上背包
-
前置知识:背包\(DP\),树型\(DP\)
-
难度:蓝 2000
题目描述:
输入格式:
输出格式:
样例输入:
5 3
1 2
2 3
3 4
4 5
样例输出:
1
解题思路:
-
由于每个点都可以放置监听设备,且不监听该点,则我们需要记录该点是否放了监听器,以及该点是否被监听。
-
那么我们定义 \(dp_{u,k,p,q}\) 表示以 \(u\) 为根的子树用了 \(k\) 个监听器,下方所有节点均被监听,节点 \(u\) 是否放置监听器为 \(p\),是否被监听为 \(q\),状态下的总方案数。
-
我们发现背包每次合并需要 \(O(k^2)\) 的复杂度,而总共有 \(n\) 个节点,最大合并次数不会超过 \(\frac {n} {k}\) 次,那么总复杂度为 \(O(nk)\) 不会超时。
-
接着考虑如何状态转移,对于当前节点 \(u\),某个子节点 \(v\),我们暴力枚举 \(u\) 已经用了 \(a\) 个监听器,\(v\) 子树额外用了 \(b\) 个监听器,\(u\) 节点的状态为 \(p1\),\(q1\),\(v\) 节点的状态为 \(p2\),\(q2\),此时可以更新哪个节点。
我们需要 \(v\) 节点被监听,那么 \(p1|q2\) 需要为 \(1\),在此条件下可以更新节点 \(dp_{u,a+b,p1,p2|q1}\)。
-
那么全部更新一遍,最后 \(dp_{1,K,0,1} + dp_{1,K,1,1}\) 即是答案。
完整代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
using namespace std;
const int N = 1e5+5,M = 105, mod = 1e9+7;
int n,K,ha[N],idx;
struct Edge{int to,ne;}edge[N<<1];
inline void ins(int u,int v){
edge[++idx] = {v,ha[u]}, ha[u] = idx;
}
int dp[N][M][2][2],tmp[M][2][2],maxK[N];
inline void dfs(int u,int par){
maxK[u] = 1;
dp[u][0][0][0] = dp[u][1][1][0] = 1;
for(int i=ha[u];i;i=edge[i].ne){
int v = edge[i].to;
if(v == par) continue;
dfs(v,u);
int up = min(K,maxK[u] + maxK[v]);
rep(a,0,maxK[u]) rep(b,0,min(maxK[v],K-a))
rep(p1,0,1) rep(q1,0,1) rep(p2,0,1) rep(q2,0,1)
if(p1 | q2) tmp[a+b][p1][p2|q1] = (tmp[a+b][p1][p2|q1] + 1ll*dp[u][a][p1][q1]*dp[v][b][p2][q2]%mod)%mod;
rep(k,0,up) rep(p,0,1) rep(q,0,1)
dp[u][k][p][q] = tmp[k][p][q], tmp[k][p][q] = 0;
maxK[u] = up;
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin >> n >> K;
for(int i=1,u,v;i<n;i++){
cin >> u >> v;
ins(u,v), ins(v,u);
}
dfs(1,0);
cout << (dp[1][K][0][1] + dp[1][K][1][1])%mod;
return 0;
}
P5848 [IOI 2005] mou
-
标签:动态开点线段树
-
前置知识:线段树
-
难度:蓝 2200
题目描述:
输入格式:
输出格式:
样例输入:
4
Q 1
I 1 4 2
Q 3
Q 1
I 2 2 -1
Q 3
E
样例输出:
4
1
0
3
解题思路:
-
读完题目,发现所求很简单,一个支持区间修改为同一个数,查询满足 \(h\lt sum(1,x)\) 的最小的 \(x\)。
-
发现数据中可以把高度改为负数,那么我们不只需要记录区间和 \(sm\) 还需要记录区间前缀和的最大值 \(mx\),查询时在线段树上分治即可。
-
需要注意的是,下边 \(n\le 10^9\) 我们需要动态开点,总复杂度 \(O(q·log_{2}{(10^9)})\)。
完整代码
#include<bits/stdc++.h>
#define mid (((l)+(r))>>1)
using namespace std;
const int Q = 1e5,INF = 0x3f3f3f3f;
int n,ls[Q<<6],rs[Q<<6],sum[Q<<6],pmx[Q<<6],tag[Q<<6],tot,rt;
inline void up(int u){
sum[u] = sum[ls[u]] + sum[rs[u]];
pmx[u] = max(pmx[ls[u]],sum[ls[u]] + pmx[rs[u]]);
}
inline void lazy(int v,int &u,int l,int r){
if(!u) u = ++tot;
sum[u] = (r-l+1)*v;
pmx[u] = max(0,sum[u]);
tag[u] = v;
}
inline void down(int &u,int l,int r){
if(tag[u] != -INF){
lazy(tag[u],ls[u],l,mid);
lazy(tag[u],rs[u],mid+1,r);
tag[u] = -INF;
}
}
inline void update(int x,int y,int v,int &u,int l=1,int r=n){
if(!u) tag[u = ++tot] = -INF;
if(x <= l && r <= y){ lazy(v,u,l,r); return; }
down(u,l,r);
if(x <= mid) update(x,y,v,ls[u],l,mid);
if(y > mid) update(x,y,v,rs[u],mid+1,r);
up(u);
}
inline int query(int h,int &u,int l=1,int r=n){
if(l == r) return h >= sum[u] ? l : l-1;
down(u,l,r);
if(pmx[ls[u]] <= h) return query(h-sum[ls[u]],rs[u],mid+1,r);
return query(h,ls[u],l,mid);
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin >> n;
update(1,n,0,rt);
char op; cin >> op;
while(op != 'E'){
if(op == 'Q'){
int h; cin >> h;
cout << query(h,rt) << '\n';
}else{
int l,r,v; cin >> l >> r >> v;
update(l,r,v,rt);
}
cin >> op;
}
return 0;
}