题目

LOJ523

官方题解

分析

由于某些原因,以下用「左侧点」和「右侧点」分别代替题目中的「妹子」和「男生」。

根据题意,显然能得出一个左侧点只能向一个或两个右侧点连边。这似乎启发我们把左侧点不看成点,而看成关于右侧的两个点之间的「关系」(更正:不看题解根本想不到)。换句话说,把左侧点看成边,右侧点看成点,就得到了一张 \(n\) 个点 \(m\) 条边的新图。如果一个左侧点向 \(a\) 和 \(b\) 两个右侧点连边,就在新图中加入一条连接 \((a,b)\) 的边(左侧点只连接一个右侧点就是自环)。

考虑左侧点必须选且仅选一个右侧点(注意题目中「保证左侧满匹配」),即新图中每条边必须选择它的一个端点;而新图中每个点只能被一条边选择。说得直观一点,就是要给每条边定向,指向哪个点就表示选择了这个点。同时每个点的入度最多为 \(1\) 。

那么问题就转化成了:给定一张无向图,要给每条边选择一个方向,满足每个点入度最多为 \(1\) 。每条边的两个方向分别有一个权值,要求最大化权值和。

看起来好像还是不可做?通过膜拜题解, 请再次注意这句话:保证左侧满匹配 。根据 霍尔定理 (证明先咕着),左侧满匹配当且仅当在左侧选出任意一个大小为 \(k(0\leq k\leq m)\) 的子集,与这个子集有边相连的右侧点至少有 \(k\) 个。放在新图上,任意选出 \(k\) 条边所对应的点集的大小不少于 \(k\) 。

这说明什么呢?考虑对于新图中的一个连通分量(即极大连通块),它的点数必须大于等于边数。而既然连通,点数最多是边数加 \(1\) 。因此一个连通分量的边数要么比点数少 \(1\) ,要么等于点数。即:要么是树,要么是基环树。这个图竟然有如此优美的性质。

基环树必然定向为基环外向树。对于基环树中的边,如果是树边,那么一定往远离环的方向连。而所有环边都必须连成一个方向(即要么全部顺时针,要么全部逆时针)。因此每个基环树只有两种方案:要么所有顺时针的环边加上外向树边,要么所有逆时针环边加上外向树边。维护这两种情况的答案即可。修改时如果是内向树边就不理(必不选),外向树边直接给最终答案加上差值(必选),环边就更新对应方向的答案。

树会定向成以一个点为根的有向树,因此要分别维护以每个点为根的答案。先随便以一个点为根求出 dfs 序,然后考虑每条边的贡献:如果一条边是从上往下的,那么当根不在 下端点 的子树内时会贡献答案;如果一条边是从下往上的,那么当根在下端点的子树内时会贡献答案。在线段树上区间修改并维护最大值即可。

代码

