ABC 杂题

ABC186E Throne

n 个圆形排列的椅子,一开始你在 s+1 上,每次可以向右移动 k 个位置,求移动到 1 的最小步数,或报告无解。

2n,k109

很容易想到构造方程:

s+qk0(modn)

q(ns)k1(modn)

直接 exgcd 求逆元,算出在 [1,n1] 范围内的解即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
void exgcd(int a,int b,int &inv,int &x,int &y){
    if(!b) inv=a,x=1,y=0;
    else exgcd(b,a%b,inv,y,x), y-=(a/b)*x;
}
int T;
signed main(){
    cin>>T;
    while(T--){
        int n,s,k,inv,x,y;
        cin>>n>>s>>k;
        int gcd=__gcd(__gcd(n,s),k);
        n/=gcd;s/=gcd;k/=gcd;
        exgcd(k,n,inv,x,y);
        if(inv!=1){
            cout<<-1<<'\n';
        }else{
            inv=(x+n)%n;
            cout<<(n-s)*inv%n<<'\n';
        }
    }
    return 000;
}

ABC186F Rook on Grid

有一个 n×m 的网格,其中有 k 个障碍 (xi,yi),有个猴子,它每走一步可以沿着一个方向走任意格,不能穿过障碍,求猴子在两步及以内可以到达的格子数。

n,m,k2×105

Xi 为第 i 列从上到下可以到达的最大坐标,Yi 为第 i 行从左到右可以到达的最大坐标。

先处理出从左往右可以到达的格子数 Yi,然后考虑从上到下且不重复的格子,若对于 j[1,Xi],Yj<i,则将格子 (i,j) 加入答案,这个限制直接用树状数组二维数点维护即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+34;

int n,m,k;
int x[maxn],y[maxn];
int ans=0;
int tree[maxn];
vector<int>query[maxn];
int lowbit(int x){return x&-x;}
void add(int pos,int c){pos++;for(;pos<=200030;pos+=lowbit(pos)) tree[pos]+=c;}
int q(int pos){pos++;int res=0;for(;pos;pos-=lowbit(pos)) res+=tree[pos];return res;}
signed main(){
    cin>>n>>m>>k;
    // assert(m<=n);
    for(int i=1;i<=max(n,m);i++) x[i]=n+1;
    for(int i=1;i<=max(n,m);i++) y[i]=m+1;
    for(int i=1,X,Y;i<=k;i++){
        cin>>X>>Y;
        x[Y]=min(x[Y],X);
        y[X]=min(y[X],Y);
    }
    
    for(int i=1;i<=x[1]-1;i++) ans+=y[i]-1, query[y[i]].emplace_back(i);
    for(int i=x[1];i<=n;i++) query[1].emplace_back(i);
    for(int i=1;i<y[1];i++){
        for(int j:query[i]) add(j,1);
        ans+=q(x[i]-1);
    }
    cout<<ans;
    return 0;
}

ABC185E Sequence Matching

给一棵树,Q 次操作:

  • 对于编号为 x 的边,和其一个端点 u,将 u 不经过 Ex=(u,v) 就能到达的点的分数加上 k

求操作完后每个点的分数。

n2×105

考虑进行标记(差分)。钦定 1 为根,处理出每个点的父亲,则对于每个询问,若 u=fav 则将 u 子树外的点加上 k,这等价于将全树的分数加上 k 再将 v 子树内的点减掉 k;否则,直接将 u 子树内的点加 k 即可。

时间复杂度 O(n+Q)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+3;

vector<int>e[maxn];

int tag[maxn],f[maxn],n,q;
struct{
    int u,v; 
}E[maxn];
void dfs(int u,int fa){
    f[u]=fa;
    for(int v:e[u]){
        if(v!=fa) dfs(v,u);
    }
}
void dfs1(int u,int fa){
    for(int v:e[u]){
        if(v!=fa){
            tag[v]+=tag[u];
            dfs1(v,u);
        }
    }
}
signed main(){
    cin>>n;
    for(int i=1,u,v;i<n;i++){
        cin>>u>>v;
        E[i]={u,v};
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }
    dfs(1,0);
    cin>>q;
    while(q--){
        int t,id,x,u,v;
        cin>>t>>id>>x;
        if(t==1){
            u=E[id].u,v=E[id].v;
        }else{
            u=E[id].v,v=E[id].u;
        }
        if(u==f[v]){
            tag[1]+=x;
            tag[v]-=x;
        }else{
            tag[u]+=x;
        }
    }
    dfs1(1,0);
    for(int i=1;i<=n;i++){
        cout<<tag[i]<<'\n';
    }
    return 0;
}

ABC367F Rearrange Query

给你两个序列 a,bq 次询问 a[l,r],b[L,R] 之间每个元素的个数是否相等。

ai,bi,l,r,L,Rn2×105

朴素的思路是 n2 的,就是考虑开个 n2 的桶,对于每个元素进行判断,但是显然爆炸,而且优化不了。

所以只能舍弃正确性,保证复杂度,将每一个数映射到一个随机数(这里将 x 映射到 hx)上,由于问题的必要条件是两个的和相等,而因为随机,所有和相等而不满足的概率极小,可以保证正确性。使用乘法,异或来判断也可以。

