CFR 杂题

CF1986D Mathematical Problem

题意:

在长度为 n 的数字串中插入 n2 个运算符(+×)使得该串的运算结果最小,多组询问。T104,n20

题解:

手模一下样例可以发现,对于 n4 的串,如果出现了 0,则总能插入 n2 个乘号,使得该式的最小值为 0;对于 n=3 的串,可以直接暴力判断插入的符号;对于 n=2 的串,不需要任何操作,只需要去除前导零输出原数即可。

先理解一个比较显然的东西:对于两个相邻位置上的两个数 a,b[1,9],当 a,b1ab>a+b,否则 aba+b

由于只有 n2 个操作符,而可以放操作符的位置有 n1 个,所以对于没有出现 0n4 的串,我们就可以先暴力枚举之间没有符号的位置,把这两位合并成一个两位数,然后循环统计答案:

  • 当前位置的值 >1,统计答案,加上该位置的值;
  • 当前位置的值 =1,不进行任何操作(相当于把 1 乘到另一个数上)。

可以结合样例 7 理解一下:

S=23311
i=1
23 3 1 1 --- ans=23+3*1*1=26
i=2
2 33 1 1 --- ans=2+33*1*1=35
i=3
2 3 31 1 --- ans=2+3+31*1=36
i=4
2 3 3 11 --- ans=2+3+3+11=19
minans=19

总时间复杂度 O(Tn2)。代码实现可能略有不同。

点击查看代码
#include<bits/stdc++.h>
#define int long long 
using namespace std;
int T,n,a[33];
char s[33];
signed main(){
    cin>>T;
    while(T--){
        cin>>n;
        cin>>s;
        if(n==2){
            cout<<(s[0]-'0')*10+s[1]-'0'<<'\n';
            continue;
        }
        if(n==3){
            int s1=s[0]-'0',s2=s[1]-'0',s3=s[2]-'0';
            cout<<min(min(s1+s2*10+s3,s1*(s2*10+s3)),min(s1*10+s2+s3,(s1*10+s2)*s3))<<'\n';
            continue;
        }
        int f0=0;
        for(int i=0;s[i];i++){
            if(s[i]=='0') f0=1;
        }
        if(f0){
            cout<<"0\n";
            continue;
        }
        int ans=0x3f3f3f3f3f3f3f3f;
        for(int i=1;i<n;i++){
            a[i]=(s[i-1]-'0')*10+s[i]-'0';
            for(int j=1;j<i;j++){
                a[j]=s[j-1]-'0';
            }
            for(int j=i+1;j<n;j++){
                a[j]=s[j]-'0';
            }
            int cnt=0;
            for(int j=1;j<n;j++){
                while(a[j]==1&&j<n) j++;
                if(j<n) cnt+=a[j];
                /* 这里可以改成
                if(a[j]!=1) cnt+=a[j];
                */
            }
            ans=min(ans,cnt);
        }
        cout<<ans<<'\n';
    }
    return 0-0;
}

CF1975D Paint the Tree

题意:

给你一棵白色的树,只有 a 为红色,b 为蓝色,树上有两个棋子 PA,PB 分别在节点 a,b 上,在每一单位时间内,进行以下操作:

  • PA 移动到相邻节点 a,若 a 为白色,则将 a 变为红色;
  • PB 移动到相邻节点 b,若 b 为红色,则将 b 变为蓝色。

求将整棵树染成蓝色的最短时间。n2×105

题解:

首先我们需要知道一点:如果 PB 走的点是白色的,那么它走的就没意义。

所以我们首先要最小化 PB 没意义的时间。很简单,就是尽快让 PA,PB 相遇,让它们一直走 (a,b) 的最短路径直到互相经过就停下。

从这一刻开始,PB 走的就有意义了。而且,PB 只要跟着 PA 走,就一定是不劣的,因为 PB 每一步走的都是红点。

接下来就是用最短时间遍历整棵树,除了从出发点到终点的路径,每条边都必须被经过两次。这其实和 ABC361E 的思想很像了,当然本题规定了出发点,就直接以出发点为根 DFS 出最深度最大的点 x,让 PB 的终点为 x,即可保证用时最小。

PA,PB 相遇的时间为 tx 的深度为 d,则答案为 t+2(n1)d,时间复杂度 O(n)

小技巧:设 ab 的最短路径长度为 l,记录其路径为 f(f0=a,fl=b),则相遇后,PA=fl+12,PB=fl2,t=l+12

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+3;
int n,pa,pb,ans,T;
vector<int>e[maxn];
int dep[maxn],stk[maxn],top,f[maxn],len;
void dfs(int u,int fa){
    if(u==pb){
        for(int i=1;i<=top;i++) f[i]=stk[i];
        len=top;
        return;
    }
    for(int v:e[u]){
        if(v!=fa){
            stk[++top]=v;
            dfs(v,u);
            top--;
        }
    }
}
void dfs1(int u,int fa,int depth){
    dep[u]=depth;
    for(int v:e[u]){
        if(v!=fa){
            dfs1(v,u,depth+1);
        }
    }
}
void solve(){
    ans=0;
    cin>>n;
    cin>>pa>>pb;
    for(int i=1,u,v;i<n;i++){
        cin>>u>>v;
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }
    dfs(f[0]=pa,0);
    if(len&1){
        ans+=len/2+1;
        pb=f[len/2];
        pa=f[len/2]+1;
    }else{
        ans+=len/2;
        pa=pb=f[len/2];
    }
    dfs1(pb,0,0);
    int mxdep=0;
    for(int i=1;i<=n;i++){
        mxdep=max(mxdep,dep[i]);
        e[i].clear();
    }
    top=0;
    cout<<ans+2*(n-1)-mxdep<<'\n';
}
signed main(){
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

CF1982D Beauty of the mountains

题意:

给你一个 n×m 的矩阵,每个格子有高度 ai,j,每个格子都属于有雪或无雪两种状态,现在你可以进行无限次操作:

  • k×k 的子矩阵高度加上 c(cN)

询问是否能使整个矩阵中,有雪的格子的高度和等于无雪的格子的高度和。

n,m500,nm2.5×105

题解:

不妨令有/无雪的格子的高度对答案的贡献为正/负数,当前整个矩阵的答案和 H,我们就需要用操作提供贡献 w 使得 w+H=0

对于子矩阵加上 c,设有/无雪的格子数量为 x,y,则加上的贡献为 x×c,y×c,对整个矩阵的贡献为 (xy)×c

我们就前缀和求出每个 k×k 子矩阵中 (xy) 的值,记录在数组 b 中,则问题就转化为判断 (nk+1)2 元一次不定方程 (i=1nk+1j=1mk+1ci,jbi,j)+H=0 是否有整数解。由裴蜀定理即可解决。时间复杂度 O(nmlogV)V 为值域。

n 元一次不定方程 i=1ncixi=k 有整数解的充分必要条件是 k=gcdi=1n(ci)。由乘法分配律可知,本题中只要 Hgcd 的整数倍即可。注意判断 gcd0 的情况。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=503;
int T,n,m,k,a[maxn][maxn],pre[maxn][maxn],sum,g[maxn*maxn],cnt;
void solve(){
    sum=cnt=0;
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            char ch;
            cin>>ch;
            if(ch=='0'){
                a[i][j]=-a[i][j];
            }
            sum+=a[i][j];
            pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+(ch=='1'?1:-1);
        }
    }
    for(int i=1;i<=n-k+1;i++){
        for(int j=1;j<=m-k+1;j++){
            g[++cnt]=pre[i+k-1][j+k-1]-pre[i-1][j+k-1]-pre[i+k-1][j-1]+pre[i-1][j-1];
        }
    }
    int gcd=g[1];
    for(int i=1;!g[i]&&i<=cnt;i++) gcd=g[i];
    for(int i=1;i<=cnt;i++){
        if(g[i]==0) continue;
        gcd=__gcd(gcd,g[i]);
    }
    if(!sum){
        cout<<"YES\n";
        return;
    }
    if(!gcd){
        cout<<"NO\n";
        return;
    }
    if(sum%gcd==0) cout<<"YES\n";
    else cout<<"NO\n";
}
signed main(){
    cin>>T;
    while(T--){
        solve();
    }
    return 0l;
}

