Logo __vector__ 的博客

博客

标签
暂无

AT_keyence2019_e 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-11-02 21:46:52

似乎没有题解提到枚举二进制位这个 trick,就发一篇题解。

由于边的信息可以通过点的信息表示出来,并且边很多,无法直接存储,考虑使用 Boruvka 算法。

考虑每一轮加边该如何处理。

考虑将加边前的图中每个节点所在的连通块编号视为这个点的类别

考虑正常算法流程,对于每个连通块,都需要找到其他连通块中最优的点,使得本连通块与之建的边权是最小的。

显然,本轮需要加入的边所连接的两个点的类别肯定是不同的。

考虑朴素做法,正着扫一遍再反着扫一遍,扫描的过程中必然需要记录每个类别的最优点是哪个。

这样的话,有 $O(\log V)$ 级别的加边轮数,每次扫描复杂度 $O(n^2)$,炸了。

考虑简化上述过程。上述过程的本质在于任意两个不同类别的点都需要 chk 一次。

考虑这样一个 trick:枚举每个二进制位,仅对类别编号在这一位不同的点对进行处理。

这样的话,每次处理过程只需要 $O(n)$ 扫一遍(不是 $O(n^2)$ 是因为这里从 $n$ 种类别变成了两种类别,即这一位是 $0$ 还是 $1$)。

复杂度变为 $O(n \log n \log V )$ 。

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = a; i <= b; i++)
#define REP(i, a, b) for (int i = a; i >= b; i--)
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
template <class T>
void ckmx(T &a, T b) { a = max(a, b); }
template <class T>
void ckmn(T &a, T b) { a = min(a, b); }
template <class T>
T gcd(T a, T b) { return !b ? a : gcd(b, a % b); }
template <class T>
T lcm(T a, T b) { return a \/ gcd(a, b) * b; }
#define gc getchar()
#define eb emplace_back
#define pc putchar
#define ep empty()
#define fi first
#define se second
#define pln pc('\n');
template <class T>
void wrint(T x)
{
    if (x < 0)
    {
        x = -x;
        pc('-');
    }
    if (x >= 10)
    {
        wrint(x \/ 10);
    }
    pc(x % 10 ^ 48);
}
template <class T>
void wrintln(T x)
{
    wrint(x);
    pln
}
template <class T>
void read(T &x)
{
    x = 0;
    int f = 1;
    char ch = gc;
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = gc;
    }
    while (isdigit(ch))
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = gc;
    }
    x *= f;
}
const int maxn = 2e5 + 5;
int n;
ll d, a[maxn];
struct MS
{
    int fa[maxn];
    int get(int x) { return (fa[x] == x ? x : fa[x] = get(fa[x])); }
    void ms(int a, int b)
    {
        if (get(a) != get(b))
        {
            fa[get(a)] = get(b);
        }
    }
    void init() { FOR(i, 1, n)
                  fa[i] = i; }
} ms;
ll mn[maxn];
int mnid[maxn];
bool at(int s, int i) { return (s & (1 << i)); }
void solve(int id_of_test)
{
    read(n);
    read(d);
    FOR(i, 1, n) { read(a[i]); }
    ms.init();
    ll ans = 0;
    while (1)
    {
        memset(mn, 0x7f, sizeof mn);
        memset(mnid, 0, sizeof mnid);
        bool flg = 0;
        FOR(bit, 0, 17)
        {
            {
                ll _mn = mn[0];
                int _mnid = 0;
                FOR(i, 1, n)
                {
                    _mn += d;
                    if (at(ms.get(i), bit))
                    {
                        if (_mn + a[i] < mn[ms.get(i)])
                        {
                            mnid[ms.get(i)] = _mnid;
                            mn[ms.get(i)] = _mn + a[i];
                        }
                    }
                    if (!at(ms.get(i), bit))
                    {
                        if (_mn > a[i])
                        {
                            _mn = a[i];
                            _mnid = i;
                        }
                    }
                }
                _mn = mn[0];
                _mnid = 0;
                REP(i, n, 1)
                {
                    _mn += d;
                    if (at(ms.get(i), bit))
                    {
                        if (_mn + a[i] < mn[ms.get(i)])
                        {
                            mn[ms.get(i)] = _mn + a[i];
                            mnid[ms.get(i)] = _mnid;
                        }
                    }
                    if (!at(ms.get(i), bit))
                    {
                        if (_mn > a[i])
                        {
                            _mn = a[i];
                            _mnid = i;
                        }
                    }
                }
            }
            {
                ll _mn = mn[0];
                int _mnid = 0;
                FOR(i, 1, n)
                {
                    _mn += d;
                    if (!at(ms.get(i), bit))
                    {
                        if (_mn + a[i] < mn[ms.get(i)])
                        {
                            mnid[ms.get(i)] = _mnid;
                            mn[ms.get(i)] = _mn + a[i];
                        }
                    }
                    if (at(ms.get(i), bit))
                    {
                        if (_mn > a[i])
                        {
                            _mn = a[i];
                            _mnid = i;
                        }
                    }
                }
                _mn = mn[0];
                _mnid = 0;
                REP(i, n, 1)
                {
                    _mn += d;
                    if (!at(ms.get(i), bit))
                    {
                        if (_mn + a[i] < mn[ms.get(i)])
                        {
                            mn[ms.get(i)] = _mn + a[i];
                            mnid[ms.get(i)] = _mnid;
                        }
                    }
                    if (at(ms.get(i), bit))
                    {
                        if (_mn > a[i])
                        {
                            _mn = a[i];
                            _mnid = i;
                        }
                    }
                }
            }
        }
        FOR(i, 1, n)
        {
            if (mnid[i])
            {
                if (ms.get(i) == ms.get(mnid[i]))
                    continue;
                ms.ms(i, mnid[i]);
                flg = 1;
                ans += mn[i];
            }
        }
        if (!flg)
        {
            break;
        }
    }
    printf("%lld\n", ans);
}
int main()
{
    int T;
    T = 1;
    FOR(_, 1, T) { solve(_); }
    return 0;
}

NOIP2024 游记

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-11-20 11:08:54

总体来说

本文章记录考前的状态,以及最后是怎么死的。

S 考完 ~ 公示获奖

CSPS 赛时采取了极端保守的策略,成功成为了 7 级钩守门员,不仅 WC 都去不了,甚至没过 CQ 一等线。

出初评分数那天,3 次推迟,那天下午和死刑犯等待执行的感觉差不多。

经过 3 次死刑改死缓,最终还是没有执行(没挂分)

考试前 ~ 出分前,每天能在凌晨 3:00 保持清醒,出分后晚上 10:00 就撑不住了。

随后,就是看着小图灵一个省一个省的测试,我看着自己的全国排名不断变化,最终定格在 8.9%。松了一口气。

无论如何,暂时解除了考前几个月的高压的心态。

2024.11.19

出 CSP 分数线,就比洛谷 7 级高 1 分(更新:现在是高 5 分)

T2,T3 赛后认真复盘了下,均在 10min 以内想到正解,很遗憾。

看了看奖项认证帖子,看到一群人建议初三 220 的退役,破防。

2024.11.22

打了场 abc,这次总算没有被 hba 课占了。

感觉状态一般,翻译器还坏了,被迫读原文。

perf2101,比较意外的是机房参赛的里面 rk1。

2024.11.23

上 hba 课的时候,关电脑忘了里面还有笔,结果屏幕被扎破,因此晚上没打 CodeTon。

2024.11.24

上午梦熊模拟,被打爆,喜提 60 分的高分。

chb 284,恐怖。

下午比较开心的一集,很久没组队打比赛了,游记

2024.11.26

洛谷开始发钩子了,然而我得人工认证。

2024.11.27

qbxt 的 NOIP 普及组模拟赛,又挂了很多分。 $380 \rightarrow 310$。

2024.11.28

摆烂。
我的人工认证邮件被处理了,获得了 7 级蓝钩。
R.I P 陪了我 3 年的绿钩。

2024.11.29

摆烂。

晚上试机,再次尝试找 CSP-S 没成功面积的 @zhaohanwen,然而失败了,因为不能去别的考场。

想颓废,玩了会 MC,却发现早已失去了兴趣。

然后就是在知乎上刷哈利波特相关内容,感觉挺有意思的。

2024.11.30 Day 1

早上被收了手机,然后直接去了考场。

这次和往常不同,考前半个小时一般是试机时间,这次居然不让写板子。

开考,先看 T1,看起来像是贪心,先跳过,做 T2.

T2 写了一个假的,但我以为是对的 dp 式子,误以为矩阵快速幂就足以解决。

然后,看 T3,T4,尝试正解,然而到比赛进行到一个小时的时候仍然未能解决。

这时候,我想着返回去先把 T1 过了。

没想到检查了一遍思路,发现刚才做法假了。