时间复杂度 O(nlogn)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+3;
int a1[maxn],a2[maxn],b1[maxn],b2[maxn];
int l,r,L,R,n,q;
const int mod1=1000000241;
const int mod2=1000000931;
int ha1(int x){
    int ret=1,de=41;
    for(;x;x>>=1,de=de*de%mod1) if(x&1) ret=ret*de%mod1;
    return ret; 
}
int ha2(int x){
    int ret=1,de=37;
    for(;x;x>>=1,de=de*de%mod2) if(x&1) ret=ret*de%mod2;
    return ret; 
}
signed main(){
    cin>>n>>q;
    for(int i=1,x;i<=n;i++){
        cin>>x;
        a1[i]=(a1[i-1]+ha1(x))%mod1;
        // a2[i]=(a2[i-1]+ha2(x))%mod2;
    }
    for(int i=1,x;i<=n;i++){
        cin>>x;
        b1[i]=(b1[i-1]+ha1(x))%mod1;
        // b2[i]=(b2[i-1]+ha2(x))%mod2;
    }
    while(q--){
        cin>>l>>r>>L>>R;
        if(R-L!=r-l){
            cout<<"No\n";
            continue;
        }
        int l1=(a1[r]-a1[l-1]+mod1)%mod1;
        // int r1=(a2[r]-a2[l-1]+mod2)%mod2;
        int l2=(b1[R]-b1[L-1]+mod1)%mod1;
        // int r2=(b2[R]-b2[L-1]+mod2)%mod2;
        if(l1==l2){
            cout<<"Yes\n";
        }else{
            cout<<"No\n";
        }
    }
    return 0;
}
// 单模数都能过

ABC365E Xor Sigma Problem

给你一个序列 a,求其每个子段的异或和的和。

n2×105,aiV108

由于异或的可加性,求异或和可以用前缀和处理掉,设前缀数组为 b,则题目转化为求 i=1nj=i+1nbjbi1

加法和异或并不具有交换结合律,但是可以发现每次的 bj 数量是递减的,而异或是按位运算,所以考虑拆位,每位开一个桶 tx 记录二进制第 x 为为 1 的个数,对于一个 bi 只有 bj 在第 x 位上不同才有贡献,这样计算是 O(logV) 的,同样从桶中删除一个 b 也是 log 的,所以总复杂度为 O(nlogV)

最终的式子:

ans=i=1nj=1log2bi+12jf(i,j)

f(i,j)={nitjbi&2j=1tjbi&2j=0

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+3;
int a[maxn],b[maxn];
int n;
int ton[64];
bitset<32>bit[maxn];
signed main(){
    cin>>n;
    bit[0]=0;
    for(int i=1,x;i<=n;i++){
        cin>>x;
        a[i]=x;
        b[i]=b[i-1]^x;
        bit[i]=b[i];
        for(int j=0;j<31;j++){
            ton[j]+=bit[i][j];
        } 
    }
    int ans=0;
    for(int i=1;i<n;i++){
        for(int j=0;j<31;j++){
            ton[j]-=bit[i][j];
        }
        for(int j=0;j<31;j++){
            int cnt=bit[i-1][j]?n-i-ton[j]:ton[j];
            ans+=(1ll<<j)*cnt;
        }
    }
    cout<<ans;
    return 0;
}

三倍经验:
P9236 [蓝桥杯 2023 省 A] 异或和之和

P3917 异或序列


ABC371E I Hate Sigma Problems

f(i,j)[i,j] 内不同数字的个数,求 i=1nj=inf(i,j)

ain2×105

gii 位置前面第一个与 ai 相同的位置,则每个 i 的贡献区间为 [li,ri](li[gi,i],ri[i,n]),其贡献为 (igi)(ni+1),时间复杂度 O(n)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+3;
int n,ans;
int a[maxn],f[maxn];
vector<int>v[maxn];
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++) v[i].emplace_back(0);
    for(int i=1;i<=n;i++){
        cin>>a[i];
        f[i]=v[a[i]].back();
        v[a[i]].emplace_back(i);
    }
    for(int i=1;i<=n;i++){
        ans=ans+(i-f[i])*(n-i+1);
    }
    cout<<ans;
    return 0;
}

ABC268E Chinese Restaurant (Three Star Version)

给你一个排列 p,求 mind=0n1i=0n1dis(i,p(i+d)modn)

n2×105

考虑到对于一个 pi,它离 i 的距离大概如图

img

