做题记录 2026.09

2026.09

CF1615F LEGOndary Grandmaster

看这种对 01 串相邻相同位操作的东西,立刻想到奇偶翻转。于是一次操作相当于移动 1,两个状态可达当且仅当 1 的数量相同,操作次数即对应 1 距离之和。

设 \(f_{i,j},g_{i,j}\) 分别表示起始串当前考虑的最后一个 1 在 \(i\),终止串当前考虑的最后一个 1 在 \(j\) 的答案和方案数即可。

CF1515E Phoenix and Computers

注意到不会有相邻的电脑都是自动打开,所以以自动打开的电脑为分界点,把序列分段,段间独立(多重组合数处理),段内从后往前考虑,最后一个打开的电脑一定是最左侧或最右侧,所以段内方案数 \(2^{len-1}\),dp 即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef __int128_t int128;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
int MOD;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
ll mask;
void init(){
    mask=(((int128)1)<<64)/MOD;
}
int mod(ll x){
    return x-(((int128)x*mask)>>64)*MOD;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=mod((ll)mul*a);
        a=mod((ll)a*a);
        b>>=1;
    }
    return mul;
}
const int N=405;
int pw[N],fact[N],invfact[N];
int n,f[N][N];
void __INIT__(){}
void __SOLVE__(){
    scanf("%d%d",&n,&MOD);
    init();
    pw[0]=1,fact[0]=1;
    for(int i=1;i<=n;i++) pw[i]=add2(pw[i-1],pw[i-1]),fact[i]=mod((ll)i*fact[i-1]);
    invfact[n]=qpow(fact[n],MOD-2);
    for(int i=n;i>=1;i--) invfact[i-1]=mod((ll)i*invfact[i]);
    for(int i=1;i<=n;i++) f[i][i]=mod((ll)pw[i-1]*invfact[i]);
    for(int i=1;i<=n;i++){
        for(int k=1;k<=i-2;k++){
            for(int j=1;j<=k;j++) add(f[i][j+i-k-1],mod((ll)f[k][j]*mod((ll)invfact[i-k-1]*pw[i-k-2])));
        }
    }
    int ans=0;
    for(int j=1;j<=n;j++) add(ans,mod((ll)f[n][j]*fact[j]));
    printf("%d\n",ans);
}
int main(){
    int T=1;
    // scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

*bonus1: \(\mathcal{O}(n^2)\) dp

类似连续段 dp,按时间顺序从前往后考虑,每次扩展一个段/加入一个段。

具体地,设 \(f_{i,j}\) 表示已经手动打开了 \(i\) 个数(不考虑中间的自动打开),构成了 \(j\) 个连续段的方案数。

转移:

  • \((j+1)f_{i,j}\rightarrow f_{i+1,j+1}\)
  • \(2jf_{i,j}\rightarrow f_{i+1,j}\)

答案为 \(\sum f_{n-j+1,j}\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef __int128_t int128;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
int MOD;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
ll mask;
void init(){
    mask=(((int128)1)<<64)/MOD;
}
int mod(ll x){
    return x-(((int128)x*mask)>>64)*MOD;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=mod((ll)mul*a);
        a=mod((ll)a*a);
        b>>=1;
    }
    return mul;
}
const int N=405;
int pw[N],fact[N],invfact[N];
int n,f[N][N];
void __INIT__(){}
void __SOLVE__(){
    scanf("%d%d",&n,&MOD);
    init();
    f[0][0]=1;
    for(int i=0;i<=n;i++){
        for(int j=0;j<=i;j++){
            add(f[i+1][j+1],mod((ll)(j+1)*f[i][j]));
            add(f[i+1][j],mod(2ll*j*f[i][j]));
        }
    }
    int ans=0;
    for(int j=1;j<=n;j++) add(ans,f[n-j+1][j]);
    printf("%d\n",ans);
}
int main(){
    int T=1;
    // scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

*bonus2: 组合数学

参考

枚举段数 \(k\),把 \(n-k+1\) 次手动操作分配到 \(k\) 段中,各段内部方案数之积为 \(2^{n-2k+1}\),则

\[ans=\sum_{k=1}^{n}k!{n-k+1 \brace k}2^{n-2k+1} \]

笔者自研了一个多项式推导,不过比较麻烦而且不够巧妙,不提及。

*CF1442D Sum

又忘了不优性了(在保证答案不变的情况下,适当放宽约束)

设每行分别取了 \(c_i\) 个,发现最优解应满足 \(\forall i\ne j,\ a_{i,c_i+1}\le a_{j,c_j}\),否则可令 \(c_i\leftarrow c_i+1,c_j\leftarrow c_j-1\),取等不好看,所以加一个第二关键字变为 \((a_{i,c_i+1},i)<(a_{j,c_j},j)\)。

注意到 \((a_{i,c_i},i)\le(a_{i,c_i+1},i)<(a_{j,c_j},j)\le(a_{j,c_j+1},j)\) 与 \((a_{j,c_j+1},j)<(a_{i,c_i},i)\) 矛盾,故最多一个 \(i\) 满足 \(0<c_i<t_i\),记为 \(i_0\)。

此时笔者想更进一步,注意到被取完的行最后一个数 \(>a_{i,c_{i_0}+1}\),但这导致背包的元素特别乱。所以适当放宽,对 \(\{1,\dots,n\}\setminus\{i\}\) 跑背包,直接分治计算即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=3005;
int n,k,t[N];
vector<int> a[N];
pil e[N];
ll ans=-infll;
void solve(int l,int r,vector<ll> &f){
    if(l==r){
        ll sum=0;
        for(int i=0;i<=t[l]&&i<=k;i++){
            sum+=a[l][i];
            chkmax(ans,f[k-i]+sum);
        }
        return;
    }
    vector<ll> f0=f;
    int mid=(l+r)>>1;
    for(int i=mid+1;i<=r;i++) for(int j=k;j>=e[i].first;j--) chkmax(f[j],f[j-e[i].first]+e[i].second);
    solve(l,mid,f);
    f.swap(f0);
    for(int i=l;i<=mid;i++) for(int j=k;j>=e[i].first;j--) chkmax(f[j],f[j-e[i].first]+e[i].second);
    solve(mid+1,r,f);
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&t[i]);
        a[i].resize(t[i]+1);
        ll sum=0;
        for(int j=1;j<=t[i];j++) scanf("%d",&a[i][j]),sum+=a[i][j];
        e[i]={t[i],sum};
    }
    vector<ll> f(k+1,-infll);
    f[0]=0;
    solve(1,n,f);
    printf("%lld\n",ans);
    return 0;
}

CF1603C Extreme Extension

先考虑计算 \(f(1,n)\),从右往左贪心,显然每个数被拆成越大越好(因为数量少,且后续发展空间高),设上一个数留下了 \(x\) 的限制,则(手玩特殊情况可知)当前 \(a_i\) 应拆成共 \(\left\lceil\frac{a_i}x\right\rceil\) 个 \(\left\lfloor\frac{a_i}{\left\lceil\frac{a_i}x\right\rceil}\right\rfloor,\left\lceil\frac{a_i}{\left\lceil\frac{a_i}x\right\rceil}\right\rceil\)。