CF1990D Gird Puzzle

题意:

有一个 n×n 的网格,其中第 i 行的前 ai 个格子为黑色(如果 ai=0 说明这一行没有黑色格子),其他格子为白色。

在一次操作中,你可以从下面任选一种:

  • 将一个 2×2 的区域全部染成白色。
  • 将一整行全部染成白色。

你的目标是将整个网格全部染成白色,并且保证总操作次数最少。

题解:

n2×105,显然暴力不可做。

尝试寻找一些性质,可以发现,当 ai>4 时,不可能使用操作 1,因为会更劣;对于 ai4 的情况,可能用操作 1 或操作 2,DP 或贪心即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+3;
int T,n,a[maxn],f[maxn],g[2];
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    g[0]=g[1]=n;
    for(int i=1;i<=n;i++){
        f[i]=f[i-1]+1;
        if(!a[i]) f[i]=f[i-1];
        if(a[i]<=2){
            f[i]=min(f[i],i+g[i&1^1]);
            g[i&1]=min(g[i&1],f[i-1]-i);
        }
        if(a[i]>4) g[0]=g[1]=n;
    }
    cout<<f[n]<<'\n';
}
signed main(){
    cin>>T;
    while(T--){
        solve();
    }
    return 0l;
}

CF1951D Buying Jewels

题意:

Alice 有 n 枚硬币,她要用这些硬币去商店恰好消费 k 件商品。

商店有不超过 60 个摊位,每个摊位都只有一种商品,每种商品都有无数个。

Alice 会从第一个摊位一直消费到最后一个摊位,直至不够硬币消费为止。Alice 在当前摊位消费时,会尽可能多地消费。

构造一种方案使得 Alice 恰好消费 k 件商品。

题解:

k>n 显然无解。
k=n,显然设置一个 11 即可。
n/2<k<n 则除了 n=2k+1 的情况外均无解。

考虑一种构造:

2
n-k+1 1

AC。灵光乍现()

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int T,n,k;
void solve(){
    cin>>n>>k;
    if(k==1){
        cout<<"YES\n1\n"<<n<<'\n';
        return;
    }
    if(n==k){
        cout<<"YES\n1\n1\n";
        return;
    }
    if(n-(k-1)<k){
        cout<<"NO\n";
        return;
    }
    cout<<"YES\n2\n";
    cout<<n-(k-1)<<' '<<1<<'\n';
}
signed main(){
    cin>>T;
    while(T--){
        solve();
    }
    return 0l;
}

CF514E Darth Vader and Tree

军训被硬控 7 天的题……

给一棵无限高的树,每个节点有 n 个子节点,(u,soni) 的长度为 di,求从根出发,在总距离 x 的前提下,可以到达的点的个数。

n105,x109,V=dmax100

fi 表示距离根长度为 i 的点的数量,则显然有转移 fi=j=1nfidj,初始 f0=1,答案为 i=0xfi。时间复杂度 O(nx),无法通过。

我们先修改一下转移式子,设 cntdidi 出现次数,则原式为 fi=j=1Vcntjfij 由于 V 只有 100,所以时间复杂度 O(Vx),还是无法通过。

x 的范围就很矩阵的样子。由于 fi 至多从 fiV 转移过来,所以只要开个 V+1(有一层记录前缀和 si)的矩阵就可以优化 x 了,每次只更新 fisi,其余直接继承即可。

[si1fi1fi100]×[100000cnt1cnt11000cnt2cnt20100cnt100cnt1000010]=[sififi99]

时间复杂度 O(V3logx)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
const int maxd=100;
const int maxn=2e5+3;
const int maxk=1e9+7;
int n,x,d[maxn];
struct matrix{
    int a[maxd+3][maxd+3];
    matrix(){memset(a,0,sizeof a);}
    matrix friend operator*(const matrix a,const matrix b){
        matrix c;
        for(int i=0;i<=maxd;i++)
            for(int j=0;j<=maxd;j++)
                for(int k=0;k<=maxd;k++)
                    c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod;
        return c;
    }
};
matrix qpow(matrix t,matrix a,int b){
    matrix ret=t;
    for(;b;a=a*a,b>>=1ll) if(b&1) ret=ret*a;
    return ret;
}
int cnt[maxd+3];
signed main(){
    cin>>n>>x;
    for(int i=1;i<=n;i++){
        cin>>d[i];
        cnt[d[i]]++;
    }
    matrix a,b;
    a.a[0][0]=b.a[0][0]=b.a[0][1]=1;
    for(int i=1;i<=100;i++) a.a[i][0]=a.a[i][1]=cnt[i];
    for(int i=2;i<=100;i++) a.a[i-1][i]=1;
    cout<<qpow(b,a,x).a[0][0];
    return 0;
}

CF253D Table with Letters - 2

给你一个 n×m 的矩阵,求其子矩形满足四个角字符相等且 a 的数量不超过 k 的个数。

n,m400,knm

子矩形中 a 的个数显然可以用前缀和处理。

枚举左上端点 (i,j),再枚举矩形的下边界 (l,),当 i,l 固定时,考虑到随着 j 的增大,a 的个数单增,于是可以设指针 o 从左往右扫,即可满足 k 的限制。

再考虑处理字符相等,当 i,l 固定时,一个矩形可以拆成左右两条边界颜色相等,记一个桶 t[c] 表示当前颜色 c 代表的边界数,j,o 向右扫时更新 t[c] 即可,时间复杂度 O(n3)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=403;
int pre[maxn][maxn],ton[26];
int n,m,k;
char a[maxn][maxn];
int p(int x1,int y1,int x2,int y2){
    return pre[x2][y2]-pre[x1-1][y2]-pre[x2][y1-1]+pre[x1-1][y1-1];
}
signed main(){
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
            pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+(a[i][j]=='a');
        }
    }
    int cnt=0;
    for(int i=1;i<n;i++){
        for(int l=i+1;l<=n;l++){
            for(int j=0;j<26;j++) ton[j]=0;
            for(int j=1,o=1;j<m;j++){
                if(a[i][j]!=a[l][j]) continue;
                while(o<=m&&p(i,j,l,o)<=k){
                    if(a[i][o]==a[l][o])
                    ton[a[i][o]-'a']++;
                    o++;
                }
                ton[a[i][j]-'a']--;
                if(ton[a[i][j]-'a']>0)
                cnt+=ton[a[i][j]-'a'];
            }
        }
    }
    cout<<cnt<<'\n';
    return 0;
}