由两段单调区间构成,分别单增和单减。我们可以统计初始态时上升与下降的个数之差 x,每次 dd+1 时就把初始 d=0 的和加上 x,再判断是否到达断点(相当于将贡献反向,原来 +1 的贡献在断点 2 后就变为 1),再而,n 为奇数时,dismax 有两个点 t,t+1 取到,所以在这两个点时,先是将 +10,再在 t+101,开一个桶记录每个 dx 的增减即可,时间复杂度 O(n),代码使用 O(nlogn) 的写法。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+3;
int n,a[maxn],b[maxn],c[maxn],ad,de,sum,ans=0x3f3f3f3f3f3f3f3f;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
signed main(){
    cin>>n;
    if(n&1){
        for(int i=0;i<n;i++){
            cin>>a[i];
            int dis=min(abs(a[i]-i),n-abs(a[i]-i));
            int dit=min(abs(a[i]-(i+1)),n-abs(a[i]-(i+1)));
            int pos1=a[i],pos2=(a[i]+n/2)%n,pos3=(a[i]+n/2+1)%n,dis1=pos1-i,dis2=pos2-i,dis3=pos3-i;
            if(dis<dit) ad++;
            else if(dis>dit) ad--;
            if(i>pos1) dis1+=n;
            if(i>pos2) dis2+=n;
            if(i>pos3) dis3+=n;
            q.push({dis1,2});
            q.push({dis2,-1});
            q.push({dis3,-1});
            sum+=dis;
        }
    }else{
        for(int i=0;i<n;i++){
            cin>>a[i];
            int dis=min(abs(a[i]-i),n-abs(a[i]-i));
            int dit=min(abs(a[i]-(i+1)),n-abs(a[i]-(i+1)));
            int pos1=a[i],pos2=(a[i]+n/2)%n,dis1=pos1-i,dis2=pos2-i;
            if(dis<dit) ad++;
            else ad--;
            if(i>pos1) dis1+=n;
            if(i>pos2) dis2+=n;
            q.push({dis1,2});
            q.push({dis2,-2});
            sum+=dis;
        }
    }
    while(!q.empty()&&!q.top().first) q.pop();
    ans=sum;
    for(int d=1;d<=n;d++){
        sum+=ad;
        while(!q.empty()&&q.top().first==d){
            ad+=q.top().second;
            q.pop();
        }
        ans=min(ans,sum);
    }
    cout<<ans;
    return 0;
}

ABC374E Sensor Optimization Dilemma 2

唐氏 trick 题。

加工每个产品有 n 步,每步中可以选择机器 Si,用 pi 费用加工 ai 个产品;或者选择机器 Ti,用 qi 费用加工 bi 个产品,求在总费用不超过 x 的情况下,求最大可加工的产品数。

n,ai,bi100,pi,qi,x107

显然可以二分。二分一个 t 表示产品数,枚举 Si 的数量,进而可以算出 Ti 的数量,时间复杂度 O(xnlogxn)(AT 少爷机可过),但是考虑到 ai,bi 的范围很小,实际上我们是枚举 Cpi+Dqix,令 w=Cpi+Dqixw 的循环节为 gcd(ai,bi)100,所以我们只要枚举 100C/D 即可,时间复杂度 O(nTwlogxn),槽点是它 n 怎么开这么小,导致想了一会费用流 😕。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=103;
int n,X;
int a[maxn],b[maxn],p[maxn],q[maxn];
bool check(int x){
    int cost=0;
    for(int i=1;i<=n;i++){
        int nowcost=1e13;
        int unit1=x/a[i]+1,unit2=x/b[i]+1;
        for(int j=0;j<=min(unit1,min(unit2,100ll));j++){
            int unitj1=(x-a[i]*(unit1-j)<=0?0:((x-a[i]*(unit1-j)-1)/b[i]+1));
            int unitj2=(x-b[i]*(unit2-j)<=0?0:((x-b[i]*(unit2-j)-1)/a[i]+1));
            int cost3=(unit1-j)*p[i]+q[i]*(unitj1),cost4=(unit2-j)*q[i]+p[i]*(unitj2);
            nowcost=min(nowcost,min(cost3,cost4));
        }
        cost+=nowcost;
    }
    return cost<=X;
}
signed main(){
    cin>>n>>X;
    for(int i=1;i<=n;i++){
        cin>>a[i]>>p[i]>>b[i]>>q[i];
    }
    int l=0,r=1e9,ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)){
            l=mid+1;
            ans=mid;
        }else{
            r=mid-1;
        }
    }
    cout<<ans;
    return 0;
}

ABC375F Road Blocked

给你一个无向图,q 次操作:

  • 断掉编号为 i 的边;
  • (u,v) 最短路,不存在输出 -1。

n300,mn(n1)2,q2×105,保证操作 1 次数不超过 w=300

这个对操作 1 的限制不禁让人联想到 CSP 的插入排序那题。于是我们考虑只在操作 1 的时候用 Floyd 取一遍最短路,时间复杂度 O(n3w+q)(不是你 AT 神机怎么 8e9 都跑不过了),不可过。

考虑反过来操作,将断边变成加边。首先对删了所有操作 1 中的边的图上跑一遍全局 Floyd,然后考虑加边 (u,v) 带来的影响:

  • u,v 之间的最短路;
  • 任意点到 u 的最短路;
  • 任意点到 v 的最短路;
  • 经过点 u 的最短路;
  • 经过点 v 的最短路。

第 1,4,5 三个影响直接更新即可,而对于 2,3 操作相当于 Floyd 枚举断点 kk 只取 uv。所以综上时间复杂度 O(n3+n2w+q)