心态一下子慌了。

分析了一会,感觉并没有除了贪心以外的做法,就写了一发贪心。

第一遍没过小样例,破防,我想这这场不会得寄了吧。

现在是 1h30min,再次尝试。

我发现我的思路在一定程度上会忽略一些更优解,原因在于筛除了原本就匹配的位置。

我把这个 if 语句删掉之后,过了小样例。

然后,极度紧张的状态测试大样例。

我发现我的程序输出了一堆很小的数,怎么可能是对的。

看了眼大样例,怎么刚好和答案一样!

接下来做 T2。

先写了一发暴力 dp 确定正确性。

然后大样例 2 挂了一个 testcase。

我感到很惊讶,根本不可能是错的啊。

然后改改改,然而大样例 2 无论如何都有一个点错误。

CSP-S 已经输了,我的自尊心不允许我切不掉 noip T2。

然后就换做法继续调,然而还是一个情况。

没过多久,比赛进行到了 2h30min,我还没调出来,大样例 2 仍然有一个 testcase 过不去。

现在已经到了底线,再不弃疗就完蛋了,所以我被迫去做 T3,T4。

T4 看起来比较好拿分,迅速写了 20 分。

然而特殊性质 A 怎么也想不出来,被迫弃疗。

现在还剩 1h20min,必须在 T2,T3 之前作出一个决策了。

我选择了冲 T3 60 分。

我写了一个组合数的方法,然而就是过不去样例,还剩 40min 的时候被迫弃疗,输出 1.

现在我又有两个选项:

  1. 写 T2 暴力。
  2. 写 T2 正解。

由于我很想获得一次 “胜利”,选择了 2 选项。

然后没调出来,但是有保底的特殊性质能过。

最后的总结,我用了大量时间去尝试 T2,T3,T4 的正解和高档次部分分,结果不光没写出来,还赔掉了原本能写的一些部分分。

更地狱的是,出考场后发现人均切 T2,洛谷评分 T1 甚至高于 T2。

从未设想过的结果,NOIP 直接垫底。

wjr 刚好进了 WC,在此膜拜。

高铁站,chb 想出并教了我 T4 正解,在此膜拜。

高铁的时候刚好有场 ABC,就参加了。

选 Unrated 和 chb 一开始就讨论 G 题。

然后没做出来,摆了一会。

不过由于没事可做,我在 50min 的时候开始写题,把 ABCDEF 倒着切了。

然后发现即使这样,perf 1800???离谱。那正常参赛该不会就 2400 了吧?!

看起来我是真的没啥 rp,唉。

最后

CSP-S,我当成了 NOIP 资格赛和复活赛打的,然后机房倒一,7 钩守门。

NOIP 想获得高分,采取激进策略,然后大概率又死了。

如果有机会的话,我会用 3 个月认真准备省选,争取翻盘。

到时候再看正式成绩,或许还有救?

原本这里写了一堆垃圾,想了想,删了,就这样了。

题解:CF2037F Ardent Flames

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-11-21 12:47:42

题意

有 $n$ 个编号为 $1$ 到 $n$ 的奶龙在 $x$ 坐标轴上,第 $i$ 个奶龙的生命值为 $h_i$,横坐标为 $x_i$,保证奶龙们的横坐标按照其编号升序。

你可以预先选定一个横坐标 $p$,并发动任意次数强度为 $m$ 的攻击。

每次攻击后,第 $i$ 个奶龙受到 $\max(0,m-|p-x_i|)$ 的伤害,当奶龙的生命值降到 $0$ 及以下,这个怪物就被击败了。注意 $p$ 在攻击前选定,且选定之后不能改变。

给定整数 $n,m,h_i,x_i,k$,求如何选择 $p$,使得能在最少的攻击次数内击杀至少 $k$ 个奶龙。

只需要求出最少的攻击次数。

做法

显然答案具有单调性,可以二分答案,设当前二分的答案为 $l$。

考虑如何快速检查合法性。

注意到,此时需要判断是否存在一个坐标,使得其能击败至少 $k$ 个奶龙。

考虑求出每个奶龙可以被打败的 $p$ 区间。

对于第 $i$ 个奶龙,打败它需要保证 $l\cdot(m-|p-x_i|) \ge h_i$。

得出 $m-|p-x_i| \ge \frac{h_i}{l}$。

即 $-|p-x_i| \ge \lceil \frac{h_i}{l} \rceil+m$。

$|p-x_i| \le m-\lceil \frac{h_i}{l} \rceil $。

也就是说,第 $i$ 个奶龙会被打败当且仅当选定的坐标 $p$ 与这个奶龙距离不能超过 $m-\lceil \frac{h_i}{l} \rceil$。

计算出每个奶龙会被打败的 $p$ 区间,看下是否存在一个点被大于等于 $k$ 个区间覆盖就可以解决。此部分可以离散化再差分。

【线上游记】2024 ICPC Asia Taichung Regional Contest

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-11-24 21:19:55

这次和 @Iceturky 和 @Garry_HJR 组队,我们的 CF 名字分别是 Iceturky,deng_wo_ni_xi,vector.never_fst。

队伍名称采取了 Iceturky 的 "活人不能被车将死"。

策略是跟着排行榜做题,每个人选一道。

根据我平时对他们的印象,Iceturky 擅长高思维难度的题,2400+ 的题不能没有他;deng_wo_ni_xi 写题的罚时很少,调试快;我比较喜欢 dp,数据结构之类的,但是遇到 Ad-hoc 之类的就寄。

比赛开始后,deng_wo_ni_xi 先正序开题,我中间开题,Iceturky 倒序开题。

deng_wo_ni_xi 在 2min 时秒了 A,恐怖。

我选了一道看起来比较友好的,D 题。

D 题一眼就是 bfs,我写了写,结果挂样例。
在我调试的过程中,deng_wo_ni_xi 和 Iceturky 分别切了 B,E,震撼。

反复的 D 题 wa 样例让我很紧张,求助了 deng_wo_ni_xi。

过了一会我突然意识到状态转移漏了一个加号,改对之后 AC 了。

这个时候 Iceturky 在写 M 题,我和 deng_wo_ni_xi 一起想 C。

我一开始看错了题,以为只需要选择 3 个,然后构造了一个解决方案。

然后在 deng_wo_ni_xi 的提醒下,我才意识到。

这时候 deng_wo_ni_xi 提出了一个方案,好像是跟 1 的个数有关,但由于我刚搞清楚真正的题意,并没有听懂在说什么。

突然 deng_wo_ni_xi 把这个方案否定了,我就去自己想了。

想了会,构造了一个看起来很有道理的状压 dp,展示给了 deng_wo_ni_xi,结果这个做法就是刚刚 deng_wo_ni_xi 的。

不过这时候我发现 deng_wo_ni_xi 否定这个方案的时候实际上是算错了总复杂度。

不过我自己也分析不出来严格的复杂度,就写了一发,过了。

此时 Iceturky 的 M 题 wa 了,deng_wo_ni_xi 去协助他调试 M 题。

我想了一会 H 题,可是并没有想出来。

在此期间,deng_wo_ni_xi 发现 Iceturky M 题多写了个负号,改对之后 AC 了。

看了眼通过率略低的 I 题,发现是我喜欢的数据结构。

然而我被怎么维护字典序排名卡住了。

一度想着用 vector.insert 插入。

Iceturky 提醒了我,可以离线询问预处理,成功解决了这个问题。

在此期间

我写写写,在 3h 的时候过了 I 样例,然而交上去 MLE。

Iceturky 指出我代码的问题在于 trie 空间占用过多,改成了 map 存储下一个节点就可以了。

然而又 wa 了。

和 Iceturky 讨论了一下发现,我查询的时候有可能查询到不包含当前前缀的字符串,一个解决方法是 trie 每个节点上挂一个三元组。

我和 Iceturky 各自进行修改,并同时重新通过了样例。

Iceturky 交了一发,被卡了哈希,发现是 base 太小了,改了一发就过了。

后面,尝试了 F,H,无论如何也做不出来,定格在 7 题。

[NOIP1997 普及组] 棋盘问题(加强版) 简单记录

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-26 17:43:23

原题 $n,m$ 被开到 $10^{12}$ 出到了模拟赛,我推出了一个很麻烦的做法,但是能过,记录一下。

正方形:$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}(\min(n-i+1,m-j+1))$
长方形(包括正方形):$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}(n-i+1)(m-j+1) = \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m} (nm-nj+n-im+ij-i+m-j+1) = \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}((nm+n+m)+(-nj-im)+(-i-j)+(ij)+1)$

  • 对于长方形:
    第一个括号直接计算。
    第二个括号:$nj,im$ 分别计算,其实把第三个乘 $n$ 就可以。
    第三个括号:$i,j$ 分别计算,类似于等差数列求和直接算。
    第四个括号:类似于等差数列求和。
    第五个常数:直接计算。
  • 对于正方形:
    显然,答案等于 $\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\min(i,j)$