众所周知,基环树的题嘴起来容易(虽然这个题也不容易),写起来恶心。所以耐心地去写和调吧。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <vector>
using namespace std; namespace zyt
{
template<typename T>
inline bool read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != EOF && c != '-' && !isdigit(c));
if (c == EOF)
return false;
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
return true;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
typedef pair<int, int> pii;
const int N = 5e5 + 10;
vector<pii> g[N];
int m, n, T, a[N], b[N], dfn[N], fa[N], out[N], dfncnt, ecnt, v[N << 1], val[N][2], rot[N], opp[N << 1];
bool iscir[N], incir[N], vis[N], dir[N << 1];
pii e[N << 1];
int f(const int x)
{
return x == fa[x] ? x : fa[x] = f(fa[x]);
}
void dfs(const int u, const int r)
{
vis[u] = true;
dfn[u] = ++dfncnt;
rot[u] = r;
for (vector<pii>::iterator it = g[u].begin(); it != g[u].end(); it++)
{
int v = it->first;
if (vis[v] || incir[v])
continue;
dfs(v, r);
}
out[u] = dfncnt;
}
int findcir(const int u, const int from)
{
static bool insta[N];
if (insta[u])
return u;
insta[u] = true;
for (vector<pii>::iterator it = g[u].begin(); it != g[u].end(); it++)
{
int v = it->first, tmp;
if (opp[it->second] == from)
continue;
if (~(tmp = findcir(v, it->second)))
{
insta[u] = false;
val[f(u)][0] += ::zyt::v[it->second];
val[f(u)][1] += ::zyt::v[opp[it->second]];
dir[it->second] = 0, dir[opp[it->second]] = 1;
incir[u] = true;
if (u != tmp)
return tmp;
else
return -1;
}
}
insta[u] = false;
return -1;
}
namespace Segment_Tree
{
struct node
{
int mx, tag;
}tree[N << 2];
void add(const int rot, const int x)
{
tree[rot].mx += x, tree[rot].tag += x;
}
void pushdown(const int rot)
{
if (tree[rot].tag)
{
add(rot << 1, tree[rot].tag);
add(rot << 1 | 1, tree[rot].tag);
tree[rot].tag = 0;
}
}
void update(const int rot)
{
tree[rot].mx = max(tree[rot << 1].mx, tree[rot << 1 | 1].mx);
}
void add(const int rot, const int lt, const int rt, const int ls, const int rs, const int x)
{
if (ls <= lt && rt <= rs)
return void(add(rot, x));
int mid = (lt + rt) >> 1;
pushdown(rot);
if (ls <= mid)
add(rot << 1, lt, mid, ls, rs, x);
if (rs > mid)
add(rot << 1 | 1, mid + 1, rt, ls, rs, x);
update(rot);
}
int query(const int rot, const int lt, const int rt, const int ls, const int rs)
{
if (ls <= lt && rt <= rs)
return tree[rot].mx;
int mid = (lt + rt) >> 1;
pushdown(rot);
if (rs <= mid)
return query(rot << 1, lt, mid, ls, rs);
else if (ls > mid)
return query(rot << 1 | 1, mid + 1, rt, ls, rs);
else
return max(query(rot << 1, lt, mid, ls, rs),
query(rot << 1 | 1, mid + 1, rt, ls, rs));
}
}
int work()
{
using namespace Segment_Tree;
read(m), read(n), read(T);
for (int i = 0; i < n; i++)
fa[i] = i;
for (int i = 0; i < m; i++)
read(a[i]);
for (int i = 0; i < m; i++)
read(b[i]);
for (int i = 0; i < m; i++)
{
int x, y;
x = (a[i] - b[i] + n) % n;
y = (a[i] + b[i]) % n;
if (x < y)
swap(x, y);
read(v[ecnt]);
e[ecnt] = pii(x, y);
//fprintf(stderr, "%d %d %d\n", x, y, v[ecnt]);
g[x].push_back(pii(y, ecnt++));
if (x != y)
{
read(v[ecnt]);
e[ecnt] = pii(y, x);
opp[ecnt - 1] = ecnt, opp[ecnt] = ecnt - 1;
//fprintf(stderr, "%d %d %d\n", y, x, v[ecnt]);
g[y].push_back(pii(x, ecnt++));
}
else
opp[ecnt - 1] = ecnt - 1;
if (f(x) == f(y))
iscir[f(x)] = true;
else
{
iscir[f(y)] |= iscir[f(x)];
fa[f(x)] = f(y);
}
}
for (int i = 0; i < n; i++)
if (i == f(i) && iscir[i])
findcir(i, -1);
for (int i = 0; i < n; i++)
if ((!iscir[f(i)] && i == f(i)) || incir[i])
dfs(i, i);
for (int i = 0; i < ecnt; i++)
{
if (iscir[f(e[i].first)])
{
if (!incir[e[i].first] || !incir[e[i].second])
{
if (dfn[e[i].first] < dfn[e[i].second])
val[f(e[i].first)][0] += v[i], val[f(e[i].first)][1] += v[i];
}
}
else
{
if (dfn[e[i].first] < dfn[e[i].second])
{
int r = rot[e[i].first];
add(1, 1, n, dfn[r], dfn[e[i].second] - 1, v[i]);
if (out[e[i].second] < out[r])
add(1, 1, n, out[e[i].second] + 1, out[r], v[i]);
}
else
add(1, 1, n, dfn[e[i].first], out[e[i].first], v[i]);
}
}
int q, ans = 0;
for (int i = 0; i < n; i++)
if (i == f(i))
{
if (iscir[i])
ans += max(val[i][0], val[i][1]);
else
ans += query(1, 1, n, dfn[rot[i]], out[rot[i]]);
}
write(ans), putchar('\n');
read(q);
while (q--)
{
int x, vv;
read(x), read(vv);
x -= ans * T, vv -= ans * T;
--x;
if (iscir[f(e[x].first)])
{
int t = f(e[x].first);
ans -= max(val[t][0], val[t][1]);
if (!incir[e[x].first] || !incir[e[x].second])
{
if (dfn[e[x].first] < dfn[e[x].second])
val[t][0] += vv - v[x], val[t][1] += vv - v[x];
}
else
{
val[t][dir[x]] += vv - v[x];
if (e[x].first == e[x].second)
val[t][dir[x] ^ 1] += vv - v[x];
}
ans += max(val[t][0], val[t][1]);
}
else
{
int r = rot[e[x].first];
ans -= query(1, 1, n, dfn[r], out[r]);
if (dfn[e[x].first] < dfn[e[x].second])
{
add(1, 1, n, dfn[r], dfn[e[x].second] - 1, vv - v[x]);
add(1, 1, n, out[e[x].second] + 1, out[r], vv - v[x]);
}
else
add(1, 1, n, dfn[e[x].first], out[e[x].first], vv - v[x]);
ans += query(1, 1, n, dfn[r], out[r]);
}
v[x] = vv;
write(ans), putchar('\n');
}
return 0;
}
}
int main()
{
return zyt::work();
}

