二月好题

本题单中所有题,在对照题解前后思考了一遍,在纸质笔记本上整理了第二遍,在这上面整理了第三遍,因为某些原因不少题整理了四遍

|LIS| = 3

题意

有一个长为 \(n\) 的数列,满足所有元素都在 \([1,m]\) 之间且其最长上升子序列长度为 \(3\)。求满足上述条件的不同数列数模 \(998244353\)\(3\le n\le 1000,3\le m\le 10\)

解法

因为 \(m\) 足够小,考虑进行类似数位 dp 的过程。设 \(dp_{i,j,k,l}\) 表示长为 \(i\),长为 \(1,2,3\) 的上升子序列的末尾数的最小值 \(j,k,l\)(若 \(j,k,l\) 不存在则默认为 \(x\),其中 \(x\) 大于 \(m\),例如 \(11\))。

转移方程如下:

\[dp_{i+1,a,k,l}\leftarrow dp_{i+1,a,k,l}+dp_{i,j,k,l}\ (a\le j) \]

\[dp_{i+1,j,a,l}\leftarrow dp_{i+1,j,a,l}+dp_{i,j,k,l}\ (j<a\le k) \]

\[dp_{i+1,j,k,a}\leftarrow dp_{i+1,j,k,a}+dp_{i,j,k,l}\ (k<a\le l) \]

