In mathematics you don't understand things, you just get used to them.

标签 data structures 下的文章

Description

Link.

$\operatorname{Rainyrabbit}$ 是一个数学极好的萌妹子,近期他发现了一个可爱的函数:

$$ f(n,m,k)=\sum_{d=1}^n d^k\lfloor\dfrac{n}{\operatorname{lcm}(d,m)}\rfloor $$

其中 $\operatorname{lcm}(d,m)$ 表示 $d$ 和 $m$ 的最小公倍数。

她觉得只算这一个函数太单调了于是想要对这个函数求和,给出 $a,b,c$,求:

$$ \sum_{i=1}^a\sum_{j=1}^b\sum_{k=0}^cf(i,j,k) $$

同时 $\operatorname{I.w.rabbit}$ 固定 $c$ 的值不变,给出 $T$ 组 $a,b$,请回答此时式子对 $998244353$ 取模后的值。

算是概括过了额。

Solution

先看 $f$ 怎么把那个烦死的整除去掉。

$$ \begin{aligned} f(n,m,k)&=\sum_{d=1}^{n}d^{k}\lfloor\frac{n}{\text{lcm}(d,m)}\rfloor \\ &=\sum_{d=1}^{n}d^{k}\lfloor\frac{\frac{n}{d}}{\frac{m}{\gcd(d,m)}}\rfloor \\ &=\sum_{d=1}^{n}d^{k}\lfloor\frac{\frac{n}{d}}{\frac{m}{\gcd(d,m)}}\rfloor \\ \end{aligned} $$

来看 $\lfloor\frac{\frac{n}{d}}{\frac{m}{\gcd(d,m)}}\rfloor$,因为 $\lfloor\frac{a}{b}\rfloor=\sum_{i=1}^{a}[b|i]$,所以 $\lfloor\frac{\frac{n}{d}}{\frac{m}{\gcd(d,m)}}\rfloor=\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}[\frac{m}{\gcd(d,m)}|i]=\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}[m|id]$。所以

$$ \begin{aligned} f(n,m,k)&=\sum_{d=1}^{n}d^{k}\lfloor\frac{\frac{n}{d}}{\frac{m}{\gcd(d,m)}}\rfloor \\ &=\sum_{d=1}^{n}d^{k}\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}[m|id] \\ &=\sum_{m|i}^{n}\sum_{d|i}d^{k} \\ &=\sum_{m|i}^{n}\sigma_{k}(i) \\ \end{aligned} $$

然后代回原式。

$$ \begin{aligned} \sum_{i=1}^{a}\sum_{j=1}^{b}\sum_{k=0}^{c}\sum_{j|d}^{i}\sigma_{k}(d)&=\sum_{k=0}^{c}\sum_{d=1}^{a}\sigma_{k}(d)(a-d+1)\sum_{j=1}^{b}[j|d] \\ \end{aligned} $$

考虑如何计算 $\sum_{k=0}^{c}\sigma_{k}(d)$。把函数又拆回去:

$$ \begin{aligned} \sum_{k=0}^{c}\sigma_{k}(d)&=\sum_{w|d}\sum_{k=0}^{c}w^{k}=\sum_{w|d}\frac{w^{c+1}-1}{w-1} \end{aligned} $$

最后一步是等比数列求和,然后你就可以调和级数预处理了。具体来说就是线筛的时候筛一下 $w^{c+1}$,这东西是个完全积性函数,你乱筛就行了。

设这玩意儿为 $s(d)=\sum_{w|d}\frac{w^{c+1}-1}{w-1}$,原式改写为:

$$ \sum_{d=1}^{a}s(d)(a-d+1)\sum_{j=1}^{b}[j|d] \\ $$

然后后面那个 sigma 你也可以反过来直接调和级数。还有就是 $b>a$ 的时候没有贡献,所以可以取个 $\min$,这样能多几分。

来看看怎么屮多测。

数表 那道题一样,我们把询问离线下来,以 $b$ 为关键字排序后树状数组。

把中间那个系数拆出来,变成:

$$ \sum_{d=1}^{a}(a+1)s(d)-d\times s(d)\sum_{j=1}^{b}[j|d] \\ $$

前面那个好说,直接来;后面就在树状数组修改时乘上系数即可。

综上,维护两个树状数组即可。

