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

分类 笔记 下的文章

Description

Link.

给出一个堆,然后让你填数进去,使得其满足小根堆的性质,并使编号靠前的点的数最大。

Solution

考虑贪心,把原数列降序排序,然后因为这个东西是整除分块的形式,所以一个结点的子树一定对应的是原序列的一个子区间。不过这个东西并不能用根号分治来做。

然后对于一个子树的根 $u$,我们给他 $[l,r]$ 这个区间,$\text{subtree}(u)-\{u\}=a_{[l,r)}$,结点 $u=a_{r}$,$[l,r]$ 按需分配,反正就优先队列维护就行了。

代码大概长成这样子:

void Search(int x) {
  for(int y in SonOf(x)) Search(y);
  if(x != VirtualRoot) {
    ans[x] = PriorityQueue.top();
    PriorityQueue.pop();
  }
}

那个 VirtualRoot 是为了编码方便弄出来的一个虚根,不用管。同时发现这个做法只有在 $\forall i,j,s.t.d_{i}\neq d_{j}$ 的时候才是对的。Hack 数据网上找找应该有。

对于一个与 $u$ 同层的结点,可能会出现这个结点与 $u$ 的儿子交换点权后更优的情况。

对值域 $[1,n]$ 建出一棵线段树,同时定义 $pos_{i}$ 为 $\max\{j\mid a_{i}=a_{i+1}=\cdots=a_{j}\}$。然后每次查找能用的个数不小于该结点子树大小的位置,有多解跑到最右边,然后把这个位置的能用个数减去子树大小。描述的不清楚,建议做代码阅读理解领略一下精神。

#include<bits/stdc++.h>
struct node
{
    int mn,tag;
    node(int A=0,int B=0)
    {
        mn=A;
        tag=B;
    }
}nodes[2000010];
std::vector<std::vector<int> > e;
int n,siz[500010],ans[500010];
double k;
void dfs(int x)
{
    siz[x]=1;
    for(int i=0;i<int(e[x].size());++i)
    {
        int y=e[x][i];
        dfs(y);
        siz[x]+=siz[y];
    }
}
void build(int l,int r,int x)
{
    if(l^r)
    {
        int mid=(l+r)>>1;
        build(l,mid,x<<1);
        build(mid+1,r,x<<1|1);
        nodes[x].mn=std::min(nodes[x<<1].mn,nodes[x<<1|1].mn);
    }
    else    nodes[x].mn=l;
}
void push_down(int x)
{
    if(nodes[x].tag)
    {
        int &cur=nodes[x].tag;
        nodes[x<<1].mn+=cur;
        nodes[x<<1|1].mn+=cur;
        nodes[x<<1].tag+=cur;
        nodes[x<<1|1].tag+=cur;
        cur=0;
    }
}
void ins(int l,int r,int x,int fr,int ba,int delta)
{
    if(l>ba || r<fr)    return;
    if(l>=fr && r<=ba)
    {
        nodes[x].mn+=delta;
        nodes[x].tag+=delta;
    }
    else
    {
        int mid=(l+r)>>1;
        push_down(x);
        ins(l,mid,x<<1,fr,ba,delta);
        ins(mid+1,r,x<<1|1,fr,ba,delta);
        nodes[x].mn=std::min(nodes[x<<1].mn,nodes[x<<1|1].mn);
    }
}
int find(int l,int r,int x,int d)
{
    if(l^r)
    {
        int mid=(l+r)>>1;
        push_down(x);
        if(d<=nodes[x<<1|1].mn)    return find(l,mid,x<<1,d);
        else    return find(mid+1,r,x<<1|1,d);
    }
    else    return l+(nodes[x].mn<d);
}
int getDiv(int x,double k)
{
    return int(std::floor(double(x)/k));
}
int main()
{
    scanf("%d %lf",&n,&k);
    e.resize(n+1);
    std::vector<int> a(n+10),pos(n+10);
    for(int i=1;i<=n;++i)    scanf("%d",&a[i]);
    std::sort(a.begin()+1,a.begin()+n+1,std::greater<int>());
    for(int i=n-1;i;--i)
    {
        if(a[i]==a[i+1])    pos[i]=pos[i+1]+1;
    }
    for(int i=1;i<=n;++i)    e[getDiv(i,k)].emplace_back(i);
    dfs(0);
    build(1,n,1);
    for(int i=1;i<=n;++i)
    {
        if(getDiv(i,k)^getDiv(i-1,k))    ins(1,n,1,ans[getDiv(i,k)],n,siz[getDiv(i,k)]-1);
        int tmp=find(1,n,1,siz[i]);
        tmp+=pos[tmp];
        ++pos[tmp];
        tmp-=pos[tmp]-1;
        ans[i]=tmp;
        ins(1,n,1,tmp,n,-siz[i]);
    }
    for(int i=1;i<=n;++i)    printf("%d ",a[ans[i]]);
    return 0;
}