初值有 \(dp_{0,x,x,x}=1\) ,其余 dp 值为 \(0\),目标为 \(\sum_{l\ne x,j,k} dp_{n,j,k,l}\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
int n,m,p,i,j,k,l,q;
const int md=998244353;
long long dp[1010][12][12][12],ans,t1,t2,t3;
int main(){
    scanf("%d%d",&n,&m);
    p=m++;
    for(j=p;j;--j) dp[1][j][m][m]=1;
    for(i=2;i<=n;++i){
        for(j=p;j;--j){
            t1=dp[i-1][j][m][m];
            for(k=p;k;--k){
                t2=dp[i-1][j][k][m];
                if(k>j){
                    dp[i][j][k][m]+=t1;
                    for(l=p;l;--l){
                        t3=dp[i-1][j][k][l];
                        if(l>k){
                            dp[i][j][k][l]+=t2;
                            for(q=l;q;--q){
                                if(q>k) dp[i][j][k][q]+=t3;
                                else if(q>j) dp[i][j][q][l]+=t3;
                                else dp[i][q][k][l]+=t3;
                            }
                        }
                        else{
                            if(l>j) dp[i][j][l][m]+=t2;
                            else dp[i][l][k][m]+=t2;
                        }
                    }
                }
                else dp[i][k][m][m]+=t1;
            }
        }
        for(j=1;j<=m;++j){
            for(k=j;k<=m;++k){
                for(l=k;l<=m;++l){
                    if(dp[i][j][k][l]>=md) dp[i][j][k][l]%=md;
                }
            }
        }
    }
    for(j=3;j<m;++j){
        for(k=2;k<j;++k){
            for(l=1;l<k;++l){
                ans+=dp[n][l][k][j];
                if(ans>=md) ans-=md;
            }
        }
    }
    printf("%d",ans);
    return 0;
}

Range Sort Query

题意

有一个长为 \(n\)\(1\sim n\) 的排列 \(p\)。有 \(q\) 组操作,每次操作要求将 \(p_l\sim p_r\) 升序或降序排序。求最后 \(k\)\(p\) 中的下标。\(n,q\le 2\cdot 10^5\)

p.s. 听说 P2824 和这道题挺像。

解法

考虑将值域范围缩小,同时确定 \(k\) 的位置。

可以构造两个长为 \(n\)\(01\) 序列 \(s_1,s_2\),其中 \(s_{1_i}=1\) 当且仅当 \(p_i\ge k\)\(s_{2_i}=1\) 当且仅当 \(p_i>k\) 。每次对 \(p_l\sim p_r\) 排序等效于同时对 \(s_{1_l}\sim s_{1_r}\)\(s_{2_l}\sim s_{2_r}\) 排序。而对于 \(01\) 序列升序/降序排序时很简单,只需要统计区间 \(1\) 的个数 \(a\),而后降序对应把 \(s_l\sim s_{l+a-1}\) 赋为 \(1\),把 \(s_{l+a}\sim s_r\) 赋为 \(0\);升序对应把 \(s_{r-a+1}\sim s_r\) 赋为 \(1\),把 \(s_l\sim s_{r-a}\) 赋为 \(0\)。可以用线段树做这两种操作。

最后遍历 \(s_1,s_2\),若有 \(i\) 满足 \(s_{1_i}\ne s_{2_i}\),则 \(i\) 即为答案。

时间复杂度:\(O((n+q)\log n)\)

如果把 \(01\) 序列优化成 \(012\) 序列,则只需构造一个序列,同样按上述方法维护,可以去掉 \(O(n\log n)\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int n,q,x,i,v,o,lt,rt,ans;
int l[maxn<<2],r[maxn<<2],pos[maxn];
bool v0[maxn],v1[maxn];
#define m(p) ((l[p]+r[p])>>1)
#define ls(p) p<<1
#define rs(p) p<<1|1
#define len(p) (r[p]-l[p]+1)
struct seg{
    int sum[maxn<<2],tag[maxn<<2];
    seg(){memset(tag,-1,sizeof(tag));}
    inline void Pushup(const int &p){
        sum[p]=sum[ls(p)]+sum[rs(p)];
    }
    inline void Pushdown(const int &p){
        if(~tag[p]){
            if(tag[p]){
                sum[ls(p)]=len(ls(p));
                sum[rs(p)]=len(rs(p));
                tag[ls(p)]=tag[rs(p)]=1;
            }
            else sum[ls(p)]=sum[rs(p)]=tag[ls(p)]=tag[rs(p)]=0;
            tag[p]=-1;
        }
    }
    void cover(const int p,const int &_l,const int &_r,const bool &d){
        if(_l<=l[p]&&_r>=r[p]){
            if(d){
                sum[p]=len(p);
                tag[p]=1;
            }
            else sum[p]=tag[p]=0;
            return;
        }
        Pushdown(p);
        if(_l<=m(p)) cover(ls(p),_l,_r,d);
        if(_r>m(p)) cover(rs(p),_l,_r,d);
        Pushup(p);
    }
    void query(const int p,const int &_l,const int &_r){
        if(_l<=l[p]&&_r>=r[p]){
            ans+=sum[p];
            return;
        }
        Pushdown(p);
        if(_l<=m(p)) query(ls(p),_l,_r);
        if(_r>m(p)) query(rs(p),_l,_r);
    }
}T[2];
void build(const int p,const int _l,const int _r){
    l[p]=_l;r[p]=_r;
    if(_l==_r){
        T[0].sum[p]=v0[_l]; 
        T[1].sum[p]=v1[_l];
        pos[_l]=p;return;
    }
    build(ls(p),_l,m(p));
    build(rs(p),m(p)+1,_r);
    T[0].Pushup(p);T[1].Pushup(p); 
}
void dfs(const int p){
    if(l[p]==r[p]){
        v0[l[p]]=T[0].sum[pos[l[p]]];
        v1[l[p]]=T[1].sum[pos[l[p]]];
        return;
    }
    T[0].Pushdown(p);
    T[1].Pushdown(p);
    dfs(ls(p));dfs(rs(p)); 
}
int main(){
    scanf("%d%d%d",&n,&q,&x);
    for(i=1;i<=n;++i){
        scanf("%d",&v);
        if(v>x) v0[i]=v1[i]=1;
        else if(v==x) v0[i]=1;
    }
    build(1,1,n);
    while(q--){
        scanf("%d%d%d",&o,&lt,&rt);
        T[0].query(1,lt,rt);
        if(ans&&rt-ans>=lt){
            if(o==1){
                T[0].cover(1,rt-ans+1,rt,1);
                T[0].cover(1,lt,rt-ans,0);
            }
            else{
                T[0].cover(1,lt,lt+ans-1,1);
                T[0].cover(1,lt+ans,rt,0);
            }
        }
        ans=0;
        T[1].query(1,lt,rt);
        if(ans&&rt-ans>=lt){
            if(o==1){
                T[1].cover(1,rt-ans+1,rt,1);
                T[1].cover(1,lt,rt-ans,0);
            }
            else{
                T[1].cover(1,lt,lt+ans-1,1);
                T[1].cover(1,lt+ans,rt,0);
            }
        }
        ans=0;
    }
    dfs(1);
    for(i=1;i<=n;++i){
        if(T[0].sum[pos[i]]^T[1].sum[pos[i]]){
            printf("%d\n",i);
            return 0;
        }
    } 
}

Two Exams

题意

\(n\) 个人,第 \(i\) 个人有两个分数 \(a_i,b_i\) 。现在要选 \(k\) 个数,满足若 \(\forall i,j,a_i>b_i,a_j>b_j\) ,则若选第 \(i\) 个人则必须选第 \(j\) 个人。求方案数模 \(998244353\)\(n,a_i,b_i\le 300,\forall i,j,i\ne j \rightarrow a_i\ne a_j,b_i\ne b_j\)

解法

突破口:如果把所有人按 \(a_i\) 升序排序,则对于某个人,如果其 \(b_i\) 大于未选者中的最小 \(b\) 值,则其一定不能选。

考虑以此建立 dp 方程。设 \(dp_{i,j,k}\) 表示前 \(i\) 个人中选了 \(j\) 个人,未选者的最小 \(b\) 值为 \(k\) 的方案数。转移有:

\[dp_{i+1,j,\min(b_{i+1},k)}\leftarrow dp_{i+1,j,\min(b_{i+1},k)}+dp_{i,j,k} \]

\[dp_{i+1,j+1,k}\leftarrow dp_{i+1,j+1,k}+dp_{i,j,k}(b_{i+1}<k) \]

初值:\(dp_{0,x,x}=1\),其中 \(x>n\),如 \(n+1\)。目标:\(\sum_{f\ne x} dp_{n,k,f}\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=310;
const long long md=998244353;
long long s,dp[maxn][maxn][maxn];
int n,k,i,v,j,p,b;
struct node{
    int v1,v2;
    inline bool operator <(const node &a)const{return v1<a.v1;}
}N[maxn];
int main(){
    scanf("%d%d",&n,&x);
    const int x=n+1;
    for(i=1;i<=n;++i) scanf("%d",&N[i].v1);
    for(i=1;i<=n;++i) scanf("%d",&N[i].v2);
    sort(N+1,N+x);dp[0][0][x]=1;
    for(i=1;i<=n;++i){
        b=min(i,k);v=N[i].v2;
        for(p=1;p<v;++p) dp[i][0][p]+=dp[i-1][0][p];
        for(p=v+1;p<=x;++p) dp[i][0][v]+=dp[i-1][0][p];
        for(p=1;p<=n;++p) if(dp[i][0][p]>=md) dp[i][0][p]%=md;
        for(j=1;j<=b;++j){
            for(p=1;p<v;++p) dp[i][j][p]+=dp[i-1][j][p];
            for(p=v+1;p<=x;++p){
                dp[i][j][v]+=dp[i-1][j][p];
                dp[i][j][p]+=dp[i-1][j-1][p];
            }
            for(p=1;p<=n;++p) if(dp[i][j][p]>=md) dp[i][j][p]%=md;
        }
    }
    for(p=1;p<=x;++p) s+=dp[n][k][p];
    if(s>=md) s%=md;
    printf("%d",s);
    return 0;
}

Cubic?

题意

有一个长为 \(n\) 的数组 \(a\)。有 \(q\) 个询问,每次询问 \(\prod_{i\in[l,r]}a_i\) 是否为完全立方数。\(n\le 2\cdot 10^5,a_i\le10^6\)

解法

突破口:将 \(1\sim 10^6\) 的数筛出质数,对 \(a_i\) 进行质因数分解,然后进行判断。

具体实现1(哈希):

考虑维护前缀信息。可以把每个前缀的每个质因数对应指数模 \(3\) 看成一个字符,把前缀的信息看成一个字符串。具体来讲,令 \(\prod_{j\le i}a_j=k^3p_1^{z_1}p_2^{z_2}\dots p_r^{z_r}\)\(h_i=hash(\overline{z_1z_2\dots z_r})\)(其中 \(k\) 为所有合法的 \(k\) 中最大值),则 \(\prod_{i\in[l,r]}a_i\) 当且仅当 \(h_r=h_{l-1}\)

具体实现2(莫队):

直接对加入/删除的每个数分解质因数,时刻维护上述的 \(\prod_{j\in [l,r]}a_j=k^3p_1^{z_1}p_2^{z_2}\dots p_r^{z_r}\) 中的 \(\sum_{i\le r} z_i\)\(\prod_{i\in[l,r]}a_i\) 为完全立方数当且仅当 \(\sum_{i\le r} z_i=0\)

代码

点此查看代码
//只展示hash写法
#include <bits/stdc++.h>
using namespace std;
const int maxv=1000010;
const int maxt=78600;
const int maxn=200010;
const long long pw=1997;
const long long md=1000000007;
int i,n,q,j,t,a,d,lt,rt,pr;
int v[maxv],p[maxt],r[maxv],cnt[maxv];
long long h1,h2,Pw[maxt],ha[maxn];
const char *ans[2]={"Yes\n","No\n"};
int main(){
    for(i=2;i<maxv;++i){
        if(!v[i]){
            v[i]=p[++t]=i;
            r[i]=t;
        }
        for(j=1;j<=t;++j){
            if(i*p[j]>=maxv||v[i]<p[j]) break;
            v[i*p[j]]=p[j];
        }
    }
    Pw[0]=1;
    for(i=1;i<=t;++i){
        Pw[i]=Pw[i-1]*3;
        if(Pw[i]>md) Pw[i]%=md;
    }
    scanf("%d%d",&n,&q);
    for(i=1;i<=n;++i){
        scanf("%d",&a);
        while(a!=1){
            d=v[a];pr=cnt[d];
            while(v[a]==d){
                a/=d;++cnt[d];
                if(cnt[d]==3) cnt[d]=0;
            }
            h1=((long long)Pw[r[d]])*(cnt[d]-pr);
            if(h1>=md) h1%=md;h2+=h1;
            if(h2<0) h2=(h2%md)+md;
            if(h2>=md) h2%=md;
        }
        ha[i]=h2;
    }
    while(q--){
        scanf("%d%d",&lt,&rt);
        printf("%s",ans[ha[rt]!=ha[lt-1]]);
    }
}

Range and Partition

题意

有一个长为 \(n\) 的数组 \(a\)。现在要求给定一个值域区间 \([x,y]\),使得存在一种把 \(a\) 分成连续的 \(k\) 段的方案,满足每段中在 \([x,y]\) 间的元素严格多于不在 \([x,y]\) 间的。求使得 \(y-x\) 最小的任意一种 \([x,y]\) 和对应的一种划分方案。\(t\) 组数据。\(\sum n\le 2\cdot10^5,1\le a_i\le n\)

解法

如果确定一个区间 \([x,y]\) 后考虑将 \(a\) 分成满足要求的连续 \(k\) 段,则可以新建一个数列 \(s\) 其中 \(\forall i\in [1,n]\),若 \(a_i\in[x,y]\)\(s_i=1\) ,否则 \(s_i=-1\)。此时某个子段满足要求当且仅当其对应 \(s\) 区间和为正。

故而 \(a\) 可按题意划分当且仅当 \(\sum_{i=1}^n s_i >k\),然后重复 \(k-1\) 次在 \(a\) 未被划分的前缀中取一段长度最小的区间即可构造对应方案。

故而可以统计 \(a\) 中不大于 \(i\) 的数出现的次数,记作 \(c_i\)。若 \(\exists l,r,c_r-c_{l-1}>\lceil \frac{n-k}2 \rceil\),则 \([l,r]\) 构成了一组合法解。使用双指针/二分扫描一遍即可找出使 \(y-x\) 最小的一组解。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int t,n,k,b,i,r,j,p,ax,ay,lst;
int s[maxn],a[maxn],v[maxn],c[maxn];
int main(){
    scanf("%d",&t);
    while(t--){
        ax=0;ay=1145141919;
        scanf("%d%d",&n,&k);b=(n+k)>>1;
        if((n+k)&1) ++b;
        for(i=1;i<=n;++i) scanf("%d",c+i);
        memcpy(s+1,c+1,n<<2);
        sort(s+1,s+n+1);s[n+1]=0;
        lst=v[1]=s[1];a[1]=r=1;
        for(i=2;i<=n;++i){
            while(s[i]==lst){
                ++a[r];
                ++i;
            }
            if(i>n) break;
            a[r+1]=a[r]+1;
            lst=v[++r]=s[i];
        }
        a[r+1]=v[r+1]=0;
        for(i=1;i<=r;++i){
            j=lower_bound(a+1,a+r+1,a[i-1]+b)-a;
            if(!a[j]) break;
            if(ay-ax>v[j]-v[i]){
                ay=v[j];
                ax=v[i];
            }
        }
        printf("%d %d\n",ax,ay);
        lst=1;p=0;
        for(i=1;i<=n;++i){
            if(c[i]>=ax&c[i]<=ay){
                ++p;
                if(p>0){
                    --k;if(!k) i=n; 
                    printf("%d %d\n",lst,i);
                    lst=i+1;p=0;
                }
            }
            else --p;
        }
    }
    return 0;
}

Paint the Middle

题意

有一个长为 \(n\) 的序列 \(a\)。现在要求进行若干次如下操作:找到两个值相同的数,删去它们中间的任意一个数。求最多操作次数。\(n\le 2\cdot 10^5,1\le a_i\le n\)

解法

此题解 的更清晰的说明。

考虑 dp,设 \(dp_i\) 表示 \(a_1\sim a_i\) 中删去的最多的数,\(f_i\) 表示 \(\max(\min_{j<i,a_j=a_i}j,-\inf)\)

考虑决策点 \(j\)\(f_i\) 的大小关系。

  1. \(j\) 小于 \(f_i\),则对于任意一个在 \((j,i)\)\(k\) 值,\(dp_k\) 一定不劣于 \(dp_j\),不必从 \(dp_j\) 转移。
  2. \(a_j=a_i\),则可以直接沿用 \(j\) 的最优解再删掉 \([j,i)\) 一段,即 \(dp_i=dp_j+i-j-1\)
  3. \(a_j\ne a_i\),则若 \([j,i)\)\([1,f_i)\) 之间有相同的数,一定有删去 \(f_i\) 会更优,其不会对 \([1,j]\) 之间的部分造成影响;若无相同的数,则 \(dp_j\) 一定为 \(dp_{f_i}\) 加上删去 \((f_i,j)\) 的数的贡献,一定不优于 \(f_i\),可以转为上一种情况。故而构成转移者只有 \(dp_j\) 包括删除 \(f_i\) 的情况,此时可以留下 \(f_i\),删去 \([j,i)\) 一段,即 \(dp_i=dp_j+i-j-1\)
  4. \(j=i-1\)\(f_i=-\inf\),此时可以直接转移为 \(dp_i=dp_{i-1}=dp_{i-1}+i-(i-1)-1\)

综上,我们有 \(dp_i=\max_{j\in (f_i,i)}(dp_j+i-j+1)\)。换一种方式表达,则有 \(dp_i-i=\max_{j\in (f_i,i)}(dp_j-j+1)\)。此时令 \(c_i=dp_i-i\),则 \(c_i=\max_{j\in (f_i,i)}(c_j+1)\)

可以用线段树维护区间最值。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int f[maxn],pos[maxn];
int n,i,a;
struct seg{
    int l,r,mx;
}tr[maxn<<2];
#define l(p) tr[p].l
#define r(p) tr[p].r
#define m(p) ((l(p)+r(p))>>1)
#define ls(p) p<<1
#define rs(p) p<<1|1
#define mx(p) tr[p].mx
inline void Pushup(const int &p){
    mx(p)=max(mx(ls(p)),mx(rs(p)));
}
void build(const int p,const int l,const int r){
    l(p)=l;r(p)=r;
    mx(p)=-1145141919;
    if(l==r){
        pos[l]=p;
        return;
    }
    build(ls(p),l,m(p));
    build(rs(p),m(p)+1,r);
}
void change(const int p,const int &x,const int &d){
    if(l(p)==r(p)){
        mx(p)=d;
        return;
    }
    if(x<=m(p)) change(ls(p),x,d);
    else change(rs(p),x,d);
    Pushup(p);
}
int Query(const int p,const int &l,const int &r){
    if(l<=l(p)&&r>=r(p)) return mx(p);
    int ret=-1145141919;
    if(l<=m(p)) ret=Query(ls(p),l,r);
    if(r>m(p)) ret=max(ret,Query(rs(p),l,r));
    return ret;
}
int main(){
    scanf("%d",&n);
    build(1,1,n);
    change(1,1,-1);
    for(i=1;i<=n;++i){
        scanf("%d",&a);
        if(!f[a]){
            change(1,i,mx(pos[i-1])-1);
            f[a]=i;
        }
        else change(1,i,Query(1,f[a],i-1)-1);
    }
    printf("%d",mx(pos[n])+n);
    return 0;
}

New Year Concert

题意

有一个长为 \(n\) 的数组 \(a\)。现在要求修改 \(a\) 的若干个数,满足修改后 \(a\) 任意子串的所有数的 \(\gcd\) 不等于子串长度。对于 \(\forall i\in[1,n]\),求 \(a_1\sim a_i\) 的最少修改次数。\(n\le 2\cdot 10^5,1\le a_i\le 10^9\)

解法

显然把修改者改成足够大的互不相同的质数即可,这样可以保证包括它们的长度不为 \(1\) 的区间 \(\gcd\)\(1\),包括它们的长度为 \(1\) 的区间 \(\gcd\) 不为 \(1\)。这样我们即不必考虑包括它们的区间。

同时,若区间右端点恒定为 \(i\),左端点从左往右递增,区间 \(\gcd\) 一定不增,这样可以得出区间长度等于区间 \(\gcd\) 的区间不超过 \(O(n)\) 个。

使用双指针扫到目前需要修改的区间,对区间末端修改,会影响到后面尽可能多的区间,显然比修改其他处更优。具体操作见代码。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int n,i,j,k,l,r,len,lst,ans;
int lb[maxn],st[20][maxn];
int Gcd(const int &x,const int y){
    if(!y) return x;
    return Gcd(y,x%y);
}
inline int SegGcd(const int _l,const int _r){
    const int le=lb[_r-_l+1];
    return Gcd(st[le][_l],st[le][_r-(1<<le)+1]);
}
int main(){
    scanf("%d%d",&n,st[0]+1);
    for(i=2;i<=n;++i){
        scanf("%d",st[0]+i);
        Read(st[0][i]);
        lb[i]=lb[i>>1]+1;
    }
    for(i=1;i<20;++i)
        for(j=1,k=1+(1<<(i-1)),l=(1<<i);l<=n;++j,++k,++l)
            st[i][j]=Gcd(st[i-1][j],st[i-1][k]);
    l=len=1;lst=-1;
    for(r=1;r<=n;++r){
        k=SegGcd(l,r);
        while(k<len){
            ++l;--len;
            k=SegGcd(l,r);
        }
        if(k==len&&l>lst){
            lst=r;
            ++ans;
        }
        ++len;
        printf("%d ",ans);
    }
    return 0;
}

Distance Tree (Easy Version)

Distance Tree (Hard Version)

题意

有一棵大小为 \(n\) 的无向树,每条边边权初始为 \(1\)。现在要求添加一条边权为 \(x\) 的边,\(d(p)\) 表示此时 \(1\)\(p\) 的最短路长度,\(g(x,u,v)\) 表示在 \(u\)\(v\) 间添加这条边的 \(\max_{i\in [2,n]}(d(i))\)\(f(x)\) 表示 \(\min_{u,v\in [1,n],u\ne v}g(x,u,v)\)。对于 \(\forall x\in[1,n]\),求 \(f(x)\)\(t\) 组数据。\(t\le 10^4\)

简单版:\(\sum n\le 3000\)。加强版:\(\sum n\le 3\cdot 10^5\)

解法

下面默认根节点为 \(1\)

定理 1:连边时必须以 \(1\) 为端点。

证明:令 \(dep_x\) 为连边前 \(1\sim x\) 的最短路长度,连的边为 \((u,v)\)。若 \(dep_u=dep_v\),则连边无意义;令 \(dep_u<dep_v\),则将边 \((u,v)\) 变为 \((1,v)\) 一定更优。

令原树上 \(u\)\(v\) 之间的距离为 \(dis(u,v)\),加入的边为 \((1,x)\),边权为 \(s\),则 \(d(p)=\min(dep_p,dep_x+dis(x,p))\)。故而若答案为 \(ans\)\(\forall k,dep_k>ans\),必有 \(dep_x+dis(x,k)>ans-x\)

定理 2:此时需要找一条最长的路径 \(u\rightarrow v\,\,(dep_u,dep_v>ans)\) 的中点 \(p\),加边 \((1,p)\)\(\lceil\frac{dis(u,v)}2\rceil+s\le ans\) 时必有 \(\forall k,d(k)\le ans\)

证明:若 \(\exists b,d(b)>ans\),则 \(dep(b)>ans,s+dis(p,b)>ans\),也就是 \(dis(p,b)>ans-s\ge \lceil\frac{dis(u,v)}2\rceil\),此时将会形成一条长于 \(dis(u,v)\) 的路径,与定义矛盾。

所以最终只需要记 \(q_a=\max_{dep_u,dep_v>a}dis(u,v)\) 即可实现在 \(O(n\log n)\)(二分)或 \(O(n)\)(双指针)实现。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=300010;
int n,i,u,v,t,mx,tot,ans;
int h[maxn],len[maxn];
struct edge{int to,nxt;}E[maxn<<1];
int dfs(const int &p,const int &f,const int d){
    int lp,to,m1=d,m2=d,rt;
    for(lp=h[p];lp;lp=E[lp].nxt){
        to=E[lp].to;
        if(to==f) continue;
        rt=dfs(to,p,d+1);
        if(rt>m1){m2=m1;m1=rt;}
        else if(rt>m2) m2=rt;
    }
    if((rt=min(m1,m2)-1)>=0) len[rt]=max(len[rt],m1+m2-(d<<1)+1);
    return m1;
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(i=1;i<n;++i){
            scanf("%d%d",&u,&v);
            E[++tot]={v,h[u]};
            h[u]=tot;
            if(u!=1){
                E[++tot]={u,h[v]};
                h[v]=tot;
            }
            len[i]=0;
        }
        mx=dfs(1,0,0);ans=tot=0;
        for(i=mx-1;i>=0;--i) len[i]=max(len[i],len[i+1]);
        for(i=1;i<=n;++i){
            while(ans<mx&&i+(len[ans]>>1)>ans) ++ans;
            printf("%d ",ans);
        }
        putchar('\n');
        memset(h,0,sizeof(h));
    }
    return 0;
}

Spanning Tree Queries

题意

有一张 \(n\) 个点 \(m\) 条带权边的无向联通图。\(q\) 组询问,每次给出一个 \(x\),询问将图中所有边的边权改为原边权减 \(x\) 的绝对值时,其最小生成树的边权之和。每个询问相互独立。\(n\le 50,m\le 300,q\le 10^7,x\le 10^8\)

解法

考虑绝对值函数的性质。

\(x>y\),可以发现若 \(|x-k|>|y-k|\),则有且只有 \(k>\frac{x+y}2\);若 \(|x-k|<|y-k|\),则有且只有 \(k<\frac{x+y}2\)\(|x-k|\)\(|y-k|\) 的大小关系只与 \(k\)\(\frac{x+y}2\) 的大小关系有关,令 \(f(x,y)=\frac{x+y}2\)。而 \(m\) 条边的 \(f\) 值不会超过 \(O(m^2)\),也就是使得最小生成树变化的 \(x\) 不会超过 \(O(m^2)\) 个。处理出这 \(O(m^2)\) 个最小生成树,查询每个 \(x\) 对应的最小生成树的形态即可。

时间复杂度:\(O((m^3+q)\log m)\)有一些优化可以去掉 log,但是意义不大。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=55;
const int maxm=305;
const int maxm2=135310;
int n,m,i,j,p,k,a,b,c,t,l,r,x,lt,all;
int fa[maxn],mid[maxm2],del[maxm2];
long long q,sum,s[maxm2];
struct edge{
    int u,v,w;
    inline bool operator <(const edge &a)const{return w<a.w;}
    inline bool operator <(const int &v)const{return w<v;}
}E[maxm];
int Find(const int p){
    if(p==fa[p]) return p;
    return fa[p]=Find(fa[p]);
}
inline bool Merge(int x,int y){
    x=Find(x);y=Find(y);
    if(x==y) return 0;
    --all;fa[x]=y;return 1;
}
int main(){
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;++i){
        scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
        mid[i]=E[i].w;
    }
    t=m;
    sort(E+1,E+m+1);
    for(i=1;i<m;++i){
        for(j=i+1;j<=m;++j){
            mid[++t]=(E[i].w+E[j].w)>>1;
            ++t;mid[t]=mid[t-1]+1;
            ++t;mid[t]=mid[t-2]-1;
        }
    }
    sort(mid+1,mid+t+1);
    l=mid[1];r=1;
    for(i=2;i<=t;++i){
        while(mid[i]==l) ++i;
        if(i>t) break;
        l=mid[++r]=mid[i];
    }
    t=r;mid[0]=mid[1]-1;
    E[0].w=-1073741824;
    E[m+1].w=1073741824;
    for(i=0;i<=t;++i){
        c=mid[i];
        for(j=1;j<=n;++j) fa[j]=j;
        all=n-1;x=sum=0;
        r=lower_bound(E+1,E+m+1,c)-E;
        l=r-1;b=c;c<<=1;
        for(;;){
            while(E[l].w+E[r].w>c){
                if(Merge(E[l].u,E[l].v)){
                    ++x;sum-=E[l].w;
                    if(!all) goto end;
                }
                --l;
            }
            if(Merge(E[r].u,E[r].v)){
                --x;sum+=E[r].w;
                if(!all) goto end;
            }
            ++r;
        }
        end:del[i]=x;s[i]=sum;
    }
    ++t;all=n-1;sum=r=0;
    mid[t]=1145141919;
    for(i=1;i<=n;++i) fa[i]=i;
    for(i=m;i;--i){
        if(Merge(E[i].u,E[i].v)){
            sum+=E[i].w;
            if(!all) break;
        }
    }
    s[t]=-sum;del[t]=n-1;
    scanf("%d%d%d%d%d",&p,&k,&a,&b,&c);
    k-=p;sum=0;
    while(p--){
        Read(q);
        x=lower_bound(mid,mid+t,q)-mid;
        sum^=(s[x]+del[x]*q);
    }
    while(k--){
        q=(q*a+b)%c;
        x=lower_bound(mid,mid+t,q)-mid;
        sum^=(s[x]+del[x]*q);
    }
    printf("%d",sum);
    return 0;
}

Finding Zero

题意

交互题。 有一个长为 \(n\) 的数组 \(a\),其中有且只有一个 \(0\)。可以发起不超过 \(2n-2\) 组询问,每次询问需给出三个数 \(i,j,k\),返回 \(\max(a_i,a_j,a_k)-\min(a_i,a_j,a_k)\)。最后需要回答两个数 \(i,j\),满足 \(a_ia_j=0\)\(t\) 组数据。\(t\le 500,4\le n\le 1000,\sum n\le 3000\)

解法

考虑只有 \(4\) 个数 \(\{a_1,a_2,a_3,a_4\}\) 时的做法。

我们依次询问所有数除去 \(a_i(i\in [1,4])\) 的极差。包括最大值和最小值的取法有两种(可能有三种),此时去掉后极差最大的两个数之间一定没有 \(0\)

这个题中 \(n\) 较大,但是可以沿袭上述的思路,每次在 \(4\) 个数之间除去非 \(0\) 的两个数,不断进行这个操作即可。若 \(n\) 为奇数,则在最后的 \(3\) 个数之间加入一个非 \(0\) 的数,其显然不会对答案造成影响。询问数不会超过 \(2n-2\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
int t,n,i,a,b,c,d,hd,lst;
int pre[maxn],nxt[maxn];
inline void Del(const int &v){
    pre[nxt[v]]=pre[v];
    nxt[pre[v]]=nxt[v];
    if(v==hd){
        hd=nxt[v];
        pre[hd]=0;
    }
    pre[v]=nxt[v]=0;
    lst=v;--n;
}
struct node{
    int v,i;
    inline bool operator <(const node &a)const{return v>a.v;}
}C[4];
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        hd=1;
        for(i=1;i<=n;++i){
            pre[i]=i-1;
            nxt[i]=i+1;
        }
        pre[1]=nxt[n]=0;
        while(n>2){
            a=C[0].i=hd;b=C[1].i=nxt[a];
            c=C[2].i=nxt[b];d=C[3].i=nxt[c];
            printf("? %d %d %d\n",b,c,d);
            fflush(stdout);
            scanf("%d",&C[0].v);
            printf("? %d %d %d\n",a,c,d);
            fflush(stdout);
            scanf("%d",&C[1].v);
            printf("? %d %d %d\n",a,b,d);
            fflush(stdout);
            scanf("%d",&C[2].v);
            printf("? %d %d %d\n",a,b,c);
            fflush(stdout);
            scanf("%d",&C[3].v);
            sort(C,C+4);Del(C[0].i);Del(C[1].i);
            if(n==3){
                pre[hd]=lst;
                nxt[lst]=hd;
                hd=lst;++n;
            } 
        }
        printf("! %d %d\n",hd,nxt[hd]);
        fflush(stdout);
    }
    return 0;
}