点击查看代码
#include<bits/stdc++.h>
#define int long long
const int maxn=3000003;
const int inf=0x3f3f3f3f3f3f3f3f;
using namespace std;
int n,m,q;
struct edge{
    int u,v,w;
}E[maxn],d[maxn];
int dis[303][303];
int op[maxn],x[maxn],y[maxn];
void floyd(){
    memset(dis,0x3f,sizeof dis);
    for(int i=1;i<=m;i++){
        if(E[i].u) dis[E[i].u][E[i].v]=dis[E[i].v][E[i].u]=min(dis[E[i].u][E[i].v],E[i].w);
    }
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
void update(int x){
    dis[d[x].u][d[x].v]=dis[d[x].v][d[x].u]=min(dis[d[x].u][d[x].v],d[x].w);
    for(int i=1;i<=n;i++) dis[d[x].u][i]=min(dis[d[x].u][i],d[x].w+dis[d[x].v][i]);
    for(int i=1;i<=n;i++) dis[i][d[x].u]=min(dis[i][d[x].u],d[x].w+dis[i][d[x].v]);
    for(int i=1;i<=n;i++) dis[d[x].v][i]=min(dis[d[x].v][i],d[x].w+dis[d[x].u][i]);
    for(int i=1;i<=n;i++) dis[i][d[x].v]=min(dis[i][d[x].v],d[x].w+dis[i][d[x].u]);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                dis[i][j]=min(dis[i][j],dis[i][d[x].u]+dis[d[x].u][j]);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                dis[i][j]=min(dis[i][j],dis[i][d[x].v]+dis[d[x].v][j]);
}
int ans[maxn];
signed main(){
    ios::sync_with_stdio(0);
    cin>>n>>m>>q;
    for(int i=1,u,v,w;i<=m;i++){
        cin>>u>>v>>w;
        E[i]={u,v,w};
    }
    for(int i=1;i<=q;i++){
        cin>>op[i];
        if(op[i]==1){
            cin>>x[i];
            d[i]=E[x[i]];
            E[x[i]]={0,0,inf};
        }else{
            cin>>x[i]>>y[i];
        }
    }
    floyd();
    for(int i=q;i;i--){
        if(op[i]==1){
            update(i);
        }else{
            if(dis[x[i]][y[i]]==inf) ans[i]=-1;
            else ans[i]=dis[x[i]][y[i]];
        }
    }
    for(int i=1;i<=q;i++){
        if(op[i]==2) cout<<ans[i]<<'\n';
    }
    return 0;
}

ABC375G Road Blocked 2

给你一个无向图,对于每一条边,判断删去该边后最短路长度是否会变化。

n,m2×105

正解:先跑出 1,n 的单源最短路,然后就可以求出在所有最短路径上的边的边集 E,然后找,断桥边最短路长度肯定会改变。时间复杂度 O(mlogm+n)

解:先跑出 1,n 的单源最短路以及最短路径树,然后随便弄出一条 1n 的最短路径,合法解肯定在路径上。对于非路径上的边 (u,v),若其在最短路边集里,则对于路径上 [lca1(u,n),lcan(v,1)] 一段的边一定是不合法的,直接接上线段树区间覆盖即可。时间复杂度 O(mlogm)tmd 正反边的树不同构调了几天终于调出来了啊。

点击查看代码
#include<bits/stdc++.h>
#define int long long
const int maxn=4e5+3;
const int inf=0x3f3f3f3f3f3f3f3f;
using namespace std;
int n,m;
struct Ed{
    int u,v,w;
}E[maxn];
struct edge{
    int v,w,id;
}up[maxn];
vector<edge>e[maxn],g[maxn];
int vis1[maxn],visn[maxn];
struct dst{
    int id,dis;
    bool operator<(const dst &o)const{return dis>o.dis;}
};
priority_queue<dst>q;
int dis1[maxn],disn[maxn];

void dij(){
    memset(dis1,0x3f,sizeof dis1);
    memset(disn,0x3f,sizeof disn);
    q.push({1,dis1[1]=0});
    while(!q.empty()){
        dst u=q.top();
        q.pop();
        if(!vis1[u.id]){
            vis1[u.id]=1;
            for(edge v:e[u.id]){
                if(dis1[v.v]>dis1[u.id]+v.w){
                    dis1[v.v]=dis1[u.id]+v.w;
                    up[v.v]={u.id,v.w,v.id};
                    q.push({v.v,dis1[v.v]});
                }
            }
        }
    }
    for(int i=2;i<=n;i++){
        edge to=up[i];
        g[i].push_back(to);
        g[to.v].push_back({i,to.w,to.id});
    }
    q.push({n,disn[n]=0});
    while(!q.empty()){
        dst u=q.top();
        q.pop();
        if(!visn[u.id]){
            visn[u.id]=1;
            for(edge v:e[u.id]){
                if(disn[v.v]>disn[u.id]+v.w){
                    disn[v.v]=disn[u.id]+v.w;
                    q.push({v.v,disn[v.v]});
                }
            }
        }
    }
}
int stk[maxn],ttf[maxn],top,ot[maxn],ans[maxn],FA[2][19][maxn],len,dep[2][maxn],idx[maxn],gg,dfn[maxn];
void dfs1(int u,int fa,int id){
    if(gg) return;
    stk[++top]=id;
    ttf[top]=u;
    if(u==n){
        len=top-1;
        for(int i=2;i<=top;i++) ot[stk[i]]=1,idx[ttf[i]]=i-1,dfn[i-1]=stk[i];
        gg=1;
        return;
    }
    for(edge v:g[u]){
        if(v.v!=fa)
        dfs1(v.v,u,v.id);
    }
    top--;
}
void dfs2(int u,int fa,int mmp){
    FA[mmp][0][u]=fa;
    dep[mmp][u]=dep[mmp][fa]+1;
    for(edge v:g[u]){
        if(v.v!=fa){
            dfs2(v.v,u,mmp);
        }
    }
}
void init(){
    for(int o=0;o<=1;o++)
    for(int j=1;j<=18;j++)
        for(int i=1;i<=n;i++)
            FA[o][j][i]=FA[o][j-1][FA[o][j-1][i]];
}
int lca(int u,int v,int mmp){
    if(dep[mmp][u]<dep[mmp][v]) swap(u,v); 
    int step=dep[mmp][u]-dep[mmp][v];
    for(int j=0;j<=18;j++)
        if(step&(1<<j)) u=FA[mmp][j][u];
    if(u==v) return u;
    for(int j=18;~j;j--)
        if(FA[mmp][j][u]!=FA[mmp][j][v]) 
            u=FA[mmp][j][u],v=FA[mmp][j][v];
    return FA[mmp][0][u];
}
#define ls (now<<1)
#define rs (now<<1|1)
int tr[maxn<<2],tag[maxn<<2];
void pushdown(int now){
    if(tag[now]){
        tr[ls]=tr[rs]=tag[ls]=tag[rs]=tag[now];
        tag[now]=0;
    }
}
void modify(int now,int l,int r,int L,int R){
    if(L<=l&&r<=R){
        tag[now]=tr[now]=1;
        return;
    }
    int mid=(l+r)>>1;
    pushdown(now);
    if(L<=mid) modify(ls,l,mid,L,R);
    if(mid+1<=R) modify(rs,mid+1,r,L,R);
}   
void query(int now,int l,int r){
    if(l==r){
        ans[dfn[l]]=tr[now];
        return;
    }
    int mid=(l+r)>>1;
    pushdown(now);
    query(ls,l,mid);
    query(rs,mid+1,r);
}   
signed main(){
    cin>>n>>m;
    for(int i=1,u,v,w;i<=m;i++){
        cin>>u>>v>>w;
        E[i]={u,v,w};
        e[u].push_back({v,w,i});
        e[v].push_back({u,w,i});
    }
    dij();
    dfs1(1,0,0);
    dfs2(1,0,0);
    dfs2(n,0,1);
    init();
    for(int i=1;i<=m;i++){
        if(!ot[i]){
            ans[i]=1;
            if(dis1[E[i].u]+E[i].w+disn[E[i].v]==dis1[n]){
                int v1=lca(E[i].u,n,0),v2=lca(E[i].v,1,1);
                if(idx[v1]+1<=idx[v2])
                modify(1,1,len,idx[v1]+1,idx[v2]);
            }
            if(dis1[E[i].v]+E[i].w+disn[E[i].u]==dis1[n]){
                int v1=lca(E[i].v,n,0),v2=lca(E[i].u,1,1);
                if(idx[v1]+1<=idx[v2])
                modify(1,1,len,idx[v1]+1,idx[v2]);
            }
        }
    }
    query(1,1,len);
    for(int i=1;i<=m;i++){
        if(!ans[i]) cout<<"Yes\n";
        else cout<<"No\n";
    }
    return 0;
}

ABC203D Pond

给你一个 n×n 的矩阵,求其所有 k×k 的子矩阵的中位数的最小值。

n800,0ai109

考虑到中位数的经典结论:有 n/2 个数 中位数。

于是二分中位数 mid,设 f[i][j] 表示以 (i,j) 为右下角的矩形内 mid 的数量,然后枚举每个 k×k 的子矩形判断是否存在数量 k2/2。时间复杂度 O(n2logai)注意有 0。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=803;
int a[maxn][maxn],b[maxn][maxn];
int n,k;
bool chk(int x){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            b[i][j]=b[i][j-1]+b[i-1][j]-b[i-1][j-1]+(a[i][j]>x);
    for(int i=1;i+k-1<=n;i++)
        for(int j=1;j+k-1<=n;j++)
            if(b[i+k-1][j+k-1]-b[i+k-1][j-1]-b[i-1][j+k-1]+b[i-1][j-1]<=k*k/2)
                return 1;
    return 0;
}
signed main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cin>>a[i][j];
    int l=0,r=1e9,ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(chk(mid)){
            r=mid-1;
            ans=mid;
        }else
            l=mid+1;
    }
    cout<<ans;
    return 0;
}