Description

Link.

给定三个数 $n,d,mod$,求有多少种 $n$ 个点的不同构的树满足:除了度数为 $1$ 的结点外,其余结点的度数均为 $d$。答案对质数 $mod$ 取模。

Solution

感觉这个题好神啊,看 Editorial 看了半天。

先考虑 rooted 情况。设 $f(i,j,k)$ 为有 $i$ 个结点,当前根有 $j$ 棵 subtree,最大的子树大小不超过 $k$ 的答案,字数内的结点的度数皆为 given $d$(除了当前根本身)。

转移即:

$$ f(i,j,k)=f(i,j,k-1)+\left(\sum_{l=1}^{l\le d,k\times l<i}f(i-k\times l,j-l,k-1)\times\binom{f(k,d-1,k-1)+l-1}{l}\right) $$

意义我就直接 copy CSDN@forever_shi 了:

 解释一下这个式子,就是你子树大小不超过 $k$ 的可以从都不超过 $k−1$ 的转移过来,然后我们可以之前子树都是不超过 $k−1$,现在开始是不超过 $k$ 的了,也就是在当前选了若干个大小是 $k$ 的子树,而这几个是一个可重组合,于是乘那个组合数。
#include<bits/stdc++.h>
typedef long long LL;
int n,d,MOD,far[1010],exfar[1010],f[1010][20][1010];
void exGCD(int one,int ano,int &x,int &y)
{
    if(ano==0)
    {
        x=1;
        y=0;
    }
    else
    {
        exGCD(ano,one%ano,y,x);
        y-=(one/ano)*x;
    }
}
int inv(int val)
{
    int res,w;
    exGCD(val,MOD,res,w);
    return (res%MOD+MOD)%MOD;
}
int C(int n,int k) 
{
    if(n<k)    return 0;
    else
    {
        int res=1;
        for(int i=1;i<=k;++i)    res=LL(res)*(n-i+1)%MOD;
        return LL(res)*exfar[k]%MOD;
    }
}
int main()
{
    scanf("%d %d %d",&n,&d,&MOD);
    if(n<=2)
    {
        printf("1\n");
        return 0;
    }
    far[0]=exfar[0]=1;
    for(int i=1;i<=d;++i)    far[i]=LL(far[i-1])*i%MOD;
    for(int i=1;i<=d;++i)    exfar[i]=inv(far[i]);
    for(int i=0;i<=n;++i)    f[1][0][i]=1;
    for(int i=2;i<=n;++i)
    {
        for(int j=1;j<i && j<=d;++j)
        {
            for(int k=1;k<=n;++k)
            {
                f[i][j][k]=f[i][j][k-1];
                for(int l=1;l*k<i && l<=j;++l)
                {
                    if(k>1)    f[i][j][k]=(f[i][j][k]+LL(f[i-k*l][j-l][k-1])*C(f[k][d-1][k-1]+l-1,l)%MOD)%MOD;
                    else    f[i][j][k]=(f[i][j][k]+LL(f[i-k*l][j-l][k-1])*C(f[k][0][k-1]+l-1,l)%MOD)%MOD;
                }
            }
        }
    }
    if(n&1)    printf("%d\n",f[n][d][n>>1]);
    else    printf("%d\n",(f[n][d][n>>1]-C(f[n>>1][d-1][n>>1],2)+MOD)%MOD);
    return 0;
}