Fair Share

题意

\(m\) 个数组 \(a_1,a_2,\dots,a_m\),长度分别为 \(n_1,n_2,\dots,n_m\)。保证 \(\forall i\in[1,m],n_i\) 为偶数。现在需要将所有数划分为两部分,满足每个数组中刚好各有一半的数在两个部分,且每个数在两部分的出现次数相等。给出一种合法的划分方案,或判其为无解。\(\sum n_i\le 2\cdot 10^5,a_{i_j}\le 10^9\)

解法

显然某个值出现了奇数次时无解。

把每个数值和每个数组看成点,把某个数组在某个数中看成无向边,则本题的信息变成了一张图,满足每个点度数均为偶数。

若把某个数划分至某个集合看作给无向边定向,则每个点的入度和出度相等。可以在建出的图上每个连通块跑一遍欧拉回路,回路的方向即为一种划分的方案。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxm=100010,maxn=200010,maxd=300010;
int m,i,j,t,u,lst,top,cnt,tot,tote=1;
int head[maxd],has[maxm],stk[maxd],edg[maxd],rd[maxn];
bool b,tag[maxd],vis[maxn<<1],ans[maxn];
struct node{
    int val,bel,idx;
    inline bool operator <(const node &a)const{return val<a.val;}
}N[maxn];
struct edge{int to,ed,nxt;}E[maxn<<1];
inline void Add(const int &x,const int &y,const int &w){
    E[++tote]={y,w,head[x]};head[x]=tote;
    E[++tote]={x,w,head[y]};head[y]=tote;
}
const char s[3]="LR";
int main(){
    scanf("%d",&m);
    for(i=1;i<=m;++i){
        scanf("%d",has+i);
        for(j=1;j<=has[i];++j){
            ++tot;
            scanf("%d",&N[tot].val);
            N[tot].bel=i;
            N[tot].idx=tot;
        }
    }
    sort(N+1,N+tot+1);
    Add(N[1].bel,++m,N[1].idx);
    lst=N[1].val;cnt=1;
    for(i=2;i<=tot;++i){
        while(N[i].val==lst){
            Add(N[i].bel,m,N[i].idx);
            ++i;++cnt;
        }
        if(cnt&1){
            printf("NO");
            return 0;
        }
        if(i>tot) break;
        ++cnt;lst=N[i].val;
        Add(N[i].bel,++m,N[i].idx);
    }
    cnt=0;
    printf("YES\n");
    for(i=1;i<=m;++i){
        if(!tag[i]){
            stk[++top]=i;
            while(top){
                u=stk[top];
                j=head[u];
                tag[u]=1;
                while(vis[j]) j=E[j].nxt;
                if(j){
                    ++top;
                    stk[top]=E[j].to;
                    edg[top]=E[j].ed;
                    vis[j]=vis[j^1]=1;
                    head[u]=E[j].nxt;
                }
                else rd[++t]=edg[top--];
            }
        }
    }
    j=1;
    for(i=t;i;--i) ans[rd[i]]=(b^=1);
    for(i=1;i<=tot;++i){
        putchar(s[ans[i]]);
        ++cnt;
        if(cnt==has[j]){
            ++j;cnt=0;
            putchar('\n');
        }
    }
    return 0;
}