ABC232G Modulo Shortest Path

给你 n 个点的完全图,(u,v) 之间的边权为 (au+bv)modm,求 1n 的最短路。

n2×105,ai,bi<m109

前缀优化建图典题。

由于 ai,bi<m 所以 au+bv<2m,即

  • au+bv<m:边权为 au+bv
  • au+bvm:边权为 au+bvm

b 排序,可以发现对于一个确定的 ubv 成为一段前缀和一段后缀,分解为 au+bv=m 的位置,这个二分即可。

可以建出以下图:(p 为前缀辅助边,s 为后缀辅助边)

img

可以发现图中存在负边权,而跑 SPFA 会被卡。

考虑把边权转正,将图中边 1s4 加上 b4,对 s 做差分,将边 sii0,将边 sisi+1 的边权置为 bi+1bi 即可保证图不变但是边权全为正。即可跑 Dijkstra。时间复杂度 O(nlogn)

img

点击查看代码
#include<bits/stdc++.h>
#define int long long
const int maxn=5e5+3;
const int inf=0x3f3f3f3f3f3f3f3f;
using namespace std;
int n,m;
int b[maxn];
struct node{
    int i,a,b;
    bool operator<(const node &o)const{return b<o.b;}
}c[maxn];
struct edge{int v,w;};
vector<edge>e[maxn*3];
void add(int u,int v,int w){e[u].push_back({v,w});}
struct dist{
    int id,dis;
    bool operator<(const dist &o)const{return dis>o.dis;}
};
priority_queue<dist>q;
int dis[maxn*3],vis[maxn*3];
    int pos1=0,posn=0;