【LOJ523】[LibreOJ β Round #3]绯色 IOI(悬念)(霍尔定理_基环树)的更多相关文章

  1. [CSP-S模拟测试]:party?(霍尔定理+最小割+树链剖分)

    题目描述 $Treeland$国有$n$座城市,其中$1$号城市是首都,这些城市被一些单向高铁线路相连,对于城市$i\neq 1$,有一条线路从$i$到$p_i(p_i<i)$.每条线路都是一样 ...

  2. 「BZOJ 1791」「IOI 2008」Island「基环树」

    题意 求基环树森林所有基环树的直径之和 题解 考虑的一个基环树的直径,只会有两种情况,第一种是某个环上结点子树的直径,第二种是从两个环上结点子树内的最深路径,加上环上这两个结点之间的较长路径. 那就找 ...

  3. [LOJ#522]「LibreOJ β Round #3」绯色 IOI(危机)

    [LOJ#522]「LibreOJ β Round #3」绯色 IOI(危机) 试题描述 IOI 的比赛开始了.Jsp 和 Rlc 坐在一个角落,这时他们听到了一个异样的声音 …… 接着他们发现自己收 ...

  4. LibreOJ β Round #2 题解

    LibreOJ β Round #2 题解 模拟只会猜题意 题目: 给定一个长为 \(n\) 的序列,有 \(m\) 次询问,每次问所有长度大于 \(x\) 的区间的元素和的最大值. \(1 \leq ...

  5. loj #547. 「LibreOJ β Round #7」匹配字符串

    #547. 「LibreOJ β Round #7」匹配字符串   题目描述 对于一个 01 串(即由字符 0 和 1 组成的字符串)sss,我们称 sss 合法,当且仅当串 sss 的任意一个长度为 ...

  6. [LOJ#531]「LibreOJ β Round #5」游戏

    [LOJ#531]「LibreOJ β Round #5」游戏 试题描述 LCR 三分钟就解决了问题,她自信地输入了结果-- > -- 正在检查程序 -- > -- 检查通过,正在评估智商 ...

  7. [LOJ#530]「LibreOJ β Round #5」最小倍数

    [LOJ#530]「LibreOJ β Round #5」最小倍数 试题描述 第二天,LCR 终于启动了备份存储器,准备上传数据时,却没有找到熟悉的文件资源,取而代之的是而屏幕上显示的一段话: 您的文 ...

  8. [LOJ#516]「LibreOJ β Round #2」DP 一般看规律

    [LOJ#516]「LibreOJ β Round #2」DP 一般看规律 试题描述 给定一个长度为 \(n\) 的序列 \(a\),一共有 \(m\) 个操作. 每次操作的内容为:给定 \(x,y\ ...

  9. [LOJ#515]「LibreOJ β Round #2」贪心只能过样例

    [LOJ#515]「LibreOJ β Round #2」贪心只能过样例 试题描述 一共有 \(n\) 个数,第 \(i\) 个数 \(x_i\) 可以取 \([a_i , b_i]\) 中任意值. ...

随机推荐

  1. Atcoder Beginner Contest 138 简要题解

    D - Ki 题意:给一棵有根树,节点1为根,有$Q$次操作,每次操作将一个节点及其子树的所有节点的权值加上一个值,问最后每个节点的权值. 思路:dfs序再差分一下就行了. #include < ...

  2. Likelihood function

    似然函数 统计学中,似然函数是一种关于统计模型参数的函数,表示模型参数中的似然性. 给定输出x时,关于参数θ的似然函数L(θ|x)(在数值上)等于给定参数θ后变量X的概率:L(θ|x)=P(X=x|θ ...

  3. podium服务器端的微前端开发框架

    podium 是一个比较全的微前端开发框架. 具有以下特性 自治开发 强大的组合能力 基于约定的开发模式 podium 包含的组件 podlets 页面片段,是一个独立的http 服务,独立运行的,实 ...

  4. Anniversary party(hdu1520)(poj2342)题解

    原题地址:http://poj.org/problem?id=2342 题目大意: 上司和下属不能同时参加派对,求参加派对的最大活跃值. 关系满足一棵树,每个人都有自己的活跃值(-128~127) 求 ...

  5. A*G#C001

    AGC001 A BBQ Easy 贪心. https://agc001.contest.atcoder.jp/submissions/7856034 B Mysterious Light 很nb这个 ...

  6. python 类的倒入

    test.pyclass sss: def ddd(self): print("hello") test2.pyfrom testone import sss

  7. 奇袭 CodeForces 526F Pudding Monsters 题解

    考场上没有认真审题,没有看到该题目的特殊之处: 保证每一行和每一列都恰有一只军队,即每一个Xi和每一个Yi都是不一样 的. 于是无论如何也想不到复杂度小于$O(n^3)$的算法, 只好打一个二维前缀和 ...

  8. Redis(一) 数据结构与底层存储 & 事务 & 持久化 & lua

    参考文档:redis持久化:http://blog.csdn.net/freebird_lb/article/details/7778981 https://blog.csdn.net/jy69240 ...

  9. vue中select设置默认选中

    vue中select设置默认选中 一.总结 一句话总结: 通过v-model来:select上v-model的值为option默认选中的那项的值(value) 二.select设置默认选中实例 < ...

  10. uniapp - emmet

    话说,emment是官方uniapp直接引入的.基本上没做啥修改:可以点这里查看所有用法 - http://emmet.evget.com/ 1.类似css层级写法 1.1 view.ok>vi ...