最后复杂度 $O(1)$

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
int T;
#define ll __int128
const ll mod=1e9+7;
ll n,m;
ll oldn,oldm;
ll minnm;
ll qpow(ll a,ll b){
    ll ret=1;
    while(b){
        if(b&1)ret=ret*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ret;
}
ll inv(ll a){
    return qpow(a,mod-2);
}
ll inv2,inv6;
ll solvechang(){
    ll bra1=(n*m+n+m)%mod*(n*m%mod)%mod;
    ll bra3_i=((1ll+n)*(n)\/2ll)%mod*m%mod;
    ll bra3_j=((1ll+m)*m\/2ll)%mod*n%mod;
    ll bra3=-bra3_i-bra3_j;
    ll bra2=-(bra3_i*m%mod+bra3_j*n%mod)%mod;
    ll sum_1n=((1ll+n)*n%mod*inv2)%mod;
  \/\/  printf("tmp = %lld\n",(1ll+n)*n%mod);
 \/\/   printf("sum1n = %lld sum1n*m = %lld m = %lld\n",sum_1n,sum_1n*m,m);
    ll bra4=(sum_1n+sum_1n*m)%mod*m%mod*inv2%mod;
    ll bra5=n*m%mod;
 \/\/   printf("%lld %lld %lld %lld %lld\n",bra1,bra2,bra3,bra4,bra5);
    return bra1+bra2+bra3+bra4+bra5;
}
ll sum_11_nn(ll n){
    return n*(n+1)%mod*(2ll*n+1)%mod*inv6%mod;
}
ll sum(ll n){
    return (1ll+n%mod)*(n%mod)*inv2%mod;
}
ll solverect(){
    ll bra1=0,bra2=0;
    \/\/ 第一个是序列 1 对 min 的贡献,第二个是序列 2 的
    if(oldn<oldm){
        \/\/ m*1,(m-1)*2,(m-2)*3,(m-3)*4,....(m-n+1)*n
        \/\/ split to 
        \/\/ m*sum(1,n) - (0*1 + 1*2 + 2*3 + ... + (n-1)*n)
        bra1+=(m*sum(n)%mod-(sum_11_nn(n)-sum(n)))%mod;
    }else{
        \/\/ n>=m
        \/\/ 1*m,2*(m-1),3*(m-2),...,m*(m-(m-1))
        \/\/ split to m*sum(1,m) - (0*1+1*2+2*3+...+(m-1)*m)
        bra1+=m*sum(m)-(sum_11_nn(m)-sum(m));
    }
    if(oldm<oldn){
        \/\/ (n-1)*1,(n-2)*2+(n-3)*3...+(n-m)*m 
        \/\/ split to n*sum(1,m) - (1*1+2*2+3*3+...+m*m);
        bra2+=sum(m)*n%mod-sum_11_nn(m);
    }else{
        \/\/ m>=n
        \/\/ (n-1)*1,(n-2)*2+(n-3)*3+...+(n-(n-1))*(n-1)
        bra2+=n*sum(n-1)%mod-(sum_11_nn(n-1));
    }
  \/\/  printf("%lld %lld\n",bra1,bra2);
    return bra1+bra2;
}
void solve(){
    inv2=inv(2);
    inv6=inv(6);
    \/\/printf("inv2 = %lld\n",inv2);
    long long _n,_m;
    scanf("%lld%lld",&_n,&_m);
    n=_n,m=_m;
    oldn=n,oldm=m;
    minnm=min(n,m);
    minnm%=mod;
    n%=mod;
    m%=mod;
  \/\/  printf("n = %lld m = %lld\n",n,m);
    ll changans=solvechang(),rectans=solverect();
    changans-=rectans;
    changans=(changans%mod+mod)%mod;
    rectans=(rectans%mod+mod)%mod;
    printf("%lld %lld\n",(long long)rectans,(long long)changans);
}
int main()
{
	T=1;
	while(T--){
		solve();
	}
	return 0;
}

论如何 AK WFYZ公益班测试-20240330

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-30 15:39:56

晚了半个点参赛,因为此前 vp 了上一场 CF Div.4(AK)

C 没测第二个样例,刚好题目翻译有问题(事实不应该去重,但翻译成了去重),怒挂 50。
D 题我看最后一个点 $n=2000$,就以为 $n \le 2000$,事实上 $n \le 2\cdot 10^5$,挂 40。 F 题写的正解,然而要求的值看错了,导致没调出来,哎。

A - CF598A

套公式直接算。

B - CF598B

同一个长度为 $len$ 的区间执行 $k$ 次和执行 $k \mod len$ 次效果是一样的,可以以此在每次操作中快速计算出每个字符新的位置。
注意到操作次数很少,模拟就可以。

C - CF598D

bfs 就行了,注意本题洛谷翻译是错的,应该按照题目警示贴来。

D - P2846

线段树维护,每个节点记录本区间答案,以及一个 lazytag 代表本区间灯状态是否反转。

E - CF461B

设状态 $dp_{u,1/0}$,代表 $u$ 为根的子树,$u$ 所在的连通块有一个/没有黑色节点,且其他连通块合法的方案数。

  • 先考虑 $x_u$ 为 $1$ 的情况,此时对于每个子节点,如果其所在连通块有一个黑色节点,则不能保留其和 $u$ 的连边,如果没有黑色节点,则其必须和 $u$ 连边;简单地说,如果和 $u$ 连边,那么自己连通块必须有一个黑色节点,反之必须没有。
    此时显然 $dp_{u,0} = 0,dp_{u,1} =\prod\limits_{v \in son_u} (dp_{v,0}+dp_{v,1})$
  • 如果 $x_u$ 为 $0$ 呢?
    考虑 $u$ 连通块中存在一个黑色节点的情况,此时必须有恰好一个连通块中存在一个 $1$ 的子节点和自己连边,其余连通块存在一个黑色节点的子节点都必须和 $u$ 断边,另外所有连通块不存在黑色节点的子节点都必须和 $u$ 连边,得出结论: $dp_{u,1}=\sum\limits_{s_1 \in son_u} (dp_{s_1,1}\cdot \prod\limits_{s_2 \neq s_1}(dp_{s_1,0}+dp_{s_2,0}))$
    接下来考虑 $u$ 连通块中没有黑色节点的情况,此时子结点中,如果一个子节点连通块中没有黑色,那么必须保留和 $u$ 的边,否则必须删除和 $u$ 的边,显然有结论:
    $dp_{u,0} =\prod\limits_{v \in son_u} (dp_{v,0}+dp_{v,1})$
#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(itn i=a;i>=b;i--)
typedef long long ll;
const ll mod=1e9+7;
int t;
const int maxn=1e5+5;
int n;
int p[maxn];
int x[maxn];
vector<int> g[maxn];
ll dp[maxn][2];
ll qpow(ll a,ll b){
	ll ret=1;
	while(b){
		if(b&1)ret=ret*a%mod;
		a=a*a%mod;
		b>>=1; 
	}
	return ret;
}
ll inv(ll a){
	return qpow(a,mod-2);
}
void dfs(int u,int _fa){
	ll sum=1;
	for(int v:g[u]){
		if(v!=_fa){
			dfs(v,u);
			sum*=(dp[v][0]+dp[v][1]);
			sum%=mod;
		}
	}
	if(x[u]==1){
		dp[u][1]=sum;
	}else{
		for(int v:g[u]){
			if(v!=_fa){
				dp[u][1]+=dp[v][1]*(sum*inv(dp[v][0]+dp[v][1])%mod)%mod;
				dp[u][1]%=mod;
			}
		}
		dp[u][0]=sum;
	}
}
void solve(){
	scanf("%d",&n);
	FOR(i,0,n-2){
		int pi;
		scanf("%d",&pi);
		g[pi].emplace_back(i+1);
		g[i+1].emplace_back(pi);
	\/\/	printf("%d --> %d\n",pi,i+1);
	} 
	FOR(i,0,n-1){
		scanf("%d",&x[i]);
	}
	dfs(0,-1);
	printf("%lld\n",(dp[0][1]%mod+mod)%mod);
}
int main(){
	t=1;
	while(t--){
		solve();
	}
	return 0;
}

F - CF598E

$dp_{x,y,k}$ 代表初始有一个长为 $x$,宽为 $y$ 的巧克力,搞出大小为 $k$ 的巧克力的最小代价。

转移十分的暴力,枚举分割点,将其分割成两个小巧克力,并枚举第一个小巧克力和第二个小巧克力各贡献多少大小的巧克力(用来凑成大小为 $k$ 的巧克力)
然后没啥好说的,自认为本蒟蒻的代码还是比较好懂的。

#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
int T;
const int maxn=55;
ll dp[maxn][maxn][maxn];
\/\/ chang,kuan 的巧克力搞出 k
ll dfs(int chang,int kuan,int k){
    if(k==0)return 0;
    if((chang==1&&kuan==1)&&(k!=1))return 1e18;
    if(dp[chang][kuan][k]<dp[0][0][0])return dp[chang][kuan][k];
    if(k>chang*kuan){return 1e18;}
    if(k==chang*kuan){return 0;}
    FOR(lef,0,k){
        FOR(i,1,chang-1){
            ckmn(dp[chang][kuan][k],dfs(i,kuan,lef)+dfs(chang-i,kuan,k-lef)+kuan*kuan);
        }
        FOR(i,1,kuan-1){
            ckmn(dp[chang][kuan][k],dfs(chang,i,lef)+dfs(chang,kuan-i,k-lef)+chang*chang);
        }
    }
 \/\/   printf("dp[%d][%d][%d] = %lld\n",chang,kuan,k,dp[chang][kuan][k]);
    return dp[chang][kuan][k];
}
void solve(){
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    printf("%lld\n",dfs(n,m,k));
}
int main()
{
    memset(dp,0x7f,sizeof dp);
    dp[1][1][1]=0;
	scanf("%d",&T);
	while(T--){
		solve();
	}
	return 0;
}

论如何 AK WFYZ【78edu周测】-7

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-04-08 19:59:11

contest url

本场比赛题目来源是 Codeforces Educational round#3 的 B,C,D,E,F 题

难度分别为 1100,1500,2000,2100,2500,被 lxn 出到了 OI 赛制比赛,直接复制 CF 样例。

很遗憾,本场比赛每道题都写了正解,然而因为愚蠢的错误挂了一堆分。

A

枚举每一种书,计算这种书能与其他种类的书组成多少种,最后求和。

然后注意到每一对答案算了两边,答案要除以 $2$。

B

设 $\bar a$ 是 $a$ 的平均值。
那么最后最小极差显然是 $\lceil \bar a \rceil - \lfloor \bar a \rfloor $。

显然,优先用比 $\lceil \bar a \rceil$ 大的数去补充比 $\lfloor \bar a \rfloor $ 小的数,然后再用恰好是 $\lceil \bar a \rceil$ 的位置去补。如果比 $\lceil \bar a \rceil$ 大的数补完后还有剩余,那么剩下的多余总和也要进行操作。

感觉上面说的不清楚,代码:

#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
typedef long long ll;
template<class T>
void ckmx(T& a,T b){
	a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
	a=min(a,b);
}
const int maxn=2e5+5;
int n;
int m[maxn];
void solve(){
	scanf("%d",&n);
	int sum=0;
	FOR(i,1,n){
		scanf("%d",&m[i]);
		sum+=m[i];
	}
    \/\/printf("sum = %d n = %d\n",sum,n);
	int adj=sum\/n;
	int mx=adj+(sum%n!=0);
	int mn=adj;
	ll tot=0;
    \/\/ 比 adj 小的,必须到达 adj
    \/\/ 由 比 adj 大的提供援助
    \/\/ 但是可能比 adj 大的存在剩余
    \/\/ 此时需要计算剩余多少,然后
    ll rest=0;
  \/\/  printf("mx = %d\n",mx);
    FOR(i,1,n){
        if(m[i]>mx){
            rest+=m[i]-mx;
        }
    }
	FOR(i,1,n){
		if(m[i]<mn){
			tot+=mn-m[i];
            rest-=(mn-m[i]);
		}
	}
	printf("%lld\n",tot+max(0ll,rest));
}
int main(){
	solve();
	return 0;
}

C

显然随着天数增加,花费始终是不增的。

于是就有了单调性,可以二分天数。

对于一个固定的天数,我们可以知道美元/欧元付款的物品价格哪天最便宜。

然后在最便宜的那天付款就 ok 了。

\/\/ LUOGU_RID: 154894943
#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
typedef long long ll;
template<class T>
void ckmx(T& a,T b){
	a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
	a=min(a,b);
}
const int maxn=2e5+5;
int n,m,k,s;
ll bro3_to_us[maxn];
ll bro3_to_uk[maxn];
ll preminus[maxn],preminuk[maxn];
ll preidus[maxn],preiduk[maxn];\/\/ 前 i 个最小值位置 
struct Seller{
	int id;
	int tag;
	ll money;
	ll finalmoney;
}seller[maxn];
vector<pair<ll,ll> > chk(int day){
	FOR(i,1,m){
		if(seller[i].tag==1){
			seller[i].finalmoney=seller[i].money*preminus[day];
		}else{
			seller[i].finalmoney=seller[i].money*preminuk[day];
		}	
	}
	sort(seller+1,seller+m+1,[&](Seller& a,Seller& b){
		return a.finalmoney<b.finalmoney;
	});
	ll sum=0;
	FOR(i,1,k){
		sum+=seller[i].finalmoney;
	}
	vector<pair<ll,ll> > ans;
	if(sum>s){
		return ans;
	}else{
		FOR(i,1,k){
			int id;
			if(seller[i].tag==1)id=preidus[day];
			else id=preiduk[day];
			ans.emplace_back(make_pair(seller[i].id,id));
		}
		return ans;
	}
}
void solve(){
	scanf("%d%d%d%d",&n,&m,&k,&s);
	preminus[0]=preminuk[0]=1e18;
	FOR(i,1,n){
		scanf("%lld",&bro3_to_us[i]);
		preminus[i]=min(preminus[i-1],bro3_to_us[i]);
		if(bro3_to_us[i]<preminus[i-1]){
			preidus[i]=i;
		}else{
			preidus[i]=preidus[i-1];
		}
	\/\/	printf("preminus[%d] = %lld\n",i,preminus[i]);
	\/\/	printf("preidus[%d] = %lld\n",i,preidus[i]);
	}
	FOR(i,1,n){
		scanf("%lld",&bro3_to_uk[i]);
		preminuk[i]=min(preminuk[i-1],bro3_to_uk[i]);
		if(bro3_to_uk[i]<preminuk[i-1]){
			preiduk[i]=i;
		}else{
			preiduk[i]=preiduk[i-1];
		}
	}
	FOR(i,1,m){
		seller[i].id=i;
		scanf("%d%lld",&seller[i].tag,&seller[i].money);
	}
	int l=1,r=n;
	int day=-1;
	vector<pair<ll,ll> >  ans;
	while(l<=r){
		int mid=(l+r)\/2;
		auto tmp=chk(mid);
		if(!tmp.empty()){
			ans=tmp;
			day=mid;
			r=mid-1;
		}else{
			l=mid+1;
		}
	}
	printf("%d\n",day);
	for(int i=0;i<ans.size();i++){
		printf("%lld %lld\n",ans[i].first,ans[i].second);
	}
}
int main(){
	solve();
	return 0;
}

D

考虑先求出 MST。
然后对于每一次固定一条边,分两种情况:

  • 这条边在 MST 里面
    相当于没有固定。
  • 这条边不属于 MST
    可以看成加上这条边,可以注意到原来的最小生成树出现了一个环,那么我们需要在这个环上删除权值最大的边(当然,除了加上的这条边)。
    这个东西倍增就可以做。
#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
typedef long long ll;
template<class T>
void ckmx(T& a,T b){
	a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
	a=min(a,b);
}
const int maxn=2e5+5;
int n;
int m;
vector<pair<int,ll> > g[maxn];
struct EDGE{
	int id;
	int u,to;
	ll w;
}edge[maxn];
int cnt;
void add(int u,int to,ll w,int id){
	edge[++cnt].to=to;
	edge[cnt].id=id;
	edge[cnt].u=u;
	edge[cnt].w=w;
}
struct MS{
	int fa[maxn];
	void init(){
		FOR(i,1,n)fa[i]=i;
	}
	int get(int x){
		return (x==fa[x])?x:fa[x]=get(fa[x]);
	}
	void ms(int a,int b){
		if(get(a)!=get(b)){
			fa[get(a)]=get(b);
		}
	}
}ms;
int fa[21][maxn];
int dep[maxn];
int val[21][maxn];\/\/ 每个节点向上的边的权值就是这个点的值

\/\/ fa[i][u] 是 u 的 2^i 次向上 
\/\/ val[i][u] 是 u 开始,共 2^i 条边,到达 fa[i][u] 的最大值。 
void dfs(int u,int _fa,ll lstweight){
	val[0][u]=lstweight;
	fa[0][u]=_fa;
	dep[u]=dep[_fa]+1;
	FOR(i,1,20){
		fa[i][u]=fa[i-1][fa[i-1][u]];
	}
	FOR(i,1,20){
		val[i][u]=max(val[i-1][u],val[i-1][fa[i-1][u]]);
	}
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i].first,w=g[u][i].second;
		if(v!=_fa){
			dfs(v,u,w);
		}
	}
}
int query(int a,int b){
	if(dep[a]<dep[b])swap(a,b);
	int ans=0;
	REP(i,20,0){
		if(dep[fa[i][a]]>=dep[b]){
			ckmx(ans,val[i][a]);
			a=fa[i][a];
		}
	}
	if(a==b)return ans;
	REP(i,20,0){
		if(fa[i][a]!=fa[i][b]){
			ckmx(ans,val[i][a]);
			ckmx(ans,val[i][b]);
			a=fa[i][a];
			b=fa[i][b];
		}
	}
	ckmx(ans,max(val[0][a],val[0][b]));
	return ans;
}
bool ismst[maxn];
void solve(){
	scanf("%d%d",&n,&m);
	FOR(i,1,m){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w,i);
	}
	ms.init();
	sort(edge+1,edge+m+1,[&](EDGE& a,EDGE& b){
		return a.w<b.w;
	});
	int rest=n-1;
	ll mstval=0;
	FOR(i,1,m){
		if(ms.get(edge[i].u)!=ms.get(edge[i].to)){
			rest--;
			ms.ms(edge[i].u,edge[i].to);
			ismst[edge[i].id]=1;
			mstval+=edge[i].w;
			int u=edge[i].u,to=edge[i].to,w=edge[i].w;
			g[u].emplace_back(make_pair(to,w));
			g[to].emplace_back(make_pair(u,w));
			if(rest==0)break;
		}
	}
	dfs(1,0,1e9);
	sort(edge+1,edge+m+1,[&](EDGE& a,EDGE& b){
		return a.id<b.id;
	});
	FOR(i,1,m){
		if(ismst[i]){
			printf("%lld\n",mstval);
		}else{
			ll mx=query(edge[i].u,edge[i].to);
			printf("%lld\n",mstval+edge[i].w-mx);
		}
	}
}
int main(){
	solve();
	return 0;
}

