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

分类 笔记 下的文章

la traduction.

link。

如果我们对于每一个 $k\in[0,n]$ 找到所有满足存在 $k$ 个 $i$ 使得 $r_i=p_i$ 或 $r_i=q_i$ 条件的排列数量,我们就可以解决此题。

钦定 $i_1,\dots,i_k$ 使得对于每一个 $j$,满足 $r_{i_j}=p_{i_j}$。让我们来找到位置 $i_j$ 能填的数的数量。

对于每一个 $j$,我们把 $(p_{i_j},q_{i_j})$ 看作一条边。我们可以计算「每条边钦定一个点,并且没有两条边钦定同一个点」方案数。

考虑每一个非自环非独立点的连通块,显然每个点的度数 $\leqslant2$,那么每个连通块一定是:

  • 一个环,此时有两种方案;
  • 一条链,此时有链长度种方案。

所以方案数是他们的乘积。

现在,我们得去找到对于每一种 $i_1,\dots,i_k$ 的组合的上述的值。我们考虑一个把所有 $(p_i,q_i)$ 加入的图,并且确认对于每一个连通块,多少边被选择了。令 $K$ 为某个连通块的大小。如果我们知道对于每一个 $0\leqslant k\leqslant K$,在当前联通分量中选 $k$ 条边的时每一个 $r_i$ 的填数方案,那就可以简单 DP 解决。一下,我们假设图是一个环。

当所有的边都已经被选好,此时有两种方案。否则,此时图是一些链和孤立点的集合,方案数就是他们的大小乘积。这等价于从任意选择的边形成的每个连通分量中选择顶点并标记的方案总数,因此可以用组合数求出。在计数时,我们可以固定未使用边的最小索引,将循环视为计算中的路径。

link。

来一个不用 HLD / LCT 的做法。其实没有什么本质上的差别

首先容易想到离线,并且满足条件的图一定是边仙人掌,我们把离线后得到的图缩点,形成一片森林,并且标记树边。树边显然必选,主要来看非树边。

维护 $pre(x)$ 表示从根到 $x$ 的边权异或和,设当前我们考虑的非树边是 $(x,y)$,那么如果 $x,y$ 已经处于同一个环里或者 $pre(x)\oplus pre(y)\oplus w(x,y)\neq1$ 是不行的,否则就可以加进去。

然后怎么实现查找是否在同一个环里之类的就用 time-stamp & BIT 解决。

#include<bits/stdc++.h>
using namespace std;
const int N=300100,M=500100;
int n,m,ix[M],iy[M],iw[M],sjc,fa[20][N],indfn[N],outdfn[N],tag[M],dep[N],pre[N];
vector<pair<int,int>> G[N];
void dfs(int x,int las)
{
    fa[0][x]=las;
    indfn[x]=++sjc;
    dep[x]=dep[las]+1;
    for(int i=1; i<20; ++i) fa[i][x]=fa[i-1][fa[i-1][x]];
    for(auto [y,w]:G[x])    if(y!=las)    pre[y]=pre[x]^w,dfs(y,x);
    outdfn[x]=sjc;
}
int fwk[N];
void ins(int x,int y) { for(; x<=n; x+=x&-x)    fwk[x]+=y; }
int find(int x)
{
    int res=0;
    for(; x; x-=x&-x)  res+=fwk[x];
    return res;
}
int lh[N];
int dsuFind(int x)
{
    while(x!=lh[x])    x=lh[x]=lh[lh[x]];
    return x;
}
int LCA(int x,int y)
{
    if(dep[x]<dep[y])    swap(x,y);
    for(int i=19; ~i; --i)    if(dep[fa[i][x]]>=dep[y])    x=fa[i][x];
    if(x==y)    return x;
    for(int i=19; ~i; --i)    if(fa[i][x]!=fa[i][y])    x=fa[i][x],y=fa[i][y];
    return fa[0][x];
}
signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>n>>m;
    iota(lh+1,lh+n+1,1);
    for(int i=1; i<=m; ++i)
    {
        cin>>ix[i]>>iy[i]>>iw[i];
        int x=dsuFind(ix[i]),y=dsuFind(iy[i]);
        if(x!=y)
        {
            G[ix[i]].emplace_back(iy[i],iw[i]);
            G[iy[i]].emplace_back(ix[i],iw[i]);
            tag[i]=1,lh[x]=y;
        }
    }
    for(int i=1; i<=n; ++i)    if(!dep[i])    dfs(i,0);
    for(int i=1; i<=m; ++i)
    {
        if(tag[i])    cout<<"YES\n";
        else
        {
            int x=ix[i],y=iy[i];
            if(find(indfn[x])+find(indfn[y])-find(indfn[LCA(x,y)])*2>0||(pre[x]^pre[y]^iw[i])!=1)    cout<<"NO\n";
            else
            {
                cout<<"YES\n";
                while(x!=y)    (dep[x]<dep[y])&&(x^=y^=x^=y),ins(indfn[x],1),ins(outdfn[x]+1,-1),x=fa[0][x];
            }
        }
    }
    return 0;
}