这个贡献不太好化简,于是不化简了,而注意到 \(x\) 是 \(a_{i+1}\) 的整除,所以只有 \(\mathcal{O}(\sqrt{a_{i+1}})\) 种,开个桶分别对每种 \(x\) 维护对应子区间右端点个数,复杂度 \(\mathcal{O}(n\sqrt{V})\)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int N=100005;
int n,a[N];
int cnt[N],ucnt[N];
void __INIT__(){}
void __SOLVE__(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int ans=0;
    for(int i=1;i<=a[n];i++) cnt[i]=0;
    add(cnt[a[n]],1);
    for(int i=n-1;i>=1;i--){
        for(int l=1,r;l<=a[i];l=r+1){
            r=a[i]/(a[i]/l);
            ucnt[a[i]/l]=0;
        }
        for(int l=1,r;l<=a[i+1];l=r+1){
            r=a[i+1]/(a[i+1]/l);
            int v=a[i+1]/l,c=cnt[v];
            int t=(a[i]+v-1)/v;
            add(ans,(ll)(t-1)*c*i%MOD);
            add(ucnt[a[i]/t],c);
        }
        add(ucnt[a[i]],1);
        for(int l=1,r;l<=a[i];l=r+1){
            r=a[i]/(a[i]/l);
            // printf("(%d,%d) ",a[i]/l,ucnt[a[i]/l]);
            cnt[a[i]/l]=ucnt[a[i]/l];
        }
        // printf("\n");
    }
    printf("%d\n",ans);
}
int main(){
    int T=1;
    scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

[Sqrt5 Round 7] 道路替换 加强版

idea & sol by: ChenYuXiang

题意

这个最短路问题是 luogu P3084 的差分约束形式。所以单看 \(s\) 左侧和右侧都是好做的,注意最短路是满足约束的最大解,而右侧为正,左侧为负,所以右侧是最大化问题,左侧是最小化问题。又因为有最短路形式,所以可使所有点同时达到最优,在保证最大/最小化前提下 尽量往左放即可。问题是跨 \(s\) 约束。

依旧考虑存在全局最优解,所以对于右侧,只需在保证左侧有解的情况下最大化即可,左侧同理。所以在正常跑从 \(l\) 到 \(r\) dp 的基础上,跨过 \(s\) 时,把左侧的值改为 \(\{-\infty,0\}\),求方案时,先把右侧方案求出,左侧在能转移到右侧方案的位置中选。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef unsigned long long ull;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=500005;
int n,m,f[N],op[N],lim[N],res[N];
pii all[N],pos[N];
vector<pii> seg;
deque<pii> q;
template<typename Cmp>
void pb(pii p,Cmp fn){
    while(q.size()&&fn(q.back().second,p.second)) q.pop_back();
    q.push_back(p);
}
void pf(int L){while(q.size()&&q.front().first<L) q.pop_front();}
void solve(int l,int r,int s){
    lim[r+1]=r+1;
    for(int i=r,p=seg.size()-1;i>=l;i--){
        lim[i]=i;
        if(i<r) chkmin(lim[i],lim[i+1]);
        for(;p>=0&&seg[p].second==i;p--) chkmin(lim[i],seg[p].first);
    }
    int L=l-1,R=l,p=0;// [L,R)
    f[l-1]=0;
    q.clear();
    q.push_back({l-1,0});
    for(int i=l;i<=r+1;i++){
        for(;R<lim[i];R++){
            if(i<=s) pb({R,f[R]},greater<int>());
            else if(R<=s){
                if(f[R]>=inf) pb({R,-inf},less<int>());
                else pb({R,0},less<int>());
            }
            else pb({R,f[R]},less<int>());
        }
        pos[i]={L,R};
        if(L>=R){
            if(i<=s) f[i]=inf;
            else f[i]=-inf;
        }
        else{
            pf(L);
            op[i]=q.front().first,f[i]=q.front().second+1;
        }
        if(i==s){
            q.clear();
            for(int i=L;i<R;i++){
                if(i<=s){
                    if(f[i]>=inf) pb({i,-inf},less<int>());
                    else pb({i,0},less<int>());
                }
                else pb({i,f[i]},less<int>());
            }
        }
        for(;p<seg.size()&&seg[p].second==i;p++) chkmax(L,seg[p].first);
    }
    if(f[r+1]<0){
        printf("-1\n");
        return;
    }
    for(int i=l;i<=r;i++) res[i]=0;
    p=r+1;
    while(op[p]>s) res[op[p]]=1,p=op[p];
    pii mn{inf,inf};
    for(int i=pos[p].first;i<pos[p].second&&i<=s;i++) chkmin(mn,{f[i],i});
    p=mn.second;
    while(p>=l) res[p]=1,p=op[p];
    for(int i=l;i<=r;i++) res[i]+=res[i-1];
    // for(int i=l;i<=r;i++) printf("%d ",res[i]);printf("\n");
    ull ans=0;
    for(int i=l;i<=r;i++) ans^=(ull)i*(res[i]-res[s]);
    printf("%llu\n",ans);
}
int main(){
    #ifndef JZQ
    freopen("road.in","r",stdin);
    freopen("road.out","w",stdout);
    #endif
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d%d",&all[i].first,&all[i].second),all[i].first++;
    sort(all+1,all+m+1,[&](pii x,pii y){return x.second<y.second;});
    int q;
    scanf("%d",&q);
    while(q--){
        int l,r,s;
        scanf("%d%d%d",&l,&r,&s);
        seg.clear();
        for(int i=1;i<=m;i++) if(l<all[i].first&&all[i].second<=r) seg.push_back(all[i]);
        solve(l,r,s);
    }
    return 0;
}

*CF1740F Conditional Mix

好难的观察性质。

把最终集合从大到小排序,一个集合为一列,第 \(i\) 个集合大小 \(s_i\)。不难想到加 \(0\) 补全为 \(n\) 列。

最基本的 case:第 \(i\) 个数放到前 \(cnt_i\) 列中。


从这个 case 开始调整。对于两列 \(i,j\),若 \(s_i>s_j\),则 \(i\) 中存在 \(j\) 中没有的元素,可将 \(s_i\leftarrow s_i-1,s_j\leftarrow s_j+1\)。故只要满足:

  1. \(\forall 1\le i\le n,\ \sum\limits_{j=1}^is_j\le\sum\limits_{v=1}^n\min(cnt_v,i)\);
  2. \(\forall 1\le i<n,\ s_i\ge s_{i+1}\)。

就可被调整得到。

而这也是合法的必要条件,若不满足 1. 则肯定存在某一列中有重复元素。

设 dp \(f_{i,j,k}\) 表示考虑了前 \(i\) 列,和为 \(j\),\(s_i=k\) 的方案数,转移:

\[f_{i,j,k}=\sum_{k'\ge k} f_{i-1,j-k,k'} \]

\[ans=\sum_{k=0}^nf_{n,n,k} \]

维护 \(k\) 的后缀和即可,代码中只维护了后缀和,未维护原数组。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int N=2005;
int n,a[N],cnt[N],sum[N],f[2][N][N];
void __INIT__(){}
void __SOLVE__(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),cnt[a[i]]++;
    for(int i=1;i<=n;i++) sum[1]++,sum[cnt[i]+1]--;
    for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
    for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
    for(int k=0;k<=n;k++) f[0][0][k]=1;
    for(int i=1;i<=n;i++){
        int t=i&1;
        for(int j=0;j<=n;j++) for(int k=0;k<=n/i;k++) f[t][j][k]=0;
        for(int j=0;j<=sum[i];j++){
            for(int k=0;k<=j/i;k++) f[t][j][k]=f[t^1][j-k][k];
            for(int k=j/i-1;k>=0;k--) add(f[t][j][k],f[t][j][k+1]);
        }
    }
    printf("%d\n",f[n&1][n][0]);
}
int main(){
    int T=1;
    // scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

QOJ18984 [NOI2026 D1T2] 传送

ER: 80 S: 80


通行方式形如:把原图划分为若干连通块,每个连通块为内向树,根处进行传送或结束,其他结点走 \(fa\)。

\(x\) 到 \(y\) 不经过传送是好做的,只考虑经过传送的情况,先考虑第一次传送后的期望步数 \(E_1\)。

设\(y\) 所在连通块大小 \(s\),每个点到对应根距离 \(w_i\),则

\[E_1=\frac{\sum w_i}n+\frac{n-s}{n}E_1 \]

\[E_1=\frac{\sum w_i}{s} \]

在 \(s\) 不变的情况下,应最小化 \(\sum w_i\),则 其他连通块都应为孤立点,易知这同时能使第一次传送前的步数最小化。

接下来记 \(y\) 所在连通块(后省略“\(y\) 所在”)内所有点到 \(y\) 距离和为 \(w\),则总期望步数

\[E=\frac{n-s+w}{s}+1=\frac{n+w}s \]

考虑连通块内结构,首先中间的层都应用满,其次假设最后一层没有用满,而用了 \(c\) 个点,到根距离 \(d\),令 \(s,w\) 不含该层,则

\[E=\frac{n+w+cd}{s+c}=d+\frac{n+w-sd}{s+c} \]

存在一个固定的调整方向,在 \(c\) 达到极值时 \(E\) 最小化。故 连通块内每层都应填满。

打表发现随着半径 \(r\) 的增加,\(E\) 呈现单谷(后面会证明),于是三分/二分斜率+邻域查询(点分树)可做到 \(\mathcal{O}(n\log^2n)\)(\(E\) 只与 \(y\) 有关,故只需求 \(n\) 次而非 \(m\) 次)

(以上是赛时的想法)


二分很难,尝试直接观察 \(E\) 的变化。

\[\begin{align*} \Delta E&=\frac{n+w+\Delta s\cdot(r+1)}{s+\Delta s}-\frac{n+w}{s} \\ &=(n+w)\left(\frac{1}{s+\Delta s}-\frac{1}s\right)+\frac{\Delta s\cdot(r+1)}{s+\Delta s} \\ &=-(n+w)\frac{\Delta s}{(s+\Delta s)s}+\frac{\Delta s\cdot(r+1)}{s+\Delta s} \\ &=\frac{\Delta s}{s+\Delta s}\left(r+1-\frac{n+w}{s}\right) \\ \end{align*} \]

由于前面的单谷结论(还没到证明的时候),最优的 \(r\) 为第一个满足 \(s(r+1)-w\ge n\) 的位置。

考虑类似换根 DP,增量维护。绕过第一个假做法,发现还是不好直接维护,于是再做一次差分,尝试找出一些性质:

\[\begin{align*} &((s+\Delta s)(r+2)-w-\Delta s\cdot (r+1))-(s(r+1)-w) \\ =&s+\Delta s \end{align*} \]

所以 \(s(r+1)-w\) 单增,所以 \([s(r+1)-w\ge n]\) 单增,所以 \([\Delta E\ge 0]\) 单增,单谷得证。另外,到这一步可以看出 \(\mathcal{O}(n\sqrt{n})\) 的做法,但笔者认为这不够快,所以进行了进一步思考。

然后笔者得到了一个长剖+线段树的假做法。仍考虑换根,注意到保持半径不变换根重叠率很高。若 \(r'=r+1\),带入差分可知 \(E'\ge E\ge n\);若 \(r'=r-2\),类似地可知 \(E'\le E_{pre}<n\)。故:

\[r-1\le r'\le r+1 \]

所以换根时,每次 \(r\) 变化 \(\mathcal{O}(1)\),直接判一下即可。

#include"teleport.h"
#include<bits/stdc++.h>
namespace SmpaelFx{
	using namespace std;
	typedef long long ll;
	typedef pair<int,int> pii;
	typedef pair<int,ll> pil;
	typedef pair<ll,int> pli;
	typedef pair<ll,ll> pll;
	template<typename T>
	void chkmin(T &x,const T &y){x=min(x,y);}
	template<typename T>
	void chkmax(T &x,const T &y){x=max(x,y);}
	const int inf=0x3f3f3f3f;
	const ll infll=0x3f3f3f3f3f3f3f3f;
	ll gcd(ll a,ll b){
		if(!b) return a;
		return gcd(b,a%b);
	}
	bool frac_less(pli x,pli y){return (__int128_t)x.first*y.second<(__int128_t)y.first*x.second;}
	const int N=500005;
	int n,m;
	vector<int> G[N];
	int fa[N][25],dep[N];
	void dfs(int u,int f){
		fa[u][0]=f,dep[u]=dep[f]+1;
		for(auto v:G[u]){
			if(v==f) continue;
			dfs(v,u);
		}
	}
	void lca_init(){
		dfs(0,0);
		for(int l=1;l<=20;l++) for(int i=1;i<=n;i++) fa[i][l]=fa[fa[i][l-1]][l-1];
	}
	int lca(int u,int v){
		if(dep[u]>dep[v]) swap(u,v);
		for(int i=20;i>=0;i--) if(dep[fa[v][i]]>=dep[u]) v=fa[v][i];
		if(u==v) return u;
		for(int i=20;i>=0;i--) if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
		return fa[u][0];
	}
	int dis(int u,int v){return dep[u]+dep[v]-2*dep[lca(u,v)];}
	pli E[N];
	int vis[N],sz[N],tot,id[N],fat[N],dept[N];
	vector<vector<pil>> value[N];
	vector<int> dist[N];
	pii findRt(int u,int f,int n){
		sz[u]=1;
		int mx=0;
		pii mn={inf,-1};
		for(auto v:G[u]){
			if(v==f||vis[v]) continue;
			chkmin(mn,findRt(v,u,n));
			sz[u]+=sz[v];
			chkmax(mx,sz[v]);
		}
		return min(mn,{max(mx,n-sz[u]),u});
	}
	void calcVal(int u,int f,int rt,int idx,int dep){
		sz[u]=1,dist[u].push_back(dep);
		while(value[rt][0].size()<=dep) value[rt][0].push_back({0,0});
		value[rt][0][dep].first++,value[rt][0][dep].second+=dep;
		if(idx){
			while(value[rt][idx].size()<=dep) value[rt][idx].push_back({0,0});
			value[rt][idx][dep].first++,value[rt][idx][dep].second+=dep;
		}
		for(auto v:G[u]){
			if(v==f||vis[v]) continue;
			if(!idx) value[rt].push_back(vector<pil>()),calcVal(v,u,rt,++tot,dep+1);
			else calcVal(v,u,rt,idx,dep+1);
			sz[u]+=sz[v];
		}
	}
	int build(int rt,int n,int d){
		rt=findRt(rt,rt,n).second;
		tot=0,value[rt].push_back(vector<pil>());
		calcVal(rt,rt,rt,0,0);
		vis[rt]=1,dept[rt]=d;
		for(auto &v:value[rt])
			for(int i=1;i<v.size();i++) v[i].first+=v[i-1].first,v[i].second+=v[i-1].second;
		// printf("build %d\n",rt);
		// for(auto &v:value[rt]){
		// 	for(auto [s,w]:v) printf("(%d,%d) ",s,w);
		// 	printf("\n");
		// }
		int tot=0;
		for(auto v:G[rt]){
			if(vis[v]) continue;
			int nv=build(v,sz[v],d+1);
			id[nv]=++tot,fat[nv]=rt;
		}
		return rt;
	}
	template<typename T>
	inline T visit(const vector<T> &vec,int i){return vec[min(i,(int)vec.size()-1)];}
	pil query(int u,int r){
		int u0=u;
		int s=0;ll w=0;
		for(int lst=u;u!=-1;lst=u,u=fat[u]){
			int d=dist[u0][dept[u]];
			if(r<d) continue;
			auto [ds,dw]=visit(value[u][0],r-d);
			s+=ds,w+=(ll)ds*d+dw;
			if(lst!=u){
				auto [nds,ndw]=visit(value[u][id[lst]],r-d);
				s-=nds,w-=(ll)nds*d+ndw;
			}
		}
		return {s,w};
	}
	ll calc(int u,int r){
		// printf("calc %d %d\n",u,r);
		auto [s,w]=query(u,r);
		// printf("%d %lld %lld\n",s,w,(ll)s*(r+1)-w);
		return (ll)s*(r+1)-w;
	}
	void calcE(int u,int f,int r){
		if(calc(u,r)<n) for(r++;calc(u,r)<n;r++);
		else for(;r>=0&&calc(u,r-1)>=n;r--);
		auto [s,w]=query(u,r);
		ll g=gcd(s,w+n);
		E[u]={(w+n)/g,s/g};
		for(auto v:G[u]){
			if(v==f) continue;
			calcE(v,u,r);
		}
	}
	vector<pli> teleport(vector<int> u,vector<int> v,vector<int> x,vector<int> y){
		for(int i=0;i<n-1;i++) G[u[i]].push_back(v[i]),G[v[i]].push_back(u[i]);
		lca_init();
		fat[build(0,n,0)]=-1;
		calcE(0,0,0);
		vector<pli> res;
		for(int i=0;i<m;i++){
			pli ans=min({dis(x[i],y[i]),1},E[y[i]],frac_less);
			// printf("(%d,%d) vs (%d,%d)\n",dis(x[i],y[i]),1,E[y[i]].first,E[y[i]].second);
			// printf("ans=(%d,%d)\n",ans.first,ans.second);
			res.push_back(ans);
		}
		return res;
	}
}
std::vector<std::pair<long long, int>> teleport(int c, int n, int m, std::vector<int> u, std::vector<int> v, std::vector<int> x, std::vector<int> y){
	SmpaelFx::n=n,SmpaelFx::m=m;
	return SmpaelFx::teleport(u,v,x,y);
}

QOJ18986 [NOI2026 D2T1] 中位数

ER: 30 S: 100


看到最大化中位数,立刻想到二分,于是尝试线性做 01 序列。

场上想在保证段数的前提下最大化 \(1\) 段数,于是寄了

只需要划分中有 \(\left\lceil\frac{k}2\right\rceil\) 个中位数为 \(1\) 的段。任取这么多个可能会超限,但注意到对于 \(k\) 奇,最多也只会超 \(1\) 段,只需要存在两个 \(1\) 段相邻(或与边界相邻)即可;对于 \(k\) 偶,只会超 \(2\) 段,只需要存在 \(2\) 组相邻即可。

大分讨,特判 \(k\le 5\)(因为上面的结构可能需要过多的 \(1\) 段)。

#include "median.h"
#include<bits/stdc++.h>
namespace SmpaelFx{
    using namespace std;
    typedef long long ll;
    typedef pair<int,int> pii;
    typedef pair<int,ll> pil;
    typedef pair<ll,int> pli;
    typedef pair<ll,ll> pll;
    template<typename T>
    void chkmin(T &x,const T &y){x=min(x,y);}
    template<typename T>
    void chkmax(T &x,const T &y){x=max(x,y);}
    const int inf=0x3f3f3f3f;
    const ll infll=0x3f3f3f3f3f3f3f3f;
    const int N=1000005;
    int n,k,lst[N<<1],pre[N],pre2[N],f[N][3],g[N][15],mx[15];
    int a[N],b[N],cnt[N];
    template<typename T>
    T DEBUG(T x){printf("%d\n",x);return x;}
    #define RETURN(x) {return (x);}
    bool check(int mid){
        for(int i=0;i<=(n<<1);i++) lst[i]=-1;
        int c=n;
        lst[c]=0,pre[0]=0;
        for(int i=0;i<=n;i++) for(int j=0;j<3;j++) f[i][j]=-inf;
        f[0][0]=0;
        for(int i=1;i<=n;i++){
            b[i]=(a[i]>=mid);
            if(b[i]) c++;
            else c--;
            cnt[i]=c-n;
            pre[i]=pre2[i]=-1;
            if(b[i]) pre[i]=i-1;
            if(lst[c]!=-1){
                if(pre[i]==-1) pre[i]=lst[c];
                else pre2[i]=lst[c];
            }
            lst[c]=i;
            for(int j=0;j<3;j++){
                f[i][j]=b[i]+f[i-1][j];
                if(i==n&&pre[i]>=0&&j) chkmax(f[i][j],f[pre[i]][j-1]+1);
            }
            if(pre[i]>=0&&pre[pre[i]]>=0){
                for(int j=1;j<3;j++) chkmax(f[i][j],f[pre[pre[i]]][j-1]+1+(!!pre[i]));
                if(i==n) chkmax(f[i][2],f[pre[pre[i]]][0]+1+(!!pre[i]));
            }
            if(pre2[i]>=0&&pre[pre2[i]]>=0){
                for(int j=1;j<3;j++) chkmax(f[i][j],f[pre[pre2[i]]][j-1]+1+(!!pre2[i]));
                if(i==n) chkmax(f[i][2],f[pre[pre2[i]]][0]+1+(!!pre2[i]));
            }
            if(pre[i]>0&&pre[pre[i]]>=0&&pre[pre[pre[i]]]>=0) chkmax(f[i][2],f[pre[pre[pre[i]]]][0]+2+(!!pre[pre[i]]));
            if(pre2[i]>0&&pre[pre2[i]]>=0&&pre[pre[pre2[i]]]>=0) chkmax(f[i][2],f[pre[pre[pre2[i]]]][0]+2+(!!pre[pre2[i]]));
            if(pre[i]>0&&pre2[pre[i]]>=0&&pre[pre2[pre[i]]]>=0) chkmax(f[i][2],f[pre[pre2[pre[i]]]][0]+2+(!!pre2[pre[i]]));
            if(pre2[i]>0&&pre2[pre2[i]]>=0&&pre[pre2[pre2[i]]]>=0) chkmax(f[i][2],f[pre[pre2[pre2[i]]]][0]+2+(!!pre2[pre2[i]]));
        }
        if(k==2||k==3||k==5){
            mx[0]=0;
            for(int j=1;j<=k;j++) mx[j]=-inf;
            for(int i=1;i<=n;i++){
                g[i][0]=-inf;
                for(int j=k;j>=1;j--){
                    g[i][j]=mx[j-1];
                    if(pre[i]>=0) chkmax(g[i][j],g[pre[i]][j-1]+1);
                    if(pre2[i]>=0) chkmax(g[i][j],g[pre2[i]][j-1]+1);
                    chkmax(mx[j],g[i][j]);
                }
            }
            return g[n][k]>=(k+1)/2;
        }
        if(k&1) RETURN(f[n][2]>=(k+1)/2);
        RETURN(f[n][1]>=(k+1)/2);
    }
    void init(int c,int t){}
    int median(){
        int l=1,r=n;
        while(l<r){
            int mid=(l+r+1)>>1;
            if(check(mid)) l=mid;
            else r=mid-1;
        }
        return l;
    }
}
void init(int c, int t){
    SmpaelFx::init(c,t);
}
int median(int n, int k, std::vector<int> a){
    SmpaelFx::n=n,SmpaelFx::k=k;
    std::copy(a.begin(),a.end(),SmpaelFx::a+1);
    return SmpaelFx::median();
}

QOJ18988. [NOI2026 D2T3] 彩虹树

ERS: 40


发现无论是枚举 \(S\) 计数 \(c\) 还是反过来都不好做,尝试同时计数 \(S,c\)。

于是想到设 \(f_{u,i,j}\) 表示 \(u\) 子树,\(c_u=i\),有 \(j\) 种颜色待匹配(有祖先颜色为他)。\(j\) 是用来贪心判定的,所以每个 \((S,c)\) 应该贡献到对应可能的最小的 \(j\) 处。

发现为了避免重复,不能逐个添加子树,而是要直接从子结点颜色数贡献到 \(u\)。

不妨先做二叉树,此时没有上面重复的问题。设两个儿子的状态分别是 \((i_1,j_1),(i_2,j_2)\),颜色交集大小 \(x\),则 \((i',j')=(i_1+i_2-x,j_1+j_2-\min\{j_1,j_2,x\})\)。发现这些点构成一条横线+一条斜线,用两个差分数组分别维护即可。

(以上是赛时做法,我声称后面没想到是时间不够了)


猜测对于多棵子树,也可以写成类似的形式,尝试合并 \((i',j')\) 与 \((i_3,j_3)\),可得一个两个变量的式子。打表发现对固定的 \(i''\),只取最小的 \(j''\),则满足 \(j''=\max\{j_1,j_2,j_3,i''-(i_1+i_2+i_3)+(j_1+j_2+j_3)\}\)。猜测 \(j'=\max\{\max j,i'-\sum i+\sum j\}\)。

???+ (可能不太严谨的)证明

对两棵子树的情况,写成 \(j'=\max\{j_1,j_2,i'-(i_1+i_2)+(j_1+j_2)\}\)。

三棵子树,有

\[\begin{align*} j''&=\max\{j',j_3,i''-(i'+i_3)+(j'+j_3)\} \\ &=\max\{j_1,j_2,j_3,i'-(i_1+i_2)+(j_1+j_2),i''-(i'+i_3)+(\max\{j_1,j_2,i'-(i_1+i_2)+j_1+j_2\})\} \end{align*} \]

因为不应该与子树添加顺序有关,去掉一些非对称式可得到猜测结果。

???-

于是设一个中间状态 \(g_{a,b,c,d}\) 分别记录 \(\max i,\sum i,\max j,\sum j\),最后用刚才的差分转移可以做到 \(\mathcal{O}(n^6)\)。

感觉记得东西太多了,而且差分不一定要同时做,写出差分的三个“关键点”的式子:

\[(a,c),(b-d+c,c),(b,d) \]

发现分别可以用较少的参数维护,于是分开转移,设:

  • \(g1_{a,c}\) 记录 \(\max i,\max j\)
  • \(g2_{b,c}\) 记录 \(\sum(i-j),\max j\)
  • \(g3_{b,d}\) 记录 \(\sum i,\sum j\)

复杂度 \(\mathcal{O}(n^4)\)。

#include"rainbow.h"
#include<bits/stdc++.h>
namespace SmpaelFx{
    using namespace std;
    typedef long long ll;
    typedef pair<int,int> pii;
    typedef pair<int,ll> pil;
    typedef pair<ll,int> pli;
    typedef pair<ll,ll> pll;
    template<typename T>
    void chkmin(T &x,const T &y){x=min(x,y);}
    template<typename T>
    void chkmax(T &x,const T &y){x=max(x,y);}
    const int inf=0x3f3f3f3f;
    const ll infll=0x3f3f3f3f3f3f3f3f;
    const int MOD=998244353;
    void add(int &x,int y){
        x+=y;
        if(x>=MOD) x-=MOD;
    }
    int add2(int x,int y){
        x+=y;
        if(x>=MOD) x-=MOD;
        return x;
    }
    int qpow(int a,ll b){
        int mul=1;
        while(b){
            if(b&1) mul=(ll)mul*a%MOD;
            a=(ll)a*a%MOD;
            b>>=1;
        }
        return mul;
    }
    const int N=205;
    int n,fa[N],sz[N];
    vector<int> G[N];
    int f[N][N][N],g[N][N],h[N][N],d1[N][N],d2[N][N];
    void dfs(int u){
        sz[u]=1;
        int mx=1;
        for(auto v:G[u]){
            dfs(v);
            chkmax(mx,sz[v]);
            sz[u]+=sz[v];
        }
        for(int i=0;i<=sz[u];i++) for(int j=0;j<=i;j++) d1[i][j]=d2[i][j]=0;
        // pt1: (max(mi,1),max(mj-1,0))
        sz[u]=1,mx=1;
        for(int a=0;a<=mx;a++) for(int c=0;c<=a;c++) g[a][c]=0;
        g[0][0]=1;
        for(auto v:G[u]){
            for(int a=0;a<=max(mx,sz[v]);a++) for(int c=0;c<=a;c++) h[a][c]=0;
            for(int a=0;a<=mx;a++) for(int i=0;i<=sz[v];i++) for(int c=0;c<=a;c++) for(int j=0;j<=i;j++)
                add(h[max(a,i)][max(c,j)],(ll)g[a][c]*f[v][i][j]%MOD);
            for(int a=0;a<=max(mx,sz[v]);a++) for(int c=0;c<=a;c++) g[a][c]=h[a][c];
            sz[u]+=sz[v],chkmax(mx,sz[v]);
        }
        for(int a=0;a<=mx;a++) for(int c=0;c<=a;c++) add(d1[max(a,1)][max(c-1,0)],g[a][c]);
        // pt2: ((si+1)-sj+max(mj-1,0),max(mj-1,0))
        sz[u]=1,mx=1;
        for(int b=0;b<=sz[u];b++) for(int c=0;c<=mx;c++) g[b][c]=0;
        g[0][0]=1;
        for(auto v:G[u]){
            for(int b=0;b<=sz[u]+sz[v];b++) for(int c=0;c<=max(mx,sz[v]);c++) h[b][c]=0;
            for(int b=0;b<=sz[u];b++) for(int i=0;i<=sz[v];i++) for(int c=0;c<=mx;c++) for(int j=0;j<=i;j++)
                add(h[b+i-j][max(c,j)],(ll)g[b][c]*f[v][i][j]%MOD);
            for(int b=0;b<=sz[u]+sz[v];b++) for(int c=0;c<=max(mx,sz[v]);c++) g[b][c]=h[b][c];
            sz[u]+=sz[v],chkmax(mx,sz[v]);
        }
        for(int b=0;b<=sz[u];b++) for(int c=0;c<=mx;c++){
            add(d1[b+1+max(c-1,0)][max(c-1,0)],MOD-g[b][c]);
            add(d2[b+1+max(c-1,0)][max(c-1,0)],g[b][c]);
        }
        // pt3: ((si+1)+1,sj+1)
        sz[u]=1,mx=1;
        for(int b=0;b<=sz[u];b++) for(int d=0;d<=b;d++) g[b][d]=0;
        g[0][0]=1;
        for(auto v:G[u]){
            for(int b=0;b<=sz[u]+sz[v];b++) for(int d=0;d<=b;d++) h[b][d]=0;
            for(int b=0;b<=sz[u];b++) for(int i=0;i<=sz[v];i++) for(int d=0;d<=b;d++) for(int j=0;j<=i;j++)
                add(h[b+i][d+j],(ll)g[b][d]*f[v][i][j]%MOD);
            for(int b=0;b<=sz[u]+sz[v];b++) for(int d=0;d<=b;d++) g[b][d]=h[b][d];
            sz[u]+=sz[v],chkmax(mx,sz[v]);
        }
        for(int b=0;b<=sz[u];b++) for(int d=0;d<=b;d++) add(d2[b+2][d+1],MOD-g[b][d]);
        // calc d,f
        for(int i=1;i<=sz[u];i++) for(int j=0;j<=i;j++) add(d1[i][j],d1[i-1][j]);
        for(int i=1;i<=sz[u];i++) for(int j=1;j<=i;j++) add(d2[i][j],d2[i-1][j-1]);
        for(int i=0;i<=sz[u];i++) for(int j=i;j>=0;j--) f[u][i][j]=add2(d1[i][j],d2[i][j]),add(f[u][i][j+1],f[u][i][j]);
    }
    int rainbow(const vector<int> &fat){
        for(int i=2;i<=n;i++){
            fa[i]=fat[i-1]+1;
            G[fa[i]].push_back(i);
        }
        dfs(1);
        int ans=0;
        for(int i=1;i<=n;i++) add(ans,f[1][i][0]);
        return ans;
    }
}
int rainbow(int c, int n, std::vector<int> f){
    SmpaelFx::n=n;
    return SmpaelFx::rainbow(f);
}