Description

Link.

有一棵 $n$ 个节点的树,其中一个简单路径的集合被称为 $k$ 合法当且仅当:树的每个节点至多属于其中一条路径,且每条路径恰好包含 $k$ 个点。

对于 $k\in [1,n]$,求出 $k$ 合法路径集合的最多路径数。

即:设 $k$ 合法路径集合为 $S$,求最大的 $|S|$。

Solution

设 $f(i)$ 为 $k=i$ 时的答案,因为限定了每条路径的结点数,所以 $f(i)\le\lfloor\frac{n}{i}\rfloor$,差不多可以看出 $f(i)$ 是单调不增的。

然后仔细看这个形式,是不是长得很像数论分块?所以连续 $f(i)$ 相同的值的块长为至少 $\sqrt{n}$。

然后枚举左端点,二分找右端点,求解 $f(i)$ 应该是常见 trick。

听说直接二分常数很大,所以要写整体二分。我也不会卡常,所以就写整体二分叭。

#include<bits/stdc++.h>
int n,ans[100010],delta,f[100010];
int head[100010],nxt[200010],to[200010],cntot;
void addEdge(int one,int ano) {
    to[++cntot]=ano;
    nxt[cntot]=head[one];
    head[one]=cntot;
}
void dfs(int x,int las,int rule) {
    int fs=0,sc=0;
    for(int i=head[x];i;i=nxt[i]) {
        int y=to[i];
        if(y^las) {
            dfs(y,x,rule);
            if(f[y]>=fs) {
                sc=fs;
                fs=f[y];
            }
            else if(f[y]>=sc)    sc=f[y];
        }
    }
    if(fs+sc+1>=rule) {
        f[x]=0;
        ++delta;
    }
    else    f[x]=fs+1;
}
void rawGrass(int l,int r,int fr,int ba) {
    if(l>r || fr>ba)    return;
    if(l^r) {
        int mid=(fr+ba)>>1;
        delta=0;
        dfs(1,0,mid);
        ans[mid]=delta;
        rawGrass(delta,r,fr,mid-1);
        rawGrass(l,delta,mid+1,ba);
    }
    else {
        for(int i=fr;i<=ba;++i)    ans[i]=l;
    }
}
void read(int &hhh) {
    int x=0,f=1;
    char c=getchar();
    while(c<'0' || c>'9')    c=getchar();
    while(c>='0' && c<='9') {
        x=(x<<3)+(x<<1)+(c^'0');
        c=getchar();
    }
    hhh=x;
}
void write(int x,char las='\n') {
    static int stack[100],top=0;
    do {
        stack[++top]=x%10;
        x/=10;
    }while(x);
    while(top)    putchar(stack[top--]^'0');
    putchar(las);
}
int main() {
    read(n);
    for(int i=1,x,y;i<n;++i) {
        read(x);
        read(y);
        addEdge(x,y);
        addEdge(y,x);
    }
    rawGrass(0,n,1,n);
    for(int i=1;i<=n;++i)    write(ans[i]);
    return 0;
}

Description

Link.

小明在打比赛,包括小明自己一共有 $p$ 名选手参赛,每个人的得分是一个非负整数。最后的冠军是得分最高的人,如果得分最高的人有多个,就等概率从这些人中选一个当冠军。

现在小明已知了自己的得分大于等于 $r$,所有选手的得分和为 $s$。求小明获胜的概率,结果对 $998244353$ 取模。

Solution