Description

Link.

给出一棵树,初始边权为 $0$,支持毛毛虫虫体赋 $1$,虫足赋 $0$,以及查询路径边权和操作,$n,m\leqslant 10^5$。

Solution

立马想到按时间染色,那么判定一条边 $(u,v)$ 为重边的充要条件即 $col(u)=col(v)$(初始每个节点的颜色为 $1,\dots,n$)。

然后修改就转化为链覆盖,查询就成了查询区间相邻颜色相同二元组个数。HLD & 线段树可以解决。

说一下线段树的维护方法,维护区间左端点颜色、右端点颜色以及相邻颜色相同二元组个数(还有区间覆盖懒标)。向上 / 下更新均显。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++);
char buf[1<<21],*p1=buf,*p2=buf;
inline ll read() {
    ll x=0,f=0; char ch=getchar();
    while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+(ch&15),ch=getchar();
    return f?-x:x;
}
const int N=100100;
vector<int> G[N];
int n,m,dep[N],son[N],top[N],fa[N],sz[N],dfn[N],sjc,ton[N],col[N],cnt;
struct node { int lc,rc,num,tag; } tr[N*4],emp;
void push_up(int now) {
    tr[now].lc=tr[now<<1].lc;
    tr[now].rc=tr[now<<1|1].rc;
    tr[now].num=tr[now<<1].num+tr[now<<1|1].num+(tr[now<<1].rc==tr[now<<1|1].lc);
}
void push_down(int now,int l,int r) {
    if(!tr[now].tag) return;
    int mid=(l+r)>>1;
    tr[now<<1].num=mid-l,tr[now<<1|1].num=r-mid-1;
    tr[now<<1].lc=tr[now<<1|1].lc=tr[now].tag;
    tr[now<<1].rc=tr[now<<1|1].rc=tr[now].tag;
    tr[now<<1].tag=tr[now<<1|1].tag=tr[now].tag;
    tr[now].tag=0;
}
void build(int l,int r,int now) {
    tr[now]=emp;
    if(l==r) return ++cnt,tr[now]=node{cnt,cnt,0,0},void();
    int mid=(l+r)>>1;
    build(l,mid,now<<1),build(mid+1,r,now<<1|1);
    push_up(now);
}
void ins(int l,int r,int now,int x,int y,int v) {
    if(l>y||r<x) return;
    if(l>=x&&r<=y) return tr[now].tag=tr[now].lc=tr[now].rc=v,tr[now].num=r-l,void();
    int mid=(l+r)>>1;
    push_down(now,l,r);
    ins(l,mid,now<<1,x,y,v),ins(mid+1,r,now<<1|1,x,y,v);
    push_up(now);
}
int find0(int l,int r,int now,int x,int y) {
    if(l>=x&&r<=y) return tr[now].num;
    int mid=(l+r)>>1,res=0;
    push_down(now,l,r);
    if(mid>=x) res+=find0(l,mid,now<<1,x,y);
    if(mid<y) res+=find0(mid+1,r,now<<1|1,x,y);
    return res+(x<=mid&&y>mid&&tr[now<<1].rc==tr[now<<1|1].lc);
}
int find1(int l,int r,int now,int x) {
    if(l==r) return tr[now].lc;
    int mid=(l+r)>>1;
    push_down(now,l,r);
    return mid>=x?find1(l,mid,now<<1,x):find1(mid+1,r,now<<1|1,x);
}
void clear(int l,int r,int now) {
    tr[now]=emp;
    if(l==r) return;
    int mid=(l+r)>>1;
    clear(l,mid,now<<1),clear(mid+1,r,now<<1|1);
}
signed main() {
    function<void(int,int)> dfs0=[&](int x,int las) {
        fa[x]=las,dep[x]=dep[las]+1,sz[x]=1;
        for(int y:G[x]) if(y!=las) dfs0(y,x),sz[x]+=sz[y],(sz[y]>sz[son[x]])&&(son[x]=y);
    };
    function<void(int,int,int)> dfs1=[&](int x,int las,int t) {
        top[x]=t,ton[dfn[x]=++sjc]=x;
        if(son[x]) dfs1(son[x],x,t);
        for(int y:G[x]) if(y!=las&&y!=son[x]) dfs1(y,x,y);
    };
    for(int Case=read(); Case; --Case) {
        n=read(),m=read();
        for(int i=1,x,y; i<n; ++i) x=read(),y=read(),G[x].push_back(y),G[y].push_back(x);
        dfs0(1,0),dfs1(1,0,1),build(1,n,1);
        for(int t,x,y; m; --m) {
            t=read(),x=read(),y=read();
            if(t==1) {
                ++cnt;
                while(top[x]!=top[y]) {
                    if(dep[top[x]]<dep[top[y]]) swap(x,y);
                    ins(1,n,1,dfn[top[x]],dfn[x],cnt);
                    x=fa[top[x]];
                }
                if(dep[x]>dep[y]) swap(x,y);
                ins(1,n,1,dfn[x],dfn[y],cnt);
            } else {
                int res=0;
                while(top[x]!=top[y]) {
                    if(dep[top[x]]<dep[top[y]]) swap(x,y);
                    res+=find0(1,n,1,dfn[top[x]],dfn[x])+(find1(1,n,1,dfn[top[x]])==find1(1,n,1,dfn[fa[top[x]]]));
                    x=fa[top[x]];
                }
                if(dep[x]>dep[y]) swap(x,y);
                res+=find0(1,n,1,dfn[x],dfn[y]);
                printf("%d\n",res);
            }
        }
        for(int i=1; i<=n; ++i) dep[i]=son[i]=top[i]=fa[i]=sz[i]=dfn[i]=ton[i]=0,G[i].clear();
        sjc=cnt=0,clear(1,n,1);
    }
    return 0;
}