void dij(){
    for(int i=0;i<=3*n+10;i++) dis[i]=inf;
    q.push({pos1,dis[pos1]=0});
    while(!q.empty()){
        dist u=q.top();
        q.pop();
        if(!vis[u.id]){
            vis[u.id]=1;
            for(edge v:e[u.id]){
                if(dis[v.v]>dis[u.id]+v.w){
                    dis[v.v]=dis[u.id]+v.w;
                    q.push({v.v,dis[v.v]});
                }
            }
        }
    }
}
signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>c[i].a, c[i].i=i;
    for(int i=1;i<=n;i++) cin>>c[i].b;
    sort(c+1,c+n+1);
    for(int i=1;i<=n;i++){
        if(c[i].i==1) pos1=i;
        if(c[i].i==n) posn=i;
        add(n+i,i,c[i].b);
        add(2*n+i,i,0);
        if(i<n)  add(2*n+i,2*n+i+1,c[i+1].b-c[i].b);
        if(i>1)  add(n+i,n+i-1,0);
        int pos=upper_bound(c+1,c+n+1,(node){0,0,m-c[i].a-1})-c-1;
        if(pos>=1)add(i,n+pos,c[i].a);
        // cerr<<i<<' '<<pos<<' '<<c[pos+1].b+c[i].a-m<<'\n';
        if(pos<n) add(i,2*n+pos+1,c[pos+1].b+c[i].a-m);
    }
    dij();
    cout<<dis[posn];
    return 0;
}

ABC373G No Cross Matching

给你 n 个白点 n 个黑点,一黑一白匹配,构造所有连线没有交点的方案。

n300,无三点共线,保证有解

考虑一个结论,做二分图最小权匹配,一定合法。 四个点 p1,p2,q1,q2 如果 (p1,q1),(p2,q2) 相连且两线相交,则可以调整为 (p1,q2),(p2,q1),使其不交,且总权会减小。

直接跑费用流 O(n4)。用 KM(匈牙利算法)可以做到 O(n3),用调整法也是 O(n3)

代码使用小码量常数小的调整法。过不了 n104 的加强版。如果考虑分治可以做到 O(n2logn)

点击查看代码
#include<bits/stdc++.h>
#define int long long
const int maxn=5e3+3;
const int inf=0x3f3f3f3f3f3f3f;
using namespace std;
int n,x[maxn],y[maxn];
int p[maxn];
double getdis(int x1,int y1,int x2,int y2){
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
signed main(){
    cin>>n;
    for(int i=1;i<=2*n;i++){
        cin>>x[i]>>y[i];
        p[i]=i+n;
    }
    while(1){
        int gg=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<i;j++){
                if(getdis(x[i],y[i],x[p[i]],y[p[i]])+getdis(x[j],y[j],x[p[j]],y[p[j]])>getdis(x[i],y[i],x[p[j]],y[p[j]])+getdis(x[j],y[j],x[p[i]],y[p[i]])){
                    swap(p[i],p[j]);
                    gg=1;
                }
            }
        }
        if(!gg) break;
    }
    for(int i=1;i<=n;i++) cout<<p[i]-n<<' ';
    return 0;
}

ABC377G Edit to Match

给你 n 个字符串,记 s0 为空,对于每个字符串 i[1,n],求通过删除/添加后缀字母的操作,最少需要操作多少次使得 si=sk,k[0,i1]

|si|2×105

考虑建 trie 树的过程,实质是每次新建一条链。而我们要求的就是当前字符串 si 末节点到 sk(k[0,i1]) 末节点的最短距离。于是对于 trie 树上的每个节点记一个 fu 表示距离前面的字符串末节点的最短距离,则每次加入字符串 si 时就更新链上每个节点的 fu=min(fu,ffau+1)。答案即为 si 末节点的 fei。然后把字符串 si 的末节点计入,即 fei=0,自下而上再更新 ffau=min(ffau,fu+1)。时间复杂度 O(|si|),空间复杂度 O(|Σ||si|)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+7;
char s[maxn];
int n,trie[maxn][26],val[maxn],id[maxn],node,fa[maxn*26];
int ins(){
    int lens=strlen(s);
    int u=0,res=0;
    for(int i=0;i<lens;i++){
        if(trie[u][s[i]-'a']==-1)
            trie[u][s[i]-'a']=++node;
        fa[trie[u][s[i]-'a']]=u,u=trie[u][s[i]-'a'];
        val[u]=min(val[u],val[fa[u]]+1);
    }
    res=val[u];
    val[u]=0;
    for(;fa[u];u=fa[u])
        val[fa[u]]=min(val[fa[u]],val[u]+1);
    return res;
}
signed main(){
    cin>>n;
    memset(val,0x3f,sizeof val);
    memset(trie,-1,sizeof trie);
    val[0]=0;
    for(int i=1;i<=n;i++){
        cin>>s;
        cout<<ins()<<'\n';
    }
    return 0;
}

ABC377F Avoid Queen Attack

给你一个 n×n 的棋盘,放 m 个皇后在 (ai,bi),求不会被攻击到的格子数。