#include<cstdio>
#include<algorithm>
using namespace std;
void read(long long &x)
{
    x=0;
    char c=getchar();
    while(c<'0'||c>'9')    c=getchar();
    while(c>='0'&&c<='9')
    {
        x=(x<<3)+(x<<1)+(c^'0');
        c=getchar();
    }
}
void write(long long x)
{
    if(x>9)    write(x/10);
    putchar((x%10)^'0');
}
const long long mod=998244353;
struct node
{
    long long a,b,ID;
}nodes[200010];
struct fenwick
{
    #define lowbit(x) ((x)&-(x))
    long long fen[1000010],mx;
    void ins(long long x,long long y)
    {
        while(x<=mx)
        {
            fen[x]=(fen[x]+y)%mod;
            x+=lowbit(x);
        }
    }
    long long find(long long x)
    {
        long long res=0;
        while(x)
        {
            res=(res+fen[x])%mod;
            x^=lowbit(x);
        }
        return res;
    }
}onefe,anofe;
long long t,a,b,c,tag[1000010],prime[1000010],cnt,fu[1000010],exfu[1000010],power[1000010],cur=1,mx,ans[200010];
bool cmp(node one,node ano)
{
    return one.b<ano.b;
}
long long cqpow(long long bas,long long fur)
{
    long long res=1;
    while(fur)
    {
        if(fur&1)    res=res*bas%mod;
        bas=bas*bas%mod;
        fur>>=1;
    }
    return res;
}
void search(long long x)
{
    tag[1]=power[1]=1;
    for(long long i=2;i<=x;++i)
    {
        if(!tag[i])
        {
            prime[++cnt]=i;
            power[i]=cqpow(i,c+1);
        }
        for(long long j=1;j<=cnt&&prime[j]*i<=x;++j)
        {
            tag[prime[j]*i]=1;
            power[prime[j]*i]=power[prime[j]]*power[i]%mod;
            if(i%prime[j]==0)    break;
        }
    }
    fu[1]=(c%mod+1)%mod;
    for(long long i=2;i<=x;++i)    fu[i]=((power[i]-1+mod)%mod)*cqpow(i-1,mod-2)%mod;
    for(long long i=1;i<=x;++i)
    {
        for(long long j=i;j<=x;j+=i)    exfu[j]=(exfu[j]+fu[i])%mod;
    }
}
int main()
{
    read(t);
    read(c);
    search(1000000);
    for(long long i=1;i<=t;++i)
    {
        read(nodes[i].a);
        read(nodes[i].b);
        nodes[i].ID=i;
        nodes[i].b=min(nodes[i].a,nodes[i].b);
        mx=max(mx,nodes[i].a);
    }
    sort(nodes+1,nodes+t+1,cmp);
    onefe.mx=anofe.mx=mx;
    for(long long i=1;i<=mx&&cur<=t;++i)
    {
        for(long long j=i;j<=mx;j+=i)
        {
            onefe.ins(j,exfu[j]);
            anofe.ins(j,exfu[j]*j%mod);
        }
        while(i==nodes[cur].b)
        {
            ans[nodes[cur].ID]=((onefe.find(nodes[cur].a)*(nodes[cur].a+1))%mod-anofe.find(nodes[cur].a)+mod)%mod;
            cur++;
        }
    }
    for(long long i=1;i<=t;++i)
    {
        write(ans[i]);
        putchar('\n');
    }
    return 0;
}

「ARC 111A」Simple Math 2

Link.

$\lfloor \frac{10^N - kM^2}{M} \rfloor \equiv \lfloor \frac{10^N}{M} - kM \rfloor \equiv \lfloor \frac{10^N}{M} \rfloor - kM \equiv \lfloor \frac{10^N}{M} \rfloor \pmod M (k \in \mathbb{Z})$

#include <iostream>

using i64 = long long;

int cpow ( int bas, i64 idx, const int p ) {
    int res = 1;
    while ( idx ) {
        if ( idx & 1 )    res = ( i64 )res * bas % p;
        bas = ( i64 )bas * bas % p, idx >>= 1;
    }
    return res;
}

int main () {
    std::ios::sync_with_stdio ( 0 ); std::cin.tie ( 0 ); std::cout.tie ( 0 );
    i64 n; int m; std::cin >> n >> m;
    std::cout << ( cpow ( 10, n, m * m ) / m ) % m << '\n';
    return 0;
}

「ARC 111B」Reversible Cards

Link.

nowcoder 原题。

#include<cstdio>
int n,cab[400010],fa[400010],a,b,ans;
int findset(int x)
{
    if(fa[x])    return fa[x]=findset(fa[x]);
    else    return x;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d",&a,&b);
        a=findset(a);
        b=findset(b);
        if((a^b)&&(!cab[a]||!cab[b]))
        {
            fa[a]=b;
            cab[b]|=cab[a];
            ans++;
        }
        else if(!cab[a])
        {
            cab[a]=1;
            ans++;
        }
    }
    printf("%d\n",ans);
    return 0;
}

「ARC 111C」Too Heavy

Link.

构造出一个操作序列。

先不考虑最小,只考虑构造出来。

参考某道 ABC D 题,直接连边。

$i\rightarrow p_{i}\rightarrow p_{p_{i}}\rightarrow\cdots\rightarrow i$。

craft.png

$1\ 2$ 分别表示 person、baggage。

再想,相当于我们想要让,$1$ and $2$ 一一对应。

一个 $(u,v)$ 的 $2$(即 $v$)不能被交换只在 $a_{u}\le b_{v}$。

所以无解就是这个环中存在 $a_{u}\le b_{v}$。

剩下构造,先考虑满足规则。

贪心的选一个最大的 $a_{i}$ 进行即可。