Fibonacci Additions

题意

有两个长为 \(n\) 的数组 \(a,b\)。有 \(q\) 次修改,每次选中一个数组 \(x\),给出 \([l,r]\),然后对于 \(\forall i\le[l,r]\)\(x_i\leftarrow (x_i+F_{i-l+1})\mod M\),其中 \(F\) 为斐波那契数列 \(1,1,2,\cdots\)。每次询问后,判断是否对于 \(\forall i\in[1,n],a_i=b_i\)\(n,q\le 3\cdot 10^5,M\le 10^9+7\)

解法

斐波那契数列有如下性质:\(\forall i>1,F_i-F_{i-1}-F_{i-2}=0\;(F_0=0)\)。考虑对 \(a,b\) 分别建立 \(k_a,k_b\),满足 \(\forall i\in[1,n+2],k_{a_i}=a_i-a_{i-1}-a_{i-2},k_{b_i}=b_i-b_{i-1}-b_{i-2}\),其中 \(\forall j\not \in[1,n],a_j=b_j=0\)

令某次操作对 \(\forall i\le[l,r]\)\(a_i\leftarrow (a_i+F_{i-l+1})\mod M\),则对于 \(k_a\),有 \(k_{a_l}\leftarrow k_{a_l}+1\)\(k_{a_{r+1}}\leftarrow k_{a_{r+1}}-F_{r-l+1}-F_{r-l+2}\)\(k_{a_{r+2}}\leftarrow k_{a_{r+2}}-F_{r-l+1}\)。这样只用对 \(k_a\) 进行 \(O(1)\) 次修改可表示一次操作的影响。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
using namespace FastIO;
const int maxn=300010;
int n,q,m,i,v,l,r,len,cnt;
char o;
long long a[maxn],b[maxn],c[maxn],f[maxn];
bool f1,f2;
inline void Upd(long long &p,const long long &b){
    f1=(bool)p;p+=b;
    p=(p%m+m)%m;f2=(bool)p;
    if(f1&&(!f2)) --cnt;
    else if(f2&(!f1)) ++cnt; 
}
const int opt[2]={1,-1};
int main(){
    scanf("%d%d%d%d",&n,&q,&m,a+1);
    f[1]=1;
    for(i=2;i<=n;++i){
        scanf("%d",a+i);
        f[i]=f[i-1]+f[i-2];
        if(f[i]>=m) f[i]-=m;
    }
    for(i=1;i<=n;++i) scanf("%d",b+i);
    n+=2;
    for(i=n;i>1;--i){
        a[i]-=a[i-1]+a[i-2];
        a[i]=(a[i]%m+m)%m;
        b[i]-=b[i-1]+b[i-2];
        b[i]=(b[i]%m+m)%m;
        c[i]=a[i]-b[i];
        if(c[i]) ++cnt;
    }
    cnt+=(bool)(c[1]=a[1]-b[1]);
    while(q--){
        scanf("%c%d%d",&o,&l,&r);
        o-='A';len=r-l+1;
        Upd(c[l],1LL*opt[o]);
        Upd(c[r+1],(f[len-1]+f[len])*opt[!o]);
        Upd(c[r+2],f[len]*opt[!o]);
        if(cnt) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

Best Pair

题意

有一个长为 \(n\) 的数组 \(a\),定义 \(c_i\)\(a\)\(i\) 出现的次数。同时给定一个长为 \(2m\) 的二元对数组 \(p=\{(x_1,y_1),(y_1,x_1),(x_2,y_2),(y_2,x_2),\cdots,(x_m,y_m),(y_m,x_m)\}\)。 令

\[f(x,y)=\begin{cases}-\inf, &x=y\cup \exists i\in[1,2m],(x,y)=p_i\\ (c_x+c_y)(x+y), & \text{otherwise.}\end{cases}\]

\(\max f(x,y)\)\(t\) 组数据。\(\sum n,m\le 3\cdot 10^5,a_i\le 10^9\)

解法

我们对每个数的出现次数进行排序,同样出现次数的数降序排序,显然出现次数不会超过 \(O(\sqrt n)\) 个。然后对于每一个 \(x\),对每种出现次数选取不会与上述 \(p\) 造成冲突的中最大的一个。由于 \(c_x+c_y\) 确定,所以选最大的一个之后即不必往后考虑。并且每个冲突最后会被统计不超过两次,故而时间复杂度是对的。

时间复杂度:\(O(n\sqrt n+m)\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=300010;
int t,n,m,i,j,k,c,r,lt,rt,lp,rp,lst,top;
int a[maxn],s[maxn],p[maxn],v[maxn];
int st[maxn],ed[maxn],stk[maxn];
long long ans;
inline bool cmp(const int &x,const int &y){
    if(a[x]!=a[y]) return a[x]>a[y];
    return x>y; 
}
set<pair<int,int> > S;
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;++i) scanf("%d",v+i);
        sort(v+1,v+n+1);v[n+1]=0;
        a[1]=s[1]=r=1;p[1]=lst=v[1];
        for(i=2;i<=n;++i){
            while(v[i]==lst){++i;++a[r];}
            p[++r]=lst=v[i];s[r]=r;a[r]=1;
        }
        if(!p[r]) --r;p[r+1]=0;
        for(i=1;i<=m;++i){
            scanf("%d%d",&lt,&rt);
            lp=lower_bound(p+1,p+r+1,lt)-p;
            rp=lower_bound(p+1,p+r+1,rt)-p;
            if(p[lp]!=lt) continue;
            if(p[rp]!=rt) continue;
            S.insert(make_pair(lp,rp));
            S.insert(make_pair(rp,lp));
        }
        sort(s+1,s+r+1,cmp);
        lst=a[s[1]];st[lst]=top=1;
        stk[1]=lst;
        for(i=2;i<=r;++i){
            while(a[s[i]]==lst) ++i;
            if(i>r) break;
            ed[lst]=i-1;lst=a[s[i]];
            st[lst]=i;stk[++top]=lst;
        }
        ed[lst]=r;
        for(i=1;i<=top;++i){
            lt=stk[i];
            for(j=st[lt];j<=ed[lt];++j){
                for(k=i;k<=top;++k){
                    rt=stk[k];
                    if(k==i) c=j+1;
                    else c=st[rt];
                    for(;c<=ed[rt];++c){
                        if(S.find(make_pair(s[c],s[j]))!=S.end()) continue;
                        ans=max(ans,1ll*(lt+rt)*(p[s[c]]+p[s[j]]));
                        break;
                    }
                }
            } 
        }
        printf("%lld\n",ans);
        ans=r=0;S.clear();
        S.insert(make_pair(0,0));
    }
    return 0;
}

