ABC 杂题

ABC186E Throne

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

\(2\le n,k\le 10^9\)

很容易想到构造方程:

\[s+qk\equiv 0\pmod n \]

\[q\equiv (n-s)k^{-1}\pmod n \]

直接 exgcd 求逆元,算出在 \([1,n-1]\) 范围内的解即可。

点击查看代码
#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\times m\) 的网格,其中有 \(k\) 个障碍 \((x_i,y_i)\),有个猴子,它每走一步可以沿着一个方向走任意格,不能穿过障碍,求猴子在两步及以内可以到达的格子数。

\(n,m,k\le 2\times 10^5\)

\(X_i\) 为第 \(i\) 列从上到下可以到达的最大坐标,\(Y_i\) 为第 \(i\) 行从左到右可以到达的最大坐标。

先处理出从左往右可以到达的格子数 \(\sum Y_i\),然后考虑从上到下且不重复的格子,若对于 \(j\in[1,X_i],Y_j<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\) 不经过 \(E_x=(u,v)\) 就能到达的点的分数加上 \(k\)

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

\(n\le 2\times 10^5\)

考虑进行标记(差分)。钦定 1 为根,处理出每个点的父亲,则对于每个询问,若 \(u=fa_v\) 则将 \(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,b\)\(q\) 次询问 \(a[l,r],b[L,R]\) 之间每个元素的个数是否相等。

\(a_i,b_i,l,r,L,R\le n\le 2\times 10^5\)

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

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

时间复杂度 \(O(n\log n)\)

点击查看代码
#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\),求其每个子段的异或和的和。

\(n\le 2\times 10^5,a_i\le V\le 10^8\)

由于异或的可加性,求异或和可以用前缀和处理掉,设前缀数组为 \(b\),则题目转化为求 \(\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n} b_{j} \oplus b_{i-1}\)

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

最终的式子:

\[ans=\sum\limits_{i=1}^n\sum\limits_{j=1}^{\log_2 b_i+1}2^j f(i,j) \]

\[f(i,j)=\begin{cases}n-i-t_j&b_{i}\And 2^j=1\\t_j&b_{i}\And 2^j=0 \end{cases} \]

点击查看代码
#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]\) 内不同数字的个数,求 \(\sum\limits_{i=1}^n\sum\limits_{j=i}^n f(i,j)\)

\(a_i\le n\le 2\times 10^5\)

\(g_i\)\(i\) 位置前面第一个与 \(a_i\) 相同的位置,则每个 \(i\) 的贡献区间为 \([l_i,r_i](l_i\in[g_i,i],r_i\in[i,n])\),其贡献为 \((i-g_i)(n-i+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\),求 \(\min\limits_{d=0}^{n-1}\sum\limits_{i=0}^{n-1} dis(i,p_{(i+d)\bmod n})\)

\(n\le 2\times 10^5\)

考虑到对于一个 \(p_i\),它离 \(i\) 的距离大概如图

img

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

点击查看代码
#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\) 步,每步中可以选择机器 \(S_i\),用 \(p_i\) 费用加工 \(a_i\) 个产品;或者选择机器 \(T_i\),用 \(q_i\) 费用加工 \(b_i\) 个产品,求在总费用不超过 \(x\) 的情况下,求最大可加工的产品数。

\(n,a_i,b_i\le 100,p_i,q_i,x\le 10^7\)

显然可以二分。二分一个 \(t\) 表示产品数,枚举 \(S_i\) 的数量,进而可以算出 \(T_i\) 的数量,时间复杂度 \(O(xn\log xn)\)(AT 少爷机可过),但是考虑到 \(a_i,b_i\) 的范围很小,实际上我们是枚举 \(Cp_i+Dq_i\ge x\),令 \(w=Cp_i+Dq_i-x\)\(w\) 的循环节为 \(\gcd(a_i,b_i)\le 100\),所以我们只要枚举 \(100\)\(C/D\) 即可,时间复杂度 \(O(nT_{w}\log xn)\),槽点是它 \(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。

\(n\le 300,m\le \frac{n(n-1)}{2},q\le 2\times 10^5\),保证操作 1 次数不超过 \(w=300\)

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

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

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

第 1,4,5 三个影响直接更新即可,而对于 2,3 操作相当于 Floyd 枚举断点 \(k\)\(k\) 只取 \(u\)\(v\)。所以综上时间复杂度 \(O(n^3+n^2w+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,m\le 2\times 10^5\)

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

解:先跑出 \(1,n\) 的单源最短路以及最短路径树,然后随便弄出一条 \(1\to n\) 的最短路径,合法解肯定在路径上。对于非路径上的边 \((u,v)\),若其在最短路边集里,则对于路径上 \([lca_1(u,n),lca_n(v,1)]\) 一段的边一定是不合法的,直接接上线段树区间覆盖即可。时间复杂度 \(O(m\log m)\)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\times n\) 的矩阵,求其所有 \(k\times k\) 的子矩阵的中位数的最小值。

\(n\le 800,0\le a_i\le 10^9\)

考虑到中位数的经典结论:有 \(\lceil n/2\rceil\) 个数 $\le $ 中位数。

于是二分中位数 \(mid\),设 \(f[i][j]\) 表示以 \((i,j)\) 为右下角的矩形内 \(\ge mid\) 的数量,然后枚举每个 \(k\times k\) 的子矩形判断是否存在数量 \(\ge\lceil k^2/2\rceil\)。时间复杂度 \(O(n^2\log a_i)\)注意有 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)\) 之间的边权为 \((a_u+b_v)\bmod m\),求 \(1\)\(n\) 的最短路。