*bonus:

\(\max\) 卷积可以前缀和优化,瓶颈在 \(g3\)。

直接 ntt 可以做到 \(\mathcal{O}(n^3\log n)\)。

事实上,对于此类二维树上背包,可以参考这个文章做到 \(\mathcal{O}(n^3)\)。

QOJ18987 [NOI2026 D2T2] 木棉

ERS: 20


\(x,y\) 连边分三类:

  • \(x\) 连向 \(y\);
  • \(y\) 连向 \(x\);
  • \(x,y\) 是最后留下来的两个点。

发现只要对 \(x\)(和 \(y\))知道何时弹出即可。

以计算 \(y\) 为例。维护 \(pos_i\) 表示还原 \(prufer\) 序列过程中,做完第 \(i\) 个数后,\(y\) 在堆中有 \(pos_i\) 个比它小的。记 \(lst_i\) 为 \(i\) 在 \([l,r)\) 中最后一次出现,转移为:

\[pos_i=\max(pos_{i-1}-1,0)+[lst_{a_i}=i\land a_i<y] \]

弹出时刻即 \(lst_y\) 后第一个零点位置 \(+1\)。

首先有 \(lst\),不难想到扫描 \(r\);有 \(\max,+\),想到观察折线。发现折线为:从 \(\sum[lst_i<l]\) 开始,每次 \(-1\) 或不变,碰到 \(0\) 后,沿 \(0\) 走一段,再沿 \(1\) 走一段,以此交替。

记 \(d_i=1-[lst_{a_i}=i\land a_i<y]\)。

则零点有两类:

  • 第一个零点,为最小的 \(p\) 满足 \(\sum\limits_{i=l}^{p}d_i=\sum[lst_i<l]\) 可以通过求所有 \(d_i=1\) 位置的 kth 计算;
  • 其余零点,当且仅当 \(d_i=1\),有效的是 \(lst_y\) 后第一个。

发现 \(d_i\) 与 \(l\) 无关,于是扫 \(r\),则变为在二维范围内求 kth 和 cnt,拿 线段树套BIT + 线段树上二分 维护一下即可,复杂度 \(\mathcal{O}((n+q)\log^2n)\)。

*卡常

感谢 GeMini 3.1 Pro。

要点一:内层信息均为线性,所以可以在全局开一个内存池,而非每个节点开 vector。

要点二:内层需要离散化,查询时需要 lower_bound,可以类似分散层叠,对线段树区间 \([l,r]\),build 归并时维护每个数在 \([l,mid],[mid+1,r]\) 中的 lower_bound,这样查询只需要在根节点 lower_bound 一次。

#include "kapok.h"
#include<bits/stdc++.h>
namespace SmpaelFx{
    using namespace std;
    typedef long long ll;
    typedef pair<int,int> pii;
    typedef pair<int,ll> pil;
    typedef pair<ll,int> pli;
    typedef pair<ll,ll> pll;
    template<typename T>
    void chkmin(T &x,const T &y){x=min(x,y);}
    template<typename T>
    void chkmax(T &x,const T &y){x=max(x,y);}
    const int inf=0x3f3f3f3f;
    const ll infll=0x3f3f3f3f3f3f3f3f;
    const int N=200005;
    int n,a[N],lst[N];
    struct Query{
        int l,r,x,y,id;
    } qs[N];
    bool cmp(Query x,Query y){return x.r<y.r;}
    inline int lowbit(int x){return x&(-x);}
    int num_pool[N<<6],bit_pool[N<<6],to_l_pool[N<<6],to_r_pool[N<<6],cur=0;
    struct Node{
        int n;
        int *num,*bit,*to_l,*to_r;
        void add(int x,int v){for(x++;x<=n;x+=lowbit(x)) bit[x]+=v;}
        int query(int x){
            int s=0;
            for(;x;x-=lowbit(x)) s+=bit[x];
            return s;
        }
    } tr[N<<2];
    void build(int x,int l,int r){
        if(l==r){
            vector<int> num;
            if(l) num.push_back(a[l]);
            else for(int j=0;j<n+2;j++) num.push_back(j);
            tr[x].num=num_pool+cur,tr[x].bit=bit_pool+cur,tr[x].to_l=to_l_pool+cur,tr[x].to_r=to_r_pool+cur;
            tr[x].n=num.size(),cur+=num.size()+1;
            copy(num.begin(),num.end(),tr[x].num);
            return;
        }
        int mid=(l+r)>>1;
        build(x<<1,l,mid),build(x<<1|1,mid+1,r);
        tr[x].num=num_pool+cur,tr[x].bit=bit_pool+cur,tr[x].to_l=to_l_pool+cur,tr[x].to_r=to_r_pool+cur;
        int i=0,j=0,k=0;
        while(i<tr[x<<1].n&&j<tr[x<<1|1].n){
            tr[x].to_l[k]=i,tr[x].to_r[k]=j;
            if(tr[x<<1].num[i]==tr[x<<1|1].num[j]){
                tr[x].num[k++]=tr[x<<1].num[i];
                i++,j++;
            }
            else if(tr[x<<1].num[i]<tr[x<<1|1].num[j]) tr[x].num[k++]=tr[x<<1].num[i++];
            else tr[x].num[k++]=tr[x<<1|1].num[j++];
        }
        while(i<tr[x<<1].n) tr[x].to_l[k]=i,tr[x].to_r[k]=j,tr[x].num[k++]=tr[x<<1].num[i++];
        while(j<tr[x<<1|1].n) tr[x].to_l[k]=i,tr[x].to_r[k]=j,tr[x].num[k++]=tr[x<<1|1].num[j++];
        tr[x].to_l[k]=i,tr[x].to_r[k]=j;
        tr[x].n=k,cur+=tr[x].n+1;
    }
    void insert(int x,int l,int r,int p,int v){
        if(x==1) v=lower_bound(tr[x].num,tr[x].num+tr[x].n,v)-tr[x].num;
        tr[x].add(v,1);
        if(l==r) return;
        int mid=(l+r)>>1;
        if(p<=mid) insert(x<<1,l,mid,p,tr[x].to_l[v]);
        else insert(x<<1|1,mid+1,r,p,tr[x].to_r[v]);
    }
    void move(int x,int l,int r,int ql,int qr,int v){
        if(x==1) v=lower_bound(tr[x].num,tr[x].num+tr[x].n,v)-tr[x].num;
        if(ql<l&&qr>r) return;
        if(ql<l) tr[x].add(v,1);
        if(qr>r) tr[x].add(v,-1);
        if(l==r) return;
        int mid=(l+r)>>1;
        if(ql<=mid) move(x<<1,l,mid,ql,qr,tr[x].to_l[v]);
        if(qr>mid) move(x<<1|1,mid+1,r,ql,qr,tr[x].to_r[v]);
    }
    // [ql,qr]*[-inf,v)
    int query(int x,int l,int r,int ql,int qr,int v){
        if(x==1) v=lower_bound(tr[x].num,tr[x].num+tr[x].n,v)-tr[x].num;
        if(ql<=l&&r<=qr) return tr[x].query(v);
        int mid=(l+r)>>1,s=0;
        if(ql<=mid) s+=query(x<<1,l,mid,ql,qr,tr[x].to_l[v]);
        if(qr>mid) s+=query(x<<1|1,mid+1,r,ql,qr,tr[x].to_r[v]);
        return s;
    }
    int find(int x,int l,int r,int ql,int qr,int v,int &k){
        if(x==1) v=lower_bound(tr[x].num,tr[x].num+tr[x].n,v)-tr[x].num;
        if(ql<=l&&r<=qr){
            int s=(r-l+1)-tr[x].query(v);
            if(s<k){
                k-=s;
                return -1;
            }
        }
        if(l==r) return l;
        int mid=(l+r)>>1;
        if(ql<=mid){
            int ret=find(x<<1,l,mid,ql,qr,tr[x].to_l[v],k);
            if(ret!=-1) return ret;
        }
        return find(x<<1|1,mid+1,r,ql,qr,tr[x].to_r[v],k);
    }
    int calc(int l,int r,int v){
        if(v==r-l+2) return r+1;
        int d=query(1,0,n,0,l-1,v);
        if(!d&&lst[v]<l) return l-1;
        int k;
        int t1=find(1,0,n,max(lst[v],l),r,v,k=1);
        int t2=l-1;
        if(d) t2=find(1,0,n,l,r,v,d);
        return max(t1,t2);
    }
    vector<bool> kapok(int m){
        sort(qs,qs+m,cmp);
        vector<bool> ans(m);
        build(1,0,n);
        for(int i=0;i<n+2;i++) insert(1,0,n,0,i);
        int j=0;
        for(;j<m&&qs[j].r==0;j++) ans[qs[j].id]=1;
        for(int r=1;r<=n;r++){
            move(1,0,n,lst[a[r]],r,a[r]);
            lst[a[r]]=r;
            for(;j<m&&qs[j].r==r;j++){
                int l=qs[j].l,x=qs[j].x,y=qs[j].y;
                if(l>r){
                    ans[qs[j].id]=1;
                    continue;
                }
                int tx=calc(l,r,x),ty=calc(l,r,y);
                ans[qs[j].id]=((tx<r&&min(a[tx+1],r-l+2)==y)||(ty<r&&min(a[ty+1],r-l+2)==x)||(tx>=r&&ty>=r));
            }
        }
        for(int j=0;j<m;j++) if(qs[j].x==qs[j].y) ans[qs[j].id]=0;
        return ans;
    }
}
std::vector<bool> kapok(int c, int n, int m, std::vector<int> a,
                        std::vector<int> l, std::vector<int> r,
                        std::vector<int> x, std::vector<int> y) {
    SmpaelFx::n=n;
    std::copy(a.begin(),a.end(),SmpaelFx::a+1);
    for(int i=0;i<m;i++) SmpaelFx::qs[i]={l[i]+1,r[i],x[i],y[i],i};
    return SmpaelFx::kapok(m);
}

*优雅做法

注意到 \(d_{lst_y}=1\),所以第二类零点的查询结果一定是 \(lst_y\)。

第一类 \(\sum[lst_i<l]\) 可以预处理。

这样 \(\mathcal{O}(6q\log^2n)\) 直接变为 \(\mathcal{O}(2q\log^2n)\)。

*QOJ18985 [NOI26 D1T3] 布丁

E: 17 R: 16 S: ~30


类似 wordle,找一个比较优的决策树。

每次让新的最大等价类最小。随机+爬山,注意应不均匀随机,尽量取(可重)质因数个数更多的。

使用了 GeMini 3.1 Pro agent 进行调参。

关于卡常:预处理 gcd(如果你知道一个东西较不取模 gcd,应该也可以),用双指针代替 lower_bound。

卡常前:

#include"pudding.h"
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
int gcd(int a,int b){
    if(!b) return a;
    return gcd(b,a%b);
}
const int T=2000,T2=100,MaxFailcnt=20,lmin=5,lmax=5;
mt19937 rnd(19491001);
int query_tastiness(std::vector<int> a);
pii number[4505];
int myrand(){
    int x=rnd()%(4500*4500),rt=sqrt(x);
    while((rt+1)*(rt+1)<=x) rt++;
    return number[rt+1].second;
}
bool is_prime(int x){
    for(int i=2;i*i<=x;i++) if(x%i==0) return false;
    return true;
}
vector<int> num;
int cnt[4500*3000+5];
int calc(vector<int> a){
    sort(a.begin(),a.end());
    int s0=0;
    for(int i=0;i+1<a.size();i++) s0+=gcd(a[i],a[i+1]);
    vector<int> vec;
    for(auto x:num){
        int ns=s0;
        auto it=lower_bound(a.begin(),a.end(),x);
        if(it!=a.begin()&&it!=a.end()) ns-=gcd(*(it-1),*it);
        if(it!=a.begin()) ns+=gcd(*(it-1),x);
        if(it!=a.end()) ns+=gcd(x,*it);
        if(cnt[ns]) vec.push_back(ns);
        cnt[ns]++;
    }
    int mx=0;
    for(auto s:vec) chkmax(mx,cnt[s]),cnt[s]=0;
    return mx;
}
int tot=0;
vector<int> q[12005];
unordered_map<int,int> ch[12005];
int dfs(int d){
    // printf("d=%d\n",d);
    // for(auto i:num) printf("%d ",i);printf("\n");
    if(d==4){
        tot++;
        q[tot]=num;
        int s=0;
        for(int i=0;i+1<num.size();i++) s+=gcd(num[i],num[i+1]);
        for(int i=0;i<num.size();i++) ch[tot][s+num[i]]=-num[i];
        return tot;
    }
    int mn=inf;
    vector<int> best;
    for(int _=1;_<=T;_++){
        int len=rnd()%(lmax-lmin+1)+lmin;
        vector<int> a;
        for(int i=1;i<=len;i++) a.push_back(myrand());
        int cur=calc(a);
        if(cur<mn) mn=cur,best=a;
        int failcnt=0;
        for(int r=1;r<=T2&&failcnt<=MaxFailcnt;r++){
            int p=rnd()%a.size(),d;
            if(a[p]>1&&a[p]<4500) d=((rnd()&1)<<1)-1;
            else if(a[p]>1) d=-1;
            else d=1;
            a[p]+=d;
            int nw=calc(a);
            if(nw<mn) mn=nw,best=a;
            if(nw<cur) cur=nw,failcnt=0;
            else a[p]-=d,failcnt++;
        }
    }
    sort(best.begin(),best.end());
    int u=++tot;
    q[u]=best;
    int s0=0;
    for(int i=0;i+1<best.size();i++) s0+=gcd(best[i],best[i+1]);
    unordered_map<int,vector<int>> cls;
    for(auto x:num){
        int ns=s0;
        auto it=lower_bound(best.begin(),best.end(),x);
        if(it!=best.begin()&&it!=best.end()) ns-=gcd(*(it-1),*it);
        if(it!=best.begin()) ns+=gcd(*(it-1),x);
        if(it!=best.end()) ns+=gcd(x,*it);
        cls[ns].push_back(x);
    }
    for(auto &[k,v]:cls){
        if(v.size()==1){
            ch[u][k]=-v[0];
            continue;
        }
        num.swap(v);
        ch[u][k]=dfs(d+1);
        num.swap(v);
    }
    return u;
}
void init(int c, int t){
    for(int i=1;i<=4500;i++){
        for(int x=i,j=2;j*j<=x;j++){
            while(x%j==0){
                number[i].first++;
                x/=j;
            }
        }
        number[i].second=i;
    }
    sort(number+1,number+4500+1);
    if(c==1) for(int i=1;i<=35;i++) num.push_back(i);
    else for(int i=1;i<=3000;i++) if(c!=2||is_prime(i)) num.push_back(i);
    dfs(1);
}
int find_tastiness(int c, int m){
    int p=1;
    while(p>0){
        int s=query_tastiness(q[p]);
        p=ch[p][s];
    }
    return -p;
}

卡常后:

#include"pudding.h"
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
int gcd(int a,int b){
    if(!b) return a;
    return gcd(b,a%b);
}
const int T=2000,T2=100,MaxFailcnt=20,lmin=5,lmax=5;
mt19937 rnd(19491001);
int query_tastiness(std::vector<int> a);
int Gcd[4505][4505];
pii number[4505];
int myrand(){
    int x=rnd()%(4500*4500),rt=sqrt(x);
    while((rt+1)*(rt+1)<=x) rt++;
    return number[rt+1].second;
}
bool is_prime(int x){
    for(int i=2;i*i<=x;i++) if(x%i==0) return false;
    return true;
}
vector<int> num;
int cnt[4500*3000+5];
int calc(vector<int> a){
    sort(a.begin(),a.end());
    int s0=0;
    for(int i=0;i+1<a.size();i++) s0+=Gcd[a[i]][a[i+1]];
    vector<int> vec;
    int i=0;
    for(auto x:num){
        int ns=s0;
        while(i<a.size()&&a[i]<x) i++;
        if(i&&i<a.size()) ns-=Gcd[a[i-1]][a[i]];
        if(i) ns+=Gcd[a[i-1]][x];
        if(i<a.size()) ns+=Gcd[x][a[i]];
        if(cnt[ns]) vec.push_back(ns);
        cnt[ns]++;
    }
    int mx=0;
    for(auto s:vec) chkmax(mx,cnt[s]),cnt[s]=0;
    return mx;
}
int tot=0;
vector<int> q[12005];
unordered_map<int,int> ch[12005];
int dfs(int d){
    if(d==4){
        tot++;
        q[tot]=num;
        int s=0;
        for(int i=0;i+1<num.size();i++) s+=Gcd[num[i]][num[i+1]];
        for(int i=0;i<num.size();i++) ch[tot][s+num[i]]=-num[i];
        return tot;
    }
    int mn=inf;
    vector<int> best;
    for(int _=1;_<=T;_++){
        int len=rnd()%(lmax-lmin+1)+lmin;
        vector<int> a;
        for(int i=1;i<=len;i++) a.push_back(myrand());
        int cur=calc(a);
        if(cur<mn) mn=cur,best=a;
        int failcnt=0;
        for(int r=1;r<=T2&&failcnt<=MaxFailcnt;r++){
            int p=rnd()%a.size(),d;
            if(a[p]>1&&a[p]<4500) d=((rnd()&1)<<1)-1;
            else if(a[p]>1) d=-1;
            else d=1;
            a[p]+=d;
            int nw=calc(a);
            if(nw<mn) mn=nw,best=a;
            if(nw<cur) cur=nw,failcnt=0;
            else a[p]-=d,failcnt++;
        }
    }
    sort(best.begin(),best.end());
    int u=++tot;
    q[u]=best;
    int s0=0;
    for(int i=0;i+1<best.size();i++) s0+=Gcd[best[i]][best[i+1]];
    unordered_map<int,vector<int>> cls;
    int i=0;
    for(auto x:num){
        int ns=s0;
        while(i<best.size()&&best[i]<x) i++;
        if(i&&i<best.size()) ns-=Gcd[best[i-1]][best[i]];
        if(i) ns+=Gcd[best[i-1]][x];
        if(i<best.size()) ns+=Gcd[x][best[i]];
        cls[ns].push_back(x);
    }
    for(auto &[k,v]:cls){
        if(v.size()==1){
            ch[u][k]=-v[0];
            continue;
        }
        num.swap(v);
        ch[u][k]=dfs(d+1);
        num.swap(v);
    }
    return u;
}
void init(int c, int t){
    for(int i=1;i<=4500;i++) for(int j=1;j<=4500;j++) Gcd[i][j]=gcd(i,j);
    for(int i=1;i<=4500;i++){
        for(int x=i,j=2;j*j<=x;j++){
            while(x%j==0){
                number[i].first++;
                x/=j;
            }
        }
        number[i].second=i;
    }
    sort(number+1,number+4500+1);
    if(c==1) for(int i=1;i<=35;i++) num.push_back(i);
    else for(int i=1;i<=3000;i++) if(c!=2||is_prime(i)) num.push_back(i);
    dfs(1);
}
int find_tastiness(int c, int m){
    int p=1;
    while(p>0){
        int s=query_tastiness(q[p]);
        p=ch[p][s];
    }
    return -p;
}

极限卡常(QOJ 2s,by Claude Opus 4.8):

#include"pudding.h"
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
template<typename T> void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;

const int T=2000,T2=100,MaxFailcnt=20,lmin=5,lmax=5;
mt19937 rnd(19491001);
int query_tastiness(std::vector<int> a);

const int MAXV=4501;
static short G[MAXV][MAXV];          // gcd 表

pii number[4505];
int myrand(){
    int x=rnd()%(4500*4500),rt=(int)sqrt((double)x);
    while((rt+1)*(rt+1)<=x) rt++;
    return number[rt+1].second;
}
bool is_prime(int x){
    for(int i=2;i*i<=x;i++) if(x%i==0) return false;
    return true;
}
vector<int> num;

const int CNTSZ=40005;               // ns 最大约 27000
int cnt[CNTSZ];
int vecbuf[3005];

int calc(const vector<int>& av){
    int len=(int)av.size();
    int a[8];
    for(int i=0;i<len;i++) a[i]=av[i];
    for(int i=1;i<len;i++){           // 5 个元素插入排序
        int k=a[i],j=i-1;
        while(j>=0&&a[j]>k){a[j+1]=a[j];j--;}
        a[j+1]=k;
    }
    int s0=0;
    for(int i=0;i+1<len;i++) s0+=G[a[i]][a[i+1]];
    const int* np=num.data();
    int nn=(int)num.size();
    int vlen=0,j=0,last=a[len-1];
    for(int t=0;t<nn;t++){            // num 升序,a 升序 -> 双指针
        int x=np[t];
        while(j<len && a[j]<x) j++;
        int ns;
        if(j==0)        ns=s0+G[a[0]][x];
        else if(j==len) ns=s0+G[last][x];
        else            ns=s0 - G[a[j-1]][a[j]] + G[a[j-1]][x] + G[x][a[j]];
        if(cnt[ns]) vecbuf[vlen++]=ns;
        cnt[ns]++;
    }
    int mx=0;
    for(int i=0;i<vlen;i++){int s=vecbuf[i]; if(cnt[s]>mx)mx=cnt[s]; cnt[s]=0;}
    return mx;
}

int tot=0;
vector<int> q[12005];
unordered_map<int,int> ch[12005];

int dfs(int d){
    if(d==4){
        tot++;
        q[tot]=num;
        int s=0;
        for(int i=0;i+1<(int)num.size();i++) s+=G[num[i]][num[i+1]];
        for(int i=0;i<(int)num.size();i++) ch[tot][s+num[i]]=-num[i];
        return tot;
    }
    int mn=inf;
    vector<int> best;
    vector<int> a; a.reserve(lmax);
    for(int _=1;_<=T;_++){
        int len=rnd()%(lmax-lmin+1)+lmin;
        a.clear();
        for(int i=1;i<=len;i++) a.push_back(myrand());
        int cur=calc(a);
        if(cur<mn){mn=cur;best=a;}
        int failcnt=0;
        for(int r=1;r<=T2&&failcnt<=MaxFailcnt;r++){
            int p=rnd()%a.size(),dd;
            if(a[p]>1&&a[p]<4500) dd=((rnd()&1)<<1)-1;
            else if(a[p]>1) dd=-1;
            else dd=1;
            a[p]+=dd;
            int nw=calc(a);
            if(nw<mn){mn=nw;best=a;}
            if(nw<cur){cur=nw;failcnt=0;}
            else {a[p]-=dd;failcnt++;}
        }
    }
    sort(best.begin(),best.end());
    int u=++tot;
    q[u]=best;
    int s0=0;
    for(int i=0;i+1<(int)best.size();i++) s0+=G[best[i]][best[i+1]];
    unordered_map<int,vector<int>> cls;
    {                                // 分类同样用双指针 + gcd 表
        int len=(int)best.size(),j=0,last=best[len-1];
        const int* np=num.data(); int nn=(int)num.size();
        for(int t=0;t<nn;t++){
            int x=np[t];
            while(j<len && best[j]<x) j++;
            int ns;
            if(j==0)        ns=s0+G[best[0]][x];
            else if(j==len) ns=s0+G[last][x];
            else            ns=s0 - G[best[j-1]][best[j]] + G[best[j-1]][x] + G[x][best[j]];
            cls[ns].push_back(x);
        }
    }
    for(auto &pr:cls){
        auto &v=pr.second;
        if(v.size()==1){ ch[u][pr.first]=-v[0]; continue; }
        num.swap(v);
        ch[u][pr.first]=dfs(d+1);
        num.swap(v);
    }
    return u;
}

void init(int c,int t){
    // O(V^2) 递推建 gcd 表:gcd(i,j)=gcd(j,i%j)
    for(int i=0;i<MAXV;i++){ G[i][0]=(short)i; G[0][i]=(short)i; }
    for(int i=1;i<MAXV;i++){
        G[i][i]=(short)i;
        for(int j=1;j<i;j++){
            short g=G[j][i%j];       // 行 j 已完全算好,i%j<j
            G[i][j]=g; G[j][i]=g;
        }
    }
    for(int i=1;i<=4500;i++){
        for(int x=i,j=2;j*j<=x;j++) while(x%j==0){number[i].first++;x/=j;}
        number[i].second=i;
    }
    sort(number+1,number+4500+1);
    if(c==1) for(int i=1;i<=35;i++) num.push_back(i);
    else for(int i=1;i<=3000;i++) if(c!=2||is_prime(i)) num.push_back(i);
    dfs(1);
}

int find_tastiness(int c,int m){
    int p=1;
    while(p>0){
        int s=query_tastiness(q[p]);
        p=ch[p][s];
    }
    return -p;
}

NOI2026 总结

感觉都是一些思维链比较长、短时间内可能做不完的题,但是如果时间充足的话感觉不难。

*QOJ17641 [APIO2026 中国区 T2] 蛋糕

sub1~sub3:

先做 sub2,考虑要将 \(\{-1,0,1\}\) 分别对应到 \(\{0,1,2\}\),于是构造集合 \(\{1,1\}\) 即可完成。

这启发我们通过其他数的和来缩小 \(x\) 的范围。于是构造 \(\{1,1,2,4,\dots,2^k\}\)。可以通过比较 \(\sum\limits_{i=0}^pa_i\) 与 \(a_{p+1}\) 确定 \(x\) 的位置,然后从 \(p\) 往下逐位判断。因为要做到 \(\log W\) 次,所以找 \(p\) 需要从大往小找。需要精细实现。

*sub4:

被 sub3 启发,发现 \(\log_3W\approx 6.9\),考虑三进制,所以要把 \(\{-1,0,1\}\) 对应到三进制一位的 \(\{0,1,2\}\)(从信息论的角度亦可看出),然后不会构造了。

(*)查询不应该带 \(x\),因为如果返回 \(0\) 就“几乎”把 \(x\) 确定了,从信息论的角度看显然不现实。发现 \(3^7<W+200\),考虑构造集合 \(\{1,2,3,\dots,3^7\}\),这样对于下标(从 \(1\) 开始)为 \(i\) 的数,若 \(i\le x\),\(a_i=i\);否则 \(a_i=i-1\)。比较 \(a[3^6]+a[2\times 3^6]\) 与 \(a[3\times 3^6]\) 发现可以确定出来三进制第 \(6\) 位。对于更低的位,由于已经有了一些数 \(r\),比较 \(a[r+3^i]+a[r+2\times 3^i]\) 与 \(a[r]+a[r+3\times 3^i]\) 即可(因为 \(a[r]=r\) 恒成立)。

#include"cake.h"
#include<bits/stdc++.h>
using namespace std;
const int pw[8]={1,3,9,27,81,243,729,2187};
vector<int> bake_cakes(int N, int W, int K){
    if(W==3) return {1,1};
    if(W==2000){
        vector<int> c;
        for(int i=1;i<=pw[7];i++) c.push_back(i);
        return c;
    }
    vector<int> c{1};
    for(int i=1;i<=W;i<<=1) c.push_back(i);
    return c;
}
int find_tastiness(int m, int W, int K){
    if(W==3){
        int t=compare_tastiness({0,1},{2});
        if(t==-1) return 3;
        if(t==0) return 2;
        return 1;
    }
    if(W==2000){
        int ans=0;
        for(int i=6;i>=0;i--){
            vector<int> v1{ans+pw[i]-1,ans+2*pw[i]-1};
            vector<int> v2{ans+3*pw[i]-1};
            if(ans) v2.push_back(ans-1);
            int t=compare_tastiness(v1,v2);
            if(t==0) ans+=pw[i];
            else if(t==1) ans+=2*pw[i];
        }
        return ans;
    }
    int p;
    for(p=m-1;p>0;p--){
        vector<int> v;
        for(int i=0;i<p;i++) v.push_back(i);
        if(compare_tastiness(v,{p})==0) break;
    }
    vector<int> v{p};
    for(int i=p-1;i>0;i--){
        v.push_back(i);
        int tmp=compare_tastiness(v,{p+1});
        if(tmp>0) v.pop_back();
        if(tmp==0) break;
    }
    int ans=0;
    for(auto i:v) ans+=(1<<(i-1));
    return ans;
}

*QOJ189 I 君的商店

我是真的不会交互题啊


特殊性质:如果 \(1\) 在左侧,reverse 过来。考虑二分,显然是要跟 \(1\) 比,但是每次拿 \(1\) 个数比做不了,考虑拿相邻两个数,则两种返回值分别对应:

  • \([0,0]\) 或 \([0,1]\)
  • \([1,1]\) 或 \([0,1]\)

如此二分,最后剩一个未知数用奇偶性确定。


(*)核心点:每次钦定一个/一些数被确定显然做不到(否则奇偶性在特殊性质里不是必要条件),考虑确定两个数中的一个(但确定下来哪个不是提前知道的)。

考虑对三个数 \(a,b,c\),若 \(a\le b\):

  • 若查询得到 \(a+b\le c\),则 \(a=0\);
  • 若得到 \(a+b\ge c\),则 \(b\ge c\)。特别地,若 \(c=1\),则 \(b=1\)。

\(7n\) 做法:\(1\) 是最大值。直接花 \(2n\) 打擂台找出一个 \(1\),再用 \(5n\) 确定下来其他数,最后一个用奇偶性确定。

\(5n+\log\) 做法:不能找 \(1\),尝试利用特殊性质的做法,发现 case2 得到了 \(b\ge c\) 的性质,尝试根据此构造 非降子集,并在其上做特殊性质。具体地,令子序列最后一个元素为 \(c\),取还未被确定的两个数 \(a,b\)(\(a\le b\)):

  • \(a+b\le c\),则确定 \(a=0\);
  • \(a+b\ge c\),则子序列 push_back \(b\)。

最后会剩下一个未知数,与子序列末尾比较并交换,使最大值在子序列上。子序列跑完也会剩下一个未知数,将两个未知数比较一下,再求和与 \(1\) 比较一下,就可以确定一个,另一个用奇偶性确定。

#include"shop.h"
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int ans[N],s1[N],s2[N];
int get(vector<int> &v){
    int x=v.back();
    v.pop_back();
    return x;
}
int solve(vector<int> &vec,bool asc){
    if(!asc){
        s1[0]=vec[0],s2[0]=vec.back();
        if(!query(s1,1,s2,1)) reverse(vec.begin(),vec.end());
    }
    if(vec.size()==1) return vec[0];
    ans[vec.back()]=1;
    int l=0,r=vec.size()-2;
    while(l<r){
        int mid=(l+r)>>1;
        s1[0]=vec[mid],s1[1]=vec[mid+1],s2[0]=vec.back();
        int tmp=query(s1,2,s2,1);
        if(!tmp) r=mid;
        else l=mid+1;
    }
    for(int i=0;i<l;i++) ans[vec[i]]=0;
    int cnt=0;
    for(int i=l+1;i<vec.size();i++) ans[vec[i]]=1,cnt++;
    return vec[l];
}
void find_price(int task_id, int n, int k, int res[]){
    if(n==1){
        res[0]=1;
        return;
    }
    vector<int> vec(n);
    iota(vec.begin(),vec.end(),0);
    if(n==2){
        int a=0,b=1;
        s1[0]=a,s2[0]=b;
        if(!query(s1,1,s2,1)) swap(a,b);
        if(k) res[a]=0,res[b]=1;
        else res[a]=res[b]=1;
        return;
    }
    int p;
    if(task_id==3) p=solve(vec,false);
    else{
        int a=get(vec),b=get(vec),c=get(vec),type;
        vector<int> vec2{c};
        while(true){
            s1[0]=a,s2[0]=b;
            if(!query(s1,1,s2,1)) swap(a,b);
            s1[0]=a,s1[1]=b,s2[0]=c;
            if(query(s1,2,s2,1)){
                type=1;ans[a]=0;
                if(vec.size()) a=get(vec);
                else break;
            }
            else{
                type=2;vec2.push_back(b),c=b;
                if(vec.size()) b=get(vec);
                else break;
            }
        }
        int q;
        if(type==1) q=b;
        else q=a;
        s1[0]=c,s2[0]=q;
        if(query(s1,1,s2,1)) vec2.back()=q,swap(c,q);
        p=solve(vec2,true);
        s1[0]=p,s2[0]=q;
        if(!query(s1,1,s2,1)) swap(p,q);
        s1[0]=p,s1[1]=q,s2[0]=c;
        if(query(s1,2,s2,1)) ans[p]=0,p=q;
        else ans[q]=1;
    }
    for(int i=0;i<n;i++) if(i!=p) k^=ans[i];
    ans[p]=k;
    // for(int i=0;i<n;i++) printf("%d ",ans[i]);printf("\n");
    memcpy(res,ans,sizeof(int)*n);
}

\(5n\) 做法:每个数都需要至多 \(5\) 的代价就被确定。考虑 \(5n+\log\) 做法中,把 case2 中 \(a,b\) 中留下来的数加入另一个子集,则两个子集对应位大小关系确定(第一个 \(\ge\) 第二个)。特别地,第一个子集中的 \(0\) 对应第二个子集的数也是 \(0\),所以从左往右,用扫描(相邻两数求和与最大值比较)代替二分,扫描的代价可以认为是第二个子集上数的代价,所以仍然满足均摊的要求。其余未知数均用取两个数求和与 \(1\) 比较(\(7n\) 做法后半部分)即可。

*QOJ16122 Sorted Pairs

很牛的一道题。

(*)发现答案下界是 \(n\),因为每行都至少需要一个点被操作。接下来考虑上界。

(√)观察样例,考虑对换,即对 \(i,j\) 满足 \(a_{i,1}<a_{j,1}\land a_{i,2}<a_{j,2}\),交换 \(a_{i,2},a_{j,1}\)。发现只有“嵌套”时:

\[a= \left(\begin{matrix} 2n &1\\ 2n-1 &2\\ 2n-2 &3\\ \vdots &\vdots\\ n+1 &n \end{matrix}\right) \]

不再能对换,注意到 \(2n\) 必须移动到右侧,\(1\) 必须移动到左侧,考虑构造轮换:

\[\left(\begin{matrix} 2n &1 &2n-1 &3 &2n-3 &\dots \end{matrix}\right) \]