#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
vector<pair<int,int> > ans;
int n,a[200010],b[200010],p[200010],rev[200010],vis[200010];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)    scanf("%d",&a[i]);
    for(int i=1;i<=n;++i)    scanf("%d",&b[i]);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&p[i]);
        rev[p[i]]=i;
    }
    vector<int> per;
    for(int i=1;i<=n;++i)
    {
        if(p[i]^i)
        {
            if(a[rev[i]]<=b[i])
            {
                printf("-1\n");
                return 0;
            }
            if(!vis[i])
            {
                vis[i]=1;
                per.clear();
                per.push_back(i);
                for(int j=p[i];j^i;j=p[j])
                {
                    if(a[rev[j]]<=b[j])
                    {
                        printf("-1\n");
                        return 0;
                    }
                    vis[j]=1;
                    per.push_back(j);
                }
                int pos=0,val=0;
                for(int j=0;j<per.size();++j)
                {
                    if(a[per[pos]]<=a[per[j]])
                    {
                        pos=j;
                        val=per[j];
                    }
                }
                for(int j=pos+1;j<per.size();++j)    ans.push_back(make_pair(val,per[j]));
                for(int j=0;j<pos;++j)    ans.push_back(make_pair(val,per[j]));
            }
        }
    }
    printf("%d\n",ans.size());
    for(int i=0;i<ans.size();++i)    printf("%d %d\n",ans[i].first,ans[i].second);
    return 0;
}

「ARC 111D」Orientation

Link.

像个贪心?

  • $c_{u}\neq c_{v}$

    • $c_{u}>c_{v}$:$\rightarrow$
    • $c_{u}<c_{v}$:$\leftarrow$
  • $c_{u}=c_{v}$

在一个环里,深搜即可。

这 C D 放反了吧

#include<queue>
#include<cstdio>
#include<string>
#include<algorithm>
using namespace std;
vector<pair<int,int> > e[110];
vector<string> ans;
int n,m,c[110],eve[110][110],vis[110];
void dfs(int x)
{
    vis[x]=1;
    for(int i=1;i<=n;++i)
    {
        if(eve[x][i])
        {
            eve[i][x]=0;
            if(!vis[i])    dfs(i);
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        e[u].push_back(make_pair(v,i));
    }
    for(int i=1;i<=n;++i)    scanf("%d",&c[i]);
    ans.resize(m);
    for(int i=1;i<=n;++i)
    {
        for(int j=0;j<e[i].size();++j)
        {
            int y=e[i][j].first,id=e[i][j].second-1;
            if(c[i]>c[y])    ans[id]="->";
            else if(c[i]<c[y])    ans[id]="<-";
            else    eve[i][y]=eve[y][i]=1;
        }
    }
    for(int i=1;i<=n;++i)
    {
        for(int j=0;j<e[i].size();++j)
        {
            int y=e[i][j].first,id=e[i][j].second-1;
            dfs(i);
            if(eve[i][y])    ans[id]="->";
            else if(eve[y][i])    ans[id]="<-";
        }
    }
    for(int i=0;i<ans.size();++i)    printf("%s\n",ans[i].c_str());
    return 0;
}

「ARC 111E」Simple Math 3

Link.

即求:

$$ \sum_{i=1}^{\infty}[\lfloor\frac{A+B\times i-1}{D}\rfloor=\lfloor\frac{A+C\times i}{D}\rfloor] $$

题目说这玩意儿是 finite,然后(没加思考)跑到 U 群问成功丢人。

悲伤的故事,这告诉我们问前先思考。

原因是 $i$ 大了 $[A+B\times i,A+C\times i]$ 的长度一定 $\ge D$。

具体来说是 $i>\frac{D-2}{C-B}$ 的时候就完了。

那么式子改写为:

$$ \sum_{i=1}^{\frac{D-2}{C-B}}[\lfloor\frac{A+B\times i-1}{D}\rfloor=\lfloor\frac{A+C\times i}{D}\rfloor] $$

继续分析,此时的区间 $[A+B\times i,A+C\times i]$ 的长度小于 $D$,里面最多有一个数是 $D$ 的 multiple。

不会了 看题解 要类欧 不会了 抄板子 过题了

这种推不复杂考板的题好草人啊。。。。

upd:

official editorial 说可以用 AC lib 的 floor_sum 直接算。

屑行为 details

#include<cstdio>
int T;
long long a,b,c,d;
long long dfs(long long a,long long b,long long c,long long n)
{
    if(a>=c||b>=c)    return dfs(a%c,b%c,c,n)+(a/c)*(n+1)*n/2+(b/c)*(n+1);
    else if(a==0)    return 0;
    else    return (a*n+b)/c*n-dfs(c,c-b-1,a,(a*n+b)/c-1);
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
        printf("%lld\n",(d-2)/(c-b)-dfs(c,a,d,(d-2)/(c-b))+dfs(b,a-1,d,(d-2)/(c-b)));
    }
    return 0;
}

「ARC 111A」Simple Math 2

Link.

missing。

本来十分抗拒,但 GM 强制。

「ABC 183A」ReLU

Link.

略。

#include<cstdio>
int main()
{
    long long n;
    scanf("%lld",&n);
    printf("%lld\n",n>0?n:0);
    return 0;
}

「ABC 183B」Billiards

Link.

设答案坐标 $A(m,n)$,然后算出 $y_{AG}$ 解析式,再带 $x=S'_{x}$,$S'$ 是 $S$ 关于直线 $x=m$ 的对称点,得出来的 $y$ 要等于 $n$,然后列个方程解出来答案为 $\frac{S_{x}G_{y}+G_{x}S_{y}}{S_{y}+G_{y}}$。