CF1288E Messenger Simulator

给你一个长为 n 的链表,m 次操作,第 i 次将 ai 放至链头,求对于每个 ai,其最小/大下标。

n,m3×105

最小下标很好求,如果 ai 被操作过就为 1 否则为 i

最大下标,设 pi 表示当前 ai 的位置(在这 n 个点之前加 m 个空点方便操作,所以初始为 pi=m+i)。考虑一个 ai 的下标为其前面的数的个数加一,可以用树状数组维护。删掉原来的点接到开头,相当于在位置 pi 的树状数组上减一,在 i 上加一;查询即前缀和。时间复杂度 O((n+m)log(n+m))

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=6e5+3;
int n,m;
int a[maxn],f[maxn],g[maxn],tr[maxn],now[maxn];
int lowbit(int x){return x&-x;}
void add(int x,int c){
    for(;x<=n+m;x+=lowbit(x)) tr[x]+=c; 
}
int q(int x){
    int res=0;
    for(;x;x-=lowbit(x)) res+=tr[x];
    return res; 
}
signed main(){
    cin>>n>>m;
    for(int i=m+1;i<=m+n;i++) a[i]=i-m,now[i-m]=i,add(i,1);
    for(int i=1;i<=n;i++) f[i]=g[i]=i;
    for(int i=m;i;i--){
        cin>>a[i];
        g[a[i]]=max(g[a[i]],q(now[a[i]]));
        add(now[a[i]],-1);
        add(i,1);
        now[a[i]]=i;
        f[a[i]]=1;
    }
    for(int i=1;i<=n;i++){
        g[i]=max(g[i],q(now[i]));
        cout<<f[i]<<' '<<g[i]<<'\n';
    }
    return 0;
}

CF438D The Child and Sequence

给你一个序列 a,维护以下操作:

  • i=lrai
  • i[l,r]aiaimodx
  • 单点修改 aix

n,m105x109

1,3 操作很容易搞。考虑取模有性质:(amodx)modyx2(a>x,amodx>y)

证明:

  • yx12,则原式 xyx2
  • y<x12,显然成立。

所以有效的取模次数是 O(loga) 级别的,把不满足上述条件的 skip 掉,其余暴力修改即可。时间复杂度 O(nlognlogx)

代码使用 Codeforces AtCoder Better!模版 3min 完成。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ls (u<<1)
#define rs (u<<1|1)
using namespace std;
const int maxn=1e5+3;
int n,m;
int a[maxn];
struct Node
{
    int l, r;
    int mx;
    int sum;
    // TODO: 需要维护的信息和懒标记
}tr[maxn * 4];

void pushup(int u)
{
    tr[u].mx=max(tr[ls].mx,tr[rs].mx);
    tr[u].sum=tr[ls].sum+tr[rs].sum;
    // TODO: 利用左右儿子信息维护当前节点的信息
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r,a[l],a[l]};
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void update(int u, int l, int r, int d)
{
    if(tr[u].mx<d) return; // 没必要改直接返回
    if (tr[u].l==tr[u].r)
    {
        tr[u].mx=tr[u].sum=tr[u].sum%d;
    }
    else
    {
        // pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) update(u << 1, l, r, d);
        if (r > mid) update(u << 1 | 1, l, r, d);
        pushup(u);
    }
}void update1(int u, int l, int d)
{
    if (tr[u].l==tr[u].r)
    {
        tr[u].mx=tr[u].sum=d;
    }
    else
    {
        // pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) update1(u << 1, l, d);
        else update1(u << 1 | 1, l, d);
        pushup(u);
    }
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        return tr[u].sum;  // TODO 需要补充返回值
    }
    else
    {
        // pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        int res = 0;
        if (l <= mid ) res = query(u << 1, l, r);
        if (r > mid) res += query(u << 1 | 1, l, r);
        return res;
    }
}

signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    build(1,1,n);
    for(int i=1,op,l,r,x;i<=m;i++){
        cin>>op;
        if(op==1){
            cin>>l>>r;
            cout<<query(1,l,r)<<'\n';
        }else if(op==2){
            cin>>l>>r>>x;
            update(1,l,r,x);
        }else{
            cin>>l>>x;
            update1(1,l,x);
        }
    }
    return 0;
}

CF920F SUM and REPLACE

给你一个数列,m 次操作:

  • 将区间 [l,r] 中的数变为它们的因数个数;
  • 求区间 [l,r] 的和。

n,m3×105,ai106

这个题和上面那个 CF438D The Child and Sequence 思路有异曲同工之处。考虑到操作 1,xd(x),这个操作进行次数为 loglogx 级别的,即进行这么多次以后 x 就变成 1/2 了,于是我们暴力修改,并记录区间最大值,当区间最大值小于等于 2 时就不再操作。时间复杂度 O(mlognloglogai)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ls (now<<1)
#define rs (now<<1|1)
using namespace std;
const int maxn=1e6+3;
int n,m;
int a[maxn],d[maxn],tr[maxn<<2],mx[maxn<<2];

void pushup(int now){// ♂♂♂♂♂♂♂♂♂♂
    tr[now]=tr[ls]+tr[rs];
    mx[now]=max(mx[ls],mx[rs]);
}

void build(int now,int l,int r){
    if(l==r){
        mx[now]=tr[now]=a[l];
        return;
    }
    int mid=(l+r)>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(now);
}

void modify(int now,int l,int r,int L,int R){
    if(mx[now]<=2) return;
    if(l==r){
        mx[now]=tr[now]=d[tr[now]];
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid) modify(ls,l,mid,L,R);
    if(mid+1<=R) modify(rs,mid+1,r,L,R);
    pushup(now);     

}

int query(int now,int l,int r,int L,int R){
    if(L<=l&&r<=R){
        return tr[now];
    }
    int mid=(l+r)>>1,res=0;
    if(L<=mid) res+=query(ls,l,mid,L,R);
    if(mid+1<=R) res+=query(rs,mid+1,r,L,R);
    return res;
}


signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=1000000;i++){
        for(int j=i;j<=1000000;j+=i){
            d[j]++;
        }
    }
    build(1,1,n);
    for(int i=1,op,x,y;i<=m;i++){
        cin>>op>>x>>y;
        if(op==1){
            modify(1,1,n,x,y);
        }else{
            cout<<query(1,1,n,x,y)<<'\n';
        }
    }
    return 0;
}

CF1553G Common Divisor Graph

给一个 n 个点的图,两个点 u,v 有边当且仅当 gcd(au,av)>1m 次询问两个节点 u,v,至少要进行多少次「任选一个 i,新建节点 x,权值 ax=ai(ai+1)」操作才能让 u,v 连通。