即可,代价是 \(n+1\)。前面的对换可以用 \(2\) 的代价将问题规模 \(-2\),所以代价上界是 \(n+1\)。

具体实现,考虑按左侧从小到大排序,右侧从上往下贪心,对 \(a_{i,2}\) 每次取最靠上满足 \(a_{j,2}>a_{i,2}\) 的 \(j\),易知 \(\forall i<k<j,\ a_{k,2}<a_{i,2}\),所以不会导致原本有匹配变为无匹配(顶多是交换一下匹配),所以这样的贪心不劣。

(*)考虑什么时候答案是 \(n\)。代价为 \(n\) 的结构应该满足每行恰有一个元素被轮换,且被轮换的元素应一步到位。所以一个轮换应该在左右两侧交替,所以是偶环,所以对于 \(2\nmid n\) 一定做不到 \(n\)。

还是按左侧从小到大排序,记右侧排列为 \(p\)。一个轮换,应该交替走两类边:

  1. 从下标 \(i\),走到满足 \(p_j>p_i\) 的 \(j\),对应原来从右侧走到左侧的一条边;
  2. 从下标 \(i\),走到满足 \(j<i\) 的 \(j\),对应原来从左侧走到右侧的一条边。

发现对于 \(i\),如果 \(\{p_j\mid 1\le j\le i\}=\{v\in Z\mid n-i+1\le v\le n\}\),则 \(1\sim i\) 与 \(i+1\sim n\) 独立(即不存在一个轮换同时与两部分有交)。

只要每部分长度都是偶数即可。构造:

从下标最小位置出发,当前在 \(i\),先走到满足 \(p_j>p_i\) 的最大的 \(j\)(\(i\leftarrow j\)),再走到满足 \(j<i\) 且 \(p_j\) 最小的 \(j\)(\(i\leftarrow j\)),当 \(i\) 走到下表最大的位置时,进入第二部分。第二部分,把剩余没走的位置相邻两项分为一组,从右往左一组一组走,先走到这一组中较小的位置(2 类边),在走到较大的位置(1 类边),最后走回起点。

证明:

先看第一部分。用 \(v\) 表示走过的点的 \(p\) 值,\(i\) 表示下标。从 \(v_0\) 开始。发现 \(v_2\) 存在,否则所有 \(>v_0\) 的数构成前缀,与我们的子问题划分矛盾。又发现 \(v_2\le v_0-2\),否则 \([1,i_1]\) 内有且仅有 \(v_0-1\sim n\),构成前缀,矛盾。所以 \(v_3\) 最坏也能选 \(v_0-1\),故存在。若 \(i_3<i_1\),因为 \(v_2\) 为 \([1,i_1]\) 最小值,所以要么 \(i_3\) 为 \(i_1\) 左侧第一个没选的数,要么 \(i_3>i_1\),可以利用 \([1,\max(i_1,i_3)]\) 约束 \(i_4\)……以此类推,每一步都存在。

