联考整理 - Jan. 2026 ~ Feb. 2026
Jan. 19
A. 歌
考虑枚举最小的初段长度 \(p\) 使得存在合法划分,并对其容斥。
假设当前有若干种长度使得该序列存在合法划分,那么形态如下图所示。
图中数字相同的段有相同的颜色,首段和末端可能有残缺。
注意到大部分贡献都是重复的,设 \(f_{0/1,i}\) 表示当前选择段数奇偶性,最长段是 \(i\),可以根据上图进行 1D/1D 的转移。
时间复杂度 \(O(k^3)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("song.in");
ofstream fout("song.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
ll n,k;
struct FastMod {
unsigned long long b, m;
FastMod() = default;
FastMod(unsigned long long m) : b(((unsigned __int128)1 << 64) / m), m(m) {}
unsigned long long operator()(unsigned long long a) {
unsigned long long q = (unsigned __int128)a * b >> 64;
unsigned long long r = a - q * m;
return r >= m ? r - m : r;
}
} fastmod;
int mod;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=fastmod(1ll*x*y);}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return fastmod(1ll*x*y);}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
const int K=1e3+9;
int fac[K],ifac[K];
inline void Init(int lim){
fastmod=FastMod(mod);
fac[0]=1;
for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
ifac[lim]=Inv(fac[lim]);
for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
if(m<0||m>n) return 0;
else return Mul(fac[n],Mul(ifac[m],ifac[n-m]));
}
inline int iC(int n,int m){
if(m<0||m>n) return 0;
else return Mul(ifac[n],Mul(fac[m],fac[n-m]));
}
int f[2][K],pf[K][2],ipf[K][2];
inline void Solve(){
cin>>n>>k>>mod;
Init(k);
if(n<=k){
int ans=Mul(C(k,n),fac[n]);
for(int i=1;i<k;i++){
AddAs(ans,Mul(Mul(Mul(C(k,i),fac[i]),i),Mul(C(k-1,n-1-i),fac[n-1-i])));
}
cout<<ans<<endl;
}else{
int ans=0;
for(int i=0;i<=k;i++){
pf[i][0]=ipf[i][0]=1;
for(int j=1;j<=k;j++){
pf[i][j]=Mul(pf[i][j-1],fac[i]);
ipf[i][j]=Mul(ipf[i][j-1],ifac[i]);
}
}
for(int i=1;i<=k;i++){
int t=(n-i)/k%(mod-1),r=(n-i)%k;
for(int j=0;j<=k;j++){
for(int c:{0,1}){
pf[j][c]=QPow(fac[j],t+c);
ipf[j][c]=Inv(pf[j][c]);
}
}
auto F=[&](int i,int j){
if(i<r&&r<=j) return Mul(Mul(C(k-i,k-j),pf[j-i][0]),Mul(C(j-i,r-i),fac[r-i]));
else return Mul(C(k-i,k-j),pf[j-i][j<r]);
};
auto G=[&](int p){
return Mul(pf[k-p][0],Mul(C(k-p,max(r-p,0)),fac[max(r-p,0)]));
};
auto iG=[&](int p){
return Mul(ipf[k-p][0],Mul(iC(k-p,max(r-p,0)),ifac[max(r-p,0)]));
};
f[1][0]=G(0);
for(int j=0;i+j<=k;j++){
for(int p=j+1;i+p<=k;p++){
for(int c:{0,1}){
AddAs(f[!c][p],Mul(f[c][j],Mul(F(j,p),Mul(iG(j),G(p)))));
}
}
}
for(int j=0;j<=k;j++){
AddAs(ans,Mul(Mul(C(k-j,i),fac[i]),Sub(f[1][j],f[0][j])));
f[0][j]=f[1][j]=0;
}
}
cout<<ans<<endl;
}
}
signed main(){
int T;
cin>>T;
while(T--) Solve();
return 0;
}
B. 火力大喵
第一问答案就是最大值减最小值。
第二问就相当于查询边框上存在最大值和最小值的矩形数,考虑将其容斥成边框上没有最大值或最小值以及两个都不能有,这样就拆成三个问 \(0/1\) 矩阵中边框上都是 \(1\) 的矩形数量。
考虑分治,每次对子矩形的长边折半,枚举中线上矩形与中线的两个交点,那么对半边问的就是交点特定的 C 形数量。
不失一般性地先只考虑左半边。对于触点 \(i,j\),假设 \(i\) 向左连续 \(1\) 段比 \(j\) 短,那么贡献就是在 \(i\) 向左连续 \(1\) 段内的向下连续 \(1\) 段长度超过 \(j-i+1\) 的点的个数。枚举 \(i\) 扫描 \(j\) 可以做到单层 \(O(nm)\),总的就是 \(O(nm\log nm)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("dameow.in");
ofstream fout("dameow.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
const int N=2e3+9;
int a[N][N],b1[N][N],b2[N][N],b3[N][N],n,m;
int U[N][N],D[N][N],L[N][N],R[N][N],cnt[N],s[N][N],t[N][N];
inline void Conquer(int a[N][N],int l1,int r1,int l2,int r2,ll &ans){
if(l1==r1&&l2==r2) return ans+=a[l1][l2],void();
for(int i=l1;i<=r1;i++){
for(int j=l2;j<=r2;j++){
if(a[i][j]){
U[i][j]=(i-1>=l1?U[i-1][j]:0)+1;
L[i][j]=(j-1>=l2?L[i][j-1]:0)+1;
}else U[i][j]=L[i][j]=0;
}
}
for(int i=r1;i>=l1;i--){
for(int j=r2;j>=l2;j--){
if(a[i][j]){
D[i][j]=(i+1<=r1?D[i+1][j]:0)+1;
R[i][j]=(j+1<=r2?R[i][j+1]:0)+1;
}else D[i][j]=R[i][j]=0;
}
}
if(r1-l1>r2-l2){
int mid=l1+r1>>1;
for(int i=l2;i<=r2;i++){
s[i][i]=U[mid][i];
for(int j=mid-U[mid][i]+1;j<=mid;j++) cnt[L[j][i]]++;
for(int j=i-l2+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=l2;j<i;j++) if(U[mid][j]>U[mid][i]) s[j][i]+=cnt[i-j+1];
memset(cnt,0,(r2-l2+2)*sizeof int());
for(int j=mid-U[mid][i]+1;j<=mid;j++) cnt[R[j][i]]++;
for(int j=r2-i+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=r2;j>i;j--) if(U[mid][j]>=U[mid][i]) s[i][j]+=cnt[j-i+1];
memset(cnt,0,(r2-l2+2)*sizeof int());
}
for(int i=l2;i<=r2;i++){
t[i][i]=D[mid+1][i];
for(int j=mid+1;j<=mid+D[mid+1][i];j++) cnt[L[j][i]]++;
for(int j=i-l2+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=l2;j<i;j++) if(D[mid+1][j]>D[mid+1][i]) t[j][i]+=cnt[i-j+1];
memset(cnt,0,(r2-l2+2)*sizeof int());
for(int j=mid+1;j<=mid+D[mid+1][i];j++) cnt[R[j][i]]++;
for(int j=r2-i+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=r2;j>i;j--) if(D[mid+1][j]>=D[mid+1][i]) t[i][j]+=cnt[j-i+1];
memset(cnt,0,(r2-l2+2)*sizeof int());
}
for(int i=l2;i<=r2;i++){
for(int j=i;j<=r2;j++){
ans+=s[i][j]*t[i][j];
s[i][j]=t[i][j]=0;
}
}
Conquer(a,l1,mid,l2,r2,ans);
Conquer(a,mid+1,r1,l2,r2,ans);
}else{
int mid=l2+r2>>1;
for(int i=l1;i<=r1;i++){
s[i][i]=L[i][mid];
for(int j=mid-L[i][mid]+1;j<=mid;j++) cnt[U[i][j]]++;
for(int j=i-l1+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=l1;j<i;j++) if(L[j][mid]>L[i][mid]) s[j][i]+=cnt[i-j+1];
memset(cnt,0,(r1-l1+2)*sizeof int());
for(int j=mid-L[i][mid]+1;j<=mid;j++) cnt[D[i][j]]++;
for(int j=r1-i+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=r1;j>i;j--) if(L[j][mid]>=L[i][mid]) s[i][j]+=cnt[j-i+1];
memset(cnt,0,(r1-l1+2)*sizeof int());
}
for(int i=l1;i<=r1;i++){
t[i][i]=R[i][mid+1];
for(int j=mid+1;j<=mid+R[i][mid+1];j++) cnt[U[i][j]]++;
for(int j=i-l1+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=l1;j<i;j++) if(R[j][mid+1]>R[i][mid+1]) t[j][i]+=cnt[i-j+1];
memset(cnt,0,(r1-l1+2)*sizeof int());
for(int j=mid+1;j<=mid+R[i][mid+1];j++) cnt[D[i][j]]++;
for(int j=r1-i+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=r1;j>i;j--) if(R[j][mid+1]>=R[i][mid+1]) t[i][j]+=cnt[j-i+1];
memset(cnt,0,(r1-l1+2)*sizeof int());
}
for(int i=l1;i<=r1;i++){
for(int j=i;j<=r1;j++){
ans+=s[i][j]*t[i][j];
s[i][j]=t[i][j]=0;
}
}
Conquer(a,l1,r1,l2,mid,ans);
Conquer(a,l1,r1,mid+1,r2,ans);
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) cin>>a[i][j];
}
int mx=INT_MIN,mn=INT_MAX;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
mx=max(mx,a[i][j]);
mn=min(mn,a[i][j]);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
b1[i][j]=(a[i][j]!=mx);
b2[i][j]=(a[i][j]!=mn);
b3[i][j]=(a[i][j]!=mx&&a[i][j]!=mn);
}
}
ll ans=0,s1=0,s2=0,s3=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) ans+=1ll*i*j;
}
Conquer(b1,1,n,1,m,s1);
Conquer(b2,1,n,1,m,s2);
Conquer(b3,1,n,1,m,s3);
ans-=s1;
ans-=s2;
ans+=s3;
cout<<mx-mn<<' '<<ans<<endl;
return 0;
}
C. 松鼠威廉梦游仙境
首先答案可以被表示成 \(\displaystyle \sum_{ax+by=k} \binom{n-1+x}{x}\binom{m-i+y}{y}(c_0+c_1x+c_2y+c_3xy)\) 的形式。
考虑模拟 Lucas 定理,从高位到低位 DP 取值。设 \(f_{i,j}\) 表示当前考虑到 \(p\) 进制第 \(i\) 位,\(ax+by\) 从第 \(i-1\) 位进位 \(j\) 次。则枚举 \(x\),根据 \(k_i\) 的取值即可反向解出 \(y\)。\(c_0+c_1x+c_2y+c_3xy\) 的贡献可以放到最低位考虑,毕竟答案对 \(p\) 取模。
时间复杂度 \(O(p(a+b)\log_p\max(k,n,m))\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("squirrel.in");
ofstream fout("squirrel.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
using ull=unsigned long long;
const int N=1e6+9;
const int lgK=64;
const int A=1e2+9;
struct FastMod{
ull b,m;
FastMod()=default;
FastMod(ull m):b(((unsigned __int128)1<<64)/m),m(m){}
ull operator()(ull a){
ull q=(unsigned __int128)a*b>>64;
ull r=a-q*m;
return r>=m?r-m:r;
}
}fastmod;
int mod;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
// inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline void MulAs(int &x,int y){x=fastmod(1ll*x*y);}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
// inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int Mul(int x,int y){return fastmod(1ll*x*y);}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
int fac[N<<1],ifac[N<<1],inv[N];
inline int fInv(int x){return Mul(ifac[x],fac[x-1]);}
inline void Init(int lim){
fastmod=FastMod(mod);
fac[0]=1;
for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
ifac[lim]=Inv(fac[lim]);
for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
for(int i=1;i<=lim;i++) inv[i]=fInv(i);
}
inline int C(int n,int m){return n<mod?Mul(fac[n],Mul(ifac[m],ifac[n-m])):0;}
int f[lgK][A],tar[lgK],ni[lgK],mi[lgK],tx[N],ty[N];
ll n,m,k,a,b,c0,c1,c2,c3;
inline void Solve(){
cin>>n>>m>>k>>a>>b>>mod>>c0>>c1>>c2>>c3;
n--,m--;
ll tmp=max(k,max(n,m));
int lgk=0;
while(tmp) tmp/=mod,lgk++;
for(int i=0;i<lgk;i++){
tar[i]=k%mod,k/=mod;
ni[i]=n%mod,n/=mod;
mi[i]=m%mod,m/=mod;
}
int ans=0;
Init(mod-1);
f[lgk][0]=1;
for(int i=lgk-1;~i;i--){
for(int j=0;j<=a+b;j++){
if(!i&&j) break ;
ll s=j;
int d=Sub(tar[i],j);
for(int x=0;x<mod;x++,SubAs(d,a),s+=a){
int y=Mul(d,inv[b]),_j=(s+b*y)/mod;
if(!f[i+1][_j]) continue ;
int w=Mul(f[i+1][_j],Mul(C(ni[i]+x,x),C(mi[i]+y,y)));
AddAs(f[i][j],w);
if(!i) AddAs(ans,Mul(w,Add(Add(c0,Mul(x,c1)),Add(Mul(y,c2),Mul(Mul(x,y),c3)))));
}
}
}
for(int i=0;i<=lgk;i++) for(int j=0;j<=a+b;j++) f[i][j]=0;
cout<<ans<<endl;
}
signed main(){
int T;
cin>>T;
while(T--) Solve();
return 0;
}
Jan. 20
A. 胜兵必骄
令 \(a_i=1\) 表示有箱子,否则没箱子。
考虑找到一个有箱子且领域中有节点没箱子的节点作为根,先让所有有箱子的节点全部把自己子树里到碰到箱子为止的所有没箱子的节点和其父亲的边跑一边,即每个没箱子的节点都归其到根链上的第一个有箱子的节点管。
那么现在没有跑的就是非根的有箱子节点到其父亲的边,如果该节点父亲有箱子,那么等他父亲去领域的时候快速跑掉,否则一开始就跑掉。
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("wars.in");
ofstream fout("wars.out");
#define cin fin
#define cout fout
#define endl '\n'
const int N=1e6+9;
vector<int> e[N];
list<array<int,2>> s[N];
list<array<int,2>>::iterator in[N],out[N];
int a[N],n,root;
inline void DFS1(int x,int fa){
for(int y:e[x]){
if(y==fa) continue ;
DFS1(y,x);
if(!a[y]){
s[y].insert(s[y].begin(),{x,y});
s[y].insert(s[y].end(),{x,y});
s[x].splice(s[x].end(),s[y]);
}
}
}
inline void DFS2(int x,int fa,int ffa){
if(x!=root&&a[x]){
auto it=fa==ffa?out[ffa]:in[ffa];
in[x]=s[root].insert(it,{x,fa});
s[x].insert(s[x].begin(),{x,fa});
s[root].splice(it,s[x]);
out[x]=next(in[x]);
ffa=x;
}
for(int y:e[x]){
if(y==fa) continue ;
DFS2(y,x,ffa);
}
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
for(int x=1;x<=n;x++){
if(!a[x]) continue ;
for(int y:e[x]){
if(a[y]) continue ;
root=x;
break ;
}
if(root) break ;
}
DFS1(root,0);
in[root]=s[root].begin();
out[root]=next(in[root]);
DFS2(root,0,root);
for(auto p:s[root]) cout<<p[0]<<' '<<p[1]<<endl;
return 0;
}
B. 数据恢复
先将 \(a\) 做如下变换,令 \(p_1,\ldots,p_k\) 表示其前缀最大值位置,那么将 \([p_i,p_{i+1})\) 的位置向左循环移位一次,即把 \(p_i\) 放到 \(p_{i+1}-1\) 的位置,那么前缀最大值的限制就不对最大值之后的数产生约束。
考察任意两个位置 \(i,j(i<j)\) 之间的关系。若 \(a_i<a_j\) 则无事发生,若 \(a_i>a_j\) 则必然存在约束使得其无法交换使字典序更小,因此特别关注所有逆序对的限制,设 \(x_i\) 表示位置 \(i\) 是否清晰可见:
- 若 \(c_i=c_j=0\),那么 \(x_i,x_j\) 不同时为 \(0\)。
- 若 \(c_i=1,c_j=0\),存在 \(x_i,x_j\) 不同时为 \(0\) 的限制当且仅当 \(\displaystyle \max_{k=1}^{i-1} a_k<a_j<a_i\)。
- 若 \(c_j=1\),则 \(a_i<a_j\)。
因此考虑按值域从后向前填数,设 \(f_{i,0/1,j}\) 表示当前考虑到 \(i\),最大值是否清晰可见,目前非清晰可见的 \(c_p=0\) 最小的 \(p\) 是 \(j\),转移是简单的。
线段树优化 DP 即可做到 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("recovery.in");
ofstream fout("recovery.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
const int N=1e6+9;
const int mod=998244353;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
struct Vec{
int a[2];
Vec(){memset(a,0,sizeof a);}
Vec(int x,int y){a[0]=x,a[1]=y;}
inline int& operator [](int p){return a[p];}
friend inline Vec operator +(Vec A,Vec B){return Vec(Add(A[0],B[0]),Add(A[1],B[1]));}
};
struct Node{
int l,r;
Vec dat;
int tag;
}tr[N<<2];
int pw[N];
inline void PushUp(int x){tr[x].dat=tr[x<<1].dat+tr[x<<1|1].dat;}
inline void Push(int x,int k){
tr[x].tag+=k;
tr[x].dat[0]=tr[x].dat[1]=Mul(pw[k-1],Add(tr[x].dat[0],tr[x].dat[1]));
}
inline void PushDown(int x){
if(tr[x].tag){
Push(x<<1,tr[x].tag);
Push(x<<1|1,tr[x].tag);
tr[x].tag=0;
}
}
inline void Build(int x,int l,int r){
tr[x].l=l,tr[x].r=r,tr[x].dat=Vec(0,0),tr[x].tag=0;
if(tr[x].l==tr[x].r) return ;
int mid=tr[x].l+tr[x].r>>1;
Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
PushUp(x);
}
inline void Set(int x,int pos,Vec k){
if(tr[x].l==tr[x].r) return tr[x].dat=k,void();
PushDown(x);
int mid=tr[x].l+tr[x].r>>1;
if(pos<=mid) Set(x<<1,pos,k);
else Set(x<<1|1,pos,k);
PushUp(x);
}
inline Vec Query(int x,int l,int r){
if(l<=tr[x].l&&tr[x].r<=r) return tr[x].dat;
PushDown(x);
int mid=tr[x].l+tr[x].r>>1;
if(r<=mid) return Query(x<<1,l,r);
else if(l>mid) return Query(x<<1|1,l,r);
else return Query(x<<1,l,r)+Query(x<<1|1,l,r);
}
int a[N],c[N],id[N],pmx[N],n;
inline void Solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int mx=0;
vector<int> p;
for(int i=1;i<=n;i++){
mx=max(mx,a[i]);
if(a[i]==mx) p.push_back(i),c[i]=1;
else c[i]=0;
}
p.push_back(n+1);
for(int i=0;i+1<p.size();i++){
for(int j=p[i];j+1<p[i+1];j++){
swap(a[j],a[j+1]);
swap(c[j],c[j+1]);
}
}
for(int i=1;i<=n;i++){
id[a[i]]=i;
pmx[i]=max(pmx[i-1],a[i]);
}
pw[0]=1;
for(int i=1;i<=n;i++) pw[i]=Mul(2,pw[i-1]);
Build(1,0,n+1);
Set(1,n+1,{1,0});
for(int i=n,cur=0;i>=1;i--){
if(c[id[i]]){
cur=pmx[id[i]-1];
Push(1,1);
}else{
Vec v=Query(1,id[i],id[i]),u=Query(1,id[i],n+1);
if(i<=cur) AddAs(v[0],u[0]);
AddAs(v[1],u[1]);
Set(1,id[i],v);
}
}
cout<<Add(tr[1].dat[0],tr[1].dat[1])<<endl;
}
signed main(){
int o,T;
cin>>o>>T;
while(T--) Solve();
return 0;
}
Jan. 22
A. 冻鱼
首先 \(a_i>0\) 的数一定是一个连续段,不是一般性地,把前缀 \(0\) 段和后缀 \(0\) 段先删去。
由于 \(a_1\neq 0,a_n\neq 0\),因此路径一定经过左右端点,换言之,\(a\) 是基于以下数组的:
1 2 2 2 2 1 1 1 1 2 2 2 2 1
^ ^ ^ ^
1 s t n
(因为 1 2 2 2 3 3 3 3 3 3 2 2 2 1 是可以通过上述数组进一步加工得到的。)
考虑先令 \(a_i\leftarrow a_i-2+[i=1\vee i=n]\),后续再给 \((l,r)\) 加 \(1\)。
可以发现,所有可以在数组上做的操作全部可以归约到让相邻两项同时加 \(1\),所以再处理后的 \(a\) 的合法条件为 \(\displaystyle \forall i,\sum_{j=1}^i(-1)^{i-j}a_i\geq 0\) 且 \(\displaystyle \sum_{i=1}^n (-1)^i a_i=0\)。
设 \(b_i=\sum_{j=1}^i(-1)^{i-j}a_i\)。记 \(p_{0/1}\) 第一个 \(b<0\) 且和 \(r\) 奇偶相同/不同的位置之前根据 \(l,r\) 的奇偶性分讨:
- \(l,r\) 奇偶不同:前缀无影响,后缀影响抵消,相当于给 \(b_{l+1},b_{l+3},\ldots,b_{r-2}\) 加 \(1\),则 \(l\) 必须小于 \(p_0\)。需要保证 \([1,l],[r,n]\) 本身合法且 \(p_1\geq r\)。
- \(l,r\) 奇偶相同:前缀无影响,相当于给 \(b_{l+1},b_{l+3},\ldots,b_{r-1}\) 加 \(1\),给 \(b_r,b_{r+1},...,b_{n}\) 加上 \((-1)^{i-r+1}\),则 \(l\) 必须小于 \(p_1\)。需要保证 \([1,l]\) 本身合法,\([r,n]\) 修改后合法且 \(p_0\geq r\)。
\(l=r\) 直接归到 \(l,r\) 奇偶相同的情况即可。
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e6+9;
ifstream fin("coldfish.in");
ofstream fout("coldfish.out");
#define cin fin
#define cout fout
#define endl '\n'
int a[N],f[N],g[N],h[N],n;
inline void Solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
a[n+1]=0;
int L=0,R=n+1;
while(!a[L]&&L<=n) L++;
while(!a[R]&&R>=1) R--;
if(!R) return cout<<0<<endl,void();
else if(L==R) return cout<<(a[L]==1)<<endl,void();
a[L]++,a[R]++;
for(int i=L;i<=R;i++) a[i]-=2,a[i]-=a[i-1];
int p[2]={n+1,n+1};
for(int i=L;i<=R;i++){
if(a[i]<0){
if(a[i]==-1) p[i&1]=min(p[i&1],i);
else return cout<<0<<endl,void();
}
}
ll ans=0,cnt=0;
f[R+1]=g[R+1]=h[R+1]=1;
for(int i=R;i>=L;i--){
f[i]=(a[i]>=0)&f[i+1];
g[i]=(a[i]-(i&1)+(~i&1)>=0)&g[i+1];
h[i]=(a[i]+(i&1)-(~i&1)>=0)&h[i+1];
if(i==R){
f[i]&=a[i]==0;
g[i]&=a[i]-(i&1)+(~i&1)==0;
h[i]&=a[i]+(i&1)-(~i&1)==0;
}
if(~i&1){
if(p[1]>=i){
if(f[i]) ans+=(min(i-1,p[0]-1)-L>>1)+1;
}
if(p[0]>=i){
if(h[i]) ans+=(min(i-2,p[1]-1)-L>>1)+1;
}
if(p[0]>=i&&p[1]>=i&&h[i]) cnt++;
}else{
if(p[0]>=i){
if(f[i]) ans+=(min(i-1,p[1]-1)-L>>1)+1;
}
if(p[1]>=i){
if(g[i]) ans+=(min(i-2,p[0]-1)-L>>1)+1;
}
if(p[0]>=i&&p[1]>=i&&g[i]) cnt++;
}
}
ans<<=1;
cout<<ans+cnt<<endl;
}
signed main(){
int T;
cin>>T;
while(T--) Solve();
return 0;
}
B. 狐狸
先建出 ACAM,\(t\) 在 \(s\) 中的出现次数即为 fail 树上 \(t\) 子树内在遍历 \(s\) 中经过的节点数。
考虑把每个节点挂在 fail 树上第一个成为某个串的结尾节点的祖先下,那么把遍历 \(s\) 中经过的节点在该树上建出虚树,即可通过简单的统计计算出每组本质不同的\(t\) 在 \(s\) 中的出现次数。按答案为下标记录下来,询问时直接二分即可。
时间复杂度 \(O(L(|\Sigma|+\log n))\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("fox.in");
ofstream fout("fox.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
const int L=5e6+9;
const int Z=26;
const int lgN=20;
const int N=6e5+9;
namespace ACAM{
struct Node{
int son[Z],fail,cnt,vld;
}tr[L];
int cnt;
inline int Allc(){return ++cnt;}
inline int Insert(string &s){
int x=0;
for(char c:s){
c-='a';
if(!tr[x].son[c]) tr[x].son[c]=Allc();
x=tr[x].son[c];
}
tr[x].cnt++;
return x;
}
inline void Build(){
queue<int> q;
for(int i=0;i<Z;i++){
if(!tr[0].son[i]) continue ;
tr[tr[0].son[i]].fail=0;
q.push(tr[0].son[i]);
}
while(q.size()){
int x=q.front();
q.pop();
tr[x].vld=tr[tr[x].fail].vld;
if(tr[x].cnt) tr[x].vld=x;
for(int i=0;i<Z;i++){
if(tr[x].son[i]){
tr[tr[x].son[i]].fail=tr[tr[x].fail].son[i];
q.push(tr[x].son[i]);
}else tr[x].son[i]=tr[tr[x].fail].son[i];
}
}
}
inline void Get(string &s,vector<int> &v){
int x=0;
v.push_back(x);
for(char c:s){
c-='a';
if(!tr[x].son[c]) tr[x].son[c]=Allc();
x=tr[x].son[c];
v.push_back(x);
}
}
}
string s[N];
vector<int> e[N],imp;
int fa[N],dep[N],elr[N<<1],pos[N],n,m,q,Tid,ecnt;
inline void DFS(int x){
elr[++ecnt]=x;
pos[x]=ecnt;
for(int y:e[x]){
fa[y]=x;
dep[y]=dep[x]+ACAM::tr[imp[y]].cnt;
DFS(y);
elr[++ecnt]=x;
}
}
int mn[lgN][N];
inline void InitLCA(){
for(int i=1;i<=ecnt;i++) mn[0][i]=pos[elr[i]];
for(int k=1;k<=__lg(ecnt);k++){
for(int i=1;i+(1<<k)-1<=ecnt;i++){
mn[k][i]=min(mn[k-1][i],mn[k-1][i+(1<<k-1)]);
}
}
}
inline int LCA(int x,int y){
x=pos[x],y=pos[y];
if(x>y) swap(x,y);
int k=__lg(y-x+1);
return elr[min(mn[k][x],mn[k][y-(1<<k)+1])];
}
ll ans[L];
vector<int> ve[N],node;
inline void GetVir(vector<int> &v){
node=v;
sort(v.begin(),v.end(),[](int x,int y){return pos[x]<pos[y];});
v.erase(unique(v.begin(),v.end()),v.end());
for(int i=0;i+1<v.size();i++) node.push_back(LCA(v[i],v[i+1]));
sort(node.begin(),node.end(),[](int x,int y){return pos[x]<pos[y];});
node.erase(unique(node.begin(),node.end()),node.end());
for(int i=0;i+1<node.size();i++) ve[LCA(node[i],node[i+1])].push_back(node[i+1]);
}
int cnt[N];
inline void Calc(int x,int fa){
for(int y:ve[x]){
Calc(y,x);
cnt[x]+=cnt[y];
}
ans[cnt[x]]+=dep[x]-dep[fa];
}
inline void VirClear(){
for(int x:node) cnt[x]=0,ve[x].clear();
}
signed main(){
cin>>Tid>>n;
for(int i=1;i<=n;i++) cin>>s[i],m=max(m,(signed)s[i].size());
for(int i=1;i<=n;i++) imp.push_back(ACAM::Insert(s[i]));
ACAM::Build();
imp.push_back(0);
sort(imp.begin(),imp.end());
imp.erase(unique(imp.begin(),imp.end()),imp.end());
for(int i=1;i<imp.size();i++){
int u=ACAM::tr[ACAM::tr[imp[i]].fail].vld;
u=lower_bound(imp.begin(),imp.end(),u)-imp.begin();
e[u].push_back(i);
}
DFS(0),InitLCA();
for(int i=1;i<=n;i++){
vector<int> v;
ACAM::Get(s[i],v);
for(int &x:v){
x=ACAM::tr[x].vld;
x=lower_bound(imp.begin(),imp.end(),x)-imp.begin();
cnt[x]++;
}
GetVir(v);
Calc(0,0);
VirClear();
}
ans[0]=1ll*n*n;
for(int i=1;i<=m;i++) ans[0]-=ans[i];
for(int i=1;i<=m;i++) ans[i]+=ans[i-1];
cin>>q;
while(q--){
ll k;
cin>>k;
k=1ll*n*n-k+1;
int p=lower_bound(ans,ans+m+1,k)-ans;
cout<<p<<endl;
}
return 0;
}
Jan. 23
A. 数字王国的秩序危机
对于一个固定的 \(x\),如果第 \(i\) 位为 \(1\),那么 Trie 树上第 \(i\) 层都只能有一个儿子,否则没有限制。
那么能产生多种方案的地方只有该层的位为 \(0\),且儿子对应的是空节点的子树,而整个子树的贡献是容易 DP 的,每层的各种节点的数量也是可以预处理了。因此可以做到 \(O(n2^n)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("inequation.in");
ofstream fout("inequation.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
const int N=2e1;
const int S=(1<<18)+9;
const int mod=1e9+7;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
int a[S],c1[N],c2[N],f[N],n,lim;
inline void Solve(){
cin>>n,lim=1<<n;
for(int i=0;i<lim;i++) cin>>a[i];
for(int i=n-1;~i;i--){
c1[i]=c2[i]=0;
for(int j=0;j<lim;j+=(1<<i+1)){
bool f0=0,f1=0;
for(int k=j;k<j+(1<<i);k++) f0|=a[k];
for(int k=j+(1<<i);k<j+(1<<i+1);k++) f1|=a[k];
if(f0&&f1) c2[i]++;
else if(f0||f1) c1[i]++;
}
}
c1[n]=c2[n]=0;
if(accumulate(a,a+lim,0ll)==0) c1[n]++;
for(int x=0;x<lim;x++){
f[0]=2;
for(int i=1;i<=n;i++){
if(x>>i-1&1) f[i]=Sub(Mul(2,f[i-1]),1);
else f[i]=Mul(f[i-1],f[i-1]);
}
int ans=1;
for(int i=0;i<=n;i++){
if(x>>i&1){
if(c2[i]) ans=0;
}else{
MulAs(ans,QPow(f[i],c1[i]));
}
}
cout<<ans<<' ';
}
cout<<endl;
for(int i=0;i<lim;i++) a[i]=0;
for(int i=0;i<=n;i++) c1[i]=c2[i]=f[i]=0;
}
signed main(){
int T;
cin>>T;
while(T--) Solve();
return 0;
}
B. 泡排序冒
考虑把操作认为是有 \(n\) 个人要从 \(i\) 走到 \(a_i\),相交产生 \(1\) 的贡献,可以对 \(a_i\) 循环移位。
对 \(a_i\) 循环移位有点变态,所以对起点循环移位,然后再把起点终点互换。
最优方案下相交当且仅当两个线段相互包含,而两个线段的包含关系再循环移位的时候只会在起点越过 \(a_i\) 的时候变动。具体地,当起点跨过 \(a_i\) 前,所有不跨过 \(a_i\) 的线段都被这条线段包含,而跨过 \(a_i\) 后,所有跨过 \(a_i\) 的线段都包含这条线段。因此可以快速维护所有包含对的数量,时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("sort.in");
ofstream fout("sort.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
const int N=1e6+9;
vector<int> v[N];
int a[N],d[N<<1],s[N],p[N],n;
ll tr[N<<1];
inline void Add(int x,ll k){while(x<=n+n) tr[x]+=k,x+=x&-x;}
inline ll Ask(int x){ll sum=0;while(x) sum+=tr[x],x&=x-1;return sum;}
inline void Solve(){
cin>>n;
for(int i=0;i<n;i++) cin>>a[i],a[i]--;
for(int i=0;i<n;i++){
if(a[i]<=i) d[a[i]]++,d[i+1]--;
else d[a[i]]++,d[i+n+1]--;
p[i]=(a[i]-i+n-1)%n+1;
v[p[i]].push_back(i);
}
partial_sum(d,d+n+n,d);
for(int i=0;i<n+n;i++) s[i%n]+=d[i];
ll ans=LLONG_MAX,cur=0;
vector<array<int,2>> tmp;
for(int i=0;i<n;i++){
if(a[i]<=i) tmp.push_back({a[i],i}),tmp.push_back({a[i]+n,i+n});
else tmp.push_back({a[i],i+n});
}
sort(tmp.begin(),tmp.end(),[](auto x,auto y){return x[0]>y[0];});
for(auto t:tmp){
if(t[0]<n) cur+=Ask(t[1]+1);
Add(t[1]+1,1);
}
int tag=0;
for(int i=0;i<n;i++){
for(int j:v[i]) cur-=n-(s[a[j]]+tag);
if(i) tag+=1-v[i].size();
for(int j:v[i]) cur+=(s[a[j]]+tag)-1;
ans=min(ans,cur);
v[i].clear();
}
cout<<ans<<endl;
for(int i=0;i<=n;i++){
v[i].clear();
a[i]=s[i]=p[i]=0;
}
for(int i=0;i<=n+n;i++) d[i]=tr[i]=0;
}
signed main(){
int T;
cin>>T;
while(T--) Solve();
return 0;
}
Jan. 26
A. 序列
首先答案相当于把所有数从小到大排序,设初值 \(x=1\),若当前数 \(a_i\leq x\),那么 \(x\leftarrow x+a_i\)。
考虑倍增分块,即将 \([2^k,2^{k+1})\) 中的数分为一块。那么第一个没法完成操作的一是一个块的最小值,证明考虑反证法。因此只要找到第一个不满足要求的块即可。
考虑对每个块维护一个指针,对每个左端点维护答案该块能使答案不合法的最远右端点,二阶前缀和贡献即可,时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("seq.in");
ofstream fout("seq.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
const int N=5e5+9;
const int lgV=30;
const int mod=998244353;
const ll inf=1e18;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
ll s[N][lgV];
int ss[N][lgV];
deque<int> q[lgV];
int a[N],p[lgV],t[lgV+1],cnt[N],n,Tid;
signed main(){
cin>>Tid>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
for(int j=0;j<lgV;j++){
s[i][j]=s[i-1][j];
if(j>=__lg(a[i])) s[i][j]+=a[i];
ss[i][j]=Add(ss[i-1][j],s[i][j]%mod);
}
}
int ans=0;
t[lgV]=n+1;
fill(p,p+lgV,1);
for(int j=0;j<=__lg(a[1]);j++) q[j].push_back(1);
for(int i=1;i<=n;i++){
for(int j=0;j<lgV;j++){
while(p[j]<=n){
int val=q[j].size()?a[q[j].front()]:INT_MAX;
if((j?s[p[j]][j-1]-s[i-1][j-1]:0)+1>=val) break ;
p[j]++;
if(__lg(a[p[j]])>=j){
while(q[j].size()&&a[q[j].back()]>=a[p[j]]) q[j].pop_back();
q[j].push_back(p[j]);
}
}
}
AddAs(ans,n-i+1);
for(int j=0;j<lgV;j++){
t[j]=p[j];
if(j) t[j]=max(t[j],t[j-1]);
}
for(int j=0;j<lgV;j++){
AddAs(ans,Sub(Sub(ss[t[j+1]-1][j],ss[t[j]-1][j]),Mul(t[j+1]-t[j],s[i-1][j]%mod)));
while(q[j].size()&&q[j].front()<=i) q[j].pop_front();
}
}
cout<<ans<<endl;
return 0;
}
B. 钱财塞满
和 highest 类似的,考虑计算跨过终点的贡献,设 \(f_n\) 表示余量为 \(n\) 时的答案,那么有答案 \(\displaystyle f_n=\sum_{i< \frac n2,i+w_j\geq \frac n2} f_{i}g_jf_{n-i-w_j}\),其中所有加法和乘法都是根据最大值/方案数重定义过的。
反推出计算 \(f_m\) 所需的所有点值,将 \([\dfrac m{2^k}-C,\dfrac m{2^k}]\) 的点值划分成一层,那么 \(O(\log V)\) 层之后,\(f_m\) 就降到了可以暴力背包的量级。同时根据等比数列有 \(C\leq 2\max w\)。因此所有点值个数是 \(O(\max w\log V)\) 的。
对于相邻两层贡献的计算,不难发现一次贡献有效的范围是一个区间,而查询则是对一个单点而言的,因此若将 \(n\) 和 \(\max w\) 视为同阶,则直接扫描线就是 \(O(n^2\log V)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("qcsm.in");
ofstream fout("qcsm.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
const int N=(1<<14)+9;
const int lgV=3e1;
const int mod=1e9+7;
const ll inf=2e18;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ull*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ull*x*y%mod;}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
struct Data{
ll dat;
int cnt;
inline void Init(){dat=-inf,cnt=0;}
Data(){Init();}
Data(ll _dat,int _cnt){dat=_dat,cnt=_cnt;}
friend inline Data operator +(Data f,Data g){
return f.dat==g.dat?Data(f.dat,Add(f.cnt,g.cnt)):(f.dat>g.dat?f:g);
}
friend inline Data operator *(Data f,int k){return Data(f.dat+k,f.cnt);}
friend inline Data operator *(Data f,Data g){return Data(f.dat+g.dat,Mul(f.cnt,g.cnt));}
friend inline void operator +=(Data &f,Data g){f=f+g;}
friend inline void operator *=(Data &f,Data g){f=f*g;}
};
Data f[N<<1],g[N<<1],h[N<<2];
int w[N],v[N],n,m;
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
for(int i=1;i<=n;i++) cin>>v[i];
int maxw=*max_element(w+1,w+n+1);
int s=0;
while(m>((ll(maxw)<<s+1)-maxw)) s++;
while(s&&(m-((ll(maxw)<<s+1)-maxw)>>s)<=0) s--;
f[0]=Data(0,1);
for(int i=1;i<=(m>>s);i++){
for(int j=1;j<=n;j++) if(i>=w[j]) f[i]+=f[i-w[j]]*v[j];
}
int xl=max(0ll,(m-((ll(maxw)<<s+1)-maxw)>>s)),xr=m>>s;
for(int i=xl;i<=xr;i++) f[i-xl]=f[i];
for(int i=s-1;~i;i--){
int vl=max(0ll,(m-((ll(maxw)<<(i+1)+1)-maxw)>>(i+1))),vr=m>>(i+1);
int xl=(m-((ll(maxw)<<i+1)-maxw)>>i),xr=m>>i;
for(int j=vl;j<=vr;j++){
for(int k:{j<<1}){
if(k<xl||k>xr) continue ;
for(int l=(k>>1);l<=(k>>1)+maxw;l++){
if(k-l>=vl&&k-l<=vr) g[k-xl]+=f[k-l-vl]*h[l-vl];
}
}
for(int k=1;k<=n;k++) h[j+w[k]-vl]+=f[j-vl]*v[k];
for(int k:{j<<1|1}){
if(k<xl||k>xr) continue ;
for(int l=(k+1>>1);l<=(k>>1)+maxw;l++){
if(k-l>=vl&&k-l<=vr) g[k-xl]+=f[k-l-vl]*h[l-vl];
}
}
}
for(int j=0;j<=vr-vl+maxw;j++) h[j].Init();
for(int j=0;j<=xr-xl;j++) f[j]=g[j],g[j].Init();
}
cout<<f[min(maxw,m)].dat<<' '<<f[min(maxw,m)].cnt<<endl;
return 0;
}
Jan. 27
A. 💧
直接把直径长度最大值次大值折半分别放两边,不够就调整,剩下没用的直接折半挂中间。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("rac.in");
ofstream fout("rac.out");
#define cin fin
#define cout fout
#define endl '\n'
const int N=2e6+9;
vector<int> e[N],id;
vector<vector<int>> dmt;
int n,m,k;
int vis[N],fa[N],dep[N];
inline void DFS(int x,vector<int> &node){
vis[x]=1;
node.push_back(x);
for(int y:e[x]){
if(y==fa[x]) continue ;
fa[y]=x;
dep[y]=dep[x]+1;
DFS(y,node);
}
}
signed main(){
cin>>n>>m>>k;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1;i<=n;i++){
if(vis[i]) continue ;
vector<int> v;
fa[i]=dep[i]=0,DFS(i,v);
int p=*max_element(v.begin(),v.end(),[](int i,int j){return dep[i]<dep[j];});
v.clear();
fa[p]=dep[p]=0,DFS(p,v);
p=*max_element(v.begin(),v.end(),[](int i,int j){return dep[i]<dep[j];});
dmt.push_back(vector<int>());
while(p){
dmt.back().push_back(p);
p=fa[p];
}
}
if(dmt.size()==1) return cout<<-signed(k!=dmt[0].size()-1)<<endl,0;
id.resize(dmt.size());
iota(id.begin(),id.end(),0);
sort(id.begin(),id.end(),[](int i,int j){return dmt[i].size()>dmt[j].size();});
int l=dmt[id[0]].size()/2+dmt[id[1]].size()/2+1;
if(dmt.size()>2) l=max(l,signed(dmt[id[1]].size()/2+dmt[id[2]].size()/2+2));
l=max(l,signed(dmt[id[0]].size())-1);
int r=dmt.size()-1;
for(int i=0;i<dmt.size();i++) r+=dmt[i].size()-1;
if(k<l||k>r) return cout<<-1<<endl,0;
int len=max(0,k-signed(dmt[id[0]].size()+dmt[id[1]].size()-1));
vector<int> p(dmt.size()),q(dmt.size());
for(int i=0;i<dmt.size();i++) p[i]=q[i]=dmt[i].size()/2;
int rem=min(k-1,signed(dmt[id[0]].size()+dmt[id[1]].size()-2));
rem-=dmt[id[0]].size()/2+dmt[id[1]].size()/2;
for(int i:{id[0],id[1]}){
int dlt=min(rem,signed(dmt[i].size()-1-dmt[i].size()/2));
p[i]+=dlt,q[i]+=dlt;
rem-=dlt;
}
int cnt=0,cur=len;
for(int i=2;cur>0&&i<dmt.size();i++){
cnt++;
cur-=dmt[id[i]].size();
}
vector<int> ord(id.begin(),id.begin()+cnt+2);
swap(ord[1],ord.end()[-1]);
len-=cnt;
for(int i=1;i+1<ord.size();i++){
int dlt=min(len,signed(dmt[ord[i]].size()-1));
len-=dlt;
p[ord[i]]-=(dlt+1)/2;
q[ord[i]]+=dlt/2;
}
for(int i=0;i<dmt.size();i++) p[i]=dmt[i][p[i]],q[i]=dmt[i][q[i]];
cout<<dmt.size()-1<<endl;
for(int i=0;i+1<ord.size();i++) cout<<q[ord[i]]<<' '<<p[ord[i+1]]<<endl;
for(int i=cnt+2;i<dmt.size();i++) cout<<p[ord[(ord.size()-1)/2]]<<' '<<p[id[i]]<<endl;
return 0;
}
B. 水桶
AtCoder 原题题解讲得很清楚,拜谢了。
首先能取到的所有值显然是 \([0,\max(A,B)]\) 中能被 \(\gcd(A,B)\) 整除的值。
考虑这些值怎么能被取到,一种是让 A 桶不断加 \(B\),另一种是从 A 桶中不断舀出来 \(B\),在 B 桶中先抵达的状态要特殊处理。二分出来两边的答案,如果加起来大于合法状态数就是在中间遇到了,和合法状态数取 \(\min\) 即可。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("buc.in");
ofstream fout("buc.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
using bint=__int128;
const int N=1e6+9;
ll a,b,k;
signed main(){
cin>>a>>b>>k;
if(k==0) return cout<<1<<endl,0;
if(!a||!b) return cout<<1+!!a+!!b<<endl,0;
ll g=__gcd(a,b);
a/=g,b/=g;
if(b>a) swap(a,b);
auto F=[&](ll k){return 2*(k+1)+2*(bint(k+1)*b/a-(bint(k+1)*b%a<=b));};
auto G=[&](ll k){return 2*k+2*(bint(k)*b/a);};
ll ans=3,l=0,r=a+1;
while(l+1<r){
ll mid=l+r>>1;
if(F(mid)<=k) l=mid;
else r=mid;
}
ans+=l;
l=0,r=a+1;
while(l+1<r){
ll mid=l+r>>1;
if(G(mid)<=k) l=mid;
else r=mid;
}
ans+=l;
ans=min(ans,a+1);
cout<<ans<<endl;
return 0;
}
C. Solitary Dream
对于一个特定的点,设 \(a,b\) 分别为其到 \(A,B\) 的距离,那么 Alice 选就有 \(a\) 的贡献,Bob 选就是 \(-b\) 的贡献,这很不好。因此考虑预先加上 \(\dfrac {a-b}2\) 的贡献,那么 Alice 的贡献就是 \(\dfrac {a+b}2\),Bob 的贡献就是 \(-\dfrac {a+b}2\),为了方便,下文中先把贡献乘 \(2\)。
令 \(d\) 表示某个点到链 \((A,B)\) 的距离,则 \(a+b=\operatorname{dist}(A,B)+d\),因此可以贡献可以只考虑点到链 \((A,B)\) 的距离。
设 \(C=\operatorname{lca}(A,B)\),那么 \(A,B\) 子树以及 \(C\) 除 \(A,B\) 方向上的子树的贡献是容易计算的,需要考虑的只有 \(C\) 的到根链以及链 \((A,C),(B,C)\) 的贡献。

浙公网安备 33010602011771号