n1.5×105,m3×105,2ai106

由于 ai(ai+1) 一定为偶数,所以至多建 au(au+1),av(av+1) 两个节点即可让 u,v 连通,即答案至多为 2。

以对每个数质因数分解,塞进对应质因数并查集里,即可维护开始时的连通性。

所以分类讨论:

  • 答案为 0:初始时 u,v 在同一联通块;
  • 答案为 1:存在一个 iai+1 可以与 u,v 连通,这个可以用一个 set 维护;
  • 答案为 2:其他情况。

预处理每个数的质因数即可。时间复杂度 O(nlog2n+(m+ai)logai)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+3;
const int maxa=1e6+3;
int n,q;
int a[maxn];
vector<int>v[maxa];
vector<int>p[maxa<<1];
int fa[maxa];
int find(int x){
    if(fa[x]==x) return x;
    return fa[x]=find(fa[x]);
}
set<pair<int,int> >mp;
signed main(){
    cin>>n>>q;
    for(int i=1;i<=1000001;i++) fa[i]=i;
    for(int i=2;i<=1000001;i++){
        if(v[i].empty())
            for(int j=i;j<=1000001;j+=i)
                v[j].emplace_back(i);
    }
    for(int i=1;i<=n;i++){
        cin>>a[i];
        for(int j:v[a[i]])
            fa[find(a[i])]=find(j);
    }
    for(int i=1;i<=n;i++){
        p[i]=v[a[i]+1];
        p[i].emplace_back(a[i]);
        int siz=p[i].size();
        for(int j=0;j<siz;j++)
            for(int k=j+1;k<siz;k++)
                mp.insert({find(p[i][j]),find(p[i][k])}),
                mp.insert({find(p[i][k]),find(p[i][j])});
    }
    for(int i=1,x,y;i<=q;i++){
        cin>>x>>y;
        x=find(a[x]),y=find(a[y]);
        if(x==y){
            cout<<"0\n";
        }else if(mp.count({x,y})||mp.count({y,x})){
            cout<<"1\n";
        }else{
            cout<<"2\n";
        }
    }
    return 0;
}

CF351E Jeff and Permutation

给你一个序列 a,求将某些数变成其相反数后的最小逆序对数。

n2000,|ai|105

由于可以操作任意次,所以不妨先将数全部取正。

考虑最大的数 ai,它贡献的逆序对数,显然是独立的,当它取正时,答案为 ni,取负时贡献为 i1,将答案取 min。对于其他的数,无论它怎么操作,在最大的数的符号确定时一定与最大的数的偏序关系是确定的,所以我们先确定最大值的影响,再依次找次大值,并将答案取 min 后累加起来。时间复杂度 O(n2)

还是远古老题,数据范围开得不够。

对于一个数 ai,它由于受到比它大的数的影响,其答案为 min(i1x,niy)x/y 表示 i 左/右边比它大的数的个数。这样维护一个前缀树状数组,一个后缀的树状数组即可。时间复杂度 O(nlogn)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=200003;
int n,a[maxn],tr[2][maxn],ans[2][maxn];
int lowbit(int x){return x&-x;}
void add(int x,int c,int t){for(;x<=200000;x+=lowbit(x)) tr[t][x]+=c;}
int get(int x,int t){
    int res=0;
    for(;x;x-=lowbit(x)) res+=tr[t][x];
    return res;
}
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        if(a[i]<0) a[i]=-a[i];
        a[i]+=2;
    }
    for(int i=1;i<=n;i++){
        ans[0][i]=get(a[i]-1,0);
        add(a[i],1,0);
    }
    for(int i=n;i;i--){
        ans[1][i]=get(a[i]-1,1);
        add(a[i],1,1);
    }
    int anss=0;
    for(int i=1;i<=n;i++){
        anss+=min(ans[0][i],ans[1][i]);
    }
    cout<<anss;
    return 0;
}

CF2000H Ksyusha and the Loaded Set

给你一个集合 S={a1,a2,,an}q 次操作:

  • x 加入 S
  • xS 中删除;
  • 求该集合的 k - load。

k - load 定义为最小的 d 满足 [d,d+k1]S=

T104,n,q2×105,ai,x,kV2×106

建一棵权值线段树,点 i 的值为 [aiS],考虑二分 d+k1,即判断 [1,d+k1]最长的 0 子段长度 k,这个很经典了,就维护区间前缀/后缀/最大 0 子段长度即可。注意一点不能每组数据都重新建树,因为单组数据复杂度不能与值域有关,考虑记一个 set 模拟操作,并最后清空 set 即可。时间复杂度 O(V+nlog(n+q)+nlog2V)

存在直接线段树二分的 O(nlogV) 做法。

点击查看代码
#include<bits/stdc++.h>
#define ls (now<<1)
#define rs (now<<1|1)
const int maxn=4e6+7;
const int maxm=2e5+7;
const int N=4e6;
using namespace std;
struct TR{
    int mx0,lmx,rmx,len;
}tr[maxn<<2];
void pushup(int now){
    tr[now].mx0=max(tr[ls].rmx+tr[rs].lmx,max(tr[ls].mx0,tr[rs].mx0));
    if(tr[ls].lmx==tr[ls].len) tr[now].lmx=tr[ls].lmx+tr[rs].lmx;
    else tr[now].lmx=tr[ls].lmx;
    if(tr[rs].rmx==tr[rs].len) tr[now].rmx=tr[rs].rmx+tr[ls].rmx;
    else tr[now].rmx=tr[rs].rmx;
}
void build(int now,int l,int r){
    tr[now].len=r-l+1;
    if(l==r){
        tr[now].mx0=1;
        tr[now].lmx=1;
        tr[now].rmx=1;
        return ;
    }
    int mid=(l+r)>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(now);
}
void modify(int now,int l,int r,int x,int v){
    if(l==r){
        tr[now].mx0=v;
        tr[now].lmx=v;
        tr[now].rmx=v;
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid) modify(ls,l,mid,x,v);
    else modify(rs,mid+1,r,x,v);
    pushup(now);
}
TR query(int now,int l,int r,int R){
    if(1<=l&&r<=R) return tr[now];
    int mid=(l+r)>>1;
    TR resl=query(ls,l,mid,R),resr;
    if(mid+1<=R){
        resr=query(rs,mid+1,r,R);
        resl.mx0=max(resl.rmx+resr.lmx,max(resl.mx0,resr.mx0));
        if(resl.lmx==resl.len) resl.lmx=resl.lmx+resr.lmx;
        else resl.lmx=resl.lmx;
        if(resr.rmx==resr.len) resl.rmx=resr.rmx+resl.rmx;
        else resl.rmx=resr.rmx;
    }
    return resl;
}
int T,n,q;
set<int>intr;
void solve(){
    cin>>n;
    for(int i=1,a;i<=n;i++){
        cin>>a;
        intr.insert(a);
        modify(1,1,N,a,0);
    }
    cin>>q;
    while(q--){
        char op;
        int x;
        cin>>op>>x;
        if(op=='+'){
            modify(1,1,N,x,0);
            intr.insert(x);
        }else if(op=='-'){
            modify(1,1,N,x,1);
            intr.erase(x);
        }else{
            int l=1,r=N,ans=N;
            while(l<=r){
                int mid=(l+r)>>1;
                if(query(1,1,N,mid).mx0>=x){
                    r=mid-1;
                    ans=mid;
                }else{
                    l=mid+1;
                }
            }
            cout<<ans-x+1<<' ';
        }
    }
    cout<<'\n';
    for(int x:intr) modify(1,1,N,x,1);
    intr.clear();
}
signed main(){
    ios::sync_with_stdio(0);
    cin>>T;
    build(1,1,N);
    while(T--){
        solve();
    }
    return 0;
}