\(n\) 的构造也有基于括号序列的做法。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T,typename Cmp=less<T>>
void chkmin(T &x,const T &y,Cmp cmp=Cmp()){x=min(x,y,cmp);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=1000005;
int n,vis[N],pos[N];
pii p[N];
vector<vector<int>> ans;
template<typename Cmp,int inf>
struct SegTree{
    int tr[N<<2];
    template<typename Fun>
    void build(int x,int l,int r,const Fun &f){
        if(l==r) return (void)(tr[x]=f(l));
        int mid=(l+r)>>1;
        build(x<<1,l,mid,f),build(x<<1|1,mid+1,r,f);
        tr[x]=min(tr[x<<1],tr[x<<1|1],Cmp());
    }
    void upd(int x,int l,int r,int p,int v){
        if(l==r) return (void)(tr[x]=v);
        int mid=(l+r)>>1;
        if(p<=mid) upd(x<<1,l,mid,p,v);
        else upd(x<<1|1,mid+1,r,p,v);
        tr[x]=min(tr[x<<1],tr[x<<1|1],Cmp());
    }
    int query(int x,int l,int r,int ql,int qr){
        if(ql<=l&&r<=qr) return tr[x];
        int mid=(l+r)>>1,mn=inf;
        if(ql<=mid) chkmin(mn,query(x<<1,l,mid,ql,qr),Cmp());
        if(qr>mid) chkmin(mn,query(x<<1|1,mid+1,r,ql,qr),Cmp());
        return mn;
    }
};
SegTree<less<int>,inf> indtree;
SegTree<greater<int>,-inf> valtree;
bool solve0sub(int l,int r){
    if((r-l+1)&1) return false;
    int L=inf,R=-inf;
    for(int i=l;i<=r;i++) pos[p[i].second]=i,chkmin(L,p[i].second),chkmax(R,p[i].second),vis[i]=0;
    valtree.build(1,L,R,[&](int v){return pos[v];}),indtree.build(1,l,r,[&](int i){return p[i].second;});
    vector<int> perm;
    auto mark=[&](int cur){
        vis[cur]=1;
        valtree.upd(1,L,R,p[cur].second,-inf);
        indtree.upd(1,l,r,cur,inf);
    };
    int cur=l;
    while(true){
        perm.push_back(p[cur].second),mark(cur);
        cur=valtree.query(1,L,R,p[cur].second+1,R);
        perm.push_back(p[cur].first),mark(cur);
        if(cur==r) break;
        cur=pos[indtree.query(1,l,r,l,cur)];
    }
    vector<int> v;
    for(int i=r;i>=l;i--) if(!vis[i]) v.push_back(i);
    for(int i=0;i<v.size();i+=2){
        int x=v[i],y=v[i+1];
        if(p[x].second>p[y].second) swap(x,y);
        perm.push_back(p[x].second),perm.push_back(p[y].first);
    }
    ans.push_back(perm);
    return true;
}
bool solve0(){
    if(n&1) return false;
    ans.clear();
    int lst=0,mn=n+1;
    for(int i=1;i<=n;i++){
        chkmin(mn,p[i].second);
        if(i==n-mn+1){
            if(!solve0sub(lst+1,i)) return false;
            lst=i;
        }
    }
    printf("%d\n",n);
    return true;
}
void solve1(){
    printf("%d\n",n+1);
    ans.clear();
    set<int> num;
    vector<int> rest;
    for(int i=1;i<=n;i++) vis[i]=0,pos[p[i].second]=i,num.insert(p[i].second);
    for(int i=1;i<=n;i++){
        if(vis[i]) continue;
        num.erase(p[i].second);
        auto it=num.upper_bound(p[i].second);
        if(it==num.end()){
            rest.push_back(i);
            continue;
        }
        ans.push_back({p[i].second,p[pos[*it]].first});
        vis[i]=vis[pos[*it]]=1;
        num.erase(it);
    }
    // printf("rest ");
    // for(auto i:rest) printf("%d ",i);printf("\n");
    reverse(rest.begin(),rest.end());
    vector<int> perm;
    for(int i=0,t=1;i<rest.size();i++,t^=1){
        if(t) perm.push_back(p[rest[i]].second);
        else perm.push_back(p[rest[i]].first);
    }
    perm.push_back(p[rest[0]].first);
    ans.push_back(perm);
}
void __INIT__(){}
void __SOLVE__(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&p[i].first,&p[i].second);
    sort(p+1,p+n+1);
    if(!solve0()) solve1();
    printf("%ld\n",ans.size());
    for(auto &v:ans){
        printf("%ld ",v.size());
        for(auto i:v) printf("%d ",i);
        printf("\n");
    }
}
int main(){
    int T=1;
    scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

*QOJ1228 I 君的探险

很牛的一道题,感觉这些技巧的可扩展性很高。


暴力

直接对每个点维护当前的状态,然后扫 \(0\sim n-1\),逐个 modify,看哪些点状态改变了。

只需要对 \(0\sim n-2\) 做,改 \(i\) 后,每个点对都要查询一次,共 \(\frac{n(n-1)}2\) 次,刚好满足要求。

*求异或

逐二进制位处理,在第 \(i\) 位时,modify 所有这位为 \(1\) 的点,若一个点状态改变说明它与所有相邻点编号的异或和第 \(i\) 位为 \(1\)。

可以做:

  • \(A\) 性质;
  • \(C\) 性质:随便找一个点,modify 再对所有点 query 再 modify 回来找到其相邻点,然后利用异或和向两侧扩展,也可以转成 \(D\) 性质;
  • \(BD\) 性质:剥叶子。\(B\) 直接从大往小剥就行。\(D\) 性质发现 \(L_m,L_q\) 足够大,发现如果 \(u\) 是叶子,刚才算出的 \(u\) 处异或和为 \(s_u\),则 modify(u) 后 query(s[u]^u) 一定改变了,然后 modify(u) 回来,但这样可能找到不是叶子的点,没有关系,我们只需要保证不漏,不需要不重。

**整体二分

考虑 \(AB\) 性质,我们发现只需要找出每个点相邻且比它编号小的点(或不存在)即可。

一个简单的想法是对每个点二分,把 \([l,mid]\cap[0,u)\) modify,看 query(u) 有没有改变,若改变了,则 \(r\leftarrow mid\),否则 \(l\leftarrow mid+1\)。

然后套一个整体二分。

(*)但问题是不存在怎么办,我本来的做法是在二分结束后 modify + query 验证,但是这样太浪费,发现可以整体二分一开始不传询问进去,然后每次把 \([mid+1,r]\) 都跟刚才一样查一遍,如果有边,就放到左侧询问,否则不管。设找到的边数是 \(m\),这个过程的 query 次数大概是 \(\frac{n}2\log_2 n+m\log_2 n\)。

可以做:

  • \(AB\) 性质;
  • 正解:随机化,有结论是每次都能期望找到 \(\frac{n}3\) 条边,利用 check,每次 \(n\leftarrow \#\{u\mid check(u)=0\}\),然后做刚才的整体二分即可。
#include"explore.h"
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
mt19937 rnd(19491001);
int p[N],id[N],cnt,vis[N];
vector<int> G[N];
void solve(int l,int r,const vector<int> &nds){
	if(l==r){
		for(auto id:nds){
			report(p[l],p[id]),cnt--;
			G[p[l]].push_back(p[id]),G[p[id]].push_back(p[l]);
		}
		return;
	}
	int mid=(l+r)>>1;
	int j=0;
	vector<int> L,R;
	for(int i=l;i<=mid;i++){
		for(;j<nds.size()&&nds[j]<=i;j++){
			int e=0;
			for(auto v:G[p[nds[j]]]) if(!vis[v]&&id[v]>=l&&id[v]<i) e^=1;
			if(query(p[nds[j]])^e) L.push_back(nds[j]);
			else R.push_back(nds[j]);
		}
		modify(p[i]);
	}
	for(;j<nds.size();j++){
		int e=0;
		for(auto v:G[p[nds[j]]]) if(!vis[v]&&id[v]>=l&&id[v]<=mid) e^=1;
		if(query(p[nds[j]])^e) L.push_back(nds[j]);
		else R.push_back(nds[j]);
	}
	for(int j=mid+1;j<=r;j++){
		int e=0;
		for(auto v:G[p[j]]) if(!vis[v]&&id[v]>=l&&id[v]<=mid) e^=1;
		if(query(p[j])^e) L.push_back(j);
	}
	for(int i=l;i<=mid;i++) modify(p[i]);
	solve(l,mid,L),solve(mid+1,r,R);
}
int stat[N];
void explore(int n, int m){
	if(n<=500){
		for(int i=0;i<n-1;i++){
			modify(i);
			for(int j=i+1;j<n;j++){
				int ns=query(j);
				if(ns!=stat[j]) report(i,j);
				stat[j]=ns;
			}
		}
		return;
	}
	iota(p,p+n,0);
	if(n%10==7||n%10==8){
		solve(0,n-1,{});
		return;
	}
	cnt=m;
	while(cnt){
		shuffle(p,p+n,rnd);
		for(int i=0;i<n;i++) id[p[i]]=i;
		solve(0,n-1,{});
		if(!cnt) break;
		vector<int> np;
		for(int i=0;i<n;i++){
			if(vis[p[i]]||check(p[i])) vis[p[i]]=1;
			else np.push_back(p[i]);
		}
		n=np.size();copy(np.begin(),np.end(),p);
	}
}

*QOJ8553 Exchanging Kubic

第一次做缩连续段的最大子段和

考虑先对所有位置问一遍,得到具体值或 \(\le 0\),按正负缩连续段,然后只需要考虑- 段被完整包含的区间。

对于 +-+,问一下整体,如果不是两边 + 的值,那么可以确定 - 的值是多少,并将这三段合并为一个 +。否则得到 - 的绝对值 \(\ge\) 两侧 + 值的 \(\min\)。

考虑 + 的全局 \(\min\) 两侧,+-+-+。如果 +-+ 没有发生合并,则两侧 - 绝对值 \(\ge\) 中间 +,所以最大子段不会以中间 + 结尾(除非恰 \(\subseteq\) 中间这段 +,这种情况已经确定),可将 -+- 缩成一个 -,仍需保证之前绝对值大小的限制,而新段无限制。

发现一定能用最多 \(2\) 次查询删掉一个 - 段,而两端的 - 段无代价删除,所以总查询次数(算上一开始 \(n\) 次) \(\le n+2\times\left\lfloor\frac{n}{2}\right\rfloor\le 2n\)。

这个过程保证的是所有可能成为最大子段的和不变,所以对于一些包含不完整 + 段的区间,可能贡献到它的子段要么在内部(与不完整段无交),要么在边界,可由外部完整段减确定值得到。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=2005;
int n,tail=0,tot=0;
ll ans[N],sum[N<<1];
pii rng[N<<1];// range
int nxt[N<<1],pre[N<<1],vis[N<<1];
priority_queue<pli,vector<pli>,greater<pli>> pq;
void pb(int l,int r,int t,ll s){
    tot++;
    vis[tot]=0;
    rng[tot]={l,r};
    if(t) sum[tot]=s,pq.push({s,tot});
    else sum[tot]=-1;
    nxt[tail]=tot,pre[tot]=tail,nxt[tot]=0;
    tail=tot;
}
void era(int u){
    vis[u]=1;
    nxt[pre[u]]=nxt[u],pre[nxt[u]]=pre[u];
}
void upd(int u,ll v){
    auto [l,r]=rng[u];
    // printf("upd [%d,%d] %lld\n",l,r,v);
    for(int i=l;i<=r;i++) v-=ans[i];
    for(int i=l;i<=r;i++) if(ans[i]<=0) ans[i]+=v,v=0;
}
bool solve(int b){
    printf("? %d %d\n",rng[pre[b]].first,rng[nxt[b]].second);
    fflush(stdout);
    ll v;
    scanf("%lld",&v);
    if(v==sum[pre[b]]||v==sum[nxt[b]]) return false;
    upd(b,v-sum[nxt[b]]-sum[pre[b]]);
    rng[b]={rng[pre[b]].first,rng[nxt[b]].second},sum[b]=v;
    era(pre[b]),era(nxt[b]);
    pq.push({sum[b],b});
    return true;
}
void __INIT__(){}
void __SOLVE__(){
    scanf("%d",&n);
    tail=tot=0;
    for(int i=1;i<=n;i++) ans[i]=0;
    while(pq.size()) pq.pop();
    int lst=0,t=0;
    ll s=0;
    for(int i=1;i<=n;i++){
        printf("? %d %d\n",i,i);
        fflush(stdout);
        ll v;
        scanf("%lld",&v);
        if(v>0) ans[i]=v;
        if(i==1) lst=1,t=(v>0);
        else if(t!=(v>0)){
            pb(lst,i-1,t,s);
            lst=i,t=(v>0),s=0;
        }
        s+=v;
    }
    pb(lst,n,t,s);
    while(pq.size()){
        auto [s,u]=pq.top();
        pq.pop();
        if(vis[u]) continue;
        if(!pre[u]&&!nxt[u]) break;
        if(pre[u]){
            if(!pre[pre[u]]){
                upd(pre[u],0),era(pre[u]);
                pq.push({sum[u],u});
                continue;
            }
            if(solve(pre[u])) continue;
        }
        if(nxt[u]){
            if(!nxt[nxt[u]]){
                upd(nxt[u],0),era(nxt[u]);
                pq.push({sum[u],u});
                continue;
            }
            if(solve(nxt[u])) continue;
        }
        if(pre[u]) rng[u].first=rng[pre[u]].first,upd(pre[u],-sum[u]),era(pre[u]);
        if(nxt[u]) rng[u].second=rng[nxt[u]].second,upd(nxt[u],-sum[u]),era(nxt[u]);
    }
    if(sum[nxt[0]]<0) upd(nxt[0],0);
    printf("! ");
    for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
    printf("\n");
    fflush(stdout);
}
int main(){
    int T=1;
    scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

CF521D Shop

取对数,最终解形式为 一堆 1 + 一堆 2 + 一堆 3,类似将每个操作对对数的贡献算出来,排序,从大到小取即可,最后按 1,2,3 的顺序重排一遍。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef __int128_t int128;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int N=100005;
int k,n,m;
ll a[N];
vector<pii> op[N];
struct Op{
    int id,type;
    pll frac;
};
vector<Op> full,ans;
bool cmp(Op x,Op y){
    return (int128)x.frac.first*y.frac.second>(int128)y.frac.first*x.frac.second;
}
void __INIT__(){}
void __SOLVE__(){
    scanf("%d%d%d",&k,&n,&m);
    for(int i=1;i<=k;i++) scanf("%lld",&a[i]);
    for(int j=1;j<=n;j++){
        int t,i,b;
        scanf("%d%d%d",&t,&i,&b);
        if(t==1){
            if(b>a[i]) op[i].push_back({b-a[i],-j});
        }
        else if(t==2) op[i].push_back({b,j});
        else full.push_back({j,2,{b,1}});
    }
    for(int i=1;i<=k;i++){
        bool has1=false;
        sort(op[i].begin(),op[i].end(),greater<pii>());
        for(auto [d,j]:op[i]){
            if(j<0){
                if(has1) continue;
                has1=true;
            }
            full.push_back({abs(j),j/abs(j),{a[i]+d,a[i]}});
            a[i]+=d;
        }
    }
    sort(full.begin(),full.end(),cmp);
    for(int i=0;i<full.size()&&i<m;i++) ans.push_back(full[i]);
    sort(ans.begin(),ans.end(),[&](Op x,Op y){return x.type<y.type;});
    printf("%ld\n",ans.size());
    for(int i=0;i<ans.size();i++) printf("%d ",ans[i].id);
    printf("\n");
}
int main(){
    int T=1;
    // scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

CF1217D Coloring Edges

大水题?

只需考虑 scc(其实没必要)。

手玩一下,猜测最多只需要两种颜色,要求每一种颜色的导出子图都是 DAG。于是考虑 dfs,对树边、横叉边、前向边、返祖边分别考虑,树边、横叉边、前向边都可以填 \(1\) 而仍能保持 DAG,把所有返祖边都填 \(2\),因为返祖边保证 \(dfn\) 严格下降,所以是 DAG。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int N=5005;
int n,m,ans[N];
vector<pii> G[N];
int vis[N],instack[N];
void dfs(int u){
    vis[u]=instack[u]=1;
    for(auto [v,id]:G[u]){
        if(instack[v]) ans[id]=2;
        else ans[id]=1;
        if(!vis[v]) dfs(v);
    }
    instack[u]=0;
}
void __INIT__(){}
void __SOLVE__(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back({v,i});
    }
    for(int i=1;i<=n;i++) if(!vis[i]) dfs(i);
    int k=0;
    for(int i=1;i<=m;i++) chkmax(k,ans[i]);
    printf("%d\n",k);
    for(int i=1;i<=m;i++) printf("%d ",ans[i]);
    printf("\n");
}
int main(){
    int T=1;
    // scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

CF1470D Strange Housing

发现一个必要条件是每个点都有至少一个关键点(Teachers)相邻,即关键点为极大独立集。

考虑什么时候仍不合法,发现形如:

\(E=\{(1,2),(2,3),(3,4),(4,5),(5,6),(6,1)\}\)

\(S=\{1,4\}\)

于是考虑用 dfs 树构造极大独立集:在 dfs 过程中,能选就选。

证明:

经典但这里不需要的想法:只需证任意两个点均可达。刚才的构造给我们一种能一直向上走的感受,发现确实如此,每个点均能到根:若当前点与父亲均未被选,则当前点必然有返祖边连向一个被选了的点,所以总能向上走。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int N=300005;
int n,m,vis[N],col[N];
vector<int> G[N];
void dfs(int u){
    vis[u]=1,col[u]=1;
    for(int v:G[u]) if(vis[v]&&col[v]) col[u]=0;
    for(int v:G[u]){
        if(vis[v]) continue;
        dfs(v);
    }
}
void __INIT__(){}
void __SOLVE__(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) G[i].clear(),vis[i]=col[i]=0;
    for(int i=1;i<=m;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v),G[v].push_back(u);
    }
    dfs(1);
    for(int i=1;i<=n;i++) if(!vis[i]){
        printf("NO\n");
        return;
    }
    vector<int> ans;
    for(int i=1;i<=n;i++) if(col[i]) ans.push_back(i);
    printf("YES\n%d\n",ans.size());
    for(auto i:ans) printf("%d ",i);
    printf("\n");
}
int main(){
    int T=1;
    scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

*CF1450C2 Errich-Tac-Toe (Hard Version)

这我能不会?

肯定要找到若干对称的构造,然后证明总有一个操作数 \(\le\left\lfloor\frac{k}{3}\right\rfloor\)。

笔者的想法是,把每条正斜线都钦定一个颜色,这样有 \(6\) 组对称,但每个点都被改了 \(3\) 次,只能做到 \(\frac{k}{2}\)。形如:

XOO OXO OOX
OXO OOX XOO
OOX XOO OXO

OXX XOX XXO
XOX XXO OXX
XXO OXX XOX

所有类似的做法都只能做到 \(\frac{k}{2}\),因为每个点只有两种可能,所以每个点总会被统计一半的次数。

(*)但其实不需要这么严格,考虑怎么让每个点有三种可能,考虑添加“保持不变”。发现每个 \(1\times 3,\ 3\times 1\) 子矩形,只需要有一个 O 和 一个 X 即可。所以构造这 \(6\) 种方案(其中 ? 表示不变),每个点只会被改两次,能做到 \(\frac{k}{3}\):

XO? ?XO O?X
?XO O?X XO?
O?X XO? ?XO

X?O OX? ?OX
OX? ?OX X?O
?OX X?O OX?
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int N=305;
int n,cnt[3][2];
char bd[N][N];
void __INIT__(){}
void __SOLVE__(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%s",bd[i]+1);
    memset(cnt,0,sizeof(cnt));
    int tot=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(bd[i][j]=='O') cnt[((i-j)%3+3)%3][0]++,tot++;
            else if(bd[i][j]=='X') cnt[((i-j)%3+3)%3][1]++,tot++;
        }
    }
    for(int a=0;a<3;a++){
        for(int b=0;b<3;b++){
            if(a==b) continue;
            if(cnt[a][0]+cnt[b][1]<=tot/3){
                for(int i=1;i<=n;i++){
                    for(int j=1;j<=n;j++){
                        if(((i-j)%3+3)%3==a&&bd[i][j]!='.') bd[i][j]='X';
                        if(((i-j)%3+3)%3==b&&bd[i][j]!='.') bd[i][j]='O';
                    }
                }
                for(int i=1;i<=n;i++) printf("%s\n",bd[i]+1);
                return;
            }
        }
    }
}
int main(){
    int T=1;
    scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

P9820 [ICPC 2020 Shanghai R] Mine Sweeper II

看到除以 \(2\),尝试把 .X 交换。发现 .X 交换后,和不变(拆贡献为八相邻 .X 对可证)。

发现把 \(B\) 操作为 \(A\) 和 \(flip(A)\) 中,至少有一个满足要求。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int N=1005;
int n,m;
char a[N][N],b[N][N];
void __INIT__(){}
void __SOLVE__(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%s",a[i]+1);
    for(int i=1;i<=n;i++) scanf("%s",b[i]+1);
    int cnt=0;
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cnt+=(a[i][j]!=b[i][j]);
    if(cnt<=n*m/2){
        for(int i=1;i<=n;i++) printf("%s\n",a[i]+1);
        return;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i][j]=='.') printf("X");
            else printf(".");
        }
        printf("\n");
    }
}
int main(){
    int T=1;
    // scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

CF1364D Ehab's Last Corollary

发现长 \(k\) 的环上选 \(\left\lceil\frac{k}{2}\right\rceil\) 个点可以保证最多只有一处相邻,于是考虑选 \(\left\lceil\frac{k}{2}\right\rceil\) 个点作为候选的独立集,如果有直接连边就调整为环。

此时需要保证独立集存在一种遍历方式,时相邻两点距离为 \(2\)。考虑 dfs 生成树,取奇数/偶数层,总有一层有 \(\left\lceil\frac{k}{2}\right\rceil\) 个点,如果没有内部的边,则找到独立集,否则拉出这条边两端在树上的路径,发现黑白交替出现,最多 \(2\left\lceil\frac{k}{2}\right\rceil-1\le k\) 个点。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int N=100005;
int n,m,k,dep[N],cnt[2],fa[N],sel[N];
vector<int> G[N];
pii es[N];
int t;
vector<int> nds[2];
void dfs(int u,int f,int d){
    dep[u]=d,cnt[d&1]++,nds[d&1].push_back(u),fa[u]=f;
    for(auto v:G[u]){
        if(v==f) continue;
        if(!dep[v]) dfs(v,u,d+1);
    }
}
void __INIT__(){}
void __SOLVE__(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v),G[v].push_back(u);
        es[i]={u,v};
    }
    dfs(1,1,1);
    if(cnt[0]>cnt[1]) t=0;
    else t=1;
    for(int i=0;i<(k+1)/2;i++) sel[nds[t][i]]=1;
    int u=-1,v=-1;
    for(int i=1;i<=m;i++){
        if(sel[es[i].first]&&sel[es[i].second]){
            u=es[i].first,v=es[i].second;
            break;
        }
    }
    if(u==-1&&v==-1){
        printf("1\n");
        for(int i=0;i<(k+1)/2;i++) printf("%d ",nds[t][i]);
        printf("\n");
        return;
    }
    vector<int> v1,v2;
    while(u!=v){
        if(dep[u]>=dep[v]) v1.push_back(u),u=fa[u];
        else v2.push_back(v),v=fa[v];
    }
    printf("2\n%ld\n",v1.size()+v2.size()+1);
    for(auto i:v1) printf("%d ",i);
    printf("%d ",u);
    for(auto i:v2) printf("%d ",i);
    printf("\n");
}
int main(){
    int T=1;
    // scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

*QOJ2671 景点划分

(*)核心在于,在 \(a\le b\le c\) 的情况下,钦定 \(A,B\) 连通(如果是 \(A,C\) 连通,可以把 \(C\) 调为 \(B\),其余同理)。

然后发现 \(a\le b\le\frac{n}{2}\),于是考虑重心(笔者的想法是枚举相邻点,然后分讨,也能做一部分)。

做树,尝试把树分为两部分,包含重心的一部分给到 \(B\)(因为 \(b\) 更大)。若有一棵子树(含外子树)大小 \(\ge a\),则剩余部分大小 \(\ge n-\frac{n}{2}=\frac{n}{2}\ge b\)(重心性质)。否则,\(A\) 一定要包含当前重心,而 \(b\ge a\),所以 \(B\) 也一定要包含当前重心,无解。

扩展到图,考虑 dfs 生成树的重心。如果有一棵子树大小 \(\ge a\),和刚才一样。

否则,\(A\) 必然在若干连通的子树上,由于没有横叉边,所以必然含外子树,逐个枚举与外子树连通的子树,给到 \(A\),直到总大小 \(\ge a\)。由于不满足第一种情况,所以每棵子树大小 \(<a\),给到 \(A\) 的总大小 \(<2a\)。所以剩余部分 \(>n-2a\ge n-a-c=b\)。如果总大小始终 \(<a\),则与刚才类似,\(A,B\) 都必须包含当前重心,无解。

反套路点:对于图问题,如果你想重新 dfs 一遍,反而会破坏重心性质。

#include "split.h"
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=100005;
int n,tot,dfn[N],low[N],sz[N];
pii partsz[3];
vector<int> ans;
vector<pii> G[N],near[N];
pii build(int u,int f){
    low[u]=dfn[u]=++tot,sz[u]=1;
    int mxsz=0;
    pii mn{inf,-1};
    for(auto &[v,t]:G[u]){
        if(v==f){
            t=1;
            continue;
        }
        if(!dfn[v]){
            t=1;
            chkmin(mn,build(v,u));
            chkmin(low[u],low[v]);
            sz[u]+=sz[v],chkmax(mxsz,sz[v]);
            near[u].push_back({v,sz[v]});
        }
        else t=0,chkmin(low[u],dfn[v]);
    }
    near[u].push_back({f,n-sz[u]});
    return min(mn,{max(mxsz,n-sz[u]),u});
}
int vis[N];
void clear(int u,int f){
    vis[u]=0;
    for(auto [v,t]:G[u]){
        if(v==f||!t) continue;
        clear(v,u);
    }
}
void dfs(int u,int &k,int c){
    if(vis[u]) return;
    if(!k) return;
    vis[u]=1,ans[u]=c,k--;
    for(auto [v,t]:G[u]) dfs(v,k,c);
}
vector<int> find_split(int n_, int a, int b, int c, vector<int> p, vector<int> q) {
    n=n_;
    partsz[0]={a,1},partsz[1]={b,2},partsz[2]={c,3};
    ans=vector<int>(n,0);
    sort(partsz,partsz+3);
    for(int i=0;i<p.size();i++) G[p[i]].push_back({q[i],0}),G[q[i]].push_back({p[i],0});
    int u=build(0,0).second;
    reverse(near[u].begin(),near[u].end());
    // printf("u=%d\n",u);
    // for(auto [v,s]:near[u]) printf("(%d,%d) ",v,s);
    // printf("\n");
    for(int i=0;i<n;i++) vis[i]=1;
    for(auto [v,s]:near[u]){
        if(s>=partsz[0].first){
            clear(v,u);
            dfs(v,partsz[0].first,partsz[0].second);
            for(int i=0;i<n;i++) if(!ans[i]) vis[i]=0;
            dfs(u,partsz[1].first,partsz[1].second);
            for(int i=0;i<n;i++) if(!ans[i]) ans[i]=partsz[2].second;
            return ans;
        }
    }
    if(!u) return ans;
    int sum=near[u][0].second;
    clear(near[u][0].first,u);
    for(int i=(!!u);i<near[u].size();i++){
        auto [v,s]=near[u][i];
        if(low[v]<dfn[u]){
            clear(v,u);
            sum+=s;
            if(sum>=partsz[0].first) break;
        }
    }
    if(sum<partsz[0].first) return ans;
    dfs(near[u][0].first,partsz[0].first,partsz[0].second);
    for(int i=0;i<n;i++) if(!ans[i]) vis[i]=0;
    dfs(u,partsz[1].first,partsz[1].second);
    for(int i=0;i<n;i++) if(!ans[i]) ans[i]=partsz[2].second;
    return ans;
}

*P6892/UVA1697/QOJ7665 [ICPC 2014 WF] Baggage

观察样例,猜测答案就是 \(n\)。

再观察构造,发现形如:先调为 ...AABBAABB...(两端可能有不规则处),然后再变为 A...AB...B。

算一下中间状态的结构,发现与 \(n\text{ mod }4\) 相关。

前半部分基本流程是:

  BABABABA...BABABABA
ABBABABABA...BABAB  A
ABBA  BABA...BABABBAA
ABBAABBABA...B  ABBAA
ABBAABBA  ...BBAABBAA

后半部分就是轮流把 BB,AA 放到中间空缺出即可。

中间会有 corner case,笔者手玩出了 \(n\text{ mod }4=0,1\) 的,\(2,3\) 只做到了 \(n+1\),实际你写个搜就能找到 \(n\) 的做法,\(n=3\) 需要特判。

n=3

n=5
  BABABABABA
ABBABABAB  A
ABBA  BABBAA
---
ABBAABB  BAA
---
A  AABBBBBAA
AAAAABBBBB

*n=6
  BABABABABABA
ABBABABABAB  A
---
ABBABABA  BBAA
---
ABB  ABAABBBAA
ABBAAAB  BBBAA
A  AAABBBBBBAA
AAAAAABBBBBB

*n=7
---
  BABABABABABABA
ABBABABAB  ABABA
ABBABA  BBAABABA
ABBABAABBBAAB  A
ABBA  ABBBAABBAA
---
ABBAAAABBB  BBAA
A  AAAABBBBBBBAA
AAAAAAABBBBBBB

n=8
  BABABABABABABABA
ABBABABABABABAB  A
ABBA  BABABABABBAA
ABBAABBABAB  ABBAA
ABBAABBA  BBAABBAA
---
---
ABBAA  ABBBBAABBAA
ABBAAAAABBBB  BBAA
A  AAAAABBBBBBBBAA
AAAAAAAABBBBBBBB
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
int n;
void __INIT__(){}
void __SOLVE__(){
    scanf("%d",&n);
    if(n==3){
        printf("2 to -1\n");
        printf("5 to 2\n");
        printf("3 to -3\n");
        return;
    }
    if((n&3)==0){
        int i=-1,j=(n<<1)-2;
        while(j>i+1){
            printf("%d to %d\n",j,i),i+=4;
            printf("%d to %d\n",i,j),j-=4;
        }
        int lst=i;
        i=0,j=lst+4;
        while(j<=(n<<1)-1){
            printf("%d to %d\n",i,lst),lst=i,i+=4;
            printf("%d to %d\n",j,lst),lst=j,j+=4;
        }
        return;
    }
    if((n&3)==1){
        int i=-1,j=(n<<1)-2;
        while(j>i+1){
            printf("%d to %d\n",j,i),i+=4;
            printf("%d to %d\n",i,j),j-=4;
        }
        int lst=i;
        printf("%d to %d\n",lst+3,lst),lst+=3;
        i=0,j=lst+3;
        while(j<=(n<<1)-1){
            printf("%d to %d\n",i,lst),lst=i,i+=4;
            printf("%d to %d\n",j,lst),lst=j,j+=4;
        }
    }
    if((n&3)==2){
        int i=-1,j=(n<<1)-2;
        while(j>i+11){
            printf("%d to %d\n",j,i),i+=4;
            printf("%d to %d\n",i,j),j-=4;
        }
        printf("%d to %d\n",j,i);
        printf("%d to %d\n",j-3,j);
        printf("%d to %d\n",i+3,j-3);
        int lst=i+3;
        printf("%d to %d\n",lst+4,lst),lst+=4;
        i=0,j=lst+5;
        while(j<=(n<<1)-1){
            printf("%d to %d\n",i,lst),lst=i,i+=4;
            printf("%d to %d\n",j,lst),lst=j,j+=4;
        }
    }
    if((n&3)==3){
        int i=-1,j=(n<<1)-2;
        while(j>i+13){
            printf("%d to %d\n",j,i),i+=4;
            printf("%d to %d\n",i,j),j-=4;
        }
        printf("%d to %d\n",i+9,i);
        printf("%d to %d\n",i+6,i+9);
        printf("%d to %d\n",i+13,i+6);
        printf("%d to %d\n",i+4,i+13);
        printf("%d to %d\n",i+10,i+4);
        int lst=i+10;
        j=i+14,i=0;
        while(j<=(n<<1)-1){
            printf("%d to %d\n",i,lst),lst=i,i+=4;
            printf("%d to %d\n",j,lst),lst=j,j+=4;
        }
    }
}
int main(){
    int T=1;
    // scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

CF1495E Qingshan and Daniel

一个单 \(\log\) 爆冲做法,使用了 AI 辅助卡常。


考虑直接模拟这个过程,发现会一直在一个环内绕圈直到一个点被删除,删除这个 robot 后,有两种可能:

  • 下一个 robot 同队:把环上当前 robot 改为下个 robot;
  • 不同队:跳过环上当前 robot 和环上的下个 robot。

然后大力维护可以单 \(\log\)。

但是这个看起来就比较大,其实可以简化,注意到刚才所谓“环上点”其实就是剩余每个连续段的开头。

考虑直接维护连续段,开一个可删堆记录每个连续段被删除的时刻(一个二元组,第一关键字表示转了几圈,第二关键字表示位置)。先把出牌数都记在连续段开头,被删除时再向后更新。删除一个连续段时,合并两侧的连续段。

注意第一步可能不在环上。另外还有很多细节,见代码。

关于卡常,拿一个类似线段树(其实叫“竞赛树”,用非递归线段树实现)的东西维护堆,因为无 pushdown,所以修改一个点后直接从叶子往上 pushup。你又发现元素只增加(increase_key),所以 pushup 到一半可以提前退出。

#pragma GCC optimize("Ofast","-fno-stack-protector","-funroll-loops")
#pragma GCC target("mmx,fxsr,sse,sse2,ssse3,fma,cx16,sse4.1,sse4.2,movbe,popcnt,aes,xsave,avx,f16c,abm,fsgsbase,avx2,bmi2,xsaveopt")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=1'000'000'007;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int N=5000005;
int n,seed,base;
ll ans[N];
int rnd(){
    int ret=seed;
    seed=((ll)seed*base+233)%MOD;
    return ret;
}
void print(){
    int mul=1;
    for(int i=1;i<=n;i++) mul=(ll)mul*((((ans[i])^((ll)i*i))+1)%MOD)%MOD;
    printf("%d\n",mul);
}
ll a[N];
struct Heap {
    int tr[N<<1];
    bool cmp(int x,int y){
        if(a[x]!=a[y]) return a[x]<a[y];
        return x<y;
    }
    void build(int n){
        for(int i=1;i<=n;i++) tr[n+i-1]=i;
        for(int i=n-1;i>=1;i--){
            if(cmp(tr[i<<1],tr[i<<1|1])) tr[i]=tr[i<<1];
            else tr[i]=tr[i<<1|1];
        }
    }
    void update(int x){
        for(x+=n-1;x>>1;x>>=1){
            if(tr[x>>1]==tr[x^1]) break;
            if(cmp(tr[x],tr[x^1])) tr[x>>1]=tr[x];
            else tr[x>>1]=tr[x^1];
        }
    }
    void erase(int x){
        a[x]=infll;
        update(x);
    }
    int top(){
        int x=tr[1];
        if(a[x]==infll) return -1;
        return x;
    }
} hp;
int pre[N],nxt[N],a0[N];
bitset<N> t;
int nxtInd[N],tail[N];
void erase(int x){nxt[pre[x]]=nxt[x],pre[nxt[x]]=pre[x];}
void merge(int u,int v,ll c=0){
    ll tmp=a[v];
    hp.erase(v);
    ans[v]+=c,a[u]+=tmp-c,nxtInd[tail[u]]=v,tail[u]=tail[v];
    erase(v);
    hp.update(u);
}
void fill(int x,ll c=0){
    ans[x]+=c;
    ll s=0;
    for(int i=x;;i=nxtInd[i]){
        s+=ans[i];
        ans[i]=min((ll)a0[i],s);
        s-=ans[i];
        if(i==tail[x]) break;
    }
}
void __INIT__(){}
void __SOLVE__(){
    int m;
    scanf("%d%d",&n,&m);
    int lst=0;
    for(int i=1;i<=m;i++){
        int p,k,b,w;
        scanf("%d%d%d%d",&p,&k,&b,&w);
        seed=b,base=w;
        for(int j=lst+1;j<=p;j++) t[j]=rnd()&1,a[j]=rnd()%k+1;
        lst=p;
    }
    int s=1;
    bool flag=false;
    if(t[n]==t[1]){
        a[1]--,flag=true;
        for(int i=2;i<=n;i++) if(t[i]!=t[1]){
            s=i;
            break;
        }
        if(s==1){
            ans[1]=1;
            print();
            return;
        }
    }
    for(int i=1;i<=n;i++) pre[nxt[i]=i%n+1]=i,tail[i]=i,a0[i]=a[i];
    hp.build(n);
    for(int i=1;i<=n;i++) if(t[pre[i]]==t[i]) merge(pre[i],i);
    while(true){
        int p=hp.top();
        if(p==-1) break;
        ll c=a[p]-1;
        hp.erase(p);
        int u=pre[p],v=nxt[p];
        erase(p);
        ans[p]++;
        fill(p,c);
        int d=1;
        if(v<p) d++;
        if(u==v){
            a[v]-=d,ans[v]+=d;
            fill(u,c);
            break;
        }
        if(a[v]==c+d||t[u]!=t[v]) continue;
        merge(u,v,c+d);
    }
    if(flag) ans[1]++;
    print();
}
int main(){
    int T=1;
    // scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

*线性做法

其实差点就会了

和刚才类似,考虑每个点先“超额支出”一些牌,然后在跑一圈补偿。

问题在于怎么计算出牌数。哪队先出完是好算的,\(\sum a\) 较大的队会先出完,相等则都会出完。

(*)然后先出完的队答案可以直接算,把先出完队的 robot 出牌数贡献到后面最近的对手上即可。

然后跑两圈补偿(因为是个环)。

#pragma GCC optimize("Ofast","-fno-stack-protector","-funroll-loops")
#pragma GCC target("mmx,fxsr,sse,sse2,ssse3,fma,cx16,sse4.1,sse4.2,movbe,popcnt,aes,xsave,avx,f16c,abm,fsgsbase,avx2,bmi2,xsaveopt")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=1'000'000'007;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int N=5000005;
int n,seed,base;
int t[N],a[N],ans[N];
ll cnt[2];
int rnd(){
    int ret=seed;
    seed=((ll)seed*base+233)%MOD;
    return ret;
}
void print(){
    // for(int i=1;i<=n;i++) printf("%d ",ans[i]);
    // printf("\n");
    int mul=1;
    for(int i=1;i<=n;i++) mul=(ll)mul*((((ans[i])^((ll)i*i))+1)%MOD)%MOD;
    printf("%d\n",mul);
}
void __INIT__(){}
void __SOLVE__(){
    int m;
    scanf("%d%d",&n,&m);
    int lst=0;
    for(int i=1;i<=m;i++){
        int p,k,b,w;
        scanf("%d%d%d%d",&p,&k,&b,&w);
        seed=b,base=w;
        for(int j=lst+1;j<=p;j++) t[j]=rnd()&1,a[j]=rnd()%k+1;
        lst=p;
    }
    for(int i=n;i>=1;i--) t[i]^=t[1],cnt[t[i]]+=a[i];
    int lose=(cnt[0]>cnt[1]);
    if(t[1]!=lose) a[1]--;
    ll s=0;
    for(int i=1;i<=n;i++){
        if(t[i]==lose) ans[i]=a[i],s+=a[i];
        else{
            ans[i]=min(s,(ll)a[i]);
            s-=ans[i];
        }
    }
    for(int i=1;i<=n;i++) if(t[i]!=lose){
        s+=ans[i];
        ans[i]=min(s,(ll)a[i]);
        s-=ans[i];
    }
    if(t[1]!=lose) ans[1]++;
    print();
}
int main(){
    int T=1;
    // scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

UOJ809. 【UNR #7】那些你不要的

玩一下 \(n=1,3,5\) 发现 \(n=1\) 答案为第 \(1\) 个数,\(n=3\) 答案为第 \(1,3\) 个数中较大值,\(n=5\) 为第 \(1,3,5\) 中次大值。

再看 \(n=7\),发现只有其奇数位能成为后继状态的奇数位,并且所有后继状态奇数位能且只能由当前奇数位删一个得到。对于 小青鱼,应该删最小值;对于 智者,应该删最大值。

所以答案是所有奇数位的较大中位数。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    while(b){
        if(b&1) mul=(ll)mul*a%MOD;
        a=(ll)a*a%MOD;
        b>>=1;
    }
    return mul;
}
const int N=1000005;
int n,a[N];
void __INIT__(){}
void __SOLVE__(){
    scanf("%d",&n);
    vector<int> v;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        if(i&1) v.push_back(a[i]);
    }
    sort(v.begin(),v.end());
    printf("%d\n",v[v.size()>>1]);
}
int main(){
    int T=1;
    scanf("%d",&T);
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

QOJ15546 仙人掌

打表找规律,发现是斐波那契数列。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int n;
int main(){
    scanf("%d",&n);
    int a=0,b=1;
    while(n--) swap(a,b),add(b,a);
    printf("%d\n",a);
    return 0;
}

官方证明太 Ad-hoc 了,在和 AI 的激烈交流下,得到基于留数定理的证明。

使用 AI 辅助生成,并人工审核

文中所有有理分式都需要约分并保证分母为首 \(1\) 多项式。

定义:

  • 极点:对于有理分式 \(G(x)=\frac{A(x)}{B(x)}\),“极点”为分母的所有根;
  • Laurent 展开:对函数 \(f(x)\),定义 \(f(x)\) 在 \(a\) 处的 Laurent 展开为 \(f(x)=\sum\limits_{n=-\infty}^{+\infty}c_n(x-a)^n\);
  • 留数:函数 \(f(x)\) 在极点 \(a\) 处的 Laurent 展开式中,系数 \(c_{-1}\) 称为 \(f(x)\) 在 \(a\) 处的留数,记作 \(\operatorname{Res}(f,c)\)。

公式:

  • 连乘求导:若 \(F(x)=(x-x_0)H(x)\),则 \(F'(x_0)=H(x_0)\)。

推论:

  • 推论 1(单极点留数公式):若 \(a\) 为 \(G(x)=\frac{A(x)}{B(x)}\) 的一阶极点(即 \(a\) 不是 \(B\) 的重根),则其留数可通过洛必达法则快速求得:

    \[\operatorname{Res}(G, a)=\frac{A(a)}{B'(a)} \]

    证明:\(\operatorname{Res}(G, a)=\lim_{x\to a}(x-a)G(x)=\lim_{x\to a}\frac{(x-a)A(x)}{B(x)}=\frac{A(a)}{B'(a)}\)。
  • 推论 2(全局留数定理推论):对于有理函数 \(G(x)=\frac{A(x)}{B(x)}\),若分母最高次幂比分子最高次幂 至少大 2(即 \(\deg(B) \ge \deg(A) + 2\)),则 \(G(x)\) 所有极点处的留数之和为 \(0\)。即:

    \[\sum_k \operatorname{Res}(G, x_k)=0 \]

    无穷远处 Laurent 展开形式为 \(\displaystyle\sum_{n=-\infty}^{\infty}c_nz^n\),易知此时 \(c_{-1}=0\),则 \(\operatorname{Res}(G,\infty)=0\),代入留数定理可证。

第一步:构造全局有理函数

考虑以 \(f_i\) 为主元,观察到求和式中包含 \(\prod_{j \ne i}(f_i - f_j)\),这启发我们定义全局多项式:

\[F(x) = \prod_{j=1}^n (x - f_j) \]

其导数在 \(x = f_i\) 处的值恰好为分母:\(F'(f_i) = \prod_{j \ne i} (f_i - f_j)\)。

对于分子 \(\prod_{j \ne i}(1 + f_i - f_i f_j)\),为了补齐 \(j=i\) 的项以形成全局对称,我们乘上对应的因子 \((1 + f_i - f_i^2)\),并定义:

\[G(x) = \prod_{j=1}^n (1 + x - x f_j), H(x)=1+x-x^2 \]

于是,原式可被改写为(每一项其实都是在 \(f_i\) 处的极限):

\[S=\sum_{i=1}^n\frac{G(f_i)}{H(f_i)F'(f_i)} \]

这自然地引导我们构造出如下辅助有理函数:

\[P(x) = \frac{G(x)}{H(x)F(x)} \]

第二步:分析极点并计算留数

由于连乘求导性质,注意到 \(H(f_i)F'(f_i)\) 其实是 \(H(x)F(x)\) 在 \(f_i\) 处导数,于是发现 \(S\) 为一部分留数和,考虑利用留数定理。

函数 \(G(x)\) 的分母包含两部分,对应两类极点,注意可能有高阶极点(分母有重根),导致无法裂项,但我们可以利用连续性微调 \(f\),排除重根影响。

第一类极点:这些极点的和就是 \(S\)。

第二类极点:\(x = \phi_1, \phi_2\),其中 \(\phi\) 为 \(x^2 - x - 1 = 0\) 的根。

\[\begin{align*} \operatorname{Res}(P(x),\phi_1)&=\frac{G(\phi_1)}{-(\phi_1-\phi_2)F(\phi_1)} \\ &=-\frac{\prod\limits_{i=1}^n(1+\phi_1-\phi_1f_i)}{(\phi_1-\phi_2)\prod\limits_{i=1}^n(\phi_1-f_i)} \\ &=-\frac{\prod\limits_{i=1}^n(\phi_1^2-\phi_1f_i)}{(\phi_1-\phi_2)\prod\limits_{i=1}^n(\phi_1-f_i)} &(\phi_1^2-\phi_1-1=0\Rightarrow\phi_1+1=\phi_1^2) \\ &=-\phi_1^n\frac{\prod\limits_{i=1}^n(\phi_1-f_i)}{(\phi_1-\phi_2)\prod\limits_{i=1}^n(\phi_1-f_i)} \\ &=\frac{\phi_1^n}{\phi_2-\phi_1}=\frac{1}{\sqrt5}\left(\frac{1-\sqrt5}{2}\right)^n \\ \end{align*} \]

同理

\[\operatorname{Res}(P(x),\phi_2)=\frac{\phi_2^n}{\phi_1-\phi_2}=-\frac{1}{\sqrt5}\left(\frac{1+\sqrt5}{2}\right)^n \]

第三步:应用全局留数定理求和

检查 \(G(x)\) 的多项式度数:
分子 \(A(x)\) 的最高次数为 \(x^n\)。
分母 \(-(x^2 - x - 1)P(x)\) 的最高次数为 \(x^{n+2}\)。
满足 \(\deg(\text{分母}) = \deg(\text{分子}) + 2\),由推论 2 可知所有极点的留数和为 \(0\):

\[\sum_{i=1}^n \operatorname{Res}(G, f_i) + \operatorname{Res}(G, \phi_1) + \operatorname{Res}(G, \phi_2) = 0 \]

将第一部分替换为原式 \(S\),移项可得:

\[S = -\left( \operatorname{Res}(G, \phi_1) + \operatorname{Res}(G, \phi_2) \right) = \frac{1}{\sqrt{5}} \left[ \left(\frac{1+\sqrt{5}}{2}\right)^n - \left(\frac{1-\sqrt{5}}{2}\right)^n \right] \]

此式正是斐波那契数列的 Binet 通项公式。

最终结论

\[\sum_{i=1}^n\prod_{j\ne i}\frac{1+f_i-f_if_j}{f_i-f_j} = F_n \]

(其中 \(F_n\) 为斐波那契数列第 \(n\) 项,定义 \(F_1=1, F_2=1\))。该式的值是常数,与输入序列 \(f_i\) 完全无关。

推广

求和 \(\displaystyle S_n=\sum_{i=1}^n\prod_{j\ne i}\frac{c+bf_i-af_if_j}{f_i-f_j}\) 满足 \(S_n=bS_{n-1}+acS_{n-2}\)

*QOJ14950 Relay Jump

没看到输入了 \(s\)……不知道看到了能不能想出来。

第一想法是不变量,但由于没看到 \(s\),想不出来怎么设计。然后手玩操作,感觉很复杂,不会做。


(*)考虑不变量,显然要与被刺激的是谁有关,设 \(i\) 被刺激,跳向 \(j\),考虑 \(i\) 移动前的不变量。

\[x_1,\dots,x_i,\dots,x_j,\dots,x_n \]

\[x_1,\dots,2x_j-x_i,\dots,x_j,\dots,x_n \]

发现 \(x_j-x_i=(2x_j-x_i)-x_j\),所以不变量为

\[\sum_{j\ne i}x_j-x_i \]

直接起始、终止状态列个等式解一下就行了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=100005;
int n,s;
pii p[N],q[N];
pll sum{0,0};
void add(pll &x,pii y){x.first+=y.first,x.second+=y.second;}
void sub(pll &x,pii y){x.first-=y.first,x.second-=y.second;}
int main(){
    scanf("%d%d",&n,&s);
    for(int i=1;i<=n;i++) scanf("%d%d%d%d",&p[i].first,&p[i].second,&q[i].first,&q[i].second);
    for(int i=1;i<=n;i++){
        if(i==s) sub(sum,p[i]);
        else add(sum,p[i]);
    }
    sum.first*=-1,sum.second*=-1;
    for(int i=1;i<=n;i++) add(sum,q[i]);
    sum.first/=2,sum.second/=2;
    for(int i=1;i<=n;i++) if(sum.first==q[i].first&&sum.second==q[i].second){
        printf("%d\n",i);
        return 0;
    }
    return 0;
}

*QOJ19102 Monuments

先看 \(m=0\),把对称刻画为匹配,任意一组匹配中的两个点对称。分讨易知一对匹配的贡献为 \(|x_i+x_j|\),把 \(x\) 排序后最小配最大即可,如果 \(n\) 为奇数强行加一个 \(x_0=0\)。

对 \(m>0\),显然可以把固定且已经对称的一对点一起去掉。

(*)接下来的核心是,把固定点当成自由点参与匹配。不能匹配当且仅当两端都是固定点(对称的已经清除)。注意到不与固定点匹配的仍为最小配最大,这启发我们,一定是左侧 \(n/2\) 个点配右侧(显然能保证合法,无解当且仅当固定点数多于自由点数;最优性考虑交叉优于不交)。

然后自然地,考虑把左侧翻到右侧上面,对齐成二分图考虑,则把左侧坐标 \(\times(-1)\),然后贡献变为 \(|x_i-x_j|\)。后记第一行坐标为 \(x_i\),第二行坐标为 \(y_j\)。

注意到,最终匹配构造为分段形式,每一段要么是只有上下各一个自由点(第一类),要么固定点、自由点数量相同(第二类),并且段与段之间独立。第一类段是好做的,我们重点关注第二类怎么算。

考虑每个点何时系数为 \(+1\),何时为 \(-1\)。我们有 \(4\) 类点,按如下方式称呼:

  • \(0\) 类:第一行自由点;
  • \(1\) 类:第一行固定点;
  • \(2\) 类:第二行自由点;
  • \(3\) 类:第二行固定点。

记 \(tx_i,ty_j\) 为对应类别

以 \(0\) 类点为例,记 \(\displaystyle d_{0,i}=\sum_j[y_j\le x_i\land ty_j=3]-\sum_{i'}[x_{i'}\le x_i\land tx_{i'}=0]\),记一段 \([l,r]\) 对应 \(\displaystyle \Delta_{0,r}=\sum_{j=1}^r[ty_j=3]-\sum_{i=1}^r[tx_i=0]\)。\(x_i\) 贡献为正当且仅当 \(d_{0,i}\ge\Delta_{0,r}\),注意到若 \([l,r]\) 内固定点、自由点数量相同,则 \(\Delta_{0,l-1}=\Delta_{0,r}\),所以可以差分。

扫描 \(i\),注意到 \(\Delta_{*,i}\) 每次只变化 \(\mathcal{O}(1)\),直接拿桶维护即可,然后可以算出 \(sum_i\)。

则一段 \([l,r]\) 的贡献可以被记为 \(sum_r-sum_{l-1}\)。

然后 DP,要么从第一类段转移,要么从最短的以当前结尾的第二类段转义。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int N=500005;
template<typename T>
vector<T> merge(vector<T> a,vector<T> b){
    vector<T> c;
    int i=0,j=0;
    while(i<a.size()&&j<b.size()){
        if(a[i]<b[j]) c.push_back(a[i++]);
        else c.push_back(b[j++]);
    }
    while(i<a.size()) c.push_back(a[i++]);
    while(j<b.size()) c.push_back(b[j++]);
    return c;
}
int n;
int dx[N],dy[N],d[4];// >d: *(-1); <=d: *1
ll s[4][N],sum[N],f[N];
int lst[N<<1];
ll get_cost(vector<int> X, vector<int> P){
    vector<pii> pts_free,pts_fixed_neg2,pts_fixed_pos2;
    vector<pii> pts_neg,pts_pos,pts_fixed;
    for(int i=0,j=0;i<X.size();i++){
        if(j<P.size()&&P[j]==i){
            j++;
            if(X[i]<0) pts_neg.push_back({-X[i],-1});
            else pts_pos.push_back({X[i],1});
            continue;
        }
        pts_free.push_back({X[i],0});
    }
    reverse(pts_neg.begin(),pts_neg.end());
    pts_fixed=merge(pts_neg,pts_pos);
    if(X.size()&1) pts_fixed=merge(pts_fixed,{(pii){0,1}});
    int cnt=0;
    for(int i=0;i<pts_fixed.size();i++){
        cnt+=pts_fixed[i].second;
        if(i+1>=pts_fixed.size()||pts_fixed[i].first!=pts_fixed[i+1].first){
            if(pts_fixed[i].first==0) cnt=cnt&1;
            if(cnt<0) for(int k=1;k<=(-cnt);k++) pts_fixed_neg2.push_back({-pts_fixed[i].first,1});
            else if(cnt>0) for(int k=1;k<=cnt;k++) pts_fixed_pos2.push_back({pts_fixed[i].first,1});
            cnt=0;
        }
    }
    reverse(pts_fixed_neg2.begin(),pts_fixed_neg2.end());
    vector<pii> pts=merge(pts_free,pts_fixed_neg2);
    pts=merge(pts,pts_fixed_pos2);
    if(pts.empty()) return 0;
    // for(auto [x,t]:pts) printf("(%d,%d) ",x,t);
    // printf("\n");
    vector<pii> x,y;
    for(int i=0,j=pts.size()-1;i<j;i++,j--){
        x.push_back({-pts[i].first,pts[i].second});
        y.push_back({pts[j].first,pts[j].second+2});
    }
    reverse(x.begin(),x.end()),reverse(y.begin(),y.end());
    assert(x.size()==y.size());
    n=x.size();
    int delt[2]={0,0};
    for(int i=0,j=0;i<n;i++){
        delt[x[i].second&1]--;
        while(j<n&&y[j]<=x[i]) delt[(y[j++].second&1)^1]++;
        dx[i]=delt[x[i].second&1];
    }
    delt[0]=delt[1]=0;
    for(int j=0,i=0;j<n;j++){
        delt[y[j].second&1]--;
        while(i<n&&x[i]<y[j]) delt[(x[i++].second&1)^1]++;
        dy[j]=delt[y[j].second&1];
    }
    // for(int i=0;i<n;i++) printf("(%d,%d,%d) ",x[i].first,x[i].second,dx[i]);printf("\n");
    // for(int i=0;i<n;i++) printf("(%d,%d,%d) ",y[i].first,y[i].second,dy[i]);printf("\n");
    for(int i=0;i<n;i++){
        // printf("%d %d %d %d\n",d[0],d[1],d[2],d[3]);
        if(i) sum[i]=sum[i-1];
        s[x[i].second][dx[i]+n]+=x[i].first,s[y[i].second][dy[i]+n]+=y[i].first;
        // printf("%lld ",sum[i]);
        if(d[x[i].second]<=dx[i]) sum[i]+=x[i].first;
        else sum[i]-=x[i].first;
        if(d[y[i].second]<=dy[i]) sum[i]+=y[i].first;
        else sum[i]-=y[i].first;
        // printf("%lld ",sum[i]);
        sum[i]+=2*s[x[i].second][(--d[x[i].second])+n];
        sum[i]-=2*s[x[i].second^3][(d[x[i].second^3]++)+n];
        sum[i]+=2*s[y[i].second][(--d[y[i].second])+n];
        sum[i]-=2*s[y[i].second^3][(d[y[i].second^3]++)+n];
        // printf("%lld\n",sum[i]);
    }
    memset(lst,-1,sizeof(lst));
    int dif=0;
    for(int i=0;i<n;i++){
        if(x[i].second&1) dif++;
        else dif--;
        if(y[i].second&1) dif++;
        else dif--;
        if(!(x[i].second&1)&&!(y[i].second&1)){
            if(i) f[i]=f[i-1]+abs(x[i].first-y[i].first);
            else f[i]=abs(x[i].first-y[i].first);
        }
        else f[i]=infll;
        if(lst[dif+(n<<1)]!=-1) chkmin(f[i],f[lst[dif+(n<<1)]]+sum[i]-sum[lst[dif+(n<<1)]]);
        else if(!dif) chkmin(f[i],sum[i]);
        lst[dif+(n<<1)]=i;
    }
    if(f[n-1]>=infll) return -1;
    return f[n-1];
}

*QOJ13081/P13273 [NOI2025] 数字树

80pts

dfs 序与标记序列 \(t_u\) 一一对应,\(t_u\) 表示 \(u\) 先走左子还是右子。DFS 序是优美的当且仅当不存在两个数交叉(即 1212)。

两对叶子,当且仅当如下两种情况会对 \(t_u\) 产生限制:

.       .
|\      |\
1 x     | \
  |\    |  \
  2 x   x   x
    |\  |\  |\
    2 1 1 2 2 1

两个标 x 的点的 \(t\) 会绑定,于是连一条边。

由于保证有解,维护一下等价类,能 80pts。

*100pts

与 80pts 做法 完全不一样。

(*)发现 \(t_u\) 序列没什么好性质,考虑另一种做法,从一个合法的 dfs 序出发,每次 reverse 一个子树。这样记 \(S_u\) 为 \(u\) 子树内恰出现一次的颜色数,则所有 \(S\) 相同的点应恰 reverse 偶数次(注意若 \(|S|\le 1\),则没有这样的限制),设 \(|S|\ge 2\) 的 \(S\) 数量为 \(c\),则答案为

\[2^{2n-1-c} \]

考虑维护,这种集合数量肯定要哈希,有两种想法:

  1. 对一个时刻,维护每个点的哈希;
  2. 对一个点,维护其时间轴(本质上是最终的 \(S_u\))。

维护之后,把结点按字典序排序,则答案可以通过计算相邻两个点何时不同得到,难点在排序。

由于笔者忘了 线段树合并,所以在想法 1 上,用持久化线段树+哈希二分做到了 \(\log^3\)。

事实上,有 线段树合并,想法 2 是自然且好做的,排序的比较函数直接线段树上二分即可做到 \(\log^2\)。

关于求答案的细节:对排序后第 \(i\) 个点 \(p_i\),设 \(i\) 和 \(i-1\) 第 \(x\) 位开始不同,\(S_{p_i}\) 次小值为 \(y\),则 \(i\) 在 \(\max(x,y)\) 处开始产生贡献。

*bonus: 单 \(\log\)

有启发意义。

把线段树拓展成 \(2\) 的幂,然后可以类似后缀数组,从下往上逐层排序,每层可通过左子为第一关键字,右子为第二关键字排序。

其余部分都不是瓶颈,复杂度 \(\mathcal{O}(n\log n)\)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
typedef unsigned long long ull;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=1'000'000'007;
void add(int &x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
    x+=y;
    if(x>=MOD) x-=MOD;
    return x;
}
int qpow(int a,ll b){
    int mul=1;
    for(;b;b>>=1,a=(ll)a*a%MOD) if(b&1) mul=(ll)mul*a%MOD;
    return mul;
}
const ull B=2237;
const int N=200005;
ull pw[N];
int n,ch[N<<1][2],val[N<<2];
struct Node{
    int lc,rc,len;
    int cnt,rk;
    ull hsh;
    Node(){lc=rc=len=cnt=0,hsh=0;}
} tr[N<<7];
int L,tot=0,rt[N<<2];
vector<int> nds[N<<1];
void pushup(int x,int l,int r){
    int mid=(l+r)>>1;
    tr[x].cnt=tr[tr[x].lc].cnt+tr[tr[x].rc].cnt;
    tr[x].hsh=tr[tr[x].lc].hsh*pw[r-mid]+tr[tr[x].rc].hsh;
}
int merge(int x,int y,int l,int r){
    if(!x||!y) return x|y;
    int z=++tot;
    tr[z].len=r-l+1;
    if(l==r) return z;
    int mid=(l+r)>>1;
    tr[z].lc=merge(tr[x].lc,tr[y].lc,l,mid),tr[z].rc=merge(tr[x].rc,tr[y].rc,mid+1,r);
    pushup(z,l,r);
    return z;
}
void change(int &x,int l,int r,int p){
    tr[++tot]=tr[x],x=tot;
    tr[x].len=r-l+1;
    if(l==r) return tr[x].cnt^=1,tr[x].hsh^=1,void();
    int mid=(l+r)>>1;
    if(p<=mid) change(tr[x].lc,l,mid,p);
    if(p>mid) change(tr[x].rc,mid+1,r,p);
    pushup(x,l,r);
}
void dfs(int u){
    if(u<(n<<1)){
        for(int i=0;i<2;i++){
            dfs(ch[u][i]);
            rt[u]=merge(rt[u],rt[ch[u][i]],1,L);
        }
    }
    else change(rt[u],1,L,val[u]);
}
ull print(int x,int l,int r){
    assert(!x||tr[x].len==r-l+1);
    if(l==r){
        printf("%d",tr[x].cnt);
        assert(tr[x].cnt==tr[x].hsh);
        return tr[x].hsh;
    }
    int mid=(l+r)>>1;
    ull hsh=print(tr[x].lc,l,mid)*pw[r-mid]+print(tr[x].rc,mid+1,r);
    assert(tr[x].hsh==hsh);
    assert(tr[x].cnt==tr[tr[x].lc].cnt+tr[tr[x].rc].cnt);
    assert(tr[x].hsh==tr[tr[x].lc].hsh*pw[r-mid]+tr[tr[x].rc].hsh);
    return hsh;
}
int cnt[N<<3],mxr=1;
void sort(int l){
    int m=nds[l].size();
    vector<pii> onds(m);
    for(int i=0;i<=mxr;i++) cnt[i]=0;
    for(int i=0;i<m;i++) onds[i]={nds[l][i],tr[tr[nds[l][i]].rc].rk},cnt[onds[i].second]++;
    for(int i=1;i<=mxr;i++) cnt[i]+=cnt[i-1];
    for(int i=m-1;i>=0;i--) nds[l][--cnt[onds[i].second]]=onds[i].first;
    onds=vector<pii>(m);
    for(int i=0;i<=mxr;i++) cnt[i]=0;
    for(int i=0;i<m;i++) onds[i]={nds[l][i],tr[tr[nds[l][i]].lc].rk},cnt[onds[i].second]++;
    for(int i=1;i<=mxr;i++) cnt[i]+=cnt[i-1];
    for(int i=m-1;i>=0;i--) nds[l][--cnt[onds[i].second]]=onds[i].first;
    mxr=0;
    for(int i=0;i<m;i++){
        if(!i||tr[nds[l][i-1]].hsh!=tr[nds[l][i]].hsh) mxr++;
        tr[nds[l][i]].rk=mxr;
    }
}
int s;bool same;
int diff(int x,int y,int l,int r){
    if(s+tr[x].cnt<2||same&&tr[x].hsh==tr[y].hsh){
        s+=tr[x].cnt,same&=(tr[x].hsh==tr[y].hsh);
        return r+1;
    }
    if(l==r) return l;
    int mid=(l+r)>>1,tmp;
    if((tmp=diff(tr[x].lc,tr[y].lc,l,mid))<=mid) return tmp;
    return diff(tr[x].rc,tr[y].rc,mid+1,r);
}
int ans[N<<1];
void __INIT__(){
    pw[0]=1;
    for(int i=1;i<N;i++) pw[i]=pw[i-1]*B;
}
void __SOLVE__(){
    scanf("%d",&n);
    for(int i=1;i<(n<<1);i++) scanf("%d%d",&ch[i][0],&ch[i][1]);
    for(int i=1;i<=n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        val[a]=val[b]=i;
    }
    for(L=1;L<n;L<<=1);
    dfs(1);
    for(int i=1;i<=tot;i++){
        if(tr[i].len>1) nds[tr[i].len].push_back(i);
        else tr[i].rk=tr[i].cnt;
    }
    for(int l=2;l<=L;l<<=1) nds[l].push_back(0),sort(l);
    for(int i=1;i<nds[L].size();i++){
        s=0,same=true;
        int l=diff(nds[L][i],nds[L][i-1],1,L);
        if(l<=n) ans[l]++;
    }
    for(int i=1;i<=n;i++) printf("%d\n",qpow(2,(n<<1)-1-(ans[i]+=ans[i-1])));
}
int main(){
    int T=1;
    scanf("%*d");
    __INIT__();
    while(T--) __SOLVE__();
    return 0;
}

*CF2180H2 Bug Is Feature (Conditional Version)

递归形式的规律往往可以写成简单的 k-进制 形式。


考虑求 SG,只需分析单个游戏。设 \(d=b-a=c-b\),则可以用二元组 \((c,d)\) 记录状态,转移为:

  • \((c,d)\rightarrow (c+d,d)\):对应 \(a\leftarrow 2c-b\);
  • \((c,d)\rightarrow (c+2d,2d)\):对应 \(b\leftarrow 2c-a\)。

由于有上界,从大往小转移,考虑将 \((c,d)\) 改写为 \((\left\lfloor\frac{x-c}{d}\right\rfloor,1)\),设 SG 函数 \(f_{i,j}\),则 \(f_{i,j}=\text{mex}(f_{i-j,j},f_{i-2j,2j})\)。

注意到 \(f_{i,2^t}=f_{\left\lfloor\frac{i}{2^t}\right\rfloor,1}\),所以舍去第二维,则 \(f_i=\text{mex}(f_{i-1},f_{\left\lfloor\frac{i-2}{2}\right\rfloor})\)。然后开始打表找规律。

(*)注意到扔掉 0 后,12 交替出现,把 0 改为 .,把 12 改为 x。

(√)观察:

.xxx.x.x.xxx.xxx.xxx.x.x.xxx.x.x.xxx.x.x.xxx.xxx

奇数位都是 x,去掉:

.x...x.x.x...x...x...x.x

偶数位都是 .,去掉:

x.xxx.x.x.xx

发现此时进入上级结构,为上一轮 .x 反转的序列,而第一轮平移 1 之后也一样。

发现 \(i\) 是 x 当且仅当 \(i+1\) 结尾有偶数个 \(1\)。

发现我们要求的实际上是 \(f\) 的前缀异或和,所以考虑算 \(i\) 及以前有多少个 x,发现数位 DP 一下即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
ll a,b,c,l,r;
ll f[2][2],g[2][2];
int calcs(ll x){
    x++;
    memset(f,0,sizeof(f));
    f[1][0]=1;
    for(int i=60;i>=0;i--){
        memset(g,0,sizeof(g));
        for(int a=0;a<2;a++) for(int b=0;b<2;b++){
            for(int v=0;v<=(a?((x>>i)&1):1);v++){
                g[a&&(v==((x>>i)&1))][v?(b^1):0]+=f[a][b];
            }
        }
        memcpy(f,g,sizeof(g));
    }
    ll cnt=f[0][0]+f[1][0]-1;
    int res=0;
    if((cnt>>1)&1) res^=3;
    if(cnt&1) res^=1;
    return res;
}
int calcf(ll x){
    if(!x) return 0;
    return calcs(x)^calcs(x-1);
}
void __SOLVE__(){
    scanf("%d",&n);
    int sg=0;
    for(int i=1;i<=n;i++){
        scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&l,&r);
        ll d=b-a;
        ll nl=(l-c)/d,nr=(r-c)/d;
        if(nl==nr){
            if((r-l+1)&1) sg^=calcf(nl);
        }
        else{
            if(((nl+1)*d-l+c)&1) sg^=calcf(nl);
            if(d&1) sg^=calcs(nr-1)^calcs(nl);
            if((r-c-nr*d+1)&1) sg^=calcf(nr);
        }
    }
    if(!sg) printf("Feature\n");
    else printf("Bug\n");
}
int main(){
    int T=1;
    scanf("%d",&T);
    while(T--) __SOLVE__();
    return 0;
}
posted @ 2026-09-01 15:17  SmpaelFx  阅读(16)  评论(0)    收藏  举报