#include<cstdio>
double sx,sy,gx,gy;
int main()
{
    scanf("%lf%lf%lf%lf",&sx,&sy,&gx,&gy);
    printf("%lf\n",(sx*gy+gx*sy)/(sy+gy));
    return 0;
}

「ABC 183C」Travel

Link.

全排列枚举算答案即可。

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> per;
int n,ans;
long long k,tim[20][20];
int main()
{
    scanf("%d%lld",&n,&k);
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=n;++j)    scanf("%lld",&tim[i][j]);
    }
    per.resize(n+2);
    for(int i=1;i<=n;++i)    per[i]=i;
    per[n+1]=1;
    do
    {
        long long sum=0;
        for(int i=2;i<=n+1;++i)    sum+=tim[per[i-1]][per[i]];
        if(sum==k)    ++ans;
    }while(next_permutation(per.begin()+2,per.end()-1));
    printf("%d\n",ans);
    return 0;
}

「ABC 183D」Water Heater

Link.

前缀和。

#include<cstdio>
int n,s[200010],t[200010],p[200010],w;
long long dif[200010];
int main()
{
    scanf("%d%d",&n,&w);
    for(int i=1;i<=n;++i)    scanf("%d%d%d",&s[i],&t[i],&p[i]);
    for(int i=1;i<=n;++i)
    {
        dif[s[i]]+=p[i];
        dif[t[i]]-=p[i];
    }
    for(int i=1;i<=200000;++i)    dif[i]+=dif[i-1];
    for(int i=0;i<=200000;++i)
    {
        if(dif[i]>w)
        {
            printf("No\n");
            return 0;
        }
    }
    printf("Yes\n");
    return 0;
}

「ABC 183E」Water Heater

Link.

递推完了。