Towers

题意

有一棵大小为 \(n\) 的树,第 \(i\) 个点有一个点权 \(h_i\)。现在要在第 \(i\) 个点上赋值 \(e_i\)。定义某个点 \(i\) 被覆盖当且仅当 \(\exists u,v\in[1,n],u\ne v,e_u\ge h_i\cap e_v\ge h_i\),且 \(u\)\(v\) 间的简单路径经过 \(i\)(包括 \(u\)\(v\))。现在需要每个点被覆盖,求 \(\min\sum_{i=1}^n e_i\)

解法

我们把 \(h\) 值最大的点作为根节点,则显然需要在其两棵子树上有 \(e\) 值不小于自己的 \(h\) 值的点。

而在非根节点上,子树中一定有至少一个不小于于本身 \(h\) 点的 \(e\) 点(上述分析中一定有一个 \(e\) 值更大的不在该点子树上的点)。这样的话叶子节点 \(e\) 值必不为 \(0\),对子树的 \(e\) 值最大的点增加 \(e\) 值一定优于其他点。而对于根节点,维护其所有子树最大 \(e\) 值的最大值和次大值即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int n,i,r,x,y,t,m1,m2,nw;
int h[maxn],v[maxn];
struct edge{int to,nxt;}E[maxn<<1]; 
long long ans;
int dfs(const int &p,const int &f){
    int lp,to,mx=0;
    for(lp=h[p];lp;lp=E[lp].nxt){
        to=E[lp].to;
        if(to==f) continue;
        mx=max(mx,dfs(to,p));
    }
    if(mx<v[p]){
        ans+=v[p]-mx;
        return v[p];
    }
    return mx;
}
int main(){
    scanf("%d",&n);
    for(i=1;i<=n;++i){
        scanf("%d",v+i);
        if(v[r]<v[i]) r=i;
    }
    for(i=1;i<n;++i){
        scanf("%d%d",&x,&y);
        E[++t]={x,h[y]};h[y]=t;
        E[++t]={y,h[x]};h[x]=t;
    }
    for(i=h[r];i;i=E[i].nxt){
        nw=dfs(E[i].to,r);
        if(m1<nw){
            m2=m1;
            m1=nw;
        }
        else if(m2<nw) m2=nw;
    }
    if(m2<v[r]){
        ans+=v[r]-m2;
        if(m1<v[r]) ans+=v[r]-m1;
    }
    printf("%lld\n",ans);
    return 0;
}

Birthday

题意

有一个长为 \(n\) 的数组 \(a=\{1,2,\cdots,n\}\)。每次可以选取 \(x,y\in a(x\ge y)\),将它们取出 \(a\),同时将 \(x+y\)\(x-y\) 加入 \(a\)。现在能做不超过 \(20n\) 次上述操作,求能否将 \(a\) 变为同一个数 \(x\)。如果有解,输出使 \(x\) 最小的操作步骤;如果还有多解,输出任意一种操作步骤。\(t\) 组数据。\(\sum n\le5\cdot 10^4\)

解法

显然 \(n=2\) 时无解。

考虑最终成为的数有哪些性质。

手动模拟可以猜想最终的数一定为 \(2\) 的某次幂形式。证明:若最终的数有奇数质因子(记为 \(p\)),则模 \(p\)\(a\) 和模 \(p\)\(b\) 的数(\(a\ne b,a,b\ne 0\)),则将它们操作之后一定不会模 \(p\) 同余,同时模 \(p\)\(a\) 的两个数操作之后一定不会均整除 \(p\)。而在所有操作过程中,一定会有这两种情况之一出现。

故而 \(1\sim n\) 可以经过操作的最小数为 \(2^{\lfloor\log_2n\rfloor}\),记 \(f(n)=\lfloor\log_2n\rfloor\)。考虑一种进行操作的方式:

\(\{1,2,\cdots,c\}(c>2)\) 进行操作时,可以将 \(2^{f(c)}\) 作为本次需要产生的数,记为 \(x\)。(若 \(x=c\),则 \(c\leftarrow c-1\))此时需要进行操作的只有 \(\{x-c,x-c+1,\cdots,c\}\)。我们依次将 \((x-c,c),(x-c+1,c-1),\cdots,(\frac{x}2-1,\frac{x}2+1)\) 取出进行操作。此时原序列变成以下三部分:

  • \(\{1,2,\cdots,x-c-1\}\)。对它们递归上述操作。
  • \(\{2,4,\cdots,2c-x\}\)。将它们除以二,则其变成 \(\{1,2,\cdots,c-\frac{x}2\}\)。递归进行上述操作,同时维护它们被除以二的次数。
  • \(c-\frac{x}2\)\(x\) 和一个 \(\frac{x}2\),可能还有初始时的 \(c\)。用桶维护它们出现的次数。

特别地,对于 \(\{1,2\}\),直接用桶维护它们出现的次数即可。

此时我们用不超过 \(n\) 次操作,将原数组变成了 \(c_0\)\(2^0\)\(c_1\)\(2^1\),直到 \(c_{f(n)}\)\(2^{f(n)}\)

我们对它依次进行两次遍历。第一次遍历时对 \(\forall k<f(n)\),将相同的 \(2^k\) 进行操作,分别变成 \(\lfloor\frac{c_k}2\rfloor\)\(2^{k+1}\)\(0\)。这些操作的次数会是不超过 \(n\) 次的,同时 \(\forall c_k\!\!\!\mod 2=1\),操作之后 \(c_k=1\)

第二次遍历时,对于 \(\forall c_k\ne 0\),将 \(2^k\)\(0\) 进行两次操作,得到了 \((2^k,2^k)\rightarrow (2^{k+1},0)\)。依次进行这样的操作,次数不会超过 \(n\log_2n\)

最后将 \(0\)\(2^{f(n)}\) 进行操作即可,此操作显然不会到 \(n\) 次。

证明第一次操作一定会产生 \(0\)(或 \(\exists k<f(n),c_k>1\)):

考虑对 \(\{1,2,\cdots,n\}\) 的操作。对其操作后会留有 \(\{1,2,\cdots,2^{f(n)}-n-1\}\)\(\{2,4,\cdots,2n-2^{f(n)}\}\)\(2^{f(n)-1}\)。而 \(\max(2n-2^{f(n)},2^{f(n)}-n-1)\ge\lceil\frac{n-1}2\rceil\),则 \(f(2n-2^{f(n)}),f(2^{f(n)}-n-1)\) 中必有 \(f(n)-1\)。(有更好的方法请在评论区指出)

操作数不会高于 \(n\log_2n\),可以保证通过。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=65600;
const int all=1000010;
int t,n,i,e,j=1,tot,top;
int v[maxn],a[maxn],lg2[maxn];
int s1[all],s2[all],tp[maxn];
void dfs(const int l,int r,const int k){
    if(l>r) return;
    if(l==r){
        ++a[1<<k];
        return;
    }
    if(r-l==1){
        ++a[1<<k];
        ++a[1<<(k+1)];
        return;
    }
    int lp=l;const int s=lg2[v[l]+v[r]];
    const int la=lp=lower_bound(v+l,v+r+1,s-v[r])-v;
    while(lp<r){
        s1[++top]=v[lp]<<k;
        s2[top]=v[r]<<k;
        ++lp;--r;++tot;
        tp[tot]=tot;++a[s<<k];
    }
    ++a[(s>>1)<<k];
    memcpy(v+la,tp+1,tot<<2);
    const int ed=la+tot-1;
    tot=0;dfs(l,la-1,k);
    dfs(la,ed,k+1);
}
int main(){
    for(i=1;i<=65560;++i){
        if(i>j) j<<=1;
        lg2[i]=j;
    }
    scanf("%d",&t);
    while(t--){
        top=0;
        scanf("%d",&n);
        if(n==2){
            fputs("-1\n",stdout);
            continue;
        }
        for(i=1;i<=n;++i) v[i]=i;
        e=lg2[n];dfs(1,n,0);
        for(i=1;i<e;i<<=1){
            a[0]+=a[i]>>1;
            a[i<<1]+=a[i]>>1;
            for(j=2;j<=a[i];j+=2){
                ++top;
                s1[top]=s2[top]=i;
            }
            a[i]&=1;
        }
        for(i=1;i<e;i<<=1){
            while(a[i]){
                s1[++top]=i;
                s2[top]=0;
                s1[++top]=i;
                s2[top]=i;
                ++a[i<<1];
                --a[i];
            }
        }
        for(i=0;i<a[0];++i){
            s1[++top]=e;
            s2[top]=0;
        }
        a[0]=a[e]=0;
        printf("%d\n",top);
        for(i=1;i<=top;++i) printf("%d %d\n",s1[i],s2[i]);
    } 
}

Big Brush

题意

有一个 \(n\times m\) 的矩阵 \(a\),初始全为 \(0\) 。现在可进行若干次操作,每次操作可选定 \(i,j,k(i<n,j<m)\) ,然后将 \(a_{i,j},a_{i+1,j},a_{i,j+1},a_{i+1,j+1}\) 赋值为 \(k\)。求能否在不超过 \(nm\) 次操作后,将 \(a\) 变为给定的 \(n\times m\) 矩阵 \(c\)。如果能则输出任意一种操作方案。

解法

考虑一个经典的套路:从结果出发倒序操作。

