[套题记录]Codeforces Global Round 14
command_block
2021-10-21 21:13:09
刷着刷着就要刷完了……
有一些其他题混入其中。
# [DP] E Phoenix and Computers
**题意** : 给定 $n$ ,有 $n$ 个电脑排成一排。
初始时所有电脑都关机,你要**按某种顺序**手动开电脑,使得最终所有电脑都被开启位置。
若某电脑两侧的电脑都开机,这台电脑会立刻自动开机。不能开一个已经开了的电脑。
将手动开的电脑编号依次记下,求有多少种可能的编号序列。
答案对给定的质数 $mod$ 取模。
$n\leq 400$ ,时限$\texttt{3s}$。
------------
- $O(n^2)$ 做法可见:[一类基于笛卡尔树的排列计数DP](https://www.luogu.com.cn/blog/command-block/yi-lei-ji-yu-duan-di-pai-lie-ji-shuo-dp)
# [??] F Phoenix and Earthquake
**题意** : 在紧张又忙碌地准备联合省选时,发生了大地震,把原本要参赛的 $n$ 个城市之间的全部 $m$ 条道路震垮了。
现在,需要紧急恢复 $n-1$ 条原来的道路,使得任意两个城市可以互相到达。
好在每个城市分别存有 $w_i$ 吨沥青。修复一条道路需要 $x$ 吨沥青,如果两个城市 $i,j$ 之间有一条损坏的道路,且两个城市的沥青总量 $\geq x$ ,那么就可以消耗这两个城市的 $x$ 吨沥青来修路。
修好了路之后,装载沥青的卡车就可以在路上跑,在这一部分连通的城市中任意地来往运送沥青了。
判定能否让 $n$ 个城市连通,如果能,输出任意一种修路的方案。
$n,m\leq 3\times 10^5$ ,时间$\texttt{3s}$。
------------
首先,若沥青总和不足 $(n-1)x$ ,则显然无法完成。
猜想一旦沥青总数 $\geq (n-1)x$(称作性质A)就一定能完成,证明如下 :
只需证明图为树的情况,一般图的条件显然比树更优越。
修路 $(u,v)$ 可以视作将两端的点 $u,v$ 缩合为一个点,且新点权值 $w_u+w_v-x$ 。
不难发现,在若干缩合操作后,性质A仍然保持,于是考虑归纳。只需证对于任意满足性质的树,都至少能修出一条路。
反证,若对于任意边 $(u,v)$ 都有 $w_u+w_v<x$ ,对于边 $(u,fa_u)$ ,将限制扩大为 $w_u<x$ 。特殊地,任选一条 $(u,rt)$ ,限制写为 $w_u+w_{rt}<x$。
这样,我们把 $n$ 个点的权值(不重不漏地)划分到 $n-1$ 个不等式里面去了,且不等式右侧总和为 $(n-1)x$ ,这就能得到点权和 $<(n-1)x$ ,矛盾。证毕。
------------
接下来考虑如何快速求出一种方案。
对于一般图的情况,我们可以任保留一棵生成树,忽略其他的边,这样可以简化问题。
根据如上证明,一种正确策略是:每次修任意一条能修的边。但这样不好维护。
我们考虑将策略特化以便维护。
- 策略一
(根据`P6775 [NOI2020] 制作菜品`)考虑权值最大的点 $u$ 和权值最小的点 $v$ ,有 $w_u+w_v\geq x$ 。
证明 :若不满足,则说明 $w_u<x$ ,和最大的情况是除了最小值外都是 $x-1$ ,仅为 $(n-1)(x-1)$ ,不足 $(n-1)x$ ,矛盾。
也就是说,$u$ 点任意连边都是合法的。
实现时,用堆维护目前的权值最大点,并查集启发式合并维护边表即可。
- 策略二
考虑任意一个叶子 $u$ ,若 $w_u+w_{fa}\geq x$ ,直接连边 $(u,fa)$ 。
否则必有 $w_u<x$ ,则将点 $u$ 忽略,剩余的点形成(必然有解的)子问题。处理完剩余的点之后再连接 $(u,fa)$ (不难发现此时必然能连)。
实现时,在树上 dfs ,当点 $u$ 的子树访问完毕准备弹栈时:
- 若 $w_u\geq x$ (要考虑子树内连边的影响),则连边 $(u,fa)$ ,令 $w_{fa}\leftarrow w_{fa}+w_{u}-x$ 。
- 若 $w_u<x$ ,则将边 $(u,fa)$ 加入一个栈中。
在 dfs 完毕后,弹出栈中的边,并依次连接。
注意到上述两种策略都能导出对应的归纳证明,而且证明是构造性的,而非上文中的反证法。启发:找策略时可以尝试从构造性证明入手。
代码是策略一的。
```cpp
#include<algorithm>
#include<cstdio>
#include<vector>
#define ll long long
#define pb push_back
#define MaxN 300500
using namespace std;
int f[MaxN];
int find(int u)
{return f[u]==u ? u : f[u]=find(f[u]);}
bool merge(int u,int v){
u=find(u);v=find(v);
if (u==v)return 0;
f[u]=v;return 1;
}
vector<int> g[MaxN],id[MaxN];
int x,ans[MaxN],ans2[MaxN],tn,tn2;ll w[MaxN];
void dfs(int u,int fa)
{
int sav=0;
for (int i=0;i<g[u].size();i++)
if (g[u][i]!=fa)dfs(g[u][i],u);
else sav=id[u][i];
if (fa){
if (w[u]+w[fa]>=x){
ans[++tn]=sav;
w[fa]+=w[u]-x;
}else ans2[++tn2]=sav;
}
}
int n,m;
int main()
{
scanf("%d%d%d",&n,&m,&x);
ll sum=0;
for (int i=1;i<=n;i++){
scanf("%lld",&w[i]);
sum+=w[i];f[i]=i;
}
if (sum<1ll*(n-1)*x){puts("NO");return 0;}
for (int i=1,u,v;i<=m;i++){
scanf("%d%d",&u,&v);
if (merge(u,v)){
g[u].pb(v);id[u].pb(i);
g[v].pb(u);id[v].pb(i);
}
}
dfs(1,0);
puts("YES");
for (int i=1;i<=tn;i++)printf("%d\n",ans[i]);
for (int i=tn2;i;i--)printf("%d\n",ans2[i]);
return 0;
}
```
# G Phoenix and Odometers
**题意** : 给定一张 $n$ 个点 $m$ 条边的有向图,有边权。
进行 $q$ 次询问,每次询问给定$v,s,t$,判定是否存在一条起点终点均为 $v$ 的路径,满足 $\text{路径长}+s\equiv 0\pmod t$。
$n,m,q\leq 2\times 10^5$,时限$\texttt{2s}$。
------------
不难发现经过 $v$ 的回路都在 $v$ 所在的强连通分量内部。
于是可以对每个强连通分量分别求解。
若存在某条回路满足条件,则找出一个经过所有点的回路,将其走 $t$ 次(没有贡献),然后接上去,可以将这个回路转移到任意点,也就是说答案和 $v$ 无关。
考虑求出所有回路的一组“基”,使得所有回路都可以表示成基的线性组合(系数为整数)。
若基的 $\gcd$ 为 $g$ ,显然所有回路的权值都是 $g$ 的倍数。
进一步根据斐蜀定理,$\gcd(t,g)|s$ 时有解,否则无解。我们只需要求出 $g$ 就可以回答询问。
------------
接下来是求基的方法。
令根为 $1$,对正图和反图分别求 dfs 树,令正图上 $1\to u$ 的路径为 $P(x)$,反图上为 $Q(x)$。
对于每个 $u$ 将回路 $P(x)+Q(x)$ 加进基中,称为 $u$ 元;对于每条边 $(u,v)$ 再将回路 $P(u)+(u,v)+Q(v)$ 加入基中,称为 $(u,v)$ 元。
如此一来,对于任意一个回路,我们先将环上所有边的 $(u,v)$ 元加起来,再减去所有环上点的 $u$ 元,就实现了用基表示这个回路的方案。
复杂度 $O(n+(m+q)\log v)$ 。
```cpp
```
# H Phoenix and Bits
**题意** :维护大小为 $n$ 的集合 $S$,$q$ 次操作:
- 将 $[x,y]$ 之中的数按位与 $z$。
- 将 $[x,y]$ 之中的数按位或 $z$。
- 将 $[x,y]$ 之中的数按位异或 $z$。
- 查询 $[x,y]$ 之中有多少个不同的数。
$n\leq 2\times 10^5,q\leq 10^5,c<2^{20}$ ,时限$\texttt{4s}$。
------------
看起来很均摊。
注意到与操作相当于 取反+或+取反,故只需考虑后三种操作。
用 Trie 维护(由于 $c$ 很小,不需要动态开点,但是需要交换儿子),对于 xor 操作,打懒标记即可。
对于或操作,我们维护当前子树的或 $s_1$ 与补集的或 $s_0$。
若 $s_1\cap s_0\cap z=\varnothing$ ,说明每个受影响的位都确定,影响(是否取反)也可以预测,那么用异或标记代替。
若有交,暴力 dfs。
对于某个 Trie 节点,我们记势能为 $|s_1\cap s_0|$ 。
对于一次异或操作,会使得 $O(\log c)$ 个节点的势能不可预期地变化,总变化量为 $O(\log^2c)$。
对于或操作,每次 dfs 会使得当前节点势能减少 $1$。
总复杂度为 $O(n\log c+q\log^2c)$ 。
```cpp
```
## CF702F T-Shirts
**题意** : 有 $n$ 种衬衫,第 $i$ 种价格为 $c_i$ ,品质为 $w_i$ ,数量无限。
有 $m$ 个人依次来买衬衫,第 $i$ 个人有 $v_i$ 元。购买策略:不断买能买的品质最好的衬衫(若品质相同则买便宜的),直到买不起任意一件衬衫为止。
求每个人最终各买了多少件衬衫。
$n,q\leq 2\times 10^5$ ,时限$\texttt{4s}$ 。
------------
我们发现,假如对每个人分别处理,可以将钱数的转移视为内向树。一个经典的做法是倍增,但本题中钱数非常大,难以处理。
考虑转置,转而维护每个人的钱数,逐个考虑衬衫。
假设当前衬衫的价格为 $c$ ,即对于钱数 $>c$ 的人将钱数减去 $c$ 并将答案加一。
同:[[DS记录]P7447 [Ynoi2007] rgxsxrs](https://www.luogu.com.cn/blog/command-block/ds-ji-lu-p7447-ynoi2007-rgxsxrs)
复杂度 $O((n+m)\log^2n/\log\log n)$。
# I Phoenix and Diamonds
**题意** : $n$ 种钻石,一颗第 $i$ 种钻石重量为 $w_i$,价值为 $v_i$,一开始第 $i$ 中钻石的库存为 $a_i$。
接下来进行 $m$ 次操作:
- 修改某个 $a$
- 假设你有一个大小为 $c$ 的袋子,且按照第一关键字为价值(从大到小),第二关键字为重量(从小到大)的顺序取钻石(若取不下则跳过),求你最终可以取到钻石的价值为多少。(注意操作不会真正执行)
$n\leq 2\times 10^5,m\leq 10^5$ ,时限$\texttt{5s}$。
------------
将钻石按照拿取顺序排序。
仍然考虑倍增分段,按**重量**分为 $[2^i,2^{i+1})$。
假设现在准备拿后缀 $[i,n]$ ,记 $c\in[2^i,2^{i+1})$ 。
若使得 $c$ 减半,最多拿一个 $[2^i,2^{i+1})$ 中的钻石。
我们对于 $[1,2^i)$ 中的钻石(跳过其他钻石)进行线段树上二分,看看最右取到那里。
如果最右能越过某个 $[2^i,2^{i+1})$ 中的钻石,则检查一下这个钻石能否被取。
经过一轮操作, $c$ 的值至少减半。
时间复杂度 $O(n\log n+m\log^2n)$ ,空间复杂度 $O(n\log n)$。
```cpp
```