E

考虑依次模拟。

  1. 加入一个新的苍蝇,将其记录下来,我们先看看哪只青蛙会吃掉它。
  2. 如果没有青蛙能吃掉它,跳到步骤 1
  3. 对于能吃掉它的青蛙,我们首先需要知道它的编号。
  4. 随后我们计算出这只青蛙本次单次能吃掉的苍蝇总数,以及以此获得的奖励
  5. 将这只青蛙本次单次吃掉的苍蝇算进这只青蛙最后的答案,并更新这只青蛙的捕食范围
  6. 如果这只青蛙能再吃一些新的苍蝇,跳到步骤 4。否则退出,跳到步骤 1。

注意到本过程中,我们关心每个位置对应的青蛙,另外,青蛙会增加捕食范围。注意到每个位置会尽可能对应位置最小的青蛙,同时所有青蛙位置不同,所以我们可以用线段树记录每个位置对应的最小的青蛙的位置,并用 map 将每个青蛙的位置映射到对应的青蛙编号。这时候,青蛙捕食范围增加对应了区间取 min,也就是说,我们需要维护一个线段树,支持区间取 min,单点查询。

另外,还需要计算单次捕食吃了多少苍蝇,以及对应的奖励,同时捕食结束后被吃掉的苍蝇会消失,可以得出我们需要维护另一个线段树,支持区间总苍蝇数,区间总奖励,区间删除所有苍蝇,本质对应区间修改(设置为 0),区间求和。