CF1946E Girl Permutation

给你一个排列的前/后缀最大值的所有位置,求满足要求排列数量。

n2×105

记前缀最大值的位置为 p1<p2<<pm,后缀最大值的位置为 s1<s2<<sr

首先容易得知,由于是排列,所以有且仅有一个最大值 npm=s1 的位置。其次 p1=1,sr=n,长度为 1 的前后缀的最大值只可能是它自己。以上用来判断无解。

确定了最大值的位置,就以它为界,分开讨论。先考虑左边,是在非最大值数中选了 pm1 个数,即 (n1pm1),然后在这 pm1 个数里,最大值的位置为 pm1,类似递归的思想,继续分为左右两边区间 [1,pm1),(pm1,pm1],其中左边选掉 pm11 个,方案为 (pm2pm11),右边区间内的元素可以任意排列,方案为 (pmpm11)!,然后递归下去即可。右半边同理。时间复杂度 O(n)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+7;
const int mod=1e9+7;
int T;
int n,m0,m1,p[maxn],s[maxn];
int fac[maxn],ifac[maxn];
int qpow(int a,int b){
    int res=1;
    for(;b;b>>=1,a=a*a%mod) if(b&1) res=res*a%mod;
    return res;
}
int C(int a,int b){
    if(a<b) return 0;
    return fac[a]*ifac[a-b]%mod*ifac[b]%mod;
}
void solve(){
    cin>>n>>m0>>m1;
    for(int i=1;i<=m0;i++) cin>>p[i];
    for(int i=1;i<=m1;i++) cin>>s[i];
    if(p[m0]!=s[1]||p[1]!=1||s[m1]!=n){
        cout<<0<<'\n';
        return;
    }
    int ans=C(n-1,p[m0]-1);
    for(int i=m0;i>1;i--)
        ans=ans*C(p[i]-2,p[i-1]-1)%mod*fac[p[i]-p[i-1]-1]%mod;
    for(int i=1;i<m1;i++)
        ans=ans*C(n-s[i]-1,n-s[i+1])%mod*fac[s[i+1]-s[i]-1]%mod;
    cout<<ans<<'\n';
}
signed main(){
    cin>>T;
    fac[0]=1;
    for(int i=1;i<=200000;i++)
        fac[i]=fac[i-1]*i%mod;
    ifac[200000]=qpow(fac[200000],mod-2);
    for(int i=199999;~i;i--)
        ifac[i]=ifac[i+1]*(i+1)%mod;
    while(T--){
        solve();
    }
    return 0;
}

CF1996G Penacony

给你一个环和 m 个点对 (ai,bi),将路径 aibibiai 染色,求最小染色边数。

n,m2×105,ai<bi

对于每条边,钦定它被所有路径染过色,每次更新染色边数,这样一定是最优的。因为只要有一个点对的路径不经过该边,一定答案劣于经过该边。

考虑优化更新。将环对应到区间上,每次只在某一个节点为 ai/bi 时更新答案,每次对应三段区间加减,线段树搞即可。统计答案考虑容斥,转为求总区间里 0 的个数,充要转化为总区间最小值为 0 时,最小值的数量,这个就可以简单维护了。时间复杂度 O(mlogn)