\(n\le 2\times 10^5,a_i,b_i<m\le 10^9\)

前缀优化建图典题。

由于 \(a_i,b_i<m\) 所以 \(a_u+b_v<2m\),即

  • \(a_u+b_v<m\):边权为 \(a_u+b_v\)
  • \(a_u+b_v\ge m\):边权为 \(a_u+b_v-m\)

\(b\) 排序,可以发现对于一个确定的 \(u\)\(b_v\) 成为一段前缀和一段后缀,分解为 \(a_u+b_v=m\) 的位置,这个二分即可。

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

img

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

考虑把边权转正,将图中边 \(1\to s_4\) 加上 \(b_4\),对 \(s\) 做差分,将边 \(s_i\to i\)\(0\),将边 \(s_{i}\to s_{i+1}\) 的边权置为 \(b_{i+1}-b_i\) 即可保证图不变但是边权全为正。即可跑 Dijkstra。时间复杂度 \(O(n\log n)\)

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\) 个黑点,一黑一白匹配,构造所有连线没有交点的方案。

\(n\le 300\),无三点共线,保证有解

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

直接跑费用流 \(O(n^4)\)。用 KM(匈牙利算法)可以做到 \(O(n^3)\),用调整法也是 \(O(n^3)\)

代码使用小码量常数小的调整法。过不了 \(n\le 10^4\) 的加强版。如果考虑分治可以做到 \(O(n^2\log n)\)

点击查看代码
#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\) 个字符串,记 \(s_0\) 为空,对于每个字符串 \(i\in[1,n]\),求通过删除/添加后缀字母的操作,最少需要操作多少次使得 \(s_i=s_k,k\in[0,i-1]\)

\(\sum |s_i|\le 2\times 10^5\)

考虑建 trie 树的过程,实质是每次新建一条链。而我们要求的就是当前字符串 \(s_i\) 末节点到 \(s_k(k\in[0,i-1])\) 末节点的最短距离。于是对于 trie 树上的每个节点记一个 \(f_u\) 表示距离前面的字符串末节点的最短距离,则每次加入字符串 \(s_i\) 时就更新链上每个节点的 \(f_u=\min(f_u,f_{fa_u}+1)\)。答案即为 \(s_i\) 末节点的 \(f_{e_i}\)。然后把字符串 \(s_i\) 的末节点计入,即 \(f_{e_i}=0\),自下而上再更新 \(f_{fa_u}=\min(f_{fa_u},f_u+1)\)。时间复杂度 \(O(\sum |s_i|)\),空间复杂度 \(O(|\Sigma|\sum |s_i|)\)

点击查看代码
#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\times n\) 的棋盘,放 \(m\) 个皇后在 \((a_i,b_i)\),求不会被攻击到的格子数。

\(n\le 10^9,m\le 10^3\)

\(n\) 很大,不支持我们一个个数格子,考虑从皇后入手去容斥答案。先假设以纵向攻击范围为参考(即在白色范围内只算纵向答案),在红色范围内,只有斜率为 \(-1,0,1\) 的直线相交,这些交点(或重合的线)就是被算重的点(线),而如果交点经过了白线则不计入答案。算交点(线)的数量是 \(9m^2\) 级别的,可以接受。综上时间复杂度 \(O(m^2\log m)\)(去重)。思路很简单,但是难在实现,这就是奇观“过 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\),可以执行任意次操作:

  • 将所有 \(a_i\) 变为 \(a_{p_i}\)

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

\(n\le 2\times 10^5\)

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

开一个数组 \(req\) 记录前面的环所出现的 \(x\equiv t_i\pmod{len_i}\),其实我们只要满足的是 \(req_d\equiv t_{now}(d\mid len_{now})\) 的限制,实际上复杂度是 \(O(nd(n))\) 的,只维护 \(p^k\mid len_{now}\) 的限制也是充要的,因此可以做到 \(O(n\log n)\)

点击查看代码
#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\in[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\) 的区间随机重排,求逆序对数量的期望。

\(k\le n\le 2\times 10^5\)

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

证明:考虑对于一个数对 \((i,j)(i<j)\) 是逆序对的概率为 \(\frac{1}{2}\),数对数量为 \(\binom{n}{2}\),证毕。

所以先求出全局的逆序对数,然后枚举每个窗口,则期望逆序对数为 \(\sum(全局逆序对数 - 窗口内逆序对数(重排了没影响)+ 窗口期望逆序对数)/(n-k+1)\)

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

点击查看代码
#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\),问任意重排后 \(\sum f(a_i,b_i)\) 的最小值。

\(n,m\le 2\times 10^5\)

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

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 @ 2024-11-01 15:17  view3937  阅读(25)  评论(0)    收藏  举报
Title