「ARC 124A」LR Constraints

Link.

我们可以把 $1\sim n$ 个盒子里能放的球的编号集合全部求出来。然后就直接来。

注意题目已经给出了 $k$ 个球的位置,所以「Note that for each integer $i$ from $1$ through $K$, there must be at least one card on which we write $i$.」这个限制不用管。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
#define len(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()
const int N=1100,MOD=998244353;
int n,k,ts[N],tek[N],fin[N],Rs[N];
set<int> rs[N];
signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>n>>k,memset(fin,-1,sizeof fin);
    for(int i=1; i<=k; ++i) {
        char c; cin>>c;
        ts[i]=(c=='R');
        cin>>tek[i];
        Rs[tek[i]]=ts[i];
    }
    for(int i=1; i<=k; ++i) {
        if(~fin[tek[i]]) return puts("0"),0;
        fin[tek[i]]=i;
    }
    for(int i=1; i<=n; ++i) {
        if(~fin[i]) rs[i].emplace(fin[tek[i]]);
        else {
            auto &s=rs[i];
            for(int j=1; j<=k; ++j) s.emplace(j);
            int tmp=0;
            for(int j=i+1; j<=n; ++j) {
                if(!Rs[j]) s.erase(fin[j]);
            }
            for(int j=1; j<i; ++j) {
                if(Rs[j]) s.erase(fin[j]);
            }
        }
    }
    int res=1;
    for(int i=1; i<=n; ++i) res*=len(rs[i]),res%=MOD;
    cout<<res<<"\n";
    return 0;
}