n109,m103

n 很大,不支持我们一个个数格子,考虑从皇后入手去容斥答案。先假设以纵向攻击范围为参考(即在白色范围内只算纵向答案),在红色范围内,只有斜率为 1,0,1 的直线相交,这些交点(或重合的线)就是被算重的点(线),而如果交点经过了白线则不计入答案。算交点(线)的数量是 9m2 级别的,可以接受。综上时间复杂度 O(m2logm)(去重)。思路很简单,但是难在实现,这就是奇观“过 G 人数是 F 的两倍”的原因吧。

img

懒得写了,贺了官方题解。

点击查看代码
#include <iostream>
#include <set>

int main(){
    using namespace std;
    unsigned N, Q;
    cin >> N >> Q;

    // horizontal, vertical, and (anti)diagonal effects
    set<unsigned> horizontal, vertical, diagonal_slash, diagonal_backslash;
    for (unsigned i{}; i < Q; ++i){
        unsigned x, y;
        cin >> x >> y;
        horizontal.emplace(x);
        vertical.emplace(y);
        diagonal_slash.emplace(x + y);
        diagonal_backslash.emplace(x - y);
    }

    // vertical and horizontal effects can be immediately counted
    unsigned long ans{(N - size(horizontal)) * (N - size(vertical))};

    // anti-diagonal(with constant x + y)
    for (const auto d : diagonal_slash) {
        // the x coordinates of already inspected squares
        set<unsigned> cross_x;
        for (const auto x : horizontal)
            // insert if the intersection is inside
            if (1 <= d - x && d - x <= N)
                cross_x.emplace(x);
        for (const auto y : vertical)
            // insert if the intersection is inside
            if (1 <= d - y && d - y <= N)
                cross_x.emplace(d - y);

        // # squres (2 min(d, N + 1) - d - 1) subtracted by # already inspected squares is # newly found squares (# means "the number of")
        ans -= 2 * min(d, N + 1) - d - 1 - size(cross_x);
    }

    // diagonal(with constant x - y)
    for (const auto d : diagonal_backslash) {
        // the x coordinates of already inspected squares
        set<unsigned> cross_x;
        for (const auto x : horizontal)
            // insert if the intersection is inside
            if (1 <= x - d && x - d <= N)
                cross_x.emplace(x);
        for (const auto y : vertical)
            // insert if the intersection is inside
            if (1 <= d + y && d + y <= N)
                cross_x.emplace(d + y);
        for (const auto e : diagonal_slash)
            // insert if the intersection is inside
            if ((d + e) % 2 == 0 && 1 <= (d + e) / 2 && (d + e) / 2 <= N && 1 <= (e - d) / 2 && (e - d) / 2 <= N)
                cross_x.emplace((d + e) / 2);
        
        // # squares N - |d| subtracted by # already inspected squares is # newly found squares
        ans -= N - min(d, -d) - size(cross_x);
    }

    cout << ans << endl;
    return 0;
}

ABC371G Lexicographically Smallest Permutation

给你两个排列 a,p,可以执行任意次操作:

  • 将所有 ai 变为 api

求可以得到的字典序最小的 a

n2×105

建图,连边 ipi,形成若干置换环,由于要求字典序最小,从开头遍历置换环,贪心地找与前面的不冲突的最小的 ai 作为开头。而检查冲突需要 exCRT,需要高精度。

开一个数组 req 记录前面的环所出现的 xti(modleni),其实我们只要满足的是 reqdtnow(dlennow) 的限制,实际上复杂度是 O(nd(n)) 的,只维护 pklennow 的限制也是充要的,因此可以做到 O(nlogn)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+7;
int n;
int p[maxn],a[maxn];
vector<int>v[maxn];
int ans[maxn],pos[maxn],siz[maxn],scccnt,mi[maxn],req[maxn];
vector<int>d[maxn];
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>p[i];
        req[i]=0-1;
        if(i>1) if(d[i].empty())
        for(int k=i;k<=n;k*=i) for(int j=k;j<=n;j+=k)
            d[j].emplace_back(k);
    }
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++){
        if(!pos[i]){
            scccnt++;
            int mix=n+1;
            for(int j=i;!pos[j];j=p[j]){
                v[scccnt].emplace_back(j);
                pos[j]=++siz[scccnt];
            }
            for(int j=0;j<siz[scccnt];j++){
                int flag=1;
                for(int k:d[siz[scccnt]]){
                    if(req[k]!=-1&&j%k!=req[k]){
                        flag=0;
                        break;
                    }
                }
                if(flag)
                    if(mix==n+1||a[v[scccnt][j]]<a[v[scccnt][mix]])
                        mix=j;
            }
            for(int j=0;j<siz[scccnt];j++)
                ans[v[scccnt][j]]=a[v[scccnt][(j+mix)%siz[scccnt]]];
            for(int k:d[siz[scccnt]]) req[k]=mix%k;
        }
    }
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<' ';
    return 0;
}

ABC369G As far as possible

给你一棵以 1 为根的树,对于每个 k[1,n],求在树上任选 k 个关键点,经过这些点的最短路径最长。