显然每一次操作后必有 \(2\times 2\) 的值相同的区域。我们可以从最后完整的 \(2\times 2\) 区域出发,倒序操作,使用 bfs 维护操作过程,同时在定下某次操作后令这个 \(2\times 2\) 的区域为 \(0\)。然后定义一个同值的 \(2\times 2\) 的区域为其中可以有 \(0\)(但是不全为 \(0\),避免操作重复),每次操作后在周围 \(4\times 4\) 的区域内寻找产生的同色区域即可。如果最终还有为 \(0\) 的区域则可以判为无解。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxc=35;
const int maxn=1010;
const int maxnm=1000010;
#define Xor(x,y) (x&&y)&&(x!=y)
int n,m,x,y,i,j,t,k,p,top;
struct pos{int x,y;};
queue<pos> q;
int ax[maxnm],ay[maxnm],ac[maxnm];
int c[maxn][maxn],tx[maxc],ty[maxc];
const int dx[8]={-1,-1,-1,0,0,1,1,1};
const int dy[8]={-1,0,1,-1,1,-1,0,1};
inline bool chk(const int &x,const int &y){
    const int a=c[x][y];
    if(!a) return 0;
    if(a!=c[x-1][y]) return 0;
    if(a!=c[x][y-1]) return 0;
    if(a!=c[x-1][y-1]) return 0;
    return 1;
}
inline int chkb(const int &x,const int &y){
    if(x==1||!y) return 0;
    if(x>n||y>=m) return 0;
    int a=c[x][y];
    if(!a) a=c[x-1][y];if(!a) a=c[x][y+1];
    if(!a) a=c[x-1][y+1];if(!a) return 0;
    if(Xor(a,c[x][y])) return 0;
    if(Xor(a,c[x-1][y])) return 0;
    if(Xor(a,c[x][y+1])) return 0;
    if(Xor(a,c[x-1][y+1])) return 0;
    return a;
}
int main(){
    scanf("%d%d",&n,&m);
    memset(c,-1,sizeof(c));
    for(i=1;i<=n;++i){
        for(j=1;j<=m;++j){
            scanf("%d",c[i]+j);
            if(chk(i,j)){
                q.push((pos){i,j-1});++t;
                ax[t]=i-1;ay[t]=j-1;ac[t]=c[i][j-1];
                c[i][j]=c[i-1][j]=c[i-1][j-1]=c[i][j-1]=0;
            }
        }
    }
    while(q.size()){
        i=q.front().x;
        j=q.front().y;
        q.pop();
        for(k=0;k<8;++k){
            i+=dx[k];j+=dy[k];
            p=chkb(i,j);
            if(p){
                ++top;tx[top]=i;ty[top]=j;
                ++top;tx[top]=i-1;ty[top]=j;
                ++top;tx[top]=i;ty[top]=j+1;
                ++top;tx[top]=i-1;ty[top]=j+1;
                q.push((pos){i,j});++t;
                ax[t]=i-1;ay[t]=j;ac[t]=p;
            }
            i-=dx[k];j-=dy[k];
        }
        while(top){
            c[tx[top]][ty[top]]=0;
            --top;
        }
    }
    for(i=1;i<=n;++i){
        for(j=1;j<=m;++j){
            if(c[i][j]){
                printf("-1");
                return 0;
            }
        }
    }
    printf("%d\n",t);
    do{printf("%d %d %d\n",ax[t],ay[t],ac[t]);}while(--t); 
    Flush();
    return 0;
}

Colorful Operations

题意

有一个长为 \(n\) 的数组 \(a\),初始全为 \(0\),颜色也均为 \(0\)。现在要进行 \(q\) 次如下操作:将 \([l,r]\) 区间染成同一种颜色 \(x\),将颜色为 \(c\) 的元素全部加上 \(x\),输出目前 \(a_x\) 的值。\(n,q\le 10^6\)

解法

考虑对三种操作的实现。

对于第一种操作,可以直接用线段树维护颜色,进行区间染色。