点击查看代码
#include<bits/stdc++.h>
#define ls (now<<1)
#define rs (now<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int maxn=2e5+3;
int T;
int n,m,l[maxn],r[maxn];
struct TR{
    int mi,cnt;
    TR friend operator+(const TR &a,const TR &b){
        TR res;
        if(a.mi<b.mi) return a;
        else if(a.mi>b.mi) return b;
        else return {a.mi,a.cnt+b.cnt};
    }
}tr[maxn<<2];
int tag[maxn<<2];
void pushup(int now){
    tr[now]=tr[ls]+tr[rs];
}
void pushdown(int now){
    if(tag[now]){
        tag[ls]+=tag[now];
        tag[rs]+=tag[now];
        tr[ls].mi+=tag[now];
        tr[rs].mi+=tag[now];
        tag[now]=0;
    }
}
void build(int now,int l,int r){
    tag[now]=0;
    if(l==r){
        tr[now]={0,1};
        return ;
    }
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(now);
}
void modify(int now,int l,int r,int L,int R,int x){
    if(L<=l&&r<=R){
        tr[now].mi+=x;
        tag[now]+=x;
        return ;
    }
    pushdown(now);
    if(L<=mid) modify(ls,l,mid,L,R,x);
    if(mid+1<=R) modify(rs,mid+1,r,L,R,x);
    pushup(now);
}
int query(){
    return (tr[1].mi==0)*(tr[1].cnt);
}
int type[maxn];
vector<int>v1[maxn],v2[maxn];
void solve(){
    cin>>n>>m;
    build(1,1,n);
    for(int i=1;i<=m;i++){
        cin>>l[i]>>r[i];
        v1[l[i]].emplace_back(i);
        v2[r[i]].emplace_back(i);
        modify(1,1,n,l[i],r[i]-1,1);
    }
    int ans=n-query();
    for(int i=1;i<=n;i++){
        for(int j:v1[i]){
            modify(1,1,n,l[j],r[j]-1,-1);
            if(l[j]>1) modify(1,1,n,1,l[j]-1,1);
            modify(1,1,n,r[j],n,1);
        }
        for(int j:v2[i]){
            modify(1,1,n,l[j],r[j]-1,1);
            if(l[j]>1) modify(1,1,n,1,l[j]-1,-1);
            modify(1,1,n,r[j],n,-1);
        }
        v1[i].clear();
        v2[i].clear();
        ans=min(ans,n-query());
    }
    cout<<ans<<'\n';
}
signed main(){
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

人类智慧做法:

考虑从 aibi 变为 biai,非常类似异或。考虑给 ai,bi 赋同一个随机权,然后前缀异或一下,前缀异或和中出现次数最多的数的出现次数即为当前状态下 0 的数量最多的状态。时间复杂度 O(n+m)

点击查看 spec. 代码
#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
mt19937 Rand(20080217);
int n,m,x,y,i,T,a[200007];
unordered_map<int,int> ma;
signed main()
{
	cin>>T;
	while(T--){
		cin>>n>>m;
		for(i=1;i<=m;i++){
			cin>>x>>y;
			int r=Rand();
			a[x]^=r,a[y]^=r;
		}
		for(i=1;i<=n;i++) a[i]^=a[i-1],ma[a[i]]++;
		int ans=0;
		for(auto x:ma) ans=max(ans,x.second);
		cout<<n-ans<<endl;
		for(i=1;i<=n+1;i++) a[i]=0;
		ma.clear();
	} 
}

CF2036F XORificator 3000

[l,r] 中满足 xk(mod2i)x 的异或和。

T104,1lr1018,0i30,0k<2i

首先由于异或满足 abb=a,所以考虑拆分问题,求出 [1,l1][1,r] 的答案异或即可。

再考虑容斥,将区间 [1,x] 的答案转化成 [1,x] 所有数的异或和再异或上区间内满足 xk(mod2i) 的数。

对于求 [1,x] 所有数的异或和,这里直接摆结论了,证明可以看 这里,打表也能发现一定规律。

i=1xi={xx0(mod4)1x1(mod4)x+1x2(mod4)0x3(mod4)

于是 [1,x] 的异或和我们就可以 O(1) 求了,接下来考虑求满足 xk(mod2i) 的数的异或和。

这些数二进制下的后(低位)i 位都一样,为 k,而高位上都形如 0×2i,1×2i,2×2i,...,t×2itx 的数量。

由于异或对于每一位的独立性,诶你发现求这一部分的异或和,是不是相当于高位求一遍 [1,t] 的异或和,再异或上 tk(由于 aa=0,所以只用在 t 为奇数的时候异或一次 k 即可),时间复杂度 O(1)

综上时间复杂度 O(T)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T;
int getxor(int x){
    if(x%4==0) return x;
    if(x%4==1) return 1;
    if(x%4==2) return x+1;
    return 0;
}
int get(int x,int pos,int k){
    if(x==0) return 0;
    int t=(x>>pos);
    if(x%(1<<pos)>=k) t++;
    return getxor(x)^(getxor(t-1)<<pos)^((t&1)*k);
}
void solve(){
    int l,r,I,k;
    cin>>l>>r>>I>>k;
    cout<<(get(r,I,k)^get(l-1,I,k))<<'\n';
}
signed main(){
    cin>>T;
    while(T--) solve();
    return 0;
}

CF2036G Library of Magic

交互题。现有一个 1n 的整数序列,每个数有 2 个,在该序列里选走 3 个不同的数,求出选走的这些数。可以向交互库发起不超过 150 次询问形如:

  • xor l r 返回数值在 [l,r] 之间的数的异或和。

n1018

需要一定智慧的一题。

考虑到异或有性质 xx=0,所以区间 [l,r] 的数的异或和其实就是区间内被选走的数的异或和。

不妨设选走的这 3 个数分别为 a<b<c。查一次 [1,n] 的异或和可以得到 t=abc 的值。据此分类讨论:

  • t0。这种比较好考虑。对于这个数列的前缀异或和序列 p,具有一定的单调性,即 p1pa1=0,papb1=a,pbpc1=ab,pcpn=t。我们不关心具体数值,只关心其是否大于 0,即可二分出 a 的位置。同理也可根据其后缀异或序列单调性求出 c。进而算出 b=act
  • t=0。这就不能用上面的方法了,因为 p 不单调了(形如 00001111000,1 表示大于 0 的数)。考虑从 t=0 上获取信息。
    abc=0,a=bc。由于上述问题,对于一个区间 [l,r] 中的断点 mid,可能会出现 mid 左右的异或和均为 0 的情况。但是事实是一边有 3 个数,另一边没数(不可能出现两边都有数的情况,因为 3 个数都不同,任意 2 个数异或都一定不为 0)。我们需要找到一个 mid 能让我们在这种情况下也能正确的判断哪一边有数。
    当我们取 mid=2x1,x=log2(rl+1) 时,所有 [mid+1,r] 的数中任取两个数的异或和均 mid(二进制下位数相等且最高位都为 1,异或完就少了一位,即 <2x),即 b,c>mid,amid,不合法;任取 [l,mid] 中的两个数,由于位数不够,异或完还是 <2x,所以合法,此时我们就把范围缩小到了 [l,mid],循环做这件事直到出现某一边异或和不为 0,即转化为 t0 的情况。

综上查询次数最劣为为做两遍二分,120 次左右。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T;
int n;
bool check(int l,int r){
    cout<<"xor "<<l<<' '<<r<<'\n';
    cout.flush();
    int ret;
    cin>>ret;
    return ret;
}
void solve(){
    ck1=ckn=0;
    cin>>n;
    if(n==3){
        cout<<"ans 1 2 3\n";
        cout.flush();
        return;
    }
    cout<<"xor 1 "<<n<<'\n';
    cout.flush();
    int ret;
    cin>>ret;
    if(ret){
        int l=1,r=n,posl=1,posr=n;
        while(l<=r){
            int mid=(l+r)>>1;
            if(check(1,mid)){
                r=mid-1;
                posl=mid;
            }else{
                l=mid+1;
            }
        }
        l=1,r=n;
        while(l<=r){
            int mid=(l+r)>>1;
            if(check(mid,n)){
                l=mid+1;
                posr=mid;
            }else{
                r=mid-1;
            }
        }
        cout<<"ans "<<posl<<' '<<(ret^posl^posr)<<' '<<posr<<'\n';
        cout.flush();
    }else{
        int mx=log2(n),nmid=0;
        for(int i=mx;i>=1;i--){
            int r=min((1ll<<(i+1))-1,n),l=(1ll<<i);
            if(check(l,r)){
                nmid=l;
                break;
            }
        }
        int l=1,r=nmid,posl=nmid,posr=nmid;
        while(l<=r){
            int mid=(l+r)>>1;
            if(check(1,mid)){
                r=mid-1;
                posl=mid;
            }else{
                l=mid+1;
            }
        }
        l=nmid,r=n;
        while(l<=r){
            int mid=(l+r)>>1;
            if(check(mid,n)){
                l=mid+1;
                posr=mid;
            }else{
                r=mid-1;
            }
        }
        cout<<"ans "<<posl<<' '<<(posl^posr)<<' '<<posr<<'\n';
        cout.flush();
    }
}
signed main(){
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

CF2033G Sakurako and Chefir

给定一个 n 个节点以 1 为根的树,q 次询问,给定 u,k,对于每次询问,你需要操作节点 u 顺着边进行移动操作,满足:

  1. 最多向树根(深度减小)方向移动 k 次;
  2. 向叶子(深度增加)方向移动任意次。

问操作结束后与原点 u 的距离最大值。多组询问。

n2×105,kn

f(0/1,u)u 的儿子中,子树内深度最/次深的 儿子节点编号,这个预处理下深度就能求了。这样可以做到 O(qk)

搞个倍增 g(i,u) 表示跳 [0,2i] 次的最大答案。

则有转移 g(i,u)=max(g(i1,u),g(i1,fa(i1,u))+maxdep(fa(u))dep(u))g(0,u) 为不为他自己的 f 距离 u 的距离。

就做完了,往上跳的时候累加跳过的距离并取 max 即可。注意不要走包含自己节点的子树(这就是为什么处理 f(1,u))。时间复杂度 O((n+q)logk)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+7;
vector<int>e[maxn];
int n,q;
int dep[maxn],mxdep[maxn],f[maxn][2],g[19][maxn],fa[19][maxn];
void update(int u,int v){
    if(mxdep[f[u][0]]<=mxdep[v]){
        f[u][1]=f[u][0];
        f[u][0]=v;
        return;
    }
    if(mxdep[f[u][1]]<=mxdep[v]) f[u][1]=v;
}
void dfs(int u,int Fa){
    dep[u]=dep[Fa]+1;
    mxdep[u]=dep[u];
    fa[0][u]=Fa;
    for(int v:e[u]) if(v!=Fa)
        dfs(v,u),mxdep[u]=max(mxdep[u],mxdep[v]);
}
void dfs1(int u,int Fa){for(int v:e[u]) if(v!=Fa) dfs1(v,u),update(u,v);}
void init(){
    for(int i=1;i<=n;i++){
        if(f[fa[0][i]][0]==i){
            if(!f[fa[0][i]][1])g[0][i]=1;
            else g[0][i]=mxdep[f[fa[0][i]][1]]-dep[fa[0][i]]+1;
        }else
            g[0][i]=mxdep[f[fa[0][i]][0]]-dep[fa[0][i]]+1;
    }
    for(int i=1;i<=n;i++) f[i][0]=f[i][1]=0;
    for(int j=1;j<=17;j++)
        for(int i=1;i<=n;i++)
            fa[j][i]=fa[j-1][fa[j-1][i]];
    for(int j=1;j<=17;j++)
        for(int i=1;i<=n;i++)
            g[j][i]=max(g[j-1][i],g[j-1][fa[j-1][i]]+dep[i]-dep[fa[j-1][i]]);
}
void solve(){
    cin>>n;
    for(int i=1,u,v;i<n;i++){
        cin>>u>>v;
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }
    dfs(1,0);
    dfs1(1,0);
    init();
    cin>>q;
    for(int i=1,u,k;i<=q;i++){
        cin>>u>>k; k=min(k,dep[u]-1);
        int res=mxdep[u]-dep[u],now=0;
        for(int j=17;~j;j--)
            if(k&(1<<j)) res=max(res,g[j][u]+now),now+=dep[u]-dep[fa[j][u]],u=fa[j][u];
        cout<<res<<" \n"[i==q];
    }
    for(int i=1;i<=n;i++) e[i].clear();
}
signed main(){
    int T;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

CF1843F Omsk Metro

给你一棵树,询问 (u,v) 路径序列上权值的子段是否存在和为 k。权值只有 ±1

n2×105

诈骗题。由于权值为 ±1,所以子段和连续,所以树剖一下直接求出路径上的子段和区间 [l,r] 判断 k 是否在里面即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+7;
#define ls (now<<1)
#define rs (now<<1|1)
#define mid ((l+r)>>1)
int n,q;
int w[maxn];
vector<int>e[maxn];
int ql[maxn],qr[maxn],qk[maxn],qcnt;
int top[maxn],dfncnt,idx[maxn],dfn[maxn],son[maxn],siz[maxn],fa[maxn],dep[maxn];
void dfs1(int u,int F){
    siz[u]=1;
    fa[u]=F;
    dep[u]=dep[F]+1;
    for(int v:e[u]){
        if(v!=F){
            dfs1(v,u);
            if(siz[son[u]]<siz[v]) son[u]=v;
            siz[u]+=siz[v];
        }
    }
}
void dfs2(int u,int t){
    top[u]=t;
    dfn[u]=++dfncnt;
    idx[dfncnt]=u;
    if(son[u]) dfs2(son[u],t);
    for(int v:e[u]){
        if(v!=fa[u]&&v!=son[u]){
            dfs2(v,v);
        }
    }
}
struct node{
    int st;
    int sum,pre,suf;
    int sum1,pre1,suf1;
    node(int st=0,int sum=0,int pre=0,int suf=0,int sum1=0,int pre1=0,int suf1=0): st(st),sum(sum),pre(pre),suf(suf),sum1(sum1),pre1(pre1),suf1(suf1){}
    node friend operator+(const node &a, const node &b){
        node c;
        c.st=a.st+b.st;
        c.sum=max({0,a.sum,b.sum,a.suf+b.pre});
        c.pre=max({0,a.pre,a.st+b.pre});
        c.suf=max({0,b.suf,b.st+a.suf});
        c.sum1=min({0,a.sum1,b.sum1,a.suf1+b.pre1});
        c.pre1=min({0,a.pre1,a.st+b.pre1});
        c.suf1=min({0,b.suf1,b.st+a.suf1});
        return c;
    }
}tr[maxn<<2];
void upd(node &x,node y){x=x+y;}
void upd1(node &x,node y){x=y+x;}
void pushup(int now){
    tr[now]=tr[ls]+tr[rs];
}
void build(int now,int l,int r){
    if(l==r){
        tr[now].st=w[idx[l]];
        tr[now].sum=max(0,w[idx[l]]);
        tr[now].pre=max(0,w[idx[l]]);
        tr[now].suf=max(0,w[idx[l]]);
        tr[now].sum1=min(0,w[idx[l]]);
        tr[now].pre1=min(0,w[idx[l]]);
        tr[now].suf1=min(0,w[idx[l]]);
        return;
    }
    build(ls,l,mid); build(rs,mid+1,r);
    pushup(now);
}
node query(int now,int l,int r,int L,int R){
    if(L<=l&&r<=R){
        return tr[now];
    }
    node res;
    if(L<=mid) upd(res,query(ls,l,mid,L,R));
    if(mid+1<=R) upd(res,query(rs,mid+1,r,L,R));
    return res;
}

node query_tr(int u,int v){
    node res,re1;
    while(top[u]!=top[v]){
        if(dep[top[u]]>dep[top[v]]){
            upd1(res,query(1,1,n,dfn[top[u]],dfn[u]));
            u=fa[top[u]];
        }else{
            upd1(re1,query(1,1,n,dfn[top[v]],dfn[v]));
            v=fa[top[v]];
        }
    }
    if(dep[u]>dep[v]){
        upd1(res,query(1,1,n,dfn[v],dfn[u]));
    }else{
        upd1(re1,query(1,1,n,dfn[u],dfn[v]));
    }
    swap(res.pre,res.suf);
    swap(res.pre1,res.suf1);
    upd(res,re1);
    return res;
}
void solve(){
    cin>>q; dfncnt=qcnt=0;n=1;w[1]=1;
    for(int i=1,u,v,k;i<=q;i++){
        char op;
        cin>>op;
        if(op=='+'){
            cin>>v>>k;
            n++; w[n]=k;
            e[v].emplace_back(n);
            e[n].emplace_back(v);
        }else{
            cin>>u>>v>>k;
            qcnt++;
            ql[qcnt]=u;
            qr[qcnt]=v;
            qk[qcnt]=k;
        }
    }
    dfs1(1,0);
    dfs2(1,1);
    build(1,1,n);
    for(int i=1;i<=qcnt;i++){
        node p=query_tr(ql[i],qr[i]);
        if(!qk[i]||(p.sum1<=qk[i]&&qk[i]<=p.sum)) cout<<"YES\n";
        else cout<<"NO\n";
    }
    for(int i=1;i<=n;i++){
        son[i]=0;
        e[i].clear();
    }
}
signed main(){
    int T;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}

CF1042E Vasya and Magic Matrix

一个 nm 列的矩阵,每个位置有权值。给定一个出发点 (s,t),每次可以等概率的移动到一个权值小于当前点权值的点(不能移动就停止),同时得分加上两个点之间欧几里得距离的平方,问得分的期望,对 998244353 取模。

n,m103

显然把小于出发点权值的位置拎出来,形成一个序列(设长为 d),左边的可以走到右边。考虑期望 DP。设 f(i) 表示从 i 出发的距离期望,则有

f(i)=1dnxt(i)+1j=nxt(i)ddis(i,j)+f(j)

nxt(i) 为第一个 vj<vi 的位置。

把 dis 拆成四个部分,考虑后缀和优化一下就搞完了。时间复杂度 O(nmlognm)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e3+7;
const int mod=998244353;
int n,m,s,t;
int a[maxn][maxn];
struct node{
    int x,y,val;
    bool operator<(const node &o)const{return val>o.val;}
}b[maxn*maxn];
int cnt;
int f[maxn*maxn],g[maxn*maxn],inv[maxn*maxn];
int prex[maxn*maxn],prexx[maxn*maxn],prey[maxn*maxn],preyy[maxn*maxn];
void add(int &x,int y){x+=y;if(x>=mod) x-=mod;}
void mul(int &x,int y){x=x*y%mod;}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin>>n>>m;
    inv[1]=1;
    for(int i=2;i<=n*m;i++) 
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>a[i][j];
    cin>>s>>t;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(a[i][j]<a[s][t]) b[++cnt]={i,j,a[i][j]};
    b[++cnt]={s,t,a[s][t]};
    sort(b+1,b+cnt+1);
    for(int i=cnt;i;i--){
        prex[i]=(prex[i+1]+b[i].x)%mod;
        prexx[i]=(prexx[i+1]+b[i].x*b[i].x)%mod;
        prey[i]=(prey[i+1]+b[i].y)%mod;
        preyy[i]=(preyy[i+1]+b[i].y*b[i].y)%mod;
    }
    int pos=cnt;
    for(int i=cnt;i;i--){
        if(b[i].val==b[cnt].val) continue;
        if(b[i].val!=b[i+1].val) pos=i+1;
        add(f[i],(cnt-pos+1)*b[i].x*b[i].x%mod);
        add(f[i],(cnt-pos+1)*b[i].y*b[i].y%mod);  
        add(f[i],prexx[pos]);
        add(f[i],preyy[pos]);
        add(f[i],mod-2*b[i].x*prex[pos]%mod);
        add(f[i],mod-2*b[i].y*prey[pos]%mod);
        add(f[i],g[pos]);
        mul(f[i],inv[cnt-pos+1]);
        g[i]=(g[i+1]+f[i])%mod;
    }
    cout<<f[1];
    return 0;
}

CF1174E Ehab and the Expected GCD Problem

f(A)A 序列的前缀 gcd 中不同数值的数量。求对于长为 n 的排列,求出 f(A) 最大的排列数量。

n106

手玩/爆搜小样例可以发现,gcd 以每次 ÷2 或 3 的速度递减,即 A1=2x3y,由于 23<32 所以 y1。且 A1=2x 最优,若 2x13n 则也不劣。记 p(x)=n/xx 的倍数数量,于是考虑 DP,设 f(i,x,y) 为填数到第 i 位,当前 gcd 为 2x3y 的方案数。

若 gcd 不变则可以填 p(2x3y) 个数,由于前面已经填了 i1 个数,所以可以填 p(2x3y)(i1) 个数。
若 gcd 变成 gcd÷2 则可以填 p(2x13y) 个数,由于填 p(2x3y) gcd 不会变,所以只能填 p(2x13y)p(2x3y) 个数。
若 gcd 变成 gcd÷3 则可以填 p(2x3y1) 个数,由于填 p(2x3y) gcd 不会变,所以只能填 p(2x3y1)p(2x3y) 个数。

f(i,x,y)f(i1,x,y),f(i1,x1,y),f(i1,x,y1)

时间复杂度 O(nlogn)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+7;
const int mod=1e9+7;
int n;
int f[maxn][20][2],g[2]={1,3};
int lg2[maxn];
int biao[]={0,1,1,4,2,6,120,600,240,1440,15120,120960,7015680};
int p(int y){return n/y;}
void add(int &x,int y){x+=y;if(x>=mod) x-=mod;}
signed main(){
    cin>>n;
    if(n<=12){return cout<<biao[n],0;}
    for(int i=2;i<=n;i++)
        lg2[i]=lg2[i>>1]+1;
    f[1][lg2[n]][0]=1;
    if((1<<(lg2[n]-1))*3<=n) f[1][lg2[n]-1][1]=1;
    for(int i=2;i<=n;i++){
        for(int j=lg2[n];~j;j--){
            for(int k=1;~k;k--){
                add(f[i][j][k],1ll*f[i-1][j][k]*(p((1<<j)*g[k])-i+1)%mod);
                if(j!=lg2[n]) add(f[i][j][k],1ll*f[i-1][j+1][k]*(p((1<<j)*g[k])-p((1<<(j+1))*g[k]))%mod);
                if(!k) add(f[i][j][k],1ll*f[i-1][j][k+1]*(p(1<<j)-p((1<<j)*3))%mod);
            }
        }
    }
    cout<<f[n][0][0];
    return 0;
}

CF1158C Permutation recovery

有一个长为 n 的排列 p,记 nxt(i) 为右边第一个比 pi 大的数的位置。

现在 p 和一些 nxt(i) 丢失,构造一组合法的 p,或报告无解。

n2×105

考虑将丢失的 nxt(i) 设为 i+1。这样可以保证 i 的限制最小,合法的可能最大。

填满以后直接按 nxt(i) 从小到大构造(赋权)。判断是否产生冲突即可(单调栈)。时间复杂度 O(n)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+7;
int n,cnt,top,p[maxn],nxt[maxn],stk[maxn];
vector<int>to[maxn];
void solve(){
    cin>>n; cnt=top=0;
    for(int i=1;i<=n;i++){
        cin>>nxt[i];
        if(nxt[i]==-1) nxt[i]=i+1;
        to[nxt[i]].emplace_back(i);
    }
    for(int i=1;i<=n+1;i++){
        for(int j=to[i].size()-1;~j;j--) p[to[i][j]]=++cnt;
        to[i].clear();
    }
    for(int i=1;i<=n;i++){
        while(top&&p[stk[top]]<p[i]){
            if(nxt[stk[top]]!=i){
                cout<<"-1\n";
                return;
            }top--;
        }
        stk[++top]=i;
    }
    while(top){
        if(n+1!=nxt[stk[top]]){
            cout<<"-1\n";
            return;
        }top--;
    }
    for(int i=1;i<=n;i++) cout<<p[i]<<" \n"[i==n];
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int T;
    cin>>T;
    while(T--){
        solve();
    }
    return 0;
}
posted @   view3937  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
Title
点击右上角即可分享
微信分享提示