「ARC 124B」XOR Matching 2

Link.

预处理出 $s(i,j)=a_{i}\oplus b_{j}$,以及 $pos(i,x)$ 表示第 $i$ 层值 $x$ 的出现集合,用 std::map 均摊 $\mathcal{O}(n^{2})$ 空间。然后我们在第一层逐列考虑,对于第一层的每一种异或值,找出在每一行出现的位置集合,如果某一行没有出现就不行。最后就看集合大小是否等于 $n$。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()
#define int ll
const int N=2100;
int a[N],b[N],xr[N][N],n;
multimap<int,int> mp[N];
signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>n;
    for(int i=1; i<=n; ++i) cin>>a[i];
    for(int i=1; i<=n; ++i) cin>>b[i];
    for(int i=1; i<=n; ++i) for(int j=1; j<=n; ++j) xr[i][j]=(a[i] xor b[j]),mp[i].insert({xr[i][j],j});
    vector<int> res;
    for(int j=1; j<=n; ++j) {
        bool ok=0;
        set<int> S;
        for(int i=1; i<=n; ++i) {
            auto rg=mp[i].equal_range(xr[1][j]);
            if(mp[i].find(xr[1][j])!=mp[i].end()) {
                for(auto it=rg.first; it!=rg.second; ++it) {
                    S.emplace(it->second);
                }
            }
            else {
                ok=1;
                break;
            }
        }
        if(ok) continue;
        if(S.size()==n) {
            res.push_back(xr[1][j]);
        }
    }
    sort(all(res));
    res.erase(unique(all(res)),res.end()); 
    cout<<res.size()<<"\n";
    for(int x:res) cout<<x<<"\n";
    return 0;
}

「ARC 124C」LCM of GCDs

Link.

考场做法复杂度有问题啊,虽然屮过去了。有空来补 official editorial 做法。

// Oops, something went wrong.

「ARC 124D」Yet Another Sorting Problem

Link.

你看 ARC 又考置换群了。震撼围观吴老师精确押题。

套路题,连边 $i\rightarrow p_{i}$ 后一堆环摆出来。答案是

$$ n+m-(\text{the number of cycles in the graph})+\\ 2\times\max\{\text{the number of cycles of size of 2 or greater consisting of vertice numbered through }1\text{ to }n,\\ \text{the number of cycles of size of 2 or greater consisting of vertice numbered through }n+1\text{ to }n+m\} $$

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(),(x).end()
const int N=200100;
int n,m,p[N],vis[N];
signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>n>>m; int x0=0,x1=0,res=n+m,ls=0;
    for(int i=1; i<=n+m; ++i) cin>>p[i];
    for(int i=1; i<=n+m; ++i) {
        if(vis[i]) continue;
        int now=i,len=0,qwq=0,qaq=0;
        while(!vis[now]) {
            ++len;
            if(now<=n) qwq=1;
            else qaq=1;
            vis[now]=1;
            now=p[now];
        }
        if(!qaq&&len>=2) ++x0;
        if(!qwq&&len>=2) ++x1;
        --res;
    }
    cout<<res+2*max(x0,x1)<<"\n";
    return 0;
}

「ARC 124E」Pass to Next

Link.

「ARC 124F」Chance Meeting

Link.