抄了个 LJC00118 的非 DP 做法。

考虑直接统计总方案数和合法方案数。

总方案数即把 $s-r$ 个无标号小球放进 $p$ 个可为空的有标号小盒里,那么式子就是 $\dbinom{s-r+p-1}{p-1}$。

对于合法方案数,枚举有 $i$ 个人与自己同分为 $j$,则这部分的答案为 $\frac{\binom{n-1}{i-1}}{i}\times{\bf f}(n-i,s-ij,j)$。

${\bf f}(a,b,c)$ 为 $a$ 个人,总分 $b$,所有人严格小于 $c$ 的方案,容斥算。

#include<bits/stdc++.h>
typedef long long LL;
const int MOD=998244353;
void exGCD(int one,int ano,int &x,int &y) {
    if(ano==0) {
        x=1;
        y=0;
    }
    else {
        exGCD(ano,one%ano,y,x);
        y-=(one/ano)*x;
    }
}
int inv(int val) {
    int res,w;
    exGCD(val,MOD,res,w);
    return (res%MOD+MOD)%MOD;
}
int far[5110],exfar[5110];
int C(int n,int k) {
    if(n<k)    return 0;
    else    return LL(far[n])*exfar[k]%MOD*exfar[n-k]%MOD;
}
int s,r,n,ans;
int f(int a,int b,int c) { // a persons exist, sum of scores is b, everyone's score < c
    if(a==0) {
        if(b==0)    return 1;
        else    return 0;
    }
    int res=0,cur=1;
    for(int i=0;i<=a && i*c<=b;++i) {
        res=(res+LL(cur)*C(b-i*c+a-1,a-1)%MOD*C(a,i)%MOD+MOD)%MOD;
        cur=MOD-cur;
    }
    return res;
}
int main() {
    scanf("%d %d %d",&n,&s,&r);
    far[0]=1;
    for(int i=1;i<=s+n;++i)    far[i]=LL(far[i-1])*i%MOD;
    for(int i=0;i<=s+n;++i)    exfar[i]=inv(far[i]);
    for(int i=1;i<=n;++i) {
        for(int j=r;j<=s && i*j<=s;++j)    ans=(ans+LL(C(n-1,i-1))*f(n-i,s-i*j,j)%MOD*inv(i)%MOD)%MOD;
    }
    printf("%d\n",int(LL(ans)*inv(C(s-r+n-1,n-1))%MOD));
    return 0;
}

「ARC 116A」Odd vs Even

Link.

看 $n$ 有多少 $2$ 因子。

// Problem: A - Odd vs Even
// Contest: AtCoder - AtCoder Regular Contest 116
// URL: https://atcoder.jp/contests/arc116/tasks/arc116_a
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int make_two(LL n){int res=0; while((n&1ll)^1ll)    ++res,n>>=1; return res;}
int main()
{
    int T; scanf("%d",&T);
    while(T--)
    {
        LL n;
        scanf("%lld",&n);
        int one=make_two(n);
        if(one==1)    puts("Same");
        else if(one>1)    puts("Even");
        else    puts("Odd");
    }
    return 0;
}

「ARC 116B」Products of Min-Max

Link.

感觉这题很平庸很 B 题啊,不知道为什么那么多人说难……

首先排序,于是即算

$$ \left(\sum_{i=1}^{n}\sum_{j=i+1}^{n}a_{i}\times a_{j}\times2^{j-i-1}\right)+\left(\sum_{i=1}^{n}a_{i}^{2}\right) $$

也就是

$$ \sum_{i=1}^{n}a_{i}\sum_{j=i+1}^{n}a_{j}\times2^{j-i-1} \\ $$

$$ \sum_{j=i}^{n}a_{j}\times2^{j-i}=2\left(\sum_{j=i+1}^{n}a_{j}\times2^{j-i-1}\right)+a_{i} $$