#include<cstdio>
const int mod=1e9+7;
long long ans;
int n,m,mp[2010][2010],row[2010],col[2010],dia[5010];
char str[2010];
int add(long long a,long long b)
{
    if(a+b>=mod)    return (a+b)%mod;
    else    return a+b;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        scanf("%s",str+1);
        for(int j=1;j<=m;++j)
        {
            if(str[j]=='.')    mp[i][j]=0;
            else    mp[i][j]=1;
        }
    }
    int lay=2e3;
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            if(mp[i][j])
            {
                row[i]=0;
                col[j]=0;
                dia[i-j+lay]=0;
            }
            else
            {
                int tmp=add(add(row[i],col[j]),dia[i-j+lay]);
                if(i==1&&j==1)    ++tmp;
                row[i]=add(row[i],tmp);
                col[j]=add(col[j],tmp);
                dia[i-j+lay]=add(dia[i-j+lay],tmp);
                ans=tmp;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

「ABC 183F」Confluence

Link.

并查集板。

#pragma GCC diagnostic error "-std=c++11"
#include<map>
#include<cstdio>
using namespace std;
map<int,int> mp[200010];
int n,m,fa[200010];
void makeset()
{
    for(int i=1;i<=n;++i)    fa[i]=i;
}
int findset(int x)
{
    if(x^fa[x])    fa[x]=findset(fa[x]);
    return fa[x];
}
void mergeset(int x,int y)
{
    x=findset(x);
    y=findset(y);
    if(x^y)
    {
        if(mp[x].size()>mp[y].size())
        {
            fa[y]=x;
            for(auto p:mp[y])    mp[x][p.first]+=p.second;
        }
        else
        {
            fa[x]=y;
            for(auto p:mp[x])    mp[y][p.first]+=p.second;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    makeset();
    for(int i=1;i<=n;++i)
    {
        int x;
        scanf("%d",&x);
        mp[i][x]++;
    }
    while(m--)
    {
        int opt,opx,opy;
        scanf("%d%d%d",&opt,&opx,&opy);
        if(opt==1)    mergeset(opx,opy);
        else
        {
            int tmp=findset(opx);
            printf("%d\n",mp[tmp][opy]);
        }
    }
    return 0;
}

Description

Link.

给一个树,$n$ 个点,有点权,初始根是 1。

$m$ 个操作,种类如下:

1 x 将树根换为 $x$。

2 x y 给出两个点 $x,y$,从 $x$ 的子树中选每一个点,$y$ 的子树中选每一个点,求点权相等的情况数。

Solution

我首先认为这是 SNOI2017 一个简单的询问 搬到树上。

我们传统地把此题分为两个 $\texttt{pass}$,一个询问,一个修改。

  • $\texttt{pass 1}$:询问

我直接按 一个简单的询问 的方法讲。其实是把以前的题解 copy 过来了

由于是出现次数,满足区间加减性,所以我们可以这样表达 $\mathrm{get}(l,r,x)$(省略 $x$):

$$ \mathrm{get}(l,r)=\mathrm{get}(1,r)-\mathrm{get}(1,l-1) $$

那么我们代进原式,化一波式子($\mathrm{get}(p)=\mathrm{get}(1,p,x)$):

$$ \sum_{i=1}^{\infty}\mathrm{get}(l_{1},r_{1},x)\times\mathrm{get}(l_{2},r_{2},x) $$

$$ \sum_{i=1}^{\infty}(\mathrm{get}(1,r_{1})-\mathrm{get}(1,l_{1}-1))\times(\mathrm{get}(1,r_{2})-\mathrm{get}(1,l_{2}-1)) $$

$$ \sum_{i=1}^{\infty}\mathrm{get}(r_{1})\times\mathrm{get}(r_{2})-\mathrm{get}(r_{1})\times\mathrm{get}(l_{2}-1)-\mathrm{get}(l_{1}-1)\times\mathrm{get}(r_{2})+\mathrm{get}(l_{1}-1))\times\mathrm{get}(l_{2}-1) $$

$$ \mathrm{let}\ F(x,y)=\sum_{d}\mathrm{get}(1,l,d)\times\mathrm{get}(1,r,d) $$

则答案为:

$$ F(r_{1},r_{2})-F(r_{1},l_{2}-1)-F(l_{1}-1,r_{2})+F(l_{1}-1,l_{2}-1) $$

考虑怎么更新,比如从 $l$ 更新到 $l+1$,则:

$$ \mathrm{get(1,l)}\times\mathrm{get}(1,r) $$

$$ \mathrm{get(1,l+1)}\times\mathrm{get}(1,r) $$

$$ \mathrm{get(1,l)}\times\mathrm{get}(1,r)+\mathrm{cont}(a_{l}) $$

其中 $\mathrm{cont}(a_{l})$ 表示 $a_{l}$ 的出现次数。

则我们就知道怎么更新了,由于我们维护和的是前缀信息,所以姿势和普通莫队有点不一样。

维护两个数组 cntl[x]cntr[y] 表示答案式子

$$ F(r_{1},r_{2})-F(r_{1},l_{2}-1)-F(l_{1}-1,r_{2})+F(l_{1}-1,l_{2}-1) $$

子树的话直接 DFS 序拍到序列上。

  • $\texttt{pass 2}$:修改

现在我们面临着查询操作我们是用莫队整的,但这个修改貌似不单纯。其实也是从树剖模板缝合过来的

分类讨论,设我们当前要换的根为 $rt$,现在来处理询问,设查询的节点为 $u$,$\text{LCA}(u,v)$ 为节点 $u$ 和节点 $v$ 的最近公共祖先。

    • 如果 $rt=u$,则我们直接对整棵树进行查询。
    • 如果 $\text{LCA}(u,rt)\neq u$,此时修改不影响查询。
    • 如果 $\text{LCA}(u,rt)=u$,此时 $rt$ 在 $u$ 的子树里,那么需要查询的地方就很明确了,后面的步骤显然。

于是我们不需要实际的去处理这个修改,然后就可以直接莫队了。

(整体感觉是个 原题+假上树+树剖模板 的缝合题)

/* Clearink */

#include <cstdio>
#include <algorithm>

using namespace std;
typedef long long LL;

const int MAXN = 5e5 + 5, MAXM = 1e6 + 5;

int rint () {
    int x = 0, f = 1; char c = getchar ();
    for ( ; c < '0' || c > '9'; c = getchar () )    f = c == '-' ? -1 : f;
    for ( ; c >= '0' && c <= '9'; c = getchar () )    x = ( x << 3 ) + ( x << 1 ) + ( c & 15 );
    return x * f;
}

template<class _T>
void wint ( _T x ) {
    if ( x < 0 )    putchar ( '-' ), x = ~ x + 1;
    if ( x > 9 )    wint ( x / 10 );
    putchar ( x % 10 ^ '0' );
}

template<class _T> void swapp ( _T& x, _T& y ) { _T w = x; x = y; y = w; }

struct GraphSet {
    int to, nx;
    GraphSet () : to ( 0 ), nx ( 0 ) {}
    GraphSet ( const int a, const int b ) : to ( a ), nx ( b ) {}
} asg[MAXN * 2];

struct Quest {
    int l, r, ID, x;
    Quest () : l ( 0 ), r ( 0 ), ID ( 0 ), x ( 0 ) {}
    Quest ( const int a, const int b, const int c, const int d ) : l ( a ), r ( b ), ID ( c ), x ( d ) {}
} asq[MAXM * 8], itls[MAXN];

LL cur = 0, ans[MAXM], buc1[MAXN], buc2[MAXN];
int rt, pos[MAXN], blo = 320, col[MAXN], freq;
int n, m, bgn[MAXN], cnt, sjc, segl[MAXN], segr[MAXN], kfa[MAXN][21], a[MAXN], dept[MAXN], pri[MAXN], len;

void addE ( const int u, const int v ) { asg[++ cnt] = GraphSet ( v, bgn[u] ), bgn[u] = cnt; }
bool existcmp ( const Quest& one, const Quest& ano ) { return pos[one.l] == pos[ano.l] ? one.r < ano.r : one.l < ano.l; }

void dfs ( const int u, const int lst ) {
    kfa[u][0] = lst, dept[u] = dept[lst] + 1;
    segl[u] = ++ sjc, col[sjc] = a[u];
    for ( int i = 1; i <= 20; ++ i )    kfa[u][i] = kfa[kfa[u][i - 1]][i - 1];
    for ( int i = bgn[u]; i; i = asg[i].nx ) {
        int v = asg[i].to;
        if ( v == lst )    continue;
        dfs ( v, u );
    }
    segr[u] = sjc;
}

int calcKAC ( int u, int k ) {
    for ( int i = 20; ~ i; -- i ) {
        if ( k >= ( 1 << i ) )    k -= ( 1 << i ), u = kfa[u][i];
    }
    return u;
}

int calcLCA ( int u, int v ) {
    if ( dept[u] < dept[v] )    swapp ( u, v );
    for ( int i = 20; ~ i; -- i ) {
        if ( dept[kfa[u][i]] >= dept[v] )    u = kfa[u][i];
    }
    if ( u == v )    return u;
    for ( int i = 20; ~ i; -- i ) {
        if ( kfa[u][i] != kfa[v][i] )    u = kfa[u][i], v = kfa[v][i];
    }
    return kfa[u][0];
}

void initial () {
    for ( int i = 1; i <= n; ++ i )    pos[i] = ( i - 1 ) / blo + 1;
    sort ( pri + 1, pri + 1 + n );
    len = unique ( pri + 1, pri + 1 + n ) - pri - 1;
    for ( int i = 1; i <= n; ++ i )    a[i] = lower_bound ( pri + 1, pri + 1 + len, a[i] ) - pri;
    dfs ( 1, 0 );
}

void splitASdrug ( const int u, int& ils ) {
    if ( u == rt )    itls[++ ils] = Quest ( 1, n, 0, 0 );
    else {
        int lca = calcLCA ( u, rt );
        if ( lca != u )    itls[++ ils] = Quest ( segl[u], segr[u], 0, 0 );
        else {
            int ar = calcKAC ( rt, dept[rt] - dept[u] - 1 );
            if ( 1 <= segl[ar] - 1 )    itls[++ ils] = Quest ( 1, segl[ar] - 1, 0, 0 );
            if ( segr[ar] + 1 <= n )    itls[++ ils] = Quest ( segr[ar] + 1, n, 0, 0 );
        }
    }
}

void transASsub ( const int l1, const int r1, const int l2, const int r2, const int ID ) {
    asq[++ m] = Quest ( r1, r2, ID, 1 ), asq[++ m] = Quest ( r1, l2 - 1, ID, -1 );
    asq[++ m] = Quest ( l1 - 1, r2, ID, -1 ), asq[++ m] = Quest ( l1 - 1, l2 - 1, ID, 1 );
}

void transASmany ( const int l, const int r ) {
    ++ freq;
    int ils = 0; splitASdrug ( l, ils );
    int aim = ils; splitASdrug ( r, ils );
    for ( int i = 1; i <= aim; ++ i ) {
        for ( int j = aim + 1; j <= ils; ++ j )    transASsub ( itls[i].l, itls[i].r, itls[j].l, itls[j].r, freq );
    }
}

void add1 ( const int x ) { cur += buc2[col[x]], buc1[col[x]] ++; }
void add2 ( const int x ) { cur += buc1[col[x]], buc2[col[x]] ++; }
void sub1 ( const int x ) { cur -= buc2[col[x]], buc1[col[x]] --; }
void sub2 ( const int x ) { cur -= buc1[col[x]], buc2[col[x]] --; }
void captainMO () {
    int nowl = 0, nowr = 0;
    for ( int i = 1; i <= m; ++ i ) {
        for ( ; nowl < asq[i].l; add1 ( ++ nowl ) ) ;
        for ( ; nowr < asq[i].r; add2 ( ++ nowr ) ) ;
        for ( ; nowl > asq[i].l; sub1 ( nowl -- ) ) ;
        for ( ; nowr > asq[i].r; sub2 ( nowr -- ) ) ;
        ans[asq[i].ID] += cur * asq[i].x;
    }
}

int main () {
    n = rint (); int _waste_ = rint ();
    for ( int i = 1; i <= n; ++ i )    a[i] = pri[i] = rint ();
    for ( int i = 1; i < n; ++ i ) {
        int u = rint (), v = rint ();
        addE ( u, v ), addE ( v, u );
    }
    initial (), rt = 1;
    for ( int i = 1; i <= _waste_; ++ i ) {
        int c = rint (), x, y;
        if ( c == 1 )    rt = rint ();
        else    x = rint (), y = rint (), transASmany ( x, y );
    }
    sort ( asq + 1, asq + 1 + m, existcmp ), captainMO ();
    for ( int i = 1; i <= freq; ++ i )    wint ( ans[i] ), putchar ( '\n' );
    return 0;
}

Prob. 1

Desc. & Link.

行走的形式是比较自由的,因为只要走到了最优答案处就可以不管了,所以不需要考虑游戏的结束。

考虑二分答案。

然后预处理出每个节点到 $s$(另一棵树就是 $t$)的距离。

判断答案合法性:

首先枚举 $A$ 树的每个节点(节点编号小于当前二分的答案),然后判断需要构成答案的 $B$ 树上的节点距离 $t$ 的距离的奇偶性是否一致即可。

但是这样有一个问题:我们如何确保这个答案是整个一轮移动过程中最大的?

仔细考虑一下,我们判断成功的依据是行走过程中所有和不超过我们当前二分的值,那么转为判断这个问题(意思就是前面降智了)。

因为这是一棵树,所以该走的路径一定会走。

因为我们枚举了 $A$ 树中的节点,所以每次从哪两个节点走到 $s$、$t$ 是固定下来的。

草,直接 bfs 判断找可达最小值就行了。

$\Theta(n\log_{2}^{2}n)$,我觉得不行,先写。

草卡卡常过了。

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize(4)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#include <cstdio>
#include <queue>

using namespace std;

const int MAXN = 1e6 + 5;

namespace IO{
    const int sz=1<<22;
    char a[sz+5],b[sz+5],*p1=a,*p2=a,*t=b,p[105];
    inline char gc(){
        return p1==p2?(p2=(p1=a)+fread(a,1,sz,stdin),p1==p2?EOF:*p1++):*p1++;
    }
    template<class T> void gi(T& x){
        x=0; char c=gc();
        for(;c<'0'||c>'9';c=gc());
        for(;c>='0'&&c<='9';c=gc())
            x=x*10+(c-'0');
    }
    inline void flush(){fwrite(b,1,t-b,stdout),t=b; }
    inline void pc(char x){*t++=x; if(t-b==sz) flush(); }
    template<class T> void pi(T x,char c='\n'){
        if(x==0) pc('0'); int t=0;
        for(;x;x/=10) p[++t]=x%10+'0';
        for(;t;--t) pc(p[t]); pc(c);
    }
    struct F{~F(){flush();}}f; 
}
using IO::gi;
using IO::pi;

template<typename _T> _T MIN ( const _T x, const _T y ) { return x < y ? x : y; }

struct GraphSet {
    int to, nx;
    GraphSet ( int T = 0, int N = 0 ) { to = T, nx = N; }
} asA[MAXN * 2], asB[MAXN * 2];

int n, s, t, cntA, cntB, beginA[MAXN], beginB[MAXN], disA[MAXN], disB[MAXN], visA[MAXN], visB[MAXN];

void makeEdgeA ( const int u, const int v ) { asA[++ cntA] = GraphSet ( v, beginA[u] ), beginA[u] = cntA; }
void makeEdgeB ( const int u, const int v ) { asB[++ cntB] = GraphSet ( v, beginB[u] ), beginB[u] = cntB; }

void dfsA ( const int u, const int lst, const int dis ) {
    disA[u] = dis;
    for ( int i = beginA[u]; i; i = asA[i].nx ) {
        int v = asA[i].to;
        if ( v == lst )    continue;
        dfsA ( v, u, dis + 1 );
    }
}

void dfsB ( const int u, const int lst, const int dis ) {
    disB[u] = dis;
    for ( int i = beginB[u]; i; i = asB[i].nx ) {
        int v = asB[i].to;
        if ( v == lst )    continue;
        dfsB ( v, u, dis + 1 );
    }
}

void behaveOneSide ( int ark, int& mnA, int& mnB, int& ord, priority_queue<int, vector<int>, greater<int>>& align ) {
    int preSave = mnA;
    while ( ! align.empty () ) {
        int u = align.top ();
        if ( u + mnB > ark )    break;
        else    align.pop ();
        for ( int i = beginA[u]; i; i = asA[i].nx ) {
            int v = asA[i].to;
            if ( visA[v] )    continue;
            visA[v] = 1, align.push ( v );
            mnA = MIN ( mnA, v );
        }
    }
    if ( mnA == preSave )    ++ ord;
    else    ord = 0;
}

void behaveAnotherSide ( int ark, int& mnA, int& mnB, int& ord, priority_queue<int, vector<int>, greater<int>>& align ) {
    int preSave = mnB;
    while ( ! align.empty () ) {
        int u = align.top ();
        if ( u + mnA > ark )    break;
        else    align.pop ();
        for ( int i = beginB[u]; i; i = asB[i].nx ) {
            int v = asB[i].to;
            if ( visB[v] )    continue;
            visB[v] = 1, align.push ( v );
            mnB = MIN ( mnB, v );
        }
    }
    if ( mnB == preSave )    ++ ord;
    else    ord = 0;
}

priority_queue<int, vector<int>, greater<int>> oneQ, anotherQ;
bool check ( const int x ) {
    for ( int i = 1; i <= n; ++ i )    visA[i] = visB[i] = 0;
    for ( ; ! oneQ.empty (); oneQ.pop () ) ;
    for ( ; ! anotherQ.empty (); anotherQ.pop () ) ;
    oneQ.push ( s ), anotherQ.push ( t );
    visA[s] = 1, visB[t] = 1;
    int turn = 0, mnA = s, mnB = t, ord = 0;
    while ( mnA > 1 || mnB > 1 ) {
        turn ^= 1;
        if ( turn )    behaveOneSide ( x, mnA, mnB, ord, oneQ );
        else    behaveAnotherSide ( x, mnA, mnB, ord, anotherQ );
        if ( ord > 2 )    break;
    }
    if ( mnA == 1 && mnB == 1 )    return 1;
    else    return 0;
}

int solve ( int l, int r ) {
    while ( l + 1 < r ) {
        int mid = ( l + r ) >> 1;
        if ( check ( mid ) )    r = mid;
        else    l = mid;
    }
    return r;
}

int main () {
    int tCase;
    gi ( tCase );
    while ( tCase -- > 0 ) {
        gi ( n ), cntA = cntB = 0;
        for ( int i = 1; i <= n; ++ i )    beginA[i] = 0, beginB[i] = 0;
        for ( int i = 1, u, v; i < n; ++ i ) {
            gi ( u ), gi ( v );
            makeEdgeA ( u, v ), makeEdgeA ( v, u );
        }
        for ( int i = 1, u, v; i < n; ++ i ) {
            gi ( u ), gi ( v );
            makeEdgeB ( u, v ), makeEdgeB ( v, u );
        }
        gi ( s ), gi ( t );
        // dfsA ( s, 0, 0 ), dfsB ( t, 0, 0 );
        pi ( solve ( 1, n << 1 ) );
    }
    return 0;
}

Prob. 2

Desc. & Link.

$n$ 很小,$q$ 也很小。

感觉这个 $n$ 不是 $2^{n}$ 的算法也不是多项式算法欸。

但复杂度一定与 $n$ 有关……

草这玩意儿折半是不是可以折半搜索?

我们可以搜出两边我们可以凑出的价格,分别记为 $A_{i},i\in[1,C_{A}]$、$B_{i},i\in[1,C_{B}]$。

然后让 $A,B$ sorted。

然后枚举 $A_{i}$,找到 $B$ 中最大的能与 $A_{i}$ 相加小于等于需要的值,然后算下贡献即可(bullshit)。

不是为什么用快读本地过数据提交瓦爆啊。。。

#include<cstdio>
#include<algorithm>
using namespace std;
void read(long long &hhh)
{
    long long x=0;
    char c=getchar();
    while(((c<'0')|(c>'9'))&(c^'-'))    c=getchar();
    if(c^'-')    x=c^'0';
    char d=getchar();
    while((d>='0')&(d<='9'))
    {
        x=(x<<3)+(x<<1)+(d^'0');
        d=getchar();
    }
    if(c^'-')    hhh=x;
    else    hhh=-x;
}
void writing(long long x)
{
    if(!x)    return;
    if(x>9)    writing(x/10);
    putchar((x%10)^'0');
}
void write(long long x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    else if(!x)
    {
        putchar('0');
        putchar('\n');
        return;
    }
    writing(x);
    putchar('\n');
}
long long n,q,endone,beginano,onesiz,onebuc[2000005],anosiz,anobuc[2000005],opl,opr,cud[45];
void dfs(long long now,long long cur)
{
    if(now==endone+1)    onebuc[++onesiz]=cur;
    else
    {
        dfs(now+1,cur+cud[now]);
        dfs(now+1,cur);
    }
}
void exdfs(long long now,long long cur)
{
    if(now==n+1)    anobuc[++anosiz]=cur;
    else
    {
        exdfs(now+1,cur+cud[now]);
        exdfs(now+1,cur);
    }
}
long long solve(long long mos)
{
    long long now=anosiz;
    long long res=0;
    for(long long i=1;i<=onesiz;++i)
    {
        while(now&&onebuc[i]+anobuc[now]>mos)    now--;
        res+=now;
    }
    return res;
}
int main()
{
//    read(n);
//    read(q);
    scanf("%lld%lld",&n,&q);
//    for(long long i=1;i<=n;++i)    read(cud[i]);
    for(long long i=1;i<=n;++i)    scanf("%lld",&cud[i]);
    endone=(n>>1);
    beginano=endone+1;
    dfs(1,0);
    exdfs(beginano,0);
    sort(onebuc+1,onebuc+onesiz+1);
    sort(anobuc+1,anobuc+anosiz+1);
    while(q--)
    {
        scanf("%lld%lld",&opl,&opr);
//        read(opl);
//        read(opr);
//        write(solve(opr)-solve(opl-1));
        printf("%lld\n",solve(opr)-solve(opl-1));
    }
    return 0;
}

Prob. 4

Desc. & Link.

相当于在树上对于每一个点找出找出一条以其为链顶的链。

设 $f_{u}$ 为 $u$ 的答案。

$$ f_{u}=\max_{v\in\text{son}(u)}\{f_{v}+(a_{u}-\text{dis}(u,v))\times b_{v},0\} $$

有乘法,然后题目中一堆常数。

:-) 斜率优化

我们令 $s_{u}=\text{dis}(1,u)$,然后

$$ \begin{aligned} f_{u} &=\max_{v\in\text{son}(u)}\{f_{v}+(a_{u}+s_{u}-s_{v})\times b_{v},0\} \\ &=\max_{v\in\text{son}(u)}\{(a_{u}-s_{u})\times b_{v}+f_{v}-s_{v}\times b_{v},0\} \end{aligned} $$

令 $y=f_{u},x=a_{u}-s_{u},k=b_{v},b=f_{v}-s_{v}\times b_{v}$,那么这个东西就是一个 $y=kx+b$。

那么我们现在需要在子树里维护凸包,并且能够支持合并凸包和插入直线。