对于第二种操作,可以记 \(f_i\) 为某种颜色被加的数的总和,然后对 \(c\) 色的数加 \(v\) 时,令 \(f_c\leftarrow f_c+v\),以避免在 \(a\) 的不连续段加上某个数。同时需要考虑某个数的变色问题。设 \(c_i\)\(a_i\) 的颜色,\(d_i=a_i-f_{c_i}\),则在将 \(a_i\) 染成 \(x\) 色时需要 \(d_i\leftarrow d_i+a_{c_i}-a_x\),此时 \(d_i+a_{c_i}=d_i+a_{c_i}-a_x+a_x\),避免了冲突的问题。同时第一种操作涉及在线段树同色段区间加,但是如果涉及到了 \(k\) 个同色段,则时间复杂度会是 \(O(k\log n)\) 的,但是同时涉及了染色,同色段将会减少 \(O(k)\) 个而增加 \(O(1)\) 个,总共复杂度不会超过 \(O(q\log n)\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000010;
int n,q,pt,lt,rt,co;
long long val[maxn];
char o[7];
struct seg{
    int l,r,ct,col;
    long long adt;
    bool one;
}tr[maxn<<2];
#define l(p) tr[p].l
#define r(p) tr[p].r
#define m(p) ((l(p)+r(p))>>1)
#define ls(p) p<<1
#define rs(p) p<<1|1 
#define ct(p) tr[p].ct
#define adt(p) tr[p].adt
#define col(p) tr[p].col
#define one(p) tr[p].one
void build(const int p,const int l,const int r){
    l(p)=l;r(p)=r;
    col(p)=one(p)=1;
    if(l==r) return;
    build(ls(p),l,m(p));
    build(rs(p),m(p)+1,r);
}
inline void Pushup(const int &p){
    col(p)=col(ls(p));
    one(p)=one(ls(p))&&one(rs(p))&&col(ls(p))==col(rs(p));
}
inline void Pushdown(const int &p){
    if(adt(p)){
        adt(ls(p))+=adt(p);
        adt(rs(p))+=adt(p);
        adt(p)=0;
    }
    if(ct(p)){
        ct(ls(p))=ct(rs(p))=col(ls(p))=col(rs(p))=ct(p);
        one(ls(p))=one(rs(p))=1;ct(p)=0;
    }
}
void cover(const int p,const int &l,const int &r,const int &c){
    if(l<=l(p)&&r>=r(p)&&one(p)){
        adt(p)+=val[col(p)]-val[c];
        ct(p)=col(p)=c;return;
    }
    Pushdown(p);
    if(l<=m(p)) cover(ls(p),l,r,c);
    if(r>m(p)) cover(rs(p),l,r,c);
    Pushup(p);
}
inline long long query(const int &x){
    pt=1;
    while(l(pt)!=r(pt)){
        Pushdown(pt);
        if(x<=m(pt)) pt=ls(pt);
        else pt=rs(pt);
    }
    return adt(pt)+val[col(pt)];
}
int main(){
    scanf("%d%d",&n,&q);
    build(1,1,n);
    while(q--){
        scanf("%s%d",o,&lt);
        if(o[0]=='C'){
            scanf("%d%d",&rt,&co);
            cover(1,lt,rt,co);
        }
        else if(o[0]=='A'){
            scanf("%d",&co);
            val[lt]+=co;
        }
        else printf("%lld\n",query(lt));
    }
    return 0;
}

Cars

题意

在数轴上有 \(n\) 辆位置互不相同的车。它们初始都有一个方向,且都会一直向着这个方向做速度不为 \(0\) 的运动。已知有 \(m\) 对车是一定会相遇或不会相遇的,求每辆车初始时可能的任意一种方向和位置。\(n,m\le 2\cdot 10^5\)

解法

首先对于一定不相遇的车和一定相遇的车,显然其方向不相同,可以建图后用二分图染色确定其方向。记向左为 \(0\),向右为 \(1\),第 \(i\) 辆车的方向为 \(d_i\),坐标为 \(x_i\)

然后若两辆车 \(i,j\) 一定不会相遇,则必有 \([x_i>x_j]\oplus[d_i<d_j]\);若它们一定会相遇,则必有 \([x_i>x_j]\oplus[d_i<d_j]\)。在二分图染色的同时建好差分约束系统,用拓扑排序跑一边最长路即可。

最后要求所有车坐标不相同,可以把 \(x\) 升序排序后,把排序后的位置作为新的 \(x_i\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int n,m,i,u,v,o,toe,tob,cnt;
int in[maxn],col[maxn],hb[maxn],dis[maxn],he[maxn];
struct Edge{int to,nxt;}E[maxn<<1];
struct edge{int to,nxt;bool ed;}B[maxn<<1];
queue<int> q;
const char D[3]="LR";
inline void Add(const int &x,const int &y){
    E[++toe]={y,he[x]};
    he[x]=toe;++in[y];
}
void dfs(const int p){
    for(int _y,_i=hb[p];_i;_i=B[_i].nxt){
        _y=B[_i].to;
        if(!(~col[_y])){
            col[_y]=!col[p];
            dfs(_y); 
        }
        else if(col[_y]==col[p]){
            printf("NO");
            exit(0); 
        }
        if(col[p]^B[_i].ed) Add(p,_y);
        else Add(_y,p); 
    }
}
struct node{
    int val,idx;
    inline bool operator <(const node &a)const{return val<a.val;}
}N[maxn];
int main(){
    scanf("%d%d",&n,&m);
    memset(col,-1,sizeof(col));
    while(m--){
        scanf("%d%d%d",&o,&u,&v);o&=1;
        B[++tob]={u,hb[v],o};hb[v]=tob;
        B[++tob]={v,hb[u],o};hb[u]=tob;
    }
    for(i=1;i<=n;++i){
        if(~col[i]) continue;
        col[i]=0;dfs(i);
    }
    for(i=1;i<=n;++i){
        N[i].idx=i;
        if(!in[i]) q.push(i);
    }
    while(!q.empty()){
        u=q.front();q.pop();++cnt;
        for(i=he[u];i;i=E[i].nxt){
            v=E[i].to;
            if(dis[v]<=dis[u]) dis[v]=dis[u]+1;
            if(!(--in[v])){
                q.push(v);
                N[v].val=dis[v];
            }
        }
    } 
    if(cnt!=n){
        printf("NO");
        return 0;
    }
    printf("YES\n");
    sort(N+1,N+n+1);
    for(i=1;i<=n;++i) dis[N[i].idx]=i;
    for(i=1;i<=n;++i) printf("%c %d\n",D[col[i]],dis[i]);
    return 0;
}

Closest Pair

题意

有两个长为 \(n\) 的数组 \(x,w\)。定义 \(f(i,j)=|x_i-x_j|(w_i+w_j)\)。现在给出 \(q\) 次询问,每次给出一段区间 \([l,r]\),询问 \(\min_{i,j\in[l,r],i\ne j}f(i,j)\)\(n,q\le 3\cdot 10^5\)\(x\) 单增。

解法

首先只考虑 \(i<j\) 的情况,从而 \(f(i,j)=(x_j-x_i)(w_j+w_i)\),去掉绝对值符号。

考虑 \(\forall i<k<j\)\(k\) 什么时候可以取代 \(i\),即 \(f(i,j)>f(k,j)\)。因为 \(x_j-x_i>x_k-x_i\),所以 \(w_k\le w_i\),即 \(w_k+w_j\le w_i+w_j\),则必会有 \(f(i,j)>f(k,j)\)。可以维护一个 \(w\) 值单调递增的单调栈维护目前最优的 \(i\)

然后考虑 \(\forall i<k<j\),三者均在栈中时,可以发现 \(k\) 一定可以取代 \(j\)。故而某个元素 \(i\) 入栈时,只有其后继更换/自己被某元素弹出时,再统计答案。记 \(f_i\) 为上述答案的最小值,将询问区间离线下来,按右端点排序,则 \(\exists j,i=r_j\) 时,\(\min_{k=l_j}^{r_j} f_k\) 即为答案。可以用线段树更新最值。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=300010;
int n,q,i,t,_l,_r,h[maxn],stk[maxn];
long long que,a[maxn],w[maxn],ans[maxn];
struct edge{int l,nxt;}E[maxn];
int l[maxn<<2],r[maxn<<2],m[maxn<<2];
long long mn[maxn<<2];
#define inf 0x3f3f3f3f3f3f3f3f
#define ls(p) p<<1
#define rs(p) p<<1|1
inline void Pushup(const int &p){
    mn[p]=min(mn[ls(p)],mn[rs(p)]);
}
void build(const int p,const int lt,const int &rt){
    l[p]=lt;r[p]=rt;
    if(lt==rt) return;
    m[p]=(lt+rt)>>1;
    build(ls(p),lt,m[p]);
    build(rs(p),m[p]+1,rt);
    Pushup(p);
}
void change(const int p,const int &x,const long long &d){
    if(l[p]==r[p]){
        if(d<mn[p]) mn[p]=d;
        return;
    }
    if(x<=m[p]) change(ls(p),x,d);
    else change(rs(p),x,d);Pushup(p);
}
void query(const int p,const int &lt,const int &rt){
    if(lt<=l[p]&&rt>=r[p]){
        que=min(que,mn[p]);
        return; 
    } 
    if(lt<=m[p]) query(ls(p),lt,rt);
    if(rt>m[p]) query(rs(p),lt,rt);
}
int main(){
    scanf("%d%d",&n,&q);build(1,1,n);
    memset(mn,0x3f,sizeof(mn));
    for(i=1;i<=n;++i) scanf("%lld%lld",a+i,w+i);
    for(i=1;i<=q;++i){
        scanf("%d%d",&_l,&_r);
        E[i]={_l,h[_r]};h[_r]=i;
    }
    for(_r=1;_r<=n;++_r){
        while(t){
            _l=stk[t];
            change(1,_l,(a[_r]-a[_l])*(w[_r]+w[_l]));
            if(w[_l]>w[_r]) --t;
            else break;
        }
        stk[++t]=_r;
        for(i=h[_r];i;i=E[i].nxt){
            que=inf;
            query(1,E[i].l,_r);
            ans[i]=que;
        }
    }
    for(i=1;i<=q;++i) printf("%lld\n",ans[i]);
    return 0;
}

Construct Highway

题意

有一张 \(n\) 个点 \(m\) 条边的无向图,现在需要添加若干条边,使得图构成一棵树且第 \(i\) 个点的出度为 \(d_i\)。如果有解,输出任意一组合法方案。

解法

显然原图存在环/某点度过大/ \(\sum_{i=1}^nd_i\ne 2n-2\) 则无解,此处不进行赘述。

设第 \(i\) 个点的 \(c_i=d_i-\{\text{此点目前的度}\}\)。令连通块 \(s\)\(C_s=\sum_{u\in s}c_u\),然后不断地找 \(C\) 值为 \(1\) 的连通块,贪心地与目前 \(C\) 值最小且不为 \(1\) 的连通块连边。若最后不是只有两个 \(C\) 值为 \(1\) 的连通块,则同样无解;否则直接给两个连通块连边。

从连通块连边到具体的点的连边可以用链表。具体操作见代码。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int n,m,u,v,i,j,t,t1;
int d[maxn],h[maxn],s[maxn];
int dg[maxn],fa[maxn],nxt[maxn];
int stk[maxn],a1[maxn],a2[maxn];
int Find(const int p){
    if(p==fa[p]) return p;
    return fa[p]=Find(fa[p]);
}
inline bool Merge(int x,int y){
    x=Find(x);y=Find(y);
    if(x==y) return 0;
    fa[x]=y;return 1;
}
struct node{
    int ind,idx;
    inline bool operator <(const node &a)const{return ind<a.ind;}
}N[maxn];
int main(){
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;++i){
        scanf("%d",d+i); 
        u+=d[i];fa[i]=i;
    }
    if(u!=((n-1)<<1)){
        printf("-1");
        return 0;
    }
    for(i=1;i<=m;++i){
        scanf("%d%d",&u,&v);
        if(Merge(u,v)){
            --d[u];--d[v];
            if(d[u]<0||d[v]<0){
                printf("-1");
                return 0; 
            }
        }
        else{
            printf("-1");
            return 0; 
        }
    }
    for(i=1;i<=n;++i){
        u=Find(i);
        if(!s[u]) s[u]=++j;
        if(d[i]){
            dg[s[u]]+=d[i];
            nxt[i]=h[s[u]];
            h[s[u]]=i;
        }
    }
    t=0;
    for(i=1;i<=j;++i) N[i]={dg[i],i};
    sort(N+1,N+j+1);
    for(i=1;i<=j;++i){
        if(N[i].ind==1) stk[++t]=N[i].idx;
        else break;
    }
    while(t){
        u=stk[t--];if(!N[i].idx) continue;
        a1[++t1]=h[u];a2[t1]=h[N[i].idx];--d[a2[t1]];
        if(!d[a2[t1]]) h[N[i].idx]=nxt[a2[t1]];
        --N[i].ind;if(N[i].ind==1) stk[++t]=N[i++].idx;
    }
    a1[t1+1]=h[stk[1]];
    if(stk[2]) a2[++t1]=h[stk[2]];
    if(t1+m!=n-1) printf("-1");
    else for(i=1;i<=t1;++i) printf("%d %d\n",a1[i],a2[i]);
    return 0;
}

Teleporting Takahashi

题意

有一个人在一个三维直角坐标系中,初始时位于 \((0,0,0)\) 处。现在他每秒只能沿坐标轴走一单位且不能不走,求其在 \(T\) 秒后到达 \((X,Y,Z)\) 的方案数模 \(998244353\)\(T,|X|,|Y|,|Z|\le 10^7\)

解法

下面为了方便,将 \(X,Y,Z\) 均取绝对值,其不会对答案造成影响。

显然 \(T-X-Y-Z<0\) 时无解。\(T-X-Y-Z\) 为奇数时,由奇偶性同样可证明其无解。

考虑在 \(x,y\) 方向上走了 \(X+Y+2k\) 步。设在 \(x\) 正方向上走了 \(X+i\) 步,负方向上走了 \(i\) 步,则在 \(y\) 正方向上走了 \(Y+k-i\) 步,负方向上走了 \(k-i\) 步。方案数为:

\[\begin{aligned}C_{X+Y+2k}^k\sum_{i=0}^kC_k^iC_{X+Y+k}^{X+i}&=C_{X+Y+2k}^k\sum_{i=0}^kC_k^{k-i}C_{X+Y+k}^{X+i}\\&=C_{X+Y+2k}^kC_{X+Y+2k}^{X+k}\end{aligned} \]

其中 \(\sum_{i=0}^kC_k^{k-i}C_{X+Y+k}^{X+i}\) 可以看作在大小分别为 \(k\)\(X+Y+k\) 的集合中无任何限制地选了共 \(X+k\) 个元素。

枚举 \(k\),则总方案为:

\[\sum_{k=0}^{\frac12(T-X-Y-Z)}C_T^{X+Y+2k}C_{X+Y+2k}^kC_{X+Y+2k}^{X+k}C_{T-X-Y-2k}^{\frac12(T-X-Y-2k-Z)} \]

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long 
const ll P=998244353;
const int maxn=20000010;
ll n,x,y,z,i,t,p,a1,a2,s1,s2,ans;
ll fac[maxn],inv[maxn];
inline ll Pow(ll D,ll Z){
    ll R=1;
    do{
        if(Z&1LL){
            R*=D;
            if(R>=P) R%=P;
        }
        D*=D;
        if(D>=P) D%=P;
    }while(Z>>=1);
    return R;
}
inline ll C(const ll M,const ll &N){
    t=inv[M]*inv[N-M];
    if(t>=P) t%=P;
    t*=fac[N];if(t>=P) t%=P;
    return t;
}
int main(){
    scanf("%lld%lld%lld%lld",&n,&x,&y,&z);
    if(x<0) x=-x;if(y<0) y=-y;if(z<0) z=-z;
    p=n-(x+y+z);
    if(p<0||p&1){printf("0\n");return 0;}
    fac[1]=1;
    for(i=2;i<maxn;++i){
        fac[i]=fac[i-1]*i;
        if(fac[i]>=P) fac[i]%=P;
    }
    inv[maxn-1]=Pow(fac[maxn-1],P-2);
    for(i=maxn-2;i;--i){
        inv[i]=inv[i+1]*(i+1);
        if(inv[i]>=P) inv[i]%=P;
    }
    fac[0]=inv[0]=1;
    const ll X=p>>1;
    s1=x+y;s2=n-s1;
    for(i=0;i<=X;++i){
        a1=C(i,s1)*C(x+i,s1);
        if(a1>=P) a1%=P;
        a2=C(s1,n)*C((s2-z)>>1LL,s2);
        if(a2>=P) a2%=P;
        a2*=a1;if(a2>=P) a2%=P;
        ans+=a2;if(ans>=P) ans-=P;
        s1+=2;s2-=2;
    }
    printf("%lld\n",ans);
    return 0;
}

Repetitions Decoding

题意

有一个长为 \(n\) 的数组 \(a\)。定义一个数组为 "Tandem" 若其满足下述条件:其为 \(f_1f_2\cdots f_kf_1f_2\cdots f_k\) 的形式或其为多个 "Tandem" 数组拼接而来。现在可以在任意位置插入连着的两个相同的数,求进行不多于 \(2n^2\) 次上述操作能否将原数组变为 "Tandem" 数组。若有解则输出任意一种操作方案。

解法

显然某个数出现次数为奇数时无解。

考虑如何将数组中某个数在不改变其他数时移位。有这样一个特殊的操作方法:

\[\begin{aligned}f_{1}f_{2}\cdots f_{n}&\rightarrow f_{1}f_{2}\cdots f_{k}f_{1}f_{1}f_{k+1}\cdots f_{n}\\&\rightarrow f_{1}f_{2}\cdots f_{k}f_{1}f_{2}f_{2}f_{1}f_{k+1}\cdots f_{n}\\&\rightarrow \cdots\\&\rightarrow f_{1}f_{2}\cdots f_{k}f_{1}f_{2}\cdots f_{k}f_{k}f_{k-1}\cdots f_{2}f_{1}f_{k+1}\cdots f_{n}\end{aligned} \]

去掉长为 \(2k\) 的 "Tandem" 前缀,即等效于把原数组长为 \(k\) 的前缀翻转。考虑再进行如下过程的结果:

\[\begin{aligned}f_{1}f_{2}\cdots f_{n}&\rightarrow f_{k}f_{k-1}\cdots f_{1}f_{k+1}\cdots f_{n}\\&\rightarrow f_{k+1}f_{1}f_{2}\cdots f_{k}f_{k+2}\cdots f_{n}\end{aligned} \]

这样就把某个数在 \(2n\) 次操作内,在不改变其他数的位置的情况下移到了第一个。可以按照这样的方式对 \(a\) 进行排序,每次把未处理的最小的数前置,总操作次数不会超过 \(2n^2\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=510;
const int maxn2=500010;
int s1[maxn2],s2[maxn2],len[maxn2];
int val[maxn],sva[maxn];
int t,n,i,j,p,m,app,lst,top,tot;
bool ist;
int main(){
    scanf("%d",&t);
    while(t--){
        tot=top=0;
        app=ist=1;
        scanf("%d%d",&n,val+1);
        sva[1]=val[1];
        if(n==1){
            printf("-1\n");
            continue;
        }
        for(i=2;i<=n;++i){
            Read(val[i]);
            if(val[i]==val[i-1]) ++app;
            else{
                if(app&1) tot=ist=0;
                else if(ist) len[++tot]=app;
                app=1;
            }
            sva[i]=val[i];
        }
        if(app&1) tot=ist=0;
        else if(ist) len[++tot]=app;
        if(ist){
            printf("0\n%d\n",tot);
            for(i=1;i<=tot;++i) printf("%d ",len[i]);
            putchar('\n');
        }
        val[n+1]=sva[n+1]=1145141919;
        sort(sva+1,sva+n+1);
        app=1;lst=sva[1];
        for(i=2;i<=n;++i){
            while(sva[i]==lst){++app;++i;}
            if(app&1){
                printf("-1\n");
                app=0;break;
            }
            app=1;lst=sva[i];
        }
        if(!app) continue;
        lst=0;m=n+1;
        for(j=1;j<=n;++j) if(val[m]>val[j]) m=j;
        if(m>1){
            for(j=1;j<=m;++j){
                s1[j]=m+j-1;
                s2[j]=val[j];
            }
            len[++tot]=m<<1;
            reverse(val+1,val+m+1);
            top=m;lst=m<<1;
        }
        for(i=2;i<=n;++i){
            m=(n+1);
            for(j=i;j<=n;++j) if(val[m]>val[j]) m=j;
            for(j=1;j<m;++j){
                ++top;
                s1[top]=lst+m+j-2;
                s2[top]=val[j];
            }
            len[++tot]=(m-1)<<1;
            lst+=len[tot];
            reverse(val+1,val+m);
            for(j=1;j<=m;++j){
                ++top;
                s1[top]=lst+m+j-1;
                s2[top]=val[j];
            }
            len[++tot]=m<<1;
            lst+=len[tot];
            reverse(val+1,val+m+1);
        }
        app=1;lst=val[1];
        for(i=2;i<=n;++i){
            while(val[i]==lst){++app;++i;}
            len[++tot]=app;app=1;lst=val[i];
        }
        printf("%d\n",top);
        for(i=1;i<=top;++i) printf("%d %d\n",s1[i],s2[i]);
        printf("%d\n",tot);
        for(i=1;i<=tot;++i) printf("%d ",len[i]);
        putchar('\n');
    }
    return 0;
}

Anonymity Is Important

题意

\(n\) 个人。现在依次处理以下 \(q\) 个内容:

  1. 得知第 \(l\) 个到第 \(r\) 个人都没有患病或其中有患病者。
  2. 询问第 \(i\) 个人是否明确患病/不患病。若明确,则其是否患病。

\(n,q\le 2\cdot 10^5\)

解法

如果一个人确认是患病,则一定存在一个包含它的有患病者的区间,同时其中除了他之外的人必定确定为不患病。很多思路就是这么来的。

考虑使用并查集维护连通块。对于一个保证为无病的区间 \([l,r]\),可以在并查集中把 \([l,r+1]\) 合并,同时将 \(r+1\) 作集合代表元素,把编号小者合并到编号大者集合内;同时维护 \(\forall i\in[l,r+1]\),若 \(i\) 为一个有病者区间的左端点,则右端点的最小值,此值在连通块最右端(集合代表元素)处更新。

然后对 \(i\) 是否有病进行判断:若无病,则其一定非集合代表元素;若有病,则其一定是集合代表元素,且包括与其同一集合的元素右端点最小值一定小于其右边连通块代表元素(也就是有包括他的有病者所在的区间,且区间内此人之前和之后的人全部没有病)。(若不小于,则证明没有满足上述要求的区间,此人不能明确是否患病。)

具体实现见代码。

p.s. 此思路为 CWOI MJC 原创,感谢大佬的思路!

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int n,q,o,l,r,a,b,i;
int rt[maxn],lt[maxn];
int Find(const int p){
    if(p==rt[p]) return p;
    return rt[p]=Find(rt[p]);
}
int main(){
    scanf("%d%d",&n,&q);++n;
    for(i=1;i<=n;++i){
        rt[i]=i;
        lt[i]=1145141919;
    }
    while(q--){
        scanf("%d%d",&o,&l);
        if(o){
            if(Find(l)!=l) printf("NO\n");
            else{
                if(lt[l]<Find(l+1)) printf("YES\n");
                else printf("N/A\n");
            }
        }
        else{
            scanf("%d%d",&r,&o);
            if(o){
                a=Find(l);
                lt[a]=min(lt[a],r);
            }
            else{
                while(l<=r){
                    a=Find(l);b=Find(l+1);rt[a]=b;
                    lt[b]=min(lt[a],lt[b]);l=b;
                }
            }
        }
    }
    return 0;
}

Skate

题意

有一个 \(h\times w\) 的溜冰场,其中有 \(n\) 个障碍物。有一个人起初在 \((sx,sy)\) 处,他要通过最少的转向次数到达 \((tx,ty)\) 处并停在此处。他每次转向后会一直走到前面的障碍物处(其滑过边界会落下溜冰场),只能在开始时或到达某个障碍物前才能停下来并转向。求其最小转向次数或得出无解。

解法

显然能到达的坐标均为障碍物旁边的坐标。将所有可能的坐标整理去重后,若终点不在这之中,则可以判为无解。

然后从起点开始 bfs,对于目前到达了的每一个坐标,在障碍物中二分查找将会碰到的障碍物,得出对应能够到达的坐标。为了剪枝,可以维护目前到此处的方向,然后每次只往垂直当前方向处寻找障碍物即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
int h,w,n,i,d,ed,rk,xs,ys,xt,yt,xa,ya,ds,dr,to,tot;
int dis[maxn<<2],xp[maxn],yp[maxn];
const int dx[4]={-1,0,1,0};
const int dy[4]={0,-1,0,1};
inline bool valid(){
    if(!(xa&&ya)) return 0;
    if(xa>h&&ya>w) return 0;
    return 1;
}
struct Pos{
    int x,y;
    inline Pos operator =(const Pos &a){
        x=a.x;y=a.y;
        return *this;
    }
    inline bool operator ==(const Pos &a)const{return x==a.x&&y==a.y;} 
    inline bool operator !=(const Pos &a)const{return x!=a.x||y!=a.y;} 
    inline bool operator <(const Pos &a)const{
        if(x!=a.x) return x<a.x;
        return y<a.y;
    }
}lst,P[maxn<<2],U[maxn<<2];
struct node{
    int x,y,idx;
    inline bool operator <(const node &a)const{
        if(x!=a.x) return x<a.x;
        return y<a.y;
    }
    inline bool operator <(const Pos &a)const{
        if(x!=a.x) return x<a.x;
        return y<a.y;
    }
    inline bool operator ==(const Pos &a)const{return x==a.x&&y==a.y;}
}X[maxn],Y[maxn];
struct pos{int frm,go,dis;};
queue<pos> q;
int main(){
    scanf("%d%d%d",&h,&w,&n); 
    scanf("%d%d%d%d",&xs,&ys,&xt,&yt);
    for(i=1;i<=n;++i){
        scanf("%d%d",&xa,&ya);
        if(xa!=1) P[++tot]={xa-1,ya};
        if(xa!=h) P[++tot]={xa+1,ya};
        if(ya!=1) P[++tot]={xa,ya-1};
        if(ya!=w) P[++tot]={xa,ya+1};
        X[i]={xa,ya,i};Y[i]={ya,xa,i};
        xp[i]=xa;yp[i]=ya;
    }
    sort(X+1,X+n+1);
    sort(Y+1,Y+n+1);
    sort(P+1,P+tot+1);
    U[rk=1]=lst=P[1];
    for(i=2;i<=tot;++i){
        while(P[i]==lst) ++i;
        if(i>tot) break;lst=P[i];
        to=lower_bound(X+1,X+n+1,lst)-X;
        if(X[to]==lst) continue;U[++rk]=lst;
    }
    lst=(Pos){xt,yt};
    ed=lower_bound(U+1,U+rk+1,(Pos){xt,yt})-U;
    if(U[ed]!=(Pos){xt,yt}){
        printf("-1");
        return 0;
    }
    to=lower_bound(Y+1,Y+n+1,(node){ys,xs,0})-Y;
    if(Y[to].x==ys&&Y[to].y>xs) q.push((pos){Y[to].idx,0,1});
    --to;if(Y[to].x==ys&&Y[to].y<xs) q.push((pos){Y[to].idx,2,1});
    to=lower_bound(X+1,X+n+1,(node){xs,ys,0})-X;
    if(X[to].x==xs&&X[to].y>ys) q.push((pos){X[to].idx,1,1});
    --to;if(X[to].x==xs&&X[to].y<ys) q.push((pos){X[to].idx,3,1});
    while(!q.empty()){
        i=q.front().frm;xa=xp[i];ya=yp[i];
        i=q.front().go;xa+=dx[i];ya+=dy[i];
        to=lower_bound(U+1,U+rk+1,(Pos){xa,ya})-U;
        if(dis[to]){q.pop();continue;}
        d=dis[to]=q.front().dis;
        if(to==ed){
            printf("%d\n",d);
            return 0;
        }
        ++d;q.pop();
        if(i&1){
            to=lower_bound(Y+1,Y+n+1,(node){ya,xa,0})-Y;
            if(Y[to].x==ya&&Y[to].y>xa) q.push((pos){Y[to].idx,0,d});
            --to;if(Y[to].x==ya&&Y[to].y<xa) q.push((pos){Y[to].idx,2,d});
        }
        else{
            to=lower_bound(X+1,X+n+1,(node){xa,ya,0})-X;
            if(X[to].x==xa&&X[to].y>ya) q.push((pos){X[to].idx,1,d});
            --to;if(X[to].x==xa&&X[to].y<ya) q.push((pos){X[to].idx,3,d});
        }
    } 
    printf("-1\n");
    return 0;
}
posted @ 2022-10-04 13:03  Fran-Cen  阅读(30)  评论(0编辑  收藏  举报