于是直接 $\mathcal{O}(n)$ 算即可。智慧官方 editorial 指着这个式子说 $\mathcal{O}(n\log_{2}n)$ 不知道在干嘛。所以爆了个没什么用的标。

// Problem: B - Products of Min-Max
// Contest: AtCoder - AtCoder Regular Contest 116
// URL: https://atcoder.jp/contests/arc116/tasks/arc116_b
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD=998244353;
int n,a[200010],ans,sum;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)    scanf("%d",&a[i]);
    sort(a+1,a+n+1);
    for(int i=1;i<=n;++i)
    {
        ans=(ans+LL(sum)*a[i]%MOD+LL(a[i])*a[i]%MOD)%MOD;
        sum=((LL(sum)<<1)%MOD+a[i])%MOD;
    }
    printf("%d\n",ans);
    return 0;
}

「ARC 116C」Multiple Sequences

Link.

我们只考虑每个序列的末尾,即 $A_{n}=1,\cdots,m$ 的情况。我们先来想暴力。

对于每一个 $A_{n}$ 的取值,记为 $d$,对 $d$ 分解质因数,$d=\prod_{i=1}^{k}p_{i}^{c_{i}}$。

然后对于这个 $d$,其可以构成的序列数即 $\prod_{i=1}^{k}\binom{n+c_{i}-1}{c_{i}}$。

考虑非暴力,就把素数筛出来就行了。

// Problem: C - Multiple Sequences
// Contest: AtCoder - AtCoder Regular Contest 116
// URL: https://atcoder.jp/contests/arc116/tasks/arc116_c
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD=998244353;
vector<int> makePrime(int n)
{
    vector<int> prime,tag(n+1);
    tag[1]=1;
    for(int i=2;i<=n;++i)
    {
        if(!tag[i])    prime.push_back(i);
        for(int j=0;j<int(prime.size()) && i*prime[j]<=n;++j)
        {
            tag[i*prime[j]]=1;
            if(i%prime[j]==0)    break;
        }
    }
    return prime;
}
int n,m,ans;
vector<int> fac,ifac;
void exGCD(int one,int ano,int &x,int &y)
{
    if(ano==0)    x=1,y=0;
    else    exGCD(ano,one%ano,y,x),y-=(one/ano)*x;
}
int getInv(int val){int res,w; exGCD(val,MOD,res,w); return (res+MOD)%MOD;}
int C(int n,int k){return n<k?0:LL(fac[n])*ifac[k]%MOD*ifac[n-k]%MOD;}
int main()
{
    scanf("%d %d",&n,&m);
    vector<int> prime=makePrime(200100);
    fac.push_back(1);
    for(int i=1;i<=200100;++i)    fac.push_back(LL(fac.back())*i%MOD);
    for(int i=0;i<=200100;++i)    ifac.push_back(getInv(fac[i]));
    for(int i=1;i<=m;++i)
    {
        int curm=i,tmp=1;
        for(int j=0;j<int(prime.size()) && prime[j]<=curm;++j)
        {
            if(curm%prime[j]==0)
            {
                int ups=0;
                while(curm%prime[j]==0)    curm/=prime[j],++ups;
                tmp=LL(tmp)*C(n+ups-1,ups)%MOD;
            }
        }
        ans=(ans+tmp)%MOD;
    }
    printf("%d\n",ans);
    return 0;
}

「ARC 116D」I Wanna Win The Game

Link.

这题的 DP 感觉略有点难往这方面想。

考虑这题的限制最强的是 $\texttt{XOR}$ 和为 $0$ 以及和恰为 $m$。可以大概猜测此题与 $n$ 关系不大。

那么我们可以考虑从最低位开始做这个题。

设 $f_{i}$ 为构造出来序列的和为 $i$ 且 $\texttt{XOR}$ 和为 $0$ 的数量。

Oops, something went wrong.

「ARC 116E」Spread of Information

Link.

Oops, something went wrong.

「ARC 116F」Deque Game

Link.

Oops, something went wrong.