ABC 杂题
ABC186E Throne
有 \(n\) 个圆形排列的椅子,一开始你在 \(s+1\) 上,每次可以向右移动 \(k\) 个位置,求移动到 \(1\) 的最小步数,或报告无解。
\(2\le n,k\le 10^9\)
很容易想到构造方程:
直接 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)\)。
最终的式子:
点击查看代码
#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] 异或和之和
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\) 的距离大概如图

由两段单调区间构成,分别单增和单减。我们可以统计初始态时上升与下降的个数之差 \(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\) 为后缀辅助边)

可以发现图中存在负边权,而跑 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)\)。

点击查看代码
#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 的两倍”的原因吧。

懒得写了,贺了官方题解。
点击查看代码
#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;
}

浙公网安备 33010602011771号