快来踩爆这个蒟蒻吧|

Little_corn

园龄:1年1个月粉丝:11关注:17

📂笔记
🔖DP
2024-09-18 13:43阅读: 12评论: 0推荐: 0

关于一类偏序问题

对于一类依赖偏序关系计算答案的问题,由于我们只关注元素之间的大小关系,从而可以通过特殊的枚举方式来避免多种情况分类讨论。常见方法有:

  • <,> :通过从小到大的方式依次考虑元素。

  • abs,max,min:通过拆成 <> 的形式后,再从小到大考虑。

  • 当然如果是二位偏序问题,还可以放到坐标系下进行讨论。

一些例题:

[模拟赛]dist

Statement:

给定一棵 n(n106) 个节点带边权的树,定义 Min(x,y)(x,y) 路径上的边权最小值。求 maxr=1nviMin(r,v)

Solution:

我们只关注路径上最小的一条边,于是按边权从小到大依次考虑边带来的贡献,然后分成两个连通块做。但这样太慢了,所以逆向的进行合并,用并查集维护即可。

qwq
#include<bits/stdc++.h>
#pragma GCC optimize(3, "Ofast", "inline")
#define int long long
using namespace std;
const int N = 1e6 + 10;
int n, fa[N], siz[N], val[N];
struct Edge{
int u, v, w;
}E[N];
bool cmp(struct Edge E1, struct Edge E2){
return E1.w > E2.w;
}
int findfa(int x){return fa[x] = (fa[x] == x) ? x : findfa(fa[x]);}
void merge(int x, int y, int w){
int fx = findfa(x), fy = findfa(y);
if(fx == fy) return; if(siz[fx] < siz[fy]) swap(fx, fy);
val[fx] = max(val[fx] + siz[fy] * w, val[fy] + siz[fx] * w);
fa[fy] = fx; siz[fx] += siz[fy];
}
signed main(){
// freopen("dist3.in", "r", stdin);
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n; for(int i = 1; i <= n; i++) siz[i] = 1, fa[i] = i;
for(int i = 1; i < n; i++) cin >> E[i].u >> E[i].v >> E[i].w;
sort(E + 1, E + n, cmp);
for(int i = 1; i < n; i++) merge(E[i].u, E[i].v, E[i].w);
cout << val[findfa(1)];
return 0;
}

[模拟赛]博弈游戏

Statement:

AliceBob 正在一个有向图上玩游戏。初始图上的某个节点上放着棋子,他们轮流进行操作,每次选择棋子所在节点的一条出边把棋子移过去,如果没有出边则游戏直接结束。Alice 先手,游戏在 10100 步后结束。节点的分数恰好是它的编号(从 1 开始编号),Alice 的最终得分是棋子所经过的节点的分数的最大值。Alice 想最大化她的得分,Bob 想最小化 Alice 的得分。请问最优策略下,从每个节点出发,Alice 的得分是多少?

Solution:

fu,1/0Alice/Bob 执棋时的得分。显然有:

fu,0=max(u,max(fv,1|(u,v)))fu,1=max(u,min(fv,0|(u,v)))

注意到 max,min 这类的偏序关系,于是考虑从大到小考虑加入每个点,并逐步确定每个状态的答案。假设现在枚举到点 u,假设 fu,0/1 之前没有被确定下来,那么显然 fu,0/1=u。那么我们将新确定的答案加入到一个更行队列中,假设现在的状态是 (u,id)

  • id=0:显然我们更新的是一些点的 fv,1,那么注意到我们在从大到小枚举的过程中,最后一次更新到 fv,1 时才会对 fv,1 产生贡献,于是我们动态更行他的入度,当 v 的入度变为 0 时,就将 fv,1 赋值为 fu,0

  • id=1:此时更新的是一些点的 fv,0,只要 fv,0 没有被更新过,那么 fu,1 就是他的所有后继中最小的那个了。

拿队列更新就可以了。、

qwq
#include<bits/stdc++.h>
#define pir pair<int, int>
#pragma GCC optimize(3, "Ofast", "inline")
using namespace std;
const int N = 1e5 + 10;
int n, m, out[N], f[N][2];
struct edges{
int v, next;
}edges[N << 1];
int head[N], idx;
void add_edge(int u, int v){
edges[++idx] = {v, head[u]};
head[u] = idx;
}
void solve(int rt){
queue<pir> Q;
if(!f[rt][0]) Q.push(make_pair(rt, 0)), f[rt][0] = rt;
if(!f[rt][1]) Q.push(make_pair(rt, 1)), f[rt][1] = rt;
while(!Q.empty()){
int u = Q.front().first, id = Q.front().second; Q.pop();
// cout << u << " " << id << " " << f[u][id] << "\n";
for(int i = head[u]; i; i = edges[i].next){
int v = edges[i].v;
if(id == 0 && (!f[v][1])) f[v][1] = rt, Q.push(make_pair(v, 1));
else if(id == 1){
out[v]--;
if(!out[v]) f[v][0] = rt, Q.push(make_pair(v, 0));
}
}
}
}
signed main(){
// freopen("game.in", "r", stdin);
// freopen("game.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= m; i++){
int x, y; cin >> x >> y;
add_edge(y, x); out[x]++;
}
for(int i = n; i > 0; i--) solve(i);
for(int i = 1; i <= n; i++) cout << f[i][1] << " ";
return 0;
}

本文作者:Little_corn

本文链接:https://www.cnblogs.com/little-corn/p/18416646

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Little_corn  阅读(12)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起