贪心地选最长的链,删去,再选最长的链,如此下去一定最优。实质上是长链剖分的过程。时间复杂度 O(n)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+3;
struct edge{
    int v,w;
};
vector<edge>e[maxn];
int dis[maxn],siz[maxn],n,fa[maxn],dfncnt,son[maxn],w[maxn];
void dfs1(int u,int Fa){
    for(edge v:e[u]){
        if(v.v!=Fa){
            w[v.v]=v.w;
            dfs1(v.v,u);
            if(siz[v.v]+v.w>siz[u]) siz[u]=siz[v.v]+v.w,son[u]=v.v;
        }
    }
}
void dfs2(int u,int t){
    dis[t]+=w[u];
    if(son[u]) dfs2(son[u],t);
    for(edge v:e[u]){
        if(v.v!=fa[u]&&v.v!=son[u]){
            dfs2(v.v,v.v);
        }
    }
}
signed main(){ 
    cin>>n;
    for(int i=1,u,v,w;i<n;i++){
        cin>>u>>v>>w;
        e[u].push_back({v,w});
        e[v].push_back({u,w});
    }
    dfs0(1,0);
    dfs1(1,0);
    dfs2(1,1);
    sort(dis+1,dis+n+1,greater<int>());
    int ans=0;
    for(int i=1;i<=n;i++){
        ans+=dis[i];
        cout<<2*ans<<'\n';
    }
    return 0;
}

ABC380G Another Shuffle Window

给你一个长为 n 的排列 p,随机选择一个长为 k 的区间随机重排,求逆序对数量的期望。

kn2×105

诈骗题 + 妙妙题。对于一个无重复元素的序列重排,期望逆序对数为 (n2)2

证明:考虑对于一个数对 (i,j)(i<j) 是逆序对的概率为 12,数对数量为 (n2),证毕。

所以先求出全局的逆序对数,然后枚举每个窗口,则期望逆序对数为 (+)/(nk+1)

区间逆序对数可以维护一个树状数组,删掉开头相当于减掉窗口中小于它的数的数量。加上结尾相当于加上区间中大于它的数的数量。总时间复杂度 O(nlogn)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+7;
const int mod=998244353;
int n,k;
int a[maxn],sum;
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 tr[maxn];
#define lowbit(x) (x&-x)
void add(int x,int c){for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;}
int query(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
int quer1(int x){
    int res=query(n)-query(x-1);
    return res;
}
signed main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=n;i;i--){
        sum+=query(a[i]);
        add(a[i],1);
    }
    for(int i=n;i;i--) tr[i]=0;
    for(int i=1;i<=n;i++){
        sum+=quer1(a[i]);
        add(a[i],1);
    }
    for(int i=n;i;i--) tr[i]=0;
    int E=0,p2=qpow(2,mod-2),pn=qpow(n-k+1,mod-2),now=sum;
    for(int i=1;i<=k;i++) now-=2*quer1(a[i]),add(a[i],1);
    for(int i=1;i<=n-k+1;i++){
        E=(E+pn*(now%mod+k*(k-1)%mod*p2%mod)%mod)%mod;
        now+=2*query(a[i]-1); add(a[i],-1);
         if(i+k<=n) now-=2*quer1(a[i+k]),add(a[i+k],1);
    }
    cout<<E*p2%mod;
    return 0;
}

ABC383E Sum of Max Matching

又是一年退役季。一边上课一边口胡。

定义路径的权值为路径上的最大边权。定义 f(u,v)u,v 所有路径中的最小权值。

给你一个带权无向图,给出两个序列 a,b,问任意重排后 f(ai,bi) 的最小值。

n,m2×105

考虑向图上按边权从小到大加边,设当前边权为 r。维护一个带 vector二元权值 (a,b) 的并查集,每次贪心地将两个联通块内的 (a,b) 对消掉,显然代价为 r,代价总和最小。时间复杂度 O(mlogm)

hanhoudedidue 怒斥此为 KK 重构树板,让我们膜拜他!

bonus:如果固定 a,b 那还真是,否则要用 dp。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+7;
int n,m,k,A[maxn],B[maxn];
struct eE{
    int u,v,w;
    bool operator<(const eE &o)const{
        return w<o.w;
    }
}E[maxn];
struct mst{
    int fa[maxn],a[maxn],b[maxn];
    void init(int n){for(int i=0;i<=n;i++) fa[i]=i,a[i]=A[i],b[i]=B[i];}
    int find(int x){while(fa[x]!=x) x=fa[x]=fa[fa[x]]; return x;}
    int merge(int x,int y,int w){ // fa[y]=x;
        int fx=find(x),fy=find(y);
        a[fx]+=a[fy]; a[fy]=0;
        b[fx]+=b[fy]; b[fy]=0;
        fa[fy]=fx; int res=min(a[fx],b[fx]);
        a[fx]-=res; b[fx]-=res;
        return res*w;
    }
    bool q(int x,int y){ return find(x)==find(y); }
}s;
signed main(){
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++)
        cin>>E[i].u>>E[i].v>>E[i].w;
    for(int i=1,x;i<=k;i++)
        cin>>x, A[x]++;
    for(int i=1,x;i<=k;i++)
        cin>>x, B[x]++;
    s.init(max(n,k));
    sort(E+1,E+m+1);
    int ans=0;
    for(int i=1;i<=m;i++)
        if(!s.q(E[i].u,E[i].v))
            ans+=s.merge(E[i].u,E[i].v,E[i].w);
    cout<<ans;
    return 0;
}
posted @   view3937  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
Title
点击右上角即可分享
微信分享提示