#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
typedef long long ll;
template<class T>
void ckmx(T& a,T b){
	a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
	a=min(a,b);
}
\/*
离散化,计算每个位置被覆盖的最小编号,用 sgt1 存储 
依次枚举每只蚊子,并将其加入 sgt2 
每加入一只蚊子,就判断是否被覆盖  
如果被覆盖,那么看是哪个位置开头的青蛙将其覆盖,然后根据 sgt2 进行修改 
 
sgt1 维护单点最小值,支持区间 min 
sgt2 维护区间和,支持区间重置为 0 
 
另外维护每个位置的大小和长度 
 
 具体的,每次枚举到一个蚊子,查看覆盖它的青蛙编号  
从那个青蛙开始,查询对应影响区间在 sgt2 的区间和,记为 sum,然后这只青蛙吞食长度增加 sum, sgt1 对应修改,并再次查询区间和,以此类推 
*\/
const int maxn=2e5+5;
int n,m;
struct Disc{
	ll lsh[maxn*3];
	int top;
	void add(ll x){
		lsh[++top]=x;
	}
	void init(){
		sort(lsh+1,lsh+top+1);
		top=unique(lsh+1,lsh+top+1)-lsh-1;
	}
	int pre(int x){
		\/\/ 小于等于 x 的最后一个位置 
		return upper_bound(lsh+1,lsh+top+1,x)-lsh-1; 
	}
	int suf(int x){
		return lower_bound(lsh+1,lsh+top+1,x)-lsh;\/\/ 大于等于 x 的第一个位置 
	}
}disc;
struct QWQ{
	ll pos,len;
}qwq[maxn];
struct WZ{
	ll pos,reward;
}wz[maxn];
struct SGT1{
	int L[maxn*3*4],R[maxn*3*4];
	int lazy[maxn*12];
	void build(int pos,int l,int r){
		L[pos]=l,R[pos]=r;
		lazy[pos]=1e9+7;
		if(l==r)return;
		int mid=(l+r)\/2;
		build(pos<<1,l,mid);
		build(pos<<1|1,mid+1,r);
	}
	void chg(int pos,int l,int r,int _val){
		if(L[pos]>=l&&R[pos]<=r){
			ckmn(lazy[pos],_val);
			return;
		}
		if(R[pos]<l||L[pos]>r)return;
		int mid=(L[pos]+R[pos])\/2;
		if(mid>=l)chg(pos<<1,l,r,_val);
		if(mid<r)chg(pos<<1|1,l,r,_val);
	}
	int query(int pos,int x,int mn){
		if(L[pos]==x&&R[pos]==x){
			return min(mn,lazy[pos]);
		}
		if(R[pos]<x||L[pos]>x)return 1e9+7;
		int mid=(L[pos]+R[pos])\/2;
		int ans=1e9+7;
		if(mid>=x)ans=query(pos<<1,x,min(mn,lazy[pos]));
		if(mid<x)ckmn(ans,query(pos<<1|1,x,min(mn,lazy[pos])));
		return ans;
	} 
}sgt1;
struct SGT2{
	int L[maxn*3*4],R[maxn*3*4];
	ll val[maxn*3*4];
	bool lazy[maxn*12];
	int cnt[maxn*12];
	void build(int pos,int l,int r){
		L[pos]=l,R[pos]=r;
		if(l==r)return;
		int mid=(l+r)\/2;
		build(pos<<1,l,mid);
		build(pos<<1|1,mid+1,r);
	}
	inline int len(int pos){
		return R[pos]-L[pos]+1;
	}
	void pushup(int pos){
		val[pos]=val[pos<<1]+val[pos<<1|1];
		cnt[pos]=cnt[pos<<1]+cnt[pos<<1|1];
	}
	void pushdown(int pos){
		if(lazy[pos]){
			lazy[pos]=0;
			cnt[pos<<1]=cnt[pos<<1|1]=0;
			val[pos<<1]=val[pos<<1|1]=0;
			lazy[pos<<1]=lazy[pos<<1|1]=1;
		}
	}
	void chg(int pos,int x,int _val){
		if(L[pos]==x&&R[pos]==x){
			val[pos]+=_val;
			cnt[pos]++;
			return;
		}
		if(R[pos]<x||L[pos]>x)return;
		pushdown(pos);
		int mid=(L[pos]+R[pos])\/2;
		if(mid>=x)chg(pos<<1,x,_val);
		if(mid<x)chg(pos<<1|1,x,_val);
		pushup(pos);
	}
	void reset(int pos,int l,int r){
		if(L[pos]>=l&&R[pos]<=r){
			lazy[pos]=1;
			val[pos]=0;
			cnt[pos]=0;
			return;
		}
		if(R[pos]<l||L[pos]>r)return;
		pushdown(pos);
		int mid=(L[pos]+R[pos])\/2;
		if(mid>=l)reset(pos<<1,l,r);
		if(mid<r)reset(pos<<1|1,l,r);
		pushup(pos);
	}
	ll query(int pos,int l,int r){
		if(L[pos]>=l&&R[pos]<=r){
			return val[pos];
		}
		if(R[pos]<l||L[pos]>r)return 0;
		pushdown(pos);
		int mid=(L[pos]+R[pos])\/2;
		ll ans=0;
		if(mid>=l)ans+=query(pos<<1,l,r);
		if(mid<r)ans+=query(pos<<1|1,l,r);
		return ans;
	}
	ll count(int pos,int l,int r){
		if(L[pos]>=l&&R[pos]<=r){
			return cnt[pos];
		}
		if(R[pos]<l||L[pos]>r)return 0;
		pushdown(pos);
		int mid=(L[pos]+R[pos])\/2;
		ll ans=0;
		if(mid>=l)ans+=count(pos<<1,l,r);
		if(mid<r)ans+=count(pos<<1|1,l,r);
		return ans;
	}
}sgt2;
int ans[maxn];
map<int,int> qwql_to_qwqid;\/\/ 根据 qwq 的 l,找回 qwq 的下标
void solve(){
	scanf("%d%d",&n,&m);
	FOR(i,1,n){
		scanf("%lld%lld",&qwq[i].pos,&qwq[i].len);
		disc.add(qwq[i].pos);
		disc.add(qwq[i].pos+qwq[i].len);
	}
	FOR(i,1,m){
		scanf("%lld%lld",&wz[i].pos,&wz[i].reward);
		disc.add(wz[i].pos);
	}
	disc.init();
	sgt1.build(1,1,disc.top);
	sgt2.build(1,1,disc.top);
	FOR(i,1,n){
        qwql_to_qwqid[qwq[i].pos]=i;
		sgt1.chg(1,disc.pre(qwq[i].pos),disc.pre(qwq[i].pos+qwq[i].len),qwq[i].pos);
	\/\/	printf("i = %d : %d %d\n",i,disc.pre(qwq[i].pos),disc.pre(qwq[i].pos+qwq[i].len));
	}
	FOR(i,1,m){
		sgt2.chg(1,disc.pre(wz[i].pos),wz[i].reward);
	\/\/	printf("wz[%d].pos.disc = %d\n",i,disc.pre(wz[i].pos));
		int id=sgt1.query(1,disc.pre(wz[i].pos),1e9+7);
	\/\/	printf("preid = %d\n",id);
        id=qwql_to_qwqid[id];
    \/\/    printf("i = %d disc = %d id = %d\n",i,disc.pre(wz[i].pos),id);
		if(id>=1&&id<=n){
			int l=qwq[id].pos,r=qwq[id].pos+qwq[id].len;
            \/\/ l,r 是 qwq[id] 对应的捕食区间
			ll sum=sgt2.query(1,disc.suf(l),disc.pre(r));
			ll tmp=sgt2.count(1,disc.suf(l),disc.pre(r));
		\/\/	printf("rew = %lld\n",sum);
	\/\/		printf("l = %d r = %d fir sum = %lld\n",disc.suf(l),disc.pre(r),sum);
			while(tmp!=0){
		\/\/		printf("now l = %d r = %d\n",l,r);
				ans[id]+=tmp;
				sgt2.reset(1,disc.suf(l),disc.pre(r));
				qwq[id].len+=sum;
				r=qwq[id].pos+qwq[id].len;
             \/\/   printf("new: %d %d sum = %lld upd: %d %d %lld\n",l,r,sum,disc.suf(l),disc.pre(r),qwq[i].pos);
				sum=sgt2.query(1,disc.suf(l),disc.pre(r));
				tmp=sgt2.count(1,disc.suf(l),disc.pre(r));
                sgt1.chg(1,disc.suf(l),disc.pre(r),qwq[id].pos);
                
			}
		}
	}
	FOR(i,1,n){
		printf("%d %lld\n",ans[i],qwq[i].len);
	}
}
int main(){
	solve();
	return 0;
}

CF696E

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-04-12 14:35:33

话说背景里的 Barney 是不是作者本人?

废话

这是我第一道没看题解 AC 的 CF 3000 评分题目,不过是远古时期的树剖板子的一个稍微复杂的实现,同机房的也都切了。

大概用了 2h(包括代码里写完题解),就算是当时去打 CF 现场赛也做不到场切。

正文

考虑每次操作怎么处理更舒适。

正好,树上简单路径操作 + 子树整体操作有现成的模板:树链剖分。

现在要求前 $k$ 优的,但在树上维护主席树似乎不是很好维护,暂不考虑。

那么考虑进行 $k$ 次操作,每次取出最优的物品,并将其删除。

所以说,树剖要支持:求最优的物品;删除一个特定的物品;子树整体加法。

这个就比较板子了,更为详细的记录放到了代码注释里。

\/*
@Barney Are you the writter of the competition?
You're so crazy!
But I don't think you'll find the gf.
*\/
#include <bits\/stdc++.h>
using namespace std;
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define REP(i,a,b) for(int i=a;i>=b;i--)
#define pb push_back
#define mkpr make_pair
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
int T;
template<class T>
void ckmx(T& a,T b){
    a=max(a,b);
}
template<class T>
void ckmn(T& a,T b){
    a=min(a,b);
}
const int maxn=1e5+5;
int n,m,q;
int c[maxn];
struct Girl{
    ll val;
    int id;
};
Girl mkgr(ll val,int id){
    Girl res;
    res.val=val;
    res.id=id;
    return res;
}
vector<Girl> node_girl[maxn];\/\/ 存储每个原始树上节点的 girls,按照 val 从大到小排序,另外此时的第二个 id 是 girl 本身编号,而非树上 id

\/*
线段需要查询 barney's dream girl 的最小权重,以及对应在哪个线段树节点(由此,线段树节点编号应该映射到给定树上节点)。
同时需要支持删除单个线段树节点的单个最小权值。
具体地,线段树上每个节点,都记录对应树上节点的最小 girl 的 val,以及最小对应的树上节点编号。
合并时,先根据 val,再根据树上编号进行 ckmn
查询时,用和合并一样的方法就可以,最后根据得到的树上节点编号,查询最后一个 girl 的 id 就可以。

删除时,先看看之前查询到的树上节点编号,将其 girls 的最后一个 pop 掉,然后将书上节点编号其映射到对应线段树编号。
然后递归到线段树该节点,重新建立其对应的最小 girl id,以及对应的树上节点编号
然后 pushup

另外,树剖的时候,应当记录是哪一段的线段树,存在比赛作者的梦中情人,然后最后删除操作就去对应的地方求

增加子树权值的时候,直接更新 val,并记录 lazytag_val 代表子树的 val 要增加多少
还要记录 lazytag_leaf(不用下传),用来记录区间的叶子节点需要增加的值,这个在删除单个叶子节点的最小 girl 的时候,用来更新最新的 val

*\/
struct Graph{
    vector<int> g[maxn];
    int dfn[maxn],hs[maxn],dep[maxn],fa[maxn],siz[maxn];
    int dfncnt=0;
    int dfntoid[maxn];
    int top[maxn];
    void dfs1(int u,int _fa){
        siz[u]=1;
        fa[u]=_fa;
        dep[u]=dep[_fa]+1;
        for(int& v:g[u]){
            if(v==_fa)continue;
            dfs1(v,u);
            siz[u]+=siz[v];
            if(siz[v]>siz[hs[u]]){
                hs[u]=v;
            }
        }
    }
    void dfs2(int u,int _top){
        dfn[u]=++dfncnt;
        dfntoid[dfncnt]=u;
        top[u]=_top;
        if(hs[u]){
            dfs2(hs[u],_top);
        }
        for(int& v:g[u]){
            if(v==fa[u]||v==hs[u])continue;
            dfs2(v,v);
        }
    }
    void build(){
        dfs1(1,0);
        dfs2(1,1);
    }
}g;
struct SGT{
    int L[maxn<<2],R[maxn<<2];
    Girl val[maxn<<2];\/\/ 每个线段树节点对应的最小 girl 的 val 以及原始树上 id
    ll lazytag_val[maxn<<2];
    ll lazytag_leaf[maxn<<2];
    void pushup(int pos){
        val[pos].val=min(val[pos<<1].val,val[pos<<1|1].val);
        if(val[pos<<1].val!=val[pos<<1|1].val){
            if(val[pos<<1].val<val[pos<<1|1].val){
                val[pos].id=val[pos<<1].id;
            }else{
                val[pos].id=val[pos<<1|1].id;
            }
        }else{
            val[pos].id=min(val[pos<<1].id,val[pos<<1|1].id);
        }
    }
    void build(int pos,int l,int r){
        L[pos]=l,R[pos]=r;
   \/\/     printf("pos = %d l = %d r = %d\n",pos,l,r);
        if(l==r){
            if(node_girl[g.dfntoid[l]].empty()){
                val[pos]=mkgr(1e18,1e9);
            }
            else val[pos]=mkgr(node_girl[g.dfntoid[l]].back().val,g.dfntoid[l]);
        \/\/    printf("l = %d r = %d val = [%lld, %d]\n",l,r,val[pos].val,val[pos].id);
            return;
        }
        int mid=((l+r)>>1);
        build(pos<<1,l,mid);
        build(pos<<1|1,mid+1,r);
        pushup(pos);
       \/\/ printf("l = %d r = %d val = [%lld, %d]\n",l,r,val[pos].val,val[pos].id);
    }
    void add(int pos,ll _val){
        val[pos].val+=_val;
        lazytag_val[pos]+=_val;
    }
    void pushdown(int pos){
        if(lazytag_val[pos]){
            add(pos<<1,lazytag_val[pos]);
            add(pos<<1|1,lazytag_val[pos]);
            lazytag_val[pos]=0;
        }
    }
    void chg(int pos,int l,int r,ll _val){
        if(L[pos]>=l&&R[pos]<=r){
            add(pos,_val);
            lazytag_leaf[pos]+=_val;
            return;
        }
        if(R[pos]<l||L[pos]>r)return;
        pushdown(pos);
        int mid=((L[pos]+R[pos])>>1);
        if(mid>=l)chg(pos<<1,l,r,_val);
        if(mid<r)chg(pos<<1|1,l,r,_val);
        pushup(pos);
    }
    Girl query(int pos,int l,int r){
        if(L[pos]>=l&&R[pos]<=r){

            return val[pos];
        }
        if(R[pos]<l||L[pos]>r){
            return mkgr(1e18,1e9);
        }
        pushdown(pos);
        Girl res=mkgr(1e18,1e9);
        int mid=(L[pos]+R[pos]>>1);
        if(mid>=l)res=query(pos<<1,l,r);
        if(mid<r){
            Girl tmp=query(pos<<1|1,l,r);
            if(res.val>tmp.val){
                res=tmp;
            }else if(res.val==tmp.val){
                ckmn(res.id,tmp.id);
            }
        }
        return res;
    }
    void del(int pos,int x,ll lazyleafsum){
        lazyleafsum+=lazytag_leaf[pos];
        \/\/ 删除 x 位置的最后一个 girl,另外注意,这个 x 是 dfn=x
        if(L[pos]==x&&R[pos]==x){
            node_girl[g.dfntoid[x]].pop_back();
            if(node_girl[g.dfntoid[x]].empty()){
                val[pos]=mkgr(1e18,1e9);
            }else{
                val[pos]=mkgr(node_girl[g.dfntoid[x]].back().val+lazyleafsum,g.dfntoid[x]);
            }
            return;
        }
        if(R[pos]<x||L[pos]>x)return;
        pushdown(pos);
        int mid=(L[pos]+R[pos]>>1);
        if(mid>=x)del(pos<<1,x,lazyleafsum);
        if(mid<x)del(pos<<1|1,x,lazyleafsum);
        pushup(pos);
    }
    vector<int> op1ans;
    void OP1(int a,int b){
        \/\/ a,b 简单路径上查询最优 girl,
        Girl dreamgirl=mkgr(1e18,1e9);
        while(g.top[a]!=g.top[b]){
         \/\/   printf("a = %d b=  %d\n",a,b);
            if(g.dep[g.top[a]]<g.dep[g.top[b]])swap(a,b);
            Girl tmp=query(1,g.dfn[g.top[a]],g.dfn[a]);
            if(tmp.val<dreamgirl.val){
                dreamgirl=tmp;
            }else if((tmp.val==dreamgirl.val)&&(tmp.id<dreamgirl.id)){
                dreamgirl=tmp;
            }
            a=g.fa[g.top[a]];
        }
  \/\/      puts("???1");
        if(g.dep[a]>g.dep[b])swap(a,b);
    \/\/    puts("???2");
    \/\/    printf("a = %d b=  %d query = [%d,%d]\n",a,b,g.dfn[a],g.dfn[b]);
        Girl tmp=query(1,g.dfn[a],g.dfn[b]);
    \/\/    puts("???3");
        if(tmp.val<dreamgirl.val){
            dreamgirl=tmp;
        }else if((tmp.val==dreamgirl.val)&&(tmp.id<dreamgirl.id)){
            dreamgirl=tmp;
        }
        if(dreamgirl.id>n){
            op1ans.emplace_back(-1);
            return;
        }
    \/\/    puts("???4");
   \/\/     printf("dgid = %d\n",dreamgirl.id);
        \/\/ dreamgirl.id = 其所在的节点,而不是她本身的编号。
        op1ans.emplace_back(node_girl[dreamgirl.id].back().id);
        \/\/ 接下来,将最优 girl 从对应节点删除
        del(1,g.dfn[dreamgirl.id],0);
    }
    void op2(int u,ll _val){
        \/\/ u 为根的子树加上 _val
        chg(1,g.dfn[u],g.dfn[u]+g.siz[u]-1,_val);
    }
}sgt;
void solve(){
    scanf("%d%d%d",&n,&m,&q);
    FOR(i,1,n-1){
        int u,to;
        scanf("%d%d",&u,&to);
        g.g[u].emplace_back(to);
        g.g[to].emplace_back(u);
    }
    FOR(i,1,m){
        scanf("%d",&c[i]);
        node_girl[c[i]].emplace_back(mkgr(i,i));
    }
    FOR(i,1,n){
        sort(node_girl[i].begin(),node_girl[i].end(),[&](Girl& a,Girl& b){
            return a.val>b.val;
        });
    }
    g.build();
 \/\/   puts("???");
    sgt.build(1,1,n);

    while(q--){
        int op,a,b,c;
        scanf("%d%d%d",&op,&a,&b);
        if(op==1){
            scanf("%d",&c);
            FOR(i,1,c){
             \/\/   printf("i = %d\n",i);
                sgt.OP1(a,b);
                if(sgt.op1ans.back()==-1){
                    sgt.op1ans.pop_back();
                    break;
                }
            }
            printf("%d ",int(sgt.op1ans.size()));
            for(int& v:sgt.op1ans){
                printf("%d ",v);
            }
            puts("");
            sgt.op1ans.clear();
        }else{
            sgt.op2(a,b);
        }
    }
}
int main()
{
	T=1;
	while(T--){
		solve();
	}
	return 0;
}

文以载道(潍坊一中78级互测赛)ABEF题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-04-14 20:31:52

A

显然有一个结论:假设 $a_0 = a_{n+1} = 0$,那么如果 $\exist i \in [1,n]$ 满足 $a_i \gt a_{i-1} + a_{i+1}$,那么无解。

那么能不能推广一下呢?

事实上,只要不存在 $i \in [1,n]$ 满足 $a_i \gt a_{i-1}+a_{i+1}$,那么一定有解。

以下是我的(并不严谨的)证明:

假设 $1$ 到 $n$ 位置顺序是从左到右。考虑反复执行这个操作:
选择最靠左的不为 $0$ 的位置,记该位置的值为 $a_{lef}$,从该位置开始向右选择最长的最小值大于等于 $a_{lef}$ 的连续子段,对这个子段执行 $a_{lef}$ 次操作。
由于 $a_i \le a_{i-1} + a_{i+1}$,显然可得,除非序列变为全 $0$,否则不会出现不可操作的情况。

设计 $dp_{i,lst,now}$ 代表只考虑前 $i$ 位,第 $i-1$ 个数是 $lst$,第 $i$ 个数是 $now$ 的(判定合法时只考虑前 $i-1$ 个的)合法序列方案数。
假设第 $i-2$ 个数是 $lst_2$,那么:

$lst_2 + now \ge lst$
$lst_2 \ge lst-now$
$dp_{i,lst,now} = \sum\limits_{k \ge lst_2 \ge lst-now} dp_{i-1,lst_2,lst}$
最后答案是 $\sum\limits_{0 \le x \le k}dp_{n+1,x,0}$

用前缀和优化后复杂度是 $O(n^3)$ 的。
CF 提交记录

B

此时 $n \le 3000$,原来算法不再可行。
现在,我们应该请出数数题的一个很好用的算法:容斥!

如果一个位置 $1 \le i \le n$ 满足 $a_i \gt a_{i-1}+a_{i+1}$,我们就称之为坏位置。

设 $f(i)$ 代表钦定有 $i$ 个坏位置的方案数。

那么根据容斥公式,答案是 $\sum\limits_{i=0}^{n} (-1)^if(i)$。

我们需要对于 $\forall i \in [0,n]$ 计算出 $f(i)$。

设计状态 $dp_{i,lst,x}$ 代表只考虑前 $i$ 个,第 $i$ 个数是 $lst$,前 $i-1$ 个已经钦定了 $x$ 个坏位置的方案数。

看上去状态数是 $O(n^3)$,但注意,事实上根据容斥公式,我们关心的只有坏位置的奇偶性,所以实际有效的状态数可以压缩到 $O(n^2)$,即 $dp_{i,lst,x}$ 代表只考虑前 $i$ 个,第 $i$ 个数是 $lst$,前 $i-1$ 个已经钦定了的坏位置的数量模 $2$ 为 $x$ 的方案数。

转移的话,假设当前状态是 $dp_{i,lst,x}$,考虑是否钦定第 $i-1$ 个元素是坏位置。

  • 钦定第 $i-1$ 个位置是坏位置
    $dp_{i,lst,x} = \sum\limits_{lst_2}dp_{i-2,lst_2,x \oplus 1} \times (k-lst-lst_2)$
  • 不钦定第 $i-1$ 个位置是坏位置(注意语序,不要理解成 “钦定第 $i-1$ 个位置不是坏位置”
    $dp_{i,lst,x} = \sum\limits_{lst_2} dp_{i-1,lst_2,x}$

同理,前缀和优化可以做到 $O(n^2)$。
CF 提交记录

E

这些操作看上去很麻烦,而且似乎没有特定数据结构可以直接完成如此复杂的操作。

那么我们请出递推重器——矩阵!

对于一个元素有多个属性需要互相递推的操作,使用矩阵将会大大简化,所以考虑使用矩阵完成这些操作。

剩下部分

F

考虑枚举中点,计算每一个点作为回文串中点能给答案带来多少贡献。

具体地, $1,2,3,4,5,\cdots,n$ 以及相邻两个整点的中点均可以作为回文串中点。

假设枚举 $mid$ 作为回文串中点,接下来,枚举回文串长度,并计算出这个回文串被加入答案的概率,然后累加到答案里即可。整个过程可以用前缀和/后缀和优化。

复杂度 $O(n^2)$。

Code(By @CountingGroup)

#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
inline int read () {
	int x = 0, f = 0;
	char c = getchar ();
	for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
	for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
	return !f ? x : -x;
}
inline int ksm (int a, int b, int p) {
	int res = 1;
	while (b) {
		if (b & 1) res = res * a % p;
		b >>= 1;
		a = a * a % p;
	}
	return res;
}
const int N = 4005;
const int mod = 998244353;
int n, mp[N], ans, tot, pre[N], suf[N], siz[N];
signed main () {
	n = read ();
	tot = 1;
	pre[0] = suf[n + 1] = 1;
	for (int i = 1;i <= n; ++ i) {
		char s[40];
		scanf ("%s", s + 1);
		int m = strlen (s + 1);
		for (int j = 1;j <= m; ++ j) {
			int offset = s[j] - 'a';
			mp[i] |= (1 << offset);
		}
		tot = tot * m % mod;
		siz[i] = m;
	}
	for (int i = 1;i <= n; ++ i) pre[i] = pre[i - 1] * siz[i] % mod;
	for (int i = n;i >= 1; -- i) suf[i] = suf[i + 1] * siz[i] % mod;
	for (int mid = 1;mid <= n; ++ mid) {
		int L = mid, R = mid, cur = __builtin_popcountll (mp[mid]);
		while (true) {
			ans = (ans + cur * pre[L - 1] % mod * suf[R + 1] % mod) % mod;
			L --, R ++;
			if (L < 1 || R > n) break;
			int state = (mp[L] & mp[R]);
			cur = cur * (__builtin_popcountll (state)) % mod;
		}
	}
	for (int mid = 1;mid <= n; ++ mid) {
		int S = (mp[mid] & mp[mid + 1]);
		int L = mid, R = mid + 1, cur = __builtin_popcount (S);
		while (true) {
			ans = (ans + cur * pre[L - 1] % mod * suf[R + 1] % mod) % mod;
			L --, R ++;
			if (L < 1 || R > n) break;
			int state = (mp[L] & mp[R]);
			cur = cur * (__builtin_popcountll (state)) % mod;
		}
	}
	printf ("%lld\n", ans * ksm (tot, mod - 2, mod) % mod);
	return 0;
}

论如何 AK ABC350

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-04-21 10:20:59

感谢 weak ABCDE + G 题 LCT 送我上 1600 2kyu。
但距离 1 Dan 仍然还差 400 分,慢慢推进吧。

A

把后 3 位拆出来,根据题目判断一下就行了,另外注意这题的 atcoder better 翻译是错的。

B

开个数组记录每个位置是否有牙齿,模拟即可。

C

错误做法:

从 $1$ 到 $n$ 枚举每个位置的数,将其交换到正确位置。
Hack:

5
1 5 4 2 3

具体的,假设枚举到第 $i$ 个数,将其与 $a_i$ 位置的数交换,如果 $a_i \gt i$,并且 $a_{a_i}$ 不能放在 $i$ 位置,那么此算法就会使得 $a_{a_i}$ 被交换到错误的位置,而且不会再修正。
我赛时因此吃了 2 发罚时。

正确做法:

从 $1$ 到 $n$ 枚举每个数,记录其位置,设 $pos_i$ 为 $i$ 在数组中的位置。

依次枚举 $i$ 从 $1$ 到 $n$,如果 $pos_i \neq i$,那么交换 $a_i,a_{pos_i}$。
显然,每次交换都会让一个数处于正确位置,而不会造成另一个数从正确位置到错误位置。另外,当有 $n-1$ 个数被修正后,剩下那个数一定处于正确位置,所以操作次数不会超过 $n-1$。
submission

D

显然,最后的图中,每个连通块都必须构成完全图。
dfs 算出每个连通块大小,假设有 $k$ 个连通块,第 $i$ 个连通块有 $node_i$ 个点,$edg_i$ 个边,那么答案是 $\sum\limits_{i=1}^{k} (\frac{node_i(node_i - 1)}{2} - edg_i) = (\sum\limits_{i=1}^{k} \frac{node_i(node_i - 1)}{2}) - m$

然后就可以 $O(n)$ 做出来了。

E

设 $f(n)$ 为初始值为 $n$,变为 $0$ 的最小期望代价。
显然有:$f(n) = \sum\limits_{i=1}^{6}\frac{1}{6}(f(\lfloor \frac{n}{i} \rfloor)+y)$
本题解的傻逼作者赛时第一遍写了一发级数求和,然后过不去样例。
事实上移项就能做:
设 $s = \sum\limits_{i=2}^{6}\frac{1}{6}(f(\lfloor \frac{n}{i} \rfloor)+y)$。
那么 $f(n) = s + \frac{1}{6}(f(n)+y)$
$f(n) = s + \frac{1}{6}f(n)+\frac{1}{6}y$
$f(n)-\frac{1}{6}f(n) = s+\frac{1}{6}y$
$\frac{5}{6}f(n) = s+\frac{1}{6}y$
$f(n) = \frac{6}{5}(s+\frac{1}{6}y)$

F

用文艺平衡树模板应该能做,这里使用简单递归。
大概是这样的形式(我不会写标准伪代码,先这样吧):

define pre[i] 假设左括号代表 +1,右括号 -1,那么前 i 个字符的前缀和是 pre[i]
void f(l,r,turn) \/\/ 处理 l,r 区间,l,r 为括号,turn 代表是否翻转 [l+1,r-1] 区间  
	define seqs 用于存储合法子序列的容器
	define sub_l = [l+1,r-1] 区间的第一个左括号的位置  
    	define sub_r = 最小的满足 pre[sub_r] = pre[sub_l-1] 的 sub_r 位置   
        while [sub_l,subr] 属于合法区间,且属于 [l+1,r-1] 子序列。  
        	seqs.append([sub_l,sub_r])
          	更新 sub_l,sub_r 到下一个合法区间  
        if turn == False  
        	正向枚举 seqs,记当前枚举到 [sl,sr]  
            		输出上一个区间与 [sl,sr] 之间的空隙。  
                	执行 f(sl,sr,1)  
                正向输出最后一个区间和 r 之间的空隙。  
        else  
        	反向枚举 seqs,记当前枚举到 [sl,sr]  
            		输出上一个区间与 [sl,sr] 之间的空隙。  
                	执行 f(sl,sr,0)  
                反向输出最后枚举的区间和 l 之间的空隙。    
            

My submission

G

@Iceturky 说他的做法是根号分治 + bitset,我不会这位大佬的做法,所以这里放一个思路较为简单的 LCT 做法,复杂度 $O(Q \log n)$,复杂度比根号分治 + bitset 优很多。

维护森林的加边显然可以 LCT。

另外一个就是求 $u,v$ 都相邻的点。

第一步,并查集判联通,如果不连通直接输出 0

显然,这个点一定在 $u,v$ 的路径上,且这个点存在当且仅当 $u,v$ 路径上除了 $u,v$ 外只有一个点。

而 LCT 可以维护路径权值,那么我们只需要为将第 $i$ 个点的权值设置为 $i$,然后记 LCT 求得的路径权值和为 $s$,那么将 $u,v$ 从 $s$ 中剔除就可以了。

然后,要判断答案是否合法,此时可以用 map 记录哪些点是相连的。

由于是 Atcoder 的比赛,可以自由复制模板。

共 320 篇博客