2026 NOI 做题记录(十七)
\(\text{By DaiRuiChen007}\)
A. [CF2187F2] Al Fine (5)
设 \(a=[1,2,\dots,n]\),那么一棵子树的结构应该是满足 \(\max b[l,r]=b_l+r-l\) 的区间 \([l,r]\)。
直接递归,\(f_{l,r}\) 表示 \([l,r]\) 构成森林的方案数,每次枚举 \(l\) 所属的区间做到 \(\mathcal O(n^3)\)。
考虑观察性质优化,注意到 \(a=b\) 时答案是卡特兰数,也就是 \(n+1\) 个点生成的无标号树个数。
观察递归的过程,求 \(f_{l,r}\) 时找到最大的 \(p\) 使得 \(b[l,p]\) 合法,那么 \([l,p],(p,r]\) 必定属于不同子树,可以直接划分,即 \(p<r\) 时 \(f_{l,r}=f_{l,p}\times f_{p+1,r}\)。
对于 \(p=r\) 的情况,按所有 \([l,q]\) 合法的 \(q\) 把序列分成若干段 \((q_{i-1},q_i]\),每段 \([x,y]\) 满足 \(b[x,y]\) 值域等于 \([b_l-l+x,b_l-l+y]\),且长度 \(>1\) 时 \(b_x\ne x\)。
手玩一下分裂子树的过程,如果所有区间长度都是 \(1\),那么可以以任意顺序分裂,假设分成了 \([v_1+1,v_2-1],[v_2+1,v_3-1],\dots,[v_{k}+1,v_{k+1}-1]\),其中 \(v_1=l,v_{k+1}=r+1\),那么把 \(v_1\sim v_k\) 看成 $l-1 $ 的儿子,然后递归儿子的划分过程,这样就得到了一棵无标号树,可以发现方案数就是卡特兰数。
而对于一个长度 \(>1\) 的区间 \([x,y]\),如果分裂为 \([l,x),[x,r]\),那么由于 \(b_x\ne \min b[x,r]\),则 \(b[x,r]\) 不合法,那么我们会不断按上述找 \(p\) 的过程分裂。
此时可以发现按该过程分裂若干次后,我们会得到 \(b(y,r]\) 以及 \(b[x,y]\) 内部分裂出的若干区间,证明考虑如果存在合法区间 \(b[u,v]\) 满足 \(x\le u\le y<v\),显然 \(x<u\),则 \(b[u,y]\) 值域是 \([b_l-l+u,b_l-l+y]\),从而 \(b[l,u)\) 合法,序列应该分成 \([x,u),[u,y]\) 两个线段,所以在 \(b[x,r]\) 内找 \(p\) 等价于在 \(b[x,y]\) 内找 \(p\)。
因此这种情况下贡献就是 \(f_{y,r}\times f_{x+1,y}\)。
所以说对于这些长度大于 \(1\) 的区间,我们一定会分裂出 \(f_{x+1,y}\) 的贡献,然后我们要考虑这若干个区间之间的分裂顺序,还是按刚才的方法建树,但是如果根的某个儿子是 \(y-x>1\) 的 \(x\),那么我们会立刻分裂出 \((x,y]\),也就是说 \(x\) 在树上必定对应一个叶子。
那么我们就解决了 \(f_{l,r}\),先算出 \(\prod f_{x+1,y}\),然后对于内部的合并,看成按 dfs 序插入若干个点,有一些点必须当叶子的方案数,直接顺序 dp,只要维护当前点深度即可转移。
时间复杂度 \(\mathcal O(n^2)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5005,MOD=998244353;
int n,a[MAXN],ia[MAXN],f[MAXN][MAXN],h[MAXN];
vector <int> g[MAXN];
int dp(int l,int r) {
if(l>=r) return 1;
if(~f[l][r]) return f[l][r];
int m=upper_bound(g[l].begin(),g[l].end(),r)-g[l].begin()-1;
if(g[l][m]<r) return f[l][r]=1ll*dp(l,g[l][m])*dp(g[l][m]+1,r)%MOD;
int z=1;
for(int i=1;i<=m;++i) z=1ll*z*dp(g[l][i-1]+1,g[l][i])%MOD;
h[1]=1;
for(int i=1;i<=m;++i) {
if(g[l][i-1]<g[l][i]-1) {
for(int j=i;~j;--j) h[j]=(h[j]+h[j+1])%MOD;
} else {
for(int j=i+1;j>=1;--j) h[j]=(h[j+1]+h[j-1])%MOD;
h[0]=0;
}
}
for(int i=1;i<=m+1;++i) h[0]=(h[0]+h[i])%MOD,h[i]=0;
f[l][r]=1ll*z*h[0]%MOD,h[0]=0;
return f[l][r];
}
void solve() {
cin>>n;
for(int l=1;l<=n;++l) for(int r=l;r<=n;++r) f[l][r]=-1;
for(int i=1,x;i<=n;++i) cin>>x,ia[x]=i;
for(int i=1,x;i<=n;++i) cin>>x,a[i]=ia[x];
for(int i=1;i<=n;++i) {
g[i].clear();
for(int j=i,o=0;j<=n&&a[j]>=a[i];++j) {
o=max(o,a[j]);
if(o-a[i]==j-i) g[i].push_back(j);
}
}
cout<<dp(1,n)<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
B. [CF2180F2] Control Car (1.5)
显然是矩阵优化 dp,记录当前列球走到了第几行,转移时发现需要记录当前位置下边界是否已经被挡住,以及前一列是否在同一行(用来确定右上角能否挡住当前位置的上边界)。
计算系数的时候可以通过 \(\times \dfrac 34\) 来钦定下一列的一些格子不能往左。
时间复杂度 \(\mathcal O(n^3\log m)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=1e9+7,i4=(MOD+1)/4;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int n; ll m;
struct Mat {
ll f[205][205];
Mat() { memset(f,0,sizeof(f)); }
friend Mat operator*(const Mat&u,const Mat&v) {
Mat w;
for(int i=0;i<=4*n;++i) for(int k=0;k<=4*n;++k) if(u.f[i][k]) for(int j=0;j<=4*n;++j) {
if(v.f[k][j]) w.f[i][j]=(w.f[i][j]+u.f[i][k]*v.f[k][j])%MOD;
}
return w;
}
};
void solve() {
Mat I,Z;
cin>>n>>m,I.f[0][0]=ksm(4,n+1);
ll pw=ksm(4,n-1);
for(int x=1;x<=n;++x) {
I.f[x][0]=I.f[x+2*n][0]=pw;
I.f[x+n][0]=pw*7%MOD,I.f[x+3*n][0]=pw*6%MOD;
I.f[x][x]=I.f[x+n][x+n]=pw*3%MOD;
I.f[x+n][x]=pw*6%MOD,I.f[x+3*n][x]=pw*4%MOD;
I.f[x+2*n][x]=I.f[x+3*n][x+n]=pw*2%MOD;
for(int y=x+1;y<=n;++y) {
ll t=ksm(4,n-2)*ksm(3*i4,2*(y-x-1))%MOD;
for(int a:{0,1}) for(int b:{0,1}) for(int c:{0,1}) {
I.f[x+(a+2*b)*n][y+(c+2)*n]=t*(a?2:1)*(b?2:3)*(c?1:3)%MOD;
}
}
}
Z.f[0][1]=pw*12%MOD,Z.f[0][1+n]=pw*4%MOD;
for(int x=2;x<=n;++x) {
ll t=ksm(4,n)*ksm(3*i4,2*x-3)%MOD;
Z.f[0][x+2*n]=t*3%MOD,Z.f[0][x+3*n]=t;
}
for(;m;m>>=1,I=I*I) if(m&1) Z=Z*I;
cout<<Z.f[0][0]<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
C. [CF2183H] Minimise Cost (4)
逆向推理想到本题做法应该是 wqs 二分后决策单调性解决。
进行一些调整,对于集合 \(|x|\le |y|\),如果 \(\min x\le \max y\) 那么可以交换这两个元素,从而所有集合是排序后的区间,且值域越大的区间大小越小。
序列分段问题考虑决策单调性,\((s_j-s_i)(j-i)\) 在 \(a\) 为正的部分具有四边形不等式,而 \(a\) 为负的部分,根据调整发必须属于同一段,证明考虑把首个和最小值不属于同个集合的元素放进去。
时间复杂度 \(\mathcal O(n\log n\log V)\)。
代码:
#include<bits/stdc++.h>
#define LL __int128
using namespace std;
const int MAXN=2e5+5;
int n,m,K,a[MAXN];
LL s[MAXN];
struct info { LL w; int c; } f[MAXN];
bool operator <(const info&u,const info&v) { return u.w!=v.w?u.w<v.w:u.c<v.c; }
void write(LL z) {
if(z<0) return cout<<"-",write(-z);
if(z>9) write(z/10);
cout<<char(z%10+'0');
}
int b[MAXN],L[MAXN],R[MAXN];
info chk(LL D) {
auto vl=[&](int x,int y)->info { return {f[x].w+(s[y]-s[x])*(y-x)+D,f[x].c+1}; };
f[0]={0,0},b[1]=0,L[1]=1,R[1]=m;
for(int i=1,l=1,r=1;i<=m;++i) {
f[i]=vl(b[l],i);
if(i==m) break;
while(R[l]<=i) ++l;
L[l]=i+1;
while(l<=r&&vl(i,L[r])<vl(b[r],L[r])) --r;
if(l>r) b[++r]=i,L[r]=i+1,R[r]=m;
else {
int p=L[r];
if(R[r]>L[r]) for(int t=1<<__lg(R[r]-L[r]);t;t>>=1) if(p+t<=R[r]&&!(vl(i,p+t)<vl(b[r],p+t))) p+=t;
R[r]=p; if(p<m) b[++r]=i,L[r]=p+1,R[r]=m;
}
}
if(n==m) return f[n];
info z=vl(0,n);
for(int i=1;i<=m;++i) z=min(z,vl(i,n));
return z;
}
void solve() {
cin>>n>>K,m=0;
for(int i=1;i<=n;++i) cin>>a[i],m+=a[i]>0;
sort(a+1,a+n+1,greater<>());
for(int i=1;i<=n;++i) s[i]=a[i]+s[i-1];
if(K>m) return write((s[n]-s[K-1])*(n-K+1)+s[K-1]),cout<<"\n",void();
LL l=-4e19,r=4e19,w=r;
while(l<=r) {
LL mid=(l+r)>>1;
if(chk(mid).c<=K) w=mid,r=mid-1;
else l=mid+1;
}
write(chk(w).w-K*w),cout<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
D. [CF2190E] Median Permutation (4)
首先设 \(a_x=1,a_y=n,x<y\),那么手玩发现 \(i<x\) 时 \(a_i=\mathrm{med}(a_i,a_{i+1},a_{i+2})\),\(i>y\) 对称。
那么只要考虑 \(a[x,y]\) 范围,打表 \(a_1=1,a_n=n\) 的序列发现恰好有 \(\binom {n-2}{n/2-1}\) 个,进一步发现只要奇偶下标分别递增即可。
那么知道 \(x,y\) 后 \(a\) 可以分成两个递增序列,求值域上相邻已知元素之间的方案数用组合数算出之后乘起来即可。
时间复杂度 \(\mathcal O(n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=998244353;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int n,a[MAXN],h[MAXN];
ll fc[MAXN],ic[MAXN];
ll C(int x,int y) { return y<0||y>x?0:fc[x]*ic[y]%MOD*ic[x-y]%MOD; }
void solve() {
cin>>n,h[0]=0;
basic_string<int>b,c;
for(int i=1;i<=n;++i) cin>>a[i],h[i]=n+1;
int x=find(a+1,a+n+1,1)-a,y=find(a+1,a+n+1,n)-a;
if(x>y) reverse(a+1,a+n+1),x=n-x+1,y=n-y+1;
for(int i=x-2;i>=1;i-=2) b+=a[i];
for(int i=1+x%2;i<y;i+=2) b+=a[i];
for(int i=x+2;i<y;i+=2) c+=a[i];
for(int i=y+1;i<=n;i+=2) ((y-x)&1?c:b)+=a[i];
for(int i=n-(n-y)%2;i>y;i-=2) ((y-x)&1?c:b)+=a[i];
ll z=1;
auto upd=[&](int i,int j) { z&=(h[i]>n||h[i]==j),h[i]=j; };
for(int i=0;i<(int)b.size();++i) if(b[i]) upd(b[i]-2,i),upd(b[i]-1,i+1);
for(int i=0;i<(int)c.size();++i) if(c[i]) upd(c[i]-2,c[i]-2-i),upd(c[i]-1,c[i]-2-i);
upd(n-2,b.size());
for(int i=1,j=0;i<=n-2;++i) if(h[i]<=n) z=z*C(i-j,h[i]-h[j])%MOD,j=i;
cout<<z<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int i=fc[0]=1;i<MAXN;++i) fc[i]=fc[i-1]*i%MOD;
ic[MAXN-1]=ksm(fc[MAXN-1]);
for(int i=MAXN-1;i;--i) ic[i-1]=ic[i]*i%MOD;
int _; cin>>_;
while(_--) solve();
return 0;
}
E. [CF2162H] Beautiful Problem (2)
首先去掉被包含的区间,然后给每个区间钦定 \(\ge x\) 或 \(\le x\),相交部分必须 \(=x\)。
那么直接 dp 记录上一个区间的类型,以及当前有多少个 \(\ge x\) 且未被钦定 \(=x\) 的点时,\(=x\) 位置数最小值。
转移平凡,但是如果有 \(l_{i+1}\le r_{i-1}\) 且 \(i-1,i,i+1\) 类型不同则会错误计算,不过显然此时三个线段类型相同最优。
时间复杂度 \(\mathcal O(n^2)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005,inf=1e9;
int n,m,q,c,s[MAXN],f[MAXN][2],g[MAXN][2];
array<int,2> a[MAXN];
void solve() {
cin>>n>>m,memset(s,0,sizeof(s));
for(int i=1,x;i<=n;++i) cin>>x,++s[x];
for(int i=1;i<=m;++i) cin>>a[i][0]>>a[i][1];
sort(a+1,a+m+1),q=c=0;
for(int i=1;i<=m;++i) if(a[i][1]>a[q][1]) a[++q]=a[i];
for(int i=1;i<=n;++i) s[i]+=s[i-1];
memset(f,0x3f,sizeof(f)),f[0][0]=f[0][1]=0;
for(int i=1;i<=q;++i) {
if(a[i][0]>a[i-1][1]) {
int d=a[i][1]-a[i][0]+1; c+=d;
for(int j=c;~j;--j) {
f[j][0]=min(f[j][0],f[j][1]);
f[j][1]=j<d?inf:min(f[j-d][0],f[j-d][1]);
}
} else {
int x=a[i-1][1]-a[i][0]+1,y=a[i][1]-a[i-1][1]; c+=y;
memset(g,0x3f,sizeof(g));
for(int j=c;~j;--j) {
g[j][0]=min(f[j][0],f[j+x][1]+x);
g[j][1]=j<y?inf:min(f[j-y][1],f[j-y][0]+x);
}
memcpy(f,g,sizeof(g));
}
}
for(int i=1;i<=n;++i) {
bool o=0;
for(int j=0;j<=c;++j) {
int z=min(f[j][0],f[j][1]),x=j,y=c-j-z,l=s[i-1],r=n-s[i],h=s[i]-s[i-1];
if(l<=x+n-c&&r<=y+n-c&&z+max(x-l,0)+max(y-r,0)<=h) o=1;
}
cout<<o;
}
cout<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
*F. [CF2181K] Knit the Grid (7.5)
观察发现一个简单环可以由其内部每个格子周围的四元环异或得到。
那么最终的图一定是选择若干个格子把其周围四条边翻转得到的。
考虑题目的限制,非边界点度数不为 \(0/4\),说明四周格子不能为 \(\begin{bmatrix}0&0\\0&0\end{bmatrix},\begin{bmatrix}1&1\\1&1\end{bmatrix},\begin{bmatrix}0&1\\1&0\end{bmatrix},\begin{bmatrix}1&0\\0&1\end{bmatrix}\),限制二则是一个格子相邻的四个格子异或和为 \(0/1\)。
观察发现限制一等价于两条对角线异或和不同为 \(0\),而限制二中四个相邻元素的异或和又可以表示成左上右下对角线异或和,或者左下右上对角线异或和。
那么考虑以每条对角线异或和作为变量,此时限制都是二元的,容易表述,但此时两种方向的对角线可能有矛盾,我们需要每个斜向的环上异或和为 \(0\)。
我们发现每个斜向的四元环都被其中间格子的权值限制了两种方向的异或和,所以这个限制被自然满足了。
那么直接建立 2-SAT 即可。
时间复杂度 \(\mathcal O(nm)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=4.2e6+5;
int vc,ec,dfn[MAXN],low[MAXN],st[MAXN],tp,bl[MAXN],dc,sc;
bool ins[MAXN];
vector <int> G[MAXN];
void link(int x,int y) { G[x].push_back(y); }
int n,m,a[1005][1005][2],b[1005][1005][2],f[1005][1005];
char c[1005][1005];
void tarjan(int u) {
dfn[u]=low[u]=++dc,ins[st[++tp]=u]=1;
for(int v:G[u]) {
if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if(ins[v]) low[u]=min(low[u],low[v]);
}
if(dfn[u]==low[u]) for(++sc;ins[u];ins[st[tp--]]=0) bl[st[tp]]=sc;
}
void solve() {
cin>>n>>m,vc=ec=0;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) cin>>c[i][j];
for(int i=0;i<=n;++i) for(int j=0;j<=m;++j) for(int o:{0,1}) a[i][j][o]=++vc,b[i][j][o]=++vc;
link(a[0][0][1],a[0][0][0]);
link(b[0][m][1],b[0][m][0]);
link(b[n][0][1],b[n][0][0]);
link(a[n][m][1],a[n][m][0]);
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
int w=c[i][j]=='B';
if(i<n&&j<m) {
link(a[i][j][0],b[i][j][1]);
link(b[i][j][0],a[i][j][1]);
}
for(int o:{0,1}) {
link(a[i-1][j-1][o],a[i][j][o^w]);
link(a[i][j][o],a[i-1][j-1][o^w]);
link(b[i-1][j][o],b[i][j-1][o^w]);
link(b[i][j-1][o],b[i-1][j][o^w]);
}
}
for(int j=1;j<=m;++j) for(int o:{0,1}) {
link(b[0][j-1][o],a[0][j][o]);
link(a[0][j][o],b[0][j-1][o]);
link(a[n][j-1][o],b[n][j][o]);
link(b[n][j][o],a[n][j-1][o]);
}
for(int i=1;i<=n;++i) for(int o:{0,1}) {
link(b[i-1][0][o],a[i][0][o]);
link(a[i][0][o],b[i-1][0][o]);
link(a[i-1][m][o],b[i][m][o]);
link(b[i][m][o],a[i-1][m][o]);
}
for(int i=1;i<=vc;++i) if(!dfn[i]) tarjan(i);
for(int i=0;i<=n;++i) for(int j=0;j<=m;++j) {
if(bl[a[i][j][0]]==bl[a[i][j][1]]||bl[b[i][j][0]]==bl[b[i][j][1]]) return cout<<"NO\n",void();
}
cout<<"YES\n";
for(int i=0;i<n;++i) for(int j=0;j<m;++j) f[i+1][j+1]=f[i][j]^(bl[b[i][j][0]]>bl[b[i][j][1]]);
for(int i=0;i<=n;++i,cout<<"\n") for(int j=1;j<=m;++j) cout<<(f[i][j]^f[i+1][j]);
for(int i=1;i<=n;++i,cout<<"\n") for(int j=0;j<=m;++j) cout<<(f[i][j]^f[i][j+1]);
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) {
solve(),dc=sc=0;
for(int i=0;i<=vc;++i) dfn[i]=low[i]=bl[i]=st[i]=ins[i]=0,G[i].clear();
for(int i=0;i<=n+1;++i) for(int j=0;j<=m+1;++j) f[i][j]=0;
}
return 0;
}
*G. [CF2178H] Create or Duplicate (7)
首先如果分三种数分别处理则合并过程较难避免 \((\max,+)\) 卷积。
考虑如何合并处理,即只记录三种数总和,那么插入操作不变,翻倍操作则难以计算每次翻倍的是哪些元素。
那么我们想让这些翻倍操作结构更简单,可以考虑修改操作的顺序,即从后往前倒序对齐每次翻倍操作,即每个元素被翻倍的时刻都是一段后缀,即每次翻倍都会把已经插入 \(\ge 1\) 次的元素都翻倍。
直接 Dijkstra 求最短路。
时间复杂度 \(\mathcal O(m\log m)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
int a[3],n,k; bool v[8][MAXN];
ll d[8][MAXN];
priority_queue <array<ll,2>,vector<array<ll,2>>,greater<>> Q;
void add(int x,int y,ll z) { if(z<d[x][y]) Q.push({d[x][y]=z,x*n+y}); }
void solve() {
cin>>a[0]>>a[1]>>a[2]>>n>>k;
for(int o=1;o<8;++o) memset(d[o],0x3f,n*8),memset(v[o],0,n);
for(int o:{0,1,2}) add(1<<o,a[o],a[o]);
while(Q.size()) {
int x=Q.top()[1]/n,y=Q.top()[1]%n; Q.pop();
if(v[x][y]) continue; v[x][y]=1;
add(x,y*2%n,d[x][y]+k*__builtin_popcount(x));
for(int o:{0,1,2}) add(x|1<<o,(y+a[o])%n,d[x][y]+a[o]);
}
cout<<d[7][0]-a[0]-a[1]-a[2]<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
H. [CF2157H] Keygen 3 (3)
打表计算 \(f_{n,k}\) 表示所求排列个数,注意到 \(f_n\) 行内递减,且有一段后缀满足 \(f_{n,k}=f_{n-1,k-1}\)。
观察 \(g_{n,k}=\min(2000,f_{n,k})\),发现 \(n\ge 20\) 时 \(g_{n}\) 就是在 \(g_{n-1}\) 开头插入一项 \(2000\)。
那么预处理 \(n\le b=19\) 的所有解,然后对于更大的 \(n\),递归成 \((n-1,k-1)\),最终得到某个子问题 \((b,k)\) 或 \((m,1)\)。
显然排列 \((n,k)\to (n+1,k+1)\) 只需在开头插入一个 \(1\) 并平移值域。
然后考虑如何构造 \((m,1)\),由于 \((b,1)\) 中有非常多的排列,只要考虑其中一些有特殊性质的排列变成 \((b+1,1)\)。
依旧在开头插入 \(1\) 并平移值域,发现如果交换 \(a_1,a_{m+1}\),如果原始的 \(a_m=1\) 就能继续递归,而初始这样的排列个数的确 \(\ge 2000\) 个,所以合法。
时间复杂度 \(\mathcal O(2^b)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
int a[25],b[25],f[25];
vector <vector<int>> q;
void solve(int n,int m) {
for(int s=0;s<(1<<(n-1))&&q.size()<2000;++s) {
int x=0,t=0;
for(int i=0;i<n;++i) if(s>>i&1) a[x++]=i;
for(int i=n-1;~i;--i) if(!(s>>i&1)) a[x++]=i;
memset(b,0,sizeof(b));
for(int i=0;i<n;++i) if(!b[i]) {
++t; for(int u=i;!b[u];u=a[u]) b[u]=1;
}
if(t==m) q.push_back(vector<int>(a,a+n));
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,m;
cin>>n>>m;
if(n<=19) solve(n,m);
else if(n-m<19) {
solve(19,m-n+19);
for(auto&o:q) {
vector <int> t(n);
for(int i=0;i<n-19;++i) t[i]=i;
for(int i=0;i<19;++i) t[n-19+i]=n-19+o[i];
swap(o,t);
}
} else {
int k=n-m+1;
solve(19,1);
for(auto&o:q) {
vector <int> t(n);
for(int i=0;i<n-k;++i) t[i]=i;
for(int i=n-k;i<n-19;++i) t[i]=i+1;
for(int i=0;i<18;++i) t[n-19+i]=n-19+o[i];
t[n-1]=n-k,swap(o,t);
}
}
cout<<q.size()<<"\n";
for(auto o:q) for(int i=0;i<n;++i) cout<<o[i]+1<<" \n"[i==n-1];
return 0;
}
I. [CF2180G] Balance (1.5)
根据一定组合恒等式能把答案表示成 \(n,\sum a_i,\sum ia_i\) 的形式,观察序列发现 \(a\) 始终是回文的,从而 \(\sum ia_i=\dfrac {n+1}2\sum a_i\)。
那么考虑如何维护 \(\sum a_i\),即每次弹出的元素。
由于序列回文,所以只维护前一半元素,则操作几乎不变,只是删除操作变成删除末尾元素。
如果序列长度为偶数,则末尾就是最后一次操作的元素,否则就是最后一次操作前的元素。
实际上就是维护长度的二进制表示,每次 \(-1\) 找到最后一个被退位的位置,根据经典均摊分析复杂度 \(\mathcal O(q\log q)\)。
但是我们注意回文串长度为奇数或偶数我们没有考虑,两种情况对应的串相同,但长度为偶数时末尾元素不会被弹出。
额外维护此信息暴力模拟即可,不会影响整体复杂度。
时间复杂度 \(\mathcal O(q\log q)\),
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5,MOD=1e9+7,P=MOD-1,i2=(MOD+1)/2;
ll ksm(ll a,ll b=P-1) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int a[MAXN],b[MAXN];
void solve() {
ll n=0,q=0,S=0; int _; cin>>_;
for(int o,x,m=0;_--;) {
cin>>o;
if(o==1) {
for(x=m;b[x]<2;--x) b[x]=(b[x]+3)%4;
b[x]=(b[x]+3)%4,S=(S+MOD-a[x])%MOD;
n=(n+MOD-1)%MOD,q=(q+P-1)%P;
} else if(o==2) {
cin>>x,a[++m]=x,b[m]=(q+1)%2*2;
S=(S+(n+1)*x)%MOD,n=(2*n+1)%MOD,q=(2*q+1)%P;
} else {
cout<<((ksm(2,q)-1)*ksm(n)+ksm(2,q+P-1))%MOD*i2%MOD*S%MOD<<"\n";
}
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
J. [CF2190G] Maximize Determinant (6)
考虑如何刻画行列式信息,模拟消元的过程发现我们最关心的就是每行的 \(l_i,r_i+1\)。
事实上,构造矩阵 \(T_{i,j}=[j\ge i],B_{i,l_i}=1,B_{i,r_i+1}=-1\),则 \(B\times T=A,\det(T)=1,\det(A)=\det (B)\)。
那么只要考虑 \(\det B\),把行列式看成每行选 \(l_i/r_i+1\) 构成排列,因此把 \(l_i,r_{i+1}\) 连边,得到的必须是若干基环树,以及一棵过 \(n+1\) 的树,否则 \(\det B=0\)。
对于一个排列,其符号由每条边上填的数构成排列的逆序对,以及多少条边选择了 \(r_i+1\) 确定。
对于一个大小为 \(k\) 的环,翻转每条边匹配的方向,翻转了 \(k\) 条边,并且对排列奇偶性影响为 \(k-1\),所以此时符号恰好取反,则此时 \(\det B=0\)。
所以 \(\det B\ne 0\) 则对应的图是一棵树,每条边的定向方式唯一,即从 \(n+1\) 为根的外向树,则 \(\det B\in\{-1,1\}\),显然答案为 \(1\) 可以取到,那么目标就是让 \(\det B=1\)。
先假设原始的 \(\det B=-1\),那么要翻转其符号,先考虑修改一条边的情况,手玩发现修改这条边下端的端点,设到原端点距离为 \(k\),翻转了 \(k\) 条边,对排列奇偶性影响为 \(k\),必定无效果。
所以我们只能改变这条边两端的大小关系来修改符号,只要下端的子树标号不是值域的一段前缀即可。
如果修改两条边,直接把这两条边,即 \(B\) 的这两行交换即可,所以这种情况只要取权值最小的两条,很显然选更多边的策略都不优于该策略。
那么如果 \(\det B=0\),考虑求出最大生成森林,如果非树边个数 \(\ge 2\) 条,那么随便连然后用刚才的方式调整即可。
否则把唯一的非树边随便连接,如果此时 \(\det B=1\) 或原本的两个连通块标号值域有交,则只要改这条边,否则要么额外改权值最小的边,要么在环上换一条边删掉。
考虑在环上换边的过程,发现只有不经过 \(\mathrm{LCA}\) 的一侧路径上的边会被影响,若翻转了 \(k\) 条边,则奇偶性变换量为 \(k+1\),加入新边的下端点根据上面的分析同样无影响,而新边的方向由于值域不交也是确定的,所以只要关心这两条边的自身的方向,容易快速判断。
时间复杂度 \(\mathcal O(n\log n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
vector <int> G[MAXN];
int n,l[MAXN],r[MAXN],w[MAXN],e[MAXN],dsu[MAXN];
int a[MAXN],tr[MAXN],sz[MAXN],mx[MAXN],iv,fa[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void dfs(int u,int fz) {
sz[u]=1,mx[u]=u,fa[u]=fz;
for(int i:G[u]) if(i!=fz) {
int v=l[i]+r[i]-u; a[i]=v,iv^=v>u;
dfs(v,i),sz[u]+=sz[v],mx[u]=max(mx[u],mx[v]);
}
}
void solve() {
cin>>n,++n,iv=0;
for(int i=1;i<=n;++i) G[i].clear(),dsu[i]=i,tr[i]=0;
for(int i=1;i<n;++i) cin>>l[i]>>r[i]>>w[i],++r[i],e[i]=i;
sort(e+1,e+n,[&](int x,int y){ return w[x]<w[y]; });
ll s=0; int h=0,m=0;
for(int i=n-1;i;--i) {
int x=l[e[i]],y=r[e[i]];
if(find(x)==find(y)) s+=w[e[i]],++m,h=e[i];
else G[x].push_back(e[i]),G[y].push_back(e[i]),dsu[find(x)]=find(y);
}
if(m>1) return cout<<s<<"\n",void();
if(!m) {
dfs(n,0);
for(int i=n-1;i;--i) {
for(int x=a[i];x;x&=x-1) iv^=tr[x];
for(int x=a[i];x<n;x+=x&-x) tr[x]^=1;
}
if(iv==0) return cout<<"0\n",void();
int z=w[e[1]]+w[e[2]];
for(int i=1;i<n;++i) if(sz[i]!=mx[i]) z=min(z,w[fa[i]]);
cout<<z<<"\n";
return ;
}
if(find(1)==find(n)) return cout<<s<<"\n",void();
for(int i=1;i<n;++i) if(find(i)==find(n)&&find(i+1)==find(1)) return cout<<s<<"\n",void();
int z=s+w[e[h==e[1]?2:1]],u=l[h],v=r[h];
l[h]=1,r[h]=n,G[1].push_back(h),G[n].push_back(h);
dfs(n,0);
for(int i=n-1;i;--i) {
for(int x=a[i];x;x&=x-1) iv^=tr[x];
for(int x=a[i];x<n;x+=x&-x) tr[x]^=1;
}
if(!iv) return cout<<s<<"\n",void();
for(int c=u>v,t;u!=v;u=t) {
if(sz[v]<sz[u]) swap(u,v),c^=1;
t=l[fa[u]]+r[fa[u]]-u;
if(c==(u>t)) z=min(z,w[fa[u]]);
}
cout<<z<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
K. [CF2187E] Doors and Keys (4.5)
观察解的结构,应该是选定一些门用钥匙打开,然后每个门往前匹配首个钥匙。
考虑怎么运钥匙最优秀,可以发现对于每条边如果要运 \(k\) 个钥匙则至少要 \(\max(0,2k-1)\) 次,构造只要一把一把运到下一个格子即可取到。
那么直接 dp \(f_{i,k}\) 表示当前运输钥匙个数,显然 \(k\le \sqrt V\) 否则代价已经超过不碰钥匙的答案了。
时间复杂度 \(\mathcal O(n\sqrt V)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5,B=800;
int n,a[MAXN],f[B+5],g[B+5];
void solve() {
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
string s; cin>>s;
memset(f,0x3f,sizeof(f));
int m=0; f[0]=0;
if(s[0]=='1') f[1]=0,++m;
for(int i=1;i<=n;++i) {
memset(g,0x3f,sizeof(g)),g[0]=max(f[0],a[i])+1;
for(int j=1;j<=m;++j) {
if(f[j]>=a[i]) g[j]=min(g[j],f[j]+2*j-1);
else g[j]=min(g[j],a[i]+2*j-1),g[j-1]=min(g[j-1],f[j]+2*j-1);
}
cout<<*min_element(g,g+m+1)<<" \n"[i==n];
if(i==n) break;
memset(f,0x3f,sizeof(f));
if(s[i]=='1') {
m=min(m+1,B);
for(int j=m;j;--j) f[j]=min(f[j+1],g[j-1]);
f[0]=f[1];
} else {
for(int j=m;~j;--j) f[j]=min(f[j+1],g[j]);
}
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
L. [CF2172C] Circles Are Far from Each Other (3.5)
解的形态一定是若干个相离的同心圆组,二分一下答案 \(k\),则每组同心圆 \(r\) 只差 \(\ge k\)。
考虑从后到前维护同心圆组,注意到 \(\le r_i-k\) 的同心圆组都是等价的,未来的每个圆都能任意匹配,所以可以随便匹配。
但是我们要求最外层的圆是前 \(L\) 个,且直径总和尽可能小,根据贪心,如果有后 \(n-L\) 个的圆先匹配,否则匹配最大的即可。
用 std::deque 分别维护当前能和不能匹配的同心圆组。
时间复杂度 \(\mathcal O(n\log n)\)。
代码:
#include<bits/stdc++.h>
#define ld long double
using namespace std;
const int MAXN=1e5+5;
int n,S,m,a[MAXN];
bool chk(ld D) {
int c=1,p=n-1;
for(;p&&p+c>m;--p) c+=a[p+c]>a[p]-D;
if(p+c>m) return 0;
deque <int> L,R;
for(int i=p+1;i<=p+c;++i) L.push_back(i);
for(;p;--p) {
while(L.size()&&a[L.back()]<=a[p]-D) R.push_front(L.back()),L.pop_back();
if(R.size()) R.pop_front();
else ++c;
L.push_front(p);
}
for(int i:R) L.push_back(i);
if(L.size()==1) return 1;
ld w=D*(L.size()-1);
for(int i=0;i<(int)L.size();++i) w+=(i<2?1:2)*a[L[i]];
return w<=S;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>S>>n>>m;
for(int i=1;i<=n;++i) cin>>a[i];
ld L=0,R=S;
while(R-L>=1e-10) {
ld mid=(L+R)/2;
if(chk(mid)) L=mid;
else R=mid;
}
int x=round(L),y=1; ld z=abs(L-x);
for(int i=2;i<m;++i) {
int u=round(L*i); ld o=abs(L-(ld)u/i);
if(o<z) z=o,x=u,y=i;
}
int g=__gcd(x,y); x/=g,y/=g;
if(y==1) cout<<x<<"\n";
else cout<<x<<"/"<<y<<"\n";
return 0;
}
M. [CF2174D] Secret Message (1.5)
建立 MST,要么加入一条非树边删掉路径外最大值,如果加入两条非树边,则选的两条边必须在路径交上且必定为最大的两条边,否则仅删除其中一条更优,加入更多非树边肯定不优。
时间复杂度 \(\mathcal O(m\log m)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
const ll inf=1e18;
struct Edge { int u,v,w; };
vector <Edge> G[MAXN];
int n,m,dsu[MAXN],fa[MAXN],siz[MAXN],hson[MAXN],dep[MAXN],top[MAXN],dfn[MAXN],dc,st[18][MAXN],f[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void dfs1(int u,int fz) {
fa[u]=fz,dep[u]=dep[fz]+1,siz[u]=1,hson[u]=f[u]=0;
for(auto e:G[u]) if(e.v^fz) {
dfs1(e.v,u),siz[u]+=siz[e.v];
if(siz[e.v]>siz[hson[u]]) hson[u]=e.v;
}
}
void dfs2(int u,int up) {
dfn[u]=++dc,top[u]=up,f[u]^=f[fa[u]];
if(hson[u]) dfs2(hson[u],up);
for(auto e:G[u]) if(e.v!=fa[u]&&e.v!=hson[u]) dfs2(e.v,e.v);
for(auto e:G[u]) if(e.v!=fa[u]) st[0][dfn[e.v]]=e.w;
}
int qmx(int l,int r) {
if(l>r) return 0;
int k=__lg(r-l+1);
return max(st[k][l],st[k][r-(1<<k)+1]);
}
array<int,2> a[MAXN],b[MAXN];
int qry(int u,int v) {
int z=0,s=0,t=0;
while(top[u]^top[v]) {
if(dep[top[u]]>dep[top[v]]) a[++s]={dfn[top[u]],dfn[u]},u=fa[top[u]];
else b[++t]={dfn[top[v]],dfn[v]},v=fa[top[v]];
}
if(u!=v) a[++s]={min(dfn[u],dfn[v])+1,max(dfn[u],dfn[v])};
reverse(a+1,a+s+1);
for(int i=t;i>=1;--i) a[++s]=b[i];
inplace_merge(a+1,a+s-t+1,a+s+1);
a[0]={0,0},a[s+1]={n+1,n+1};
for(int i=0;i<=s;++i) z=max(z,qmx(a[i][1]+1,a[i+1][0]-1));
return z;
}
void solve() {
cin>>n>>m,dc=0;
for(int i=1;i<=n;++i) dsu[i]=i,G[i].clear();
vector <Edge> E(m);
for(auto&e:E) cin>>e.u>>e.v>>e.w;
sort(E.begin(),E.end(),[&](auto x,auto y) { return x.w<y.w; });
ll s=0,z=inf;
for(int i=0;i<n-1;++i) {
int u=E[i].u,v=E[i].v,w=E[i].w;
dsu[find(u)]=find(v),s+=w,G[u].push_back({u,v,w}),G[v].push_back({v,u,w});
}
int ct=0;
for(int i=1;i<=n;++i) if(dsu[i]==i) ++ct;
if(ct>1) return cout<<s<<"\n",void();
dfs1(1,0);
if(n>=3) for(int t:{n-3,n-2}) f[E[t].u==fa[E[t].v]?E[t].v:E[t].u]^=n-1-t;
dfs2(1,1);
for(int k=0;k<17;++k) for(int i=1;i+(2<<k)-1<=n;++i) st[k+1][i]=max(st[k][i],st[k][i+(1<<k)]);
int o[2]={0,0};
for(int i=n-1;i<m;++i) {
int w=qry(E[i].u,E[i].v);
if(w) z=min(z,s-w+E[i].w);
if((f[E[i].u]^f[E[i].v])==3&&!o[1]) o[o[0]?1:0]=E[i].w;
}
if(o[1]) z=min(z,s-E[n-2].w-E[n-3].w+o[0]+o[1]);
cout<<(z==inf?-1:z)<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
N. [CF2174E2] Game of Scientists (4)
手玩一下有效信息,发现 \(x\ge b\) 时我们能得到 \(x\bmod (b-1)\) 的信息。
那么尝试手玩决策树,计算两次询问能确定的最大值,从而知道首次询问最优为 \(36\),然后计算下一次询问什么才能在得到 \(-1\) 时一次确定,可以算出是 \(3239\)。
那么最后一次选一个 \(\le 35\times 3238\) 且与这两个数互质的数即可,取 \(10^5+3\) 就能区分 \(x\le 10^{10}\)。
时间复杂度 \(\mathcal O(1)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int f(int x,int y) {
if(x<y) return -1;
int s=0;
for(;x;s+=x%y,x/=y);
return s;
}
int ask(int x) { cout<<"? "<<x<<endl; int o; cin>>o; return o; }
int sol(vector<int>&a) {
if(a.size()==1) return a[0];
for(int x=a[1];x>1;--x) {
map <int,int> o;
for(int i:a) o[f(i,x)]=i;
if(o.size()==a.size()) return o[ask(x)];
}
return 0;
}
const ll X=35,Y=3238,Z=1e5+3,P=X*Y*Z;
ll solve() {
int a=ask(X+1);
if(a<0) {
int b=ask(4);
vector <int> o;
for(int i=1;i<=X;++i) if(f(i,4)==b) o.push_back(i);
return sol(o);
}
int b=ask(Y+1);
if(b<0) {
vector <int> o;
for(int i=1;i<=Y;++i) if(f(i,X+1)==a) o.push_back(i);
return sol(o);
}
int c=ask(Z+1);
if(c<0) {
for(int i=1;i<=Z;++i) if(f(i,X+1)==a&&f(i,Y+1)==b) return i;
}
return (9*Y*Z*a+1123*X*Z*b+39605*X*Y*c)%P;
}
signed main() {
int _,k,c;
cin>>_>>k>>c;
while(_--) c=solve(),cout<<"! "<<c<<endl,cin>>k;
return 0;
}
O. [CF2172G] Gene Editor (5)
问题过于复杂,较有可能的做法是建立自动机状物。
一个较好的建立方式是从 \(\epsilon\) 出发,如果存在后继未知的点则加入对应的后继,如果同时存在 \(u,u+d\),其中 \(d\in\{\texttt{AA},\texttt{BBB},s\}\),那么合并这两个点。
可以证明得到的自动机大小 \(k\le 100\)。
时间复杂度 \(\mathcla O(k^3\log n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005,MOD=998244353;
int n,m,K,id[MAXN],dsu[MAXN],f[MAXN][2],g[105][2];
int ins() { return ++n,dsu[n]=n,f[n][0]=f[n][1]=0,n; }
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void merge(int x,int y) {
x=find(x),y=find(y);
if(x==y) return ;
dsu[y]=x;
for(int o:{0,1}) if(f[y][o]) {
if(!f[x][o]) f[x][o]=f[y][o];
else merge(f[x][o],f[y][o]);
}
}
struct Mat {
int f[105][105];
Mat() { memset(f,0,sizeof(f)); }
friend Mat operator*(const Mat&u,const Mat&v) {
Mat w;
for(int i=1;i<=m;++i) for(int k=1;k<=m;++k) if(u.f[i][k]) for(int j=1;j<=m;++j) {
if(v.f[k][j]) w.f[i][j]=(w.f[i][j]+1ll*u.f[i][k]*v.f[k][j])%MOD;
}
return w;
}
};
void solve() {
string s,t;
cin>>s>>t>>K,n=m=0,dsu[0]=0,ins();
for(int o=1;o;) {
o=0;
for(int i=n;i;--i) if(dsu[i]==i) {
if(!f[i][0]) f[i][0]=ins(),f[n][0]=i,o=1;
if(!f[i][1]) f[i][1]=ins(),f[n][1]=ins(),f[n][1]=i,o=1;
}
for(int i=1;i<=n;++i) if(dsu[i]==i) {
int x=i;
for(auto c:s) x=find(f[x][c-'A']);
if(x&&i!=x) merge(i,x),o=1;
}
}
for(int i=1;i<=n;++i) if(dsu[i]==i) id[i]=++m;
for(int i=1;i<=n;++i) if(dsu[i]==i) for(int o:{0,1}) g[id[i]][o]=id[find(f[i][o])];
Mat I,Z; Z.f[1][1]=1;
for(int i=1;i<=m;++i) for(int o:{0,1}) ++I.f[i][g[i][o]];
for(;K;K>>=1,I=I*I) if(K&1) Z=Z*I;
int o=1;
for(auto c:t) o=g[o][c-'A'];
cout<<Z.f[1][o]<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
*P. [CF2165F] Arctic Acquisition (7)
记所求子序列为 \(a_{p_2},a_{p_1},a_{p_4},a_{p_3},a_{p_5}\) 对每个左端点计算最小右端点。
那么 \(p_2\) 确定时只要求 \(a_{p_1}<a_{p_2}\) 且最小的 \(p_1\) 即可。
然后考虑 \([p_4,p_3,p_5]\),枚举 \(p_3\),显然只要考虑 \(a_{p_5}=\max a[p_3,p_5]\) 的 \(p_5\),以及满足条件的最大的 \(p_4\)。
观察发现对于两个可能的 \(p_5\) 位置 \(x<y\),设其分别匹配 \(u,v\),如果 \(u=v\) 则 \(y\) 不优,否则 \(v<u<p_3<x_y\),\(a_y>a_v>a_x>a_u>a_{p_3}\),直接选取 \([p_4,p_3,p_5]=[v,x,y]\),由于 \(a_x>a_{p_3}\) 所以比 \([v,p_3,y]\) 更优。
那么此时 \((p_3,p_5)\) 对只有 \(\mathcal O(n)\) 个,算出对应的 \(p_4\) 变成二位偏序问题。
时间复杂度 \(\mathcal O(n\log n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5,N=1<<20;
int n,a[MAXN],st[MAXN],b[MAXN],tr[N<<1],f[MAXN];
vector <array<int,2>> X[MAXN],Y[MAXN];
void upd(int x,int y) { for(tr[x+=N]=y;x>1;x>>=1) tr[x>>1]=max(tr[x],tr[x^1]); }
int qry(int l,int r) {
int s=0;
for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) {
if(~l&1) s=max(s,tr[l^1]);
if(r&1) s=max(s,tr[r^1]);
}
return s;
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i],X[i].clear(),Y[i].clear();
for(int i=n,tp=0;i>=1;b[i]=st[tp],st[++tp]=i--) while(tp&&a[st[tp]]<a[i]) --tp;
for(int i=n,tp=0;i>=1;st[++tp]=i--) {
while(tp&&a[st[tp]]>a[i]) --tp;
if(tp) X[st[tp]].push_back({a[i],i});
}
for(int i=1;i<=n;++i) upd(i,0);
for(int i=1;i<=n;++i) {
if(b[i]) {
int o=qry(a[i],a[b[i]]);
if(o) Y[o].push_back({a[i],b[i]});
}
upd(a[i],i);
}
for(int i=1;i<=n;++i) upd(i,0),f[i]=n+1;
for(int i=n;i>=1;--i) {
for(auto o:X[i]) f[o[1]]=n+1-qry(o[0]+1,n);
for(auto o:Y[i]) upd(o[0],n+1-o[1]);
}
long long s=0;
for(int i=n-1;i;--i) f[i]=min(f[i],f[i+1]),s+=n+1-f[i];
cout<<s<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
Q. [CF2161H] Cycle Sort (5.5)
考虑变成 01 序列,每个 \(a,b\) 至多交换一次,观察一个 \(a_i=1\),其会按 \((i+kn)\bmod m\) 的顺序遍历找到首个 \(b_j=0\) 并交换。
假设 \(n\le m\) 且 \(\gcd (n,m)=1\),那么相当于所有 \((i+kn)\bmod m\) 构成环,有些位置上有左右括号,并且每个左括号能匹配的右括号距离在一定范围内,然后会删除右括号或插入左括号,动态维护在匹配内括号。
显然每次修改只会从匹配中修改一个括号,首先我们只要考虑所有跨过该位置的括号,手玩发现只有最后一对匹配的左括号可能被弹出,或匹配一个新的右括号,直接用线段树维护所有匹配内的左括号,求前缀最小值最后一次出现位置即可。
时间复杂度 \(\mathcal O((n+m)\log m)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef array<int,2> pii;
const int MAXN=2e5+5;
int n,m,h,a[MAXN],b[MAXN]; ll K;
bool in[MAXN];
struct Segt {
pii mn[1<<21|5]; int ad[1<<21|5];
void adt(int p,int k) { mn[p][0]+=k,ad[p]+=k; }
void psd(int p) { if(ad[p]) adt(p<<1,ad[p]),adt(p<<1|1,ad[p]),ad[p]=0; }
void psu(int p) { mn[p]=min(mn[p<<1],mn[p<<1|1]); }
void init(int l=0,int r=3*h,int p=1) {
mn[p]={-r,-r},ad[p]=0;
if(l==r) return ;
int mid=(l+r)>>1;
init(l,mid,p<<1),init(mid+1,r,p<<1|1);
psu(p);
}
void add(int ul,int ur,int z,int l=0,int r=3*h,int p=1) {
if(ul<=l&&r<=ur) return adt(p,z);
int mid=(l+r)>>1; psd(p);
if(ul<=mid) add(ul,ur,z,l,mid,p<<1);
if(mid<ur) add(ul,ur,z,mid+1,r,p<<1|1);
psu(p);
}
pii qmn(int ul,int ur,int l=0,int r=3*h,int p=1) {
if(ul<=l&&r<=ur) return mn[p];
int mid=(l+r)>>1; psd(p);
if(ur<=mid) return qmn(ul,ur,l,mid,p<<1);
if(mid<ul) return qmn(ul,ur,mid+1,r,p<<1|1);
return min(qmn(ul,ur,l,mid,p<<1),qmn(ul,ur,mid+1,r,p<<1|1));
}
int fd(int ul,int ur,int z,int l=0,int r=3*h,int p=1) {
if(mn[p][0]>z) return -1;
if(l==r) return l;
int mid=(l+r)>>1,o=-1; psd(p);
if(ul<=mid) o=fd(ul,ur,z,l,mid,p<<1);
return ~o?o:fd(ul,ur,z,mid+1,r,p<<1|1);
}
} T;
void add(int x,int z) { for(int o:{0,1,2}) T.add(x+o*h+1,3*h,z); }
void solve() {
cin>>n>>m>>K; int _=n>m;
if(_) {
swap(n,m);
for(int i=0;i<m;++i) cin>>b[i],b[i]=n+m+1-b[i];
for(int i=0;i<n;++i) cin>>a[i],a[i]=n+m+1-a[i];
} else {
for(int i=0;i<n;++i) cin>>a[i];
for(int i=0;i<m;++i) cin>>b[i];
}
int g=__gcd(n,m); h=m/g;
for(int o=0;o<g;++o) {
vector <array<int,3>> w;
for(int i=0,p=o;i<h;++i,p=(p+n)%m) {
w.push_back({b[p],i,0}),in[i]=0;
if(p<n&&p<K) w.push_back({a[p],i,1});
}
T.init();
sort(w.begin(),w.end(),greater<>());
for(auto Z:w) {
int x=Z[1]; add(x,1),in[x]|=Z[2]; auto e=T.qmn(x,x+h);
int y=(-e[1])%h,z=T.fd(x+1+h,x+1+2*h,e[0]),l=(o+1ll*y*n)%m,r=(z-1)%h;
if(in[y]&&(z<0||l+1ll*(r+h-y)%h*n>=K)) a[l]=Z[0],add(y,-1),in[y]=0;
else b[(o+1ll*r*n)%m]=Z[0];
}
}
if(_) {
for(int i=0;i<m;++i) cout<<n+m+1-b[i]<<" \n"[i==m-1];
for(int i=0;i<n;++i) cout<<n+m+1-a[i]<<" \n"[i==n-1];
} else {
for(int i=0;i<n;++i) cout<<a[i]<<" \n"[i==n-1];
for(int i=0;i<m;++i) cout<<b[i]<<" \n"[i==m-1];
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
R. [CF2165E] Rainbow Branch (4.5)
将颜色组的直径中点定为根,距离不超过 \(2k+1\) 就等价于选一些边使得不存在 \(k+1\) 条边两两为祖先后代。
观察发现选择某条边时其子树必定选满,否则可以往下调整,那么我们只会选到叶子距离 \(<k\) 的边,此时不需要根的限制,直接拓扑排序删叶子即可。
如果距离不超过 \(2k+2\),那么可以加上剩余点中的度数最大值。
时间复杂度 \(\mathcal O(n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5;
int n,e[MAXN],c[MAXN],d[MAXN],f[MAXN],h[MAXN];
void solve() {
cin>>n;
for(int i=0;i<=n;++i) e[i]=c[i]=d[i]=h[i]=0,f[i]=n;
for(int i=1,u,v;i<n;++i) cin>>u>>v,++d[u],++d[v],e[u]^=v,e[v]^=u;
queue <int> Q;
for(int i=1;i<=n;++i) {
++c[d[i]];
if(d[i]==1) Q.push(i);
}
int w=n; while(!c[w]) --w;
f[w]=2,f[1]=1;
for(int s=1,u,v;s<n;++s) {
u=Q.front(),v=e[u],Q.pop();
--c[d[v]],--d[v],++c[d[v]],e[v]^=u;
if(d[v]==1) Q.push(v),h[v]=h[u]+1;
while(!c[w]) --w;
f[s+1]=min(f[s+1],h[u]*2+3);
f[s+w]=min(f[s+w],h[u]*2+4);
}
for(int i=n-1;i;--i) f[i]=min(f[i],f[i+1]);
for(int i=1;i<n;++i) cout<<f[i]<<" \n"[i==n-1];
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
S. [CF2159E] Super-Short-Polynomial-San (4)
分块处理出所有 \((ax^2+bx+c)^{kB},(ax^2+bx+c)^r\) 即可。
考虑如何求 \(F=(ax^2+bx+c)^n\),求导发现 \((ax^2+bx+c)F'=(2ax+b)F\),从而可以线性递推。
时间复杂度 \(\mathcal O(n\sqrt q)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=6e5+5,B=1000,MOD=1e9+7;
int iv[MAXN],f[305][MAXN],g[B+5][2*B+5];
int ksm(int a,int b=MOD-2) { int s=1; for(;b;a=1ll*a*a%MOD,b>>=1) if(b&1) s=1ll*s*a%MOD; return s; }
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int a,b,c,ic,q;
cin>>a>>b>>c>>q,f[0][0]=g[0][0]=iv[1]=1,ic=ksm(c);
for(int i=2;i<MAXN;++i) iv[i]=1ll*iv[MOD%i]*(MOD-MOD/i)%MOD;
for(int t=1;t<=300;++t) {
int m=t*B,*h=f[t]; h[0]=ksm(c,m),h[1]=1ll*ksm(c,m-1)*b%MOD*m%MOD;
for(int k=2;k<=2*m;++k) h[k]=((1ll*b*(m-k+1)%MOD*h[k-1]+1ll*a*(2*m-k+2)%MOD*h[k-2])%MOD+MOD)*ic%MOD*iv[k]%MOD;
for(int k=1;k<=2*m;++k) h[k]=(h[k]+h[k-1])%MOD;
}
for(int t=0;t<B;++t) for(int i=0,*l=g[t],*r=g[t+1];i<=2*t;++i) {
r[i]=(r[i]+1ll*c*l[i])%MOD,r[i+1]=(r[i+1]+1ll*b*l[i])%MOD,r[i+2]=(r[i+2]+1ll*a*l[i])%MOD;
}
for(int z=0,n,m;q--;) {
cin>>n>>m,n^=z,m^=z,z=0;
int *h=f[n/B],r=n%B;
for(int i=max(0,m-2*r);i<=m;++i) z=(z+1ll*h[min(i,2*(n-r))]*g[r][m-i])%MOD;
cout<<z<<"\n";
}
return 0;
}
T. [CF2178I] Numbers or Fireworks (4)
首先建图,发现得到的一定是二分图,根据 \(k\bmod 8\) 分类,不为 \(0\) 时只要按 \(x\bmod 2,y\bmod 2\) 分类,否则按奇偶性分别把 \(x,y\) 除以二递归即可。
对于二分图考虑枚举较小一侧的选点,较大一侧权值容易算,难算的是较小一侧未选点的贡献。
根据组合意义相当于让这些点匹配较大一侧的某个被选点,那么直接状压 dp 记录有哪些点已经匹配过即可。
时间复杂度 \(\mathcal O(3^{n/2}n^2)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353;
void add(int&x,const int&y) { x=x+y>=MOD?x+y-MOD:x+y; }
int n,m,q,K,vs,a[32],b[32],X[32],Y[32],e[32],p[32],f[1<<15],g[1<<15];
void dfs(int u,int c) {
(c?a[m++]:b[q++])=u,vs|=1<<u;
for(int v=0;v<n;++v) if((e[u]>>v&1)&&!(vs>>v&1)) dfs(v,c^1);
}
void solve() {
cin>>n>>K,m=q=vs=0;
for(int i=0;i<n;++i) cin>>X[i]>>Y[i];
for(int i=0;i<n;++i) {
e[i]=0;
for(int j=0;j<n;++j) e[i]|=((X[i]-X[j])*(X[i]-X[j])+(Y[i]-Y[j])*(Y[i]-Y[j])==K)<<j;
}
for(int i=0;i<n;++i) if(!(vs>>i&1)) dfs(i,0);
if(m>q) swap(a,b),swap(m,q);
int z=MOD-1;
for(int o=0;o<(1<<m);++o) {
int R=0,k=0;
for(int i=0;i<m;++i) {
if(o>>i&1) R|=1<<a[i];
else p[i]=k++;
}
for(int s=0;s<(1<<k);++s) f[s]=1;
for(int i=0;i<q;++i) {
memcpy(g,f,4<<k);
int d=1;
for(int j=0;j<m;++j) if((e[b[i]]>>a[j])&1) {
if(o>>j&1) ++d;
else for(int s=0;s<(1<<k);++s) if(s>>p[j]&1) add(f[s^(1<<p[j])],f[s]);
}
for(int s=0;s<(1<<k);++s) f[s]=(f[s]+1ll*g[s]*d)%MOD;
}
add(z,f[0]);
}
cout<<z<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
U. [CF2161F] SubMST (3)
枚举 \(k\) 然后算出每个点集在距离 \(\le k\) 的点对连边后的连通块个数。
这个图是经典问题,直接考虑 bfs 序,每个连通块对应一个不向 bfs 序更小点连边的点。
直接算每个点的贡献即可。
时间复杂度 \(\mathcal O(n^2)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5005,MOD=1e9+7;
vector <int> G[MAXN];
int n,b[MAXN],d[MAXN],f[MAXN],pw[MAXN],z;
void dfs(int u,int fz) { for(int v:G[u]) if(v^fz) d[v]=d[u]+1,dfs(v,u); }
void solve() {
cin>>n,d[1]=1,z=0;
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
for(int l=1,r=b[1]=1;l<=n;++l) for(int v:G[b[l]]) if(!d[v]) d[v]=d[b[l]]+1,b[++r]=v;
for(int i=1;i<=n;++i) {
d[b[i]]=0,dfs(b[i],0),memset(f,0,sizeof(f));
for(int j=1;j<i;++j) ++f[d[b[j]]];
for(int k=1,c=0;k<=n;c+=f[k++]) z=(z+pw[n-c-1])%MOD;
}
cout<<(z+1ll*n*(MOD-pw[n]+1))%MOD<<"\n";
for(int i=1;i<=n;++i) G[i].clear(),d[i]=0;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int i=pw[0]=1;i<MAXN;++i) pw[i]=pw[i-1]*2%MOD;
int _; cin>>_;
while(_--) solve();
return 0;
}
*V. [CF2159F] Grand Finale: Snakes (8)
每个 \(f(k)\) 都是一个独立的滑动窗口问题。
观察一些性质,考虑 \(f(k,t+1)>f(k,t)\),则此时最大值必定是 \(t+1\) 时刻加入的,从而 \(\le t\) 的点已经无效,从而 \(f(k,t+1)\sim f(k,t+k)\) 递增。
那么把序列按长度 \(k\) 分段能得到若干单谷序列,从而把所有序列分成 \(n\log n\) 个单调区间,用堆每次弹出最小值并加入后继即可。
考虑如何求出序列的谷,注意到我们询问 \(f\) 的答案能确定来自哪个时刻,考虑二分维护谷的范围 \([x,y]\),可以发现 \(t\) 在谷右侧当且仅当 \(f(k,t)\) 最大值在 \([x,y]\) 范围内,可以 \(n\log^2n\) 解决。
时间复杂度 \(\mathcal O(n\log^2n+m\log n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXV=2.5e5+5;
int qry(int x,int y) { cout<<"? "<<x<<" "<<y<<endl; int z; cin>>z; return z; }
int n,m,a[MAXV],f[MAXV*2];
void solve() {
cin>>n>>m;
for(int i=0,x;i<n;++i) for(int j=0;j<n;++j) cin>>x,a[x]=i?i+j:-j;
priority_queue <array<int,4>,vector<array<int,4>>,greater<>> Q;
for(int k=1;k<=n;++k) {
for(int L=1,R,x,y;L<=2*n-1;L=R+1) {
R=min(2*n-1,L+k-1),x=L,y=R;
while(y-x>1) {
int p=(x+y)>>1,c=qry(k,p);
if(a[c]+1>=x) y=p;
else x=p;
}
Q.push({qry(k,x),x,L,k});
if(L<R) Q.push({qry(k,y),y,R,k});
}
}
for(int i=1;i<=m;++i) {
auto o=Q.top(); Q.pop(),f[i]=o[0];
if(o[1]!=o[2]) o[1]+=o[1]>o[2]?-1:1,Q.push({qry(o[3],o[1]),o[1],o[2],o[3]});
}
cout<<"! "; for(int i=1;i<=m;++i) cout<<f[i]<<" "; cout<<endl;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
W. [CF2183G] Snake Instructions (4)
操作次数越多信息越少,因此考虑询问最短的三个串:\(\texttt{L,R,LR}\)。
显然 \(\texttt{L}\to\texttt{LR}\) 时向右的一步不会有新的重合元素,否则这两个元素初始位置相同。
那么我们就能得到所有向左走后未删除的点速度。
观察发现此时不能得到的点就是左侧有一个速度为 \(0\) 的点,则无法当前点速度为 \(1\) 或 \(2\)。
根据 \(\texttt R\) 的信息来确定即可,简单模拟匹配过程,如果一个点和两个速度为 \(0\) 的点相邻则无解。
时间复杂度 \(\mathcal O(n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
vector<int> ask(string s) {
cout<<"? "<<s<<endl;
int t; cin>>t; vector<int>a(t);
for(int&x:a) cin>>x; return a;
}
int n,a[MAXN],b[MAXN],c[MAXN*4];
void solve() {
cin>>n,fill(c,c+4*n+1,0);
for(int i=1;i<=n;++i) cin>>a[i],b[i]=-1,c[a[i]]=i;
vector <int> f=ask("L"),g=ask("LR"),h=ask("R");
for(int i=0;i<(int)f.size();++i) b[c[g[i]]]=g[i]-f[i];
for(int i=1,x=0;i<=n;++i) {
if(~b[i]) x=i;
else if(a[x]-b[x]==a[i]-2) b[i]=2;
}
for(int i=n,j=h.size()-1;i&&~j;--j) {
for(b[i]=h[j]-a[i],--i;i&&~b[i]&&h[j]<=a[i]+b[i];--i);
if(i&&b[i]<0) {
if(h[j]-a[i]==1) return cout<<"! -1"<<endl,void();
if(!j||h[j-1]<a[i]) b[i]=2,--i;
}
}
cout<<"! "; for(int i=1;i<=n;++i) cout<<b[i]<<" "; cout<<endl;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
*X. [CF2187G] Many Cartesian Trees (7.5)
首先我们可以把值全部循环移位,因为我们最终都会询问值域上的所有循环移位。
那么我们可以任意指定一个位置 \(x\) 为 \(q_x=n\)。
然后分析一下 \(n-1\) 的位置 \(y\) 并递归,首先 \((y,x)\in E\),而且 \(y\) 如果不是最左边或最右边的儿子,不妨假设 \(x\) 有两个儿子满足 \(x<y<z\),那么任何时候 \(z\) 的父亲都不会跨过 \(y\),从而矛盾。
所以 \(y\) 是 \(x\) 最左边或最右边的儿子 \(l\) 或 \(r\),考虑 \(p_{n-1}\) 上的排列,如果 \(q_l=n-1\),则 \(r\) 的父亲应该落在 \([l,x)\) 上,因为此时 \(p_{r}=\max p(x,n],p_x=1\),而 \(l\) 任何时候的父亲都 \(\le x\),因为 \(p_{n-1}\) 以外的排列都有 \(p_x>p_l\),而 \(p_{n-1}\) 上 \(l\) 为根。
所以我们只要看哪个儿子的父亲不跨过 \(x\) 即可,然后递归 \(n-1,n-2,\dots\),做法完全一致。
时间复杂度 \(\mathcal O(n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3005;
int n,a[MAXN],sl[MAXN],sr[MAXN],fl[MAXN],fr[MAXN];
void solve() {
cin>>n,a[1]=n;
for(int i=1;i<=n;++i) sl[i]=fl[i]=n+1,sr[i]=fr[i]=0;
for(int i=1;i<=n;++i) {
string s; cin>>s;
for(int j=1;j<=n;++j) if(s[j-1]=='1') fl[i]=min(fl[i],j),sl[j]=min(sl[j],i),fr[i]=j,sr[j]=i;
}
for(int x=n-1,u=1;x;a[u]=x--) {
if(sl[u]>u) u=sr[u];
else if(sr[u]<u) u=sl[u];
else if(fr[sl[u]]>u) u=sr[u];
else u=sl[u];
}
for(int i=1;i<=n;++i) cout<<a[i]<<" \n"[i==n];
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
Y. [CF2161G] Bitwise And Equals (5)
首先把每个数调整成 \(X\) 的超集 \(b_i\),记 \(S=\mathrm{AND}_{i=1}^n b_i\) 设 \(S-X\) 的最高位为 \(h\),那么我们要找一个数在 \(h\) 位置上进位,然后重新调整成 \(X\) 的倍数,并且新的进位不能产生其他 \(S\) 里的公共位。
容易写出一个 \(\mathcal O(nq)\) 的暴力做法,考虑如何优化,首先假设 \(a_i\to b_i\) 的最大进位位置为 \(k\),即最大的 \(k\) 满足 \(X_k=1,\exists a_{i,k}=0\),那么 \(\le k\) 的位 \(d\) 如果不属于 \(X\) 则一定存在某个 \(a_{i,d}=0\),都不用考虑。
首先我们要维护 \(S-X\) 的最高位,可以发现这就是 \(S_0=\mathrm{AND}_{i=1}^n a_i\) 去掉低 \(k\) 位后的最高位,在处理进位后是否有新的公共位时,我们要先处理出 \(b_i\) 中被 \(n-1\) 个数包含的位集合。
由于 \(h\ge k\),所以低于 \(k\) 的位置不用管,那么我们只要处理出 \(a_i\) 中被 \(n-1\) 个数包含的位集合 \(T_0\),删掉低 \(k\) 位再删掉 \(X\) 中的位即可。
然后我们要计算 \(\sum b_i-a_i\) 的调整代价,设 \(X=2^{p_1}+2^{p_2}+\cdots+2^{p_k}\),其中 \(p_i>p_{i+1}\),那么算一个数的代价只要找到 \(p_k\) 满足 \(a_{i,p_{1}}\sim a_{i,p_{k-1}}=1,a_{i,p_k}=0\),然后求 \(a_i\) 的低 \(p_k\) 位值之和即可。
而这样的 \(a_i\) 就是 \(2^{p_1}+\cdots+2^{p_{k-1}}\) 的超集减去 \(2^{p_1}+\cdots+2^{p_k}\) 的超集,因此只要维护 \(f_{s,k}\) 表示 \(a\) 中 \(s\) 的所有超集低 \(k\) 位值之和。
然后考虑找一个数调整在 \(h\) 上进位,首先我们难维护进位调整后产生新的公共位的情况,但是这种数的 $ a_i$ 必定和 \(T_0\) 的补有交,所以这样的 \(i\) 只有 \(\mathcal O(\log V)\) 个,预处理后暴力计算即可。
那么我们只要对剩余元素计算调整的代价,首先进位的代价是找到 \(\ge h\) 的首个 \(a_{i,p}=0\),那么 \(a_{i}[h,p)\) 这些位会变成 \(0\),\(X\) 在这些位的部分需要重新加上。
其次考虑低位贡献,从 \(t=i-1\sim0\) 遍历,如果 \(X_t=0,a_{i,t}=1\) 那么代价会减去 \(2^t\),如果 \(X_t=1,a_{i,t}=0\) 则低位贡献清零,因为 \(a_i\to b_i\) 的过程中已经使得低位满足 \(X_t=a_{i,t}\)。
首先 \(h\) 必定是 \(S_0\) 的位,直接枚举并预处理贡献,用类似 FWT 的方法对每个 \(t\) 进行逐位变换即可,但是我们计算代价的时候要考虑 \(X[h,p)\) 这些位的值,由于 \(h\) 是 \(X\operatorname{AND} S_0\) 的最高位,所以 \(X\) 中 \(>h\) 的位都和 \(S_0\) 相等,那么只要用 \(S_0[h,p)\) 算代价即可 。
时间复杂度 \(\mathcal O(V\log^2V+q\log V)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,D=20,U=(1<<D)-1;
int n,q,a[MAXN],hd[D]; ll f[U+5][D+1]; array<int,2>F[U+5];
vector <int> O;
int d(int x) { return x?32-__builtin_clz(x):0; }
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>q;
int ox=U,oy=U;
for(int i=1;i<=n;++i) {
cin>>a[i],oy=(oy&a[i])|ox,ox&=a[i],++f[a[i]][0];
for(int k=1;k<=D;++k) f[a[i]][k]+=a[i]&((1<<k)-1);
}
for(int i=1;i<=n;++i) if(oy&~a[i]) O.push_back(i);
for(int k=1;k<=U;k<<=1) for(int s=0;s<=U;++s) if(s&k) for(int i=0;i<=D;++i) f[s^k][i]+=f[s][i];
memset(F,0x3f,sizeof(F));
for(int k=0,c=0;k<D;++k) if(ox>>k&1) {
auto*g=F+c; int u=(1<<k)-1; hd[k]=c,c+=u+1;
for(int i=1;i<=n;++i) if(!(~a[i]&oy)) {
int p=__builtin_ctz(~(a[i]>>k))+k;
g[a[i]&u][0]=min(g[a[i]&u][0],ox&((1<<p)-(1<<k)));
}
if(!k) continue;
for(int i=1<<(k-1);i;i>>=1) for(int s=0;s<=u;++s) if(s&i) {
auto x=g[s^i],y=g[s];
g[s^i][0]=min(x[0],y[0]-i),g[s][0]=y[0];
g[s^i][1]=min(x[1],y[1]),g[s][1]=min({x[1],y[1],x[0]});
}
}
for(int X;q--;) {
cin>>X; ll s=0;
for(int k=D-1,w=0;~k;--k) if(X>>k&1) s+=(f[w][0]-f[w|1<<k][0])*(X-w)-(k?f[w][k]-f[w|1<<k][k]:0),w|=1<<k;
int up=d(X&~ox),x=(ox>>up<<up)&~X,y=(oy>>up<<up)&~X;
if(!x) { cout<<s<<"\n"; continue; }
int h=__lg(x),id=hd[h]+(X&((1<<h)-1)),z=min(F[id][0],F[id][1]);
for(int i:O) {
int t=d(X&~a[i]),b=(a[i]>>t<<t)|(X&((1<<t)-1)),no=y&~b;
int o=(((b>>h)+1)<<h)|(X&((1<<h)-1));
t=d(X&~o),o=(o>>t<<t)|(X&((1<<t)-1));
while(o&no) {
t=d(no&o)-1,o=(((o>>t)+1)<<t)|(X&((1<<t)-1));
t=d(X&~o),o=(o>>t<<t)|(X&((1<<t)-1));
}
z=min(z,o-b);
}
cout<<z+s<<"\n";
}
return 0;
}
Z. [CF2157G] Isaac's Queries (3.5)
首先 \(f(l,r)\) 就是 \(s_{l-1}\oplus s_r\) 的最高位,考虑怎么用已知信息得到位置信息,如果 \(s_x\oplus s_z,s_y\oplus s_z\) 最高位已知,那么考虑 \(s_x\oplus s_y\) 最高位,发现只要前两个数不相等,那么答案就是其中的较大值。
由于 \(f(l,r)\) 每变小 \(1\) 概率减半,所以期望询问数不会很多,直接从长到短依次询问未确定区间即可。
时间复杂度 \(\mathcal O(n^3)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,a[105][105];
void upd(int x,int y) {
if(x>y) swap(x,y);
for(int i=0;i<=n;++i) if(i!=x&&i!=y) {
int &l=a[min(i,x)][max(i,x)],&r=a[min(i,y)][max(i,y)];
if(l!=-2&&r==-2&&l!=a[x][y]) r=max(l,a[x][y]),upd(i,y);
if(l==-2&&r!=-2&&r!=a[x][y]) l=max(r,a[x][y]),upd(i,x);
}
}
void solve() {
cin>>n;
for(int i=0;i<=n;++i) for(int j=i+1;j<=n;++j) a[i][j]=-2;
for(int d=n;d;--d) for(int i=0,j=d;j<=n;++i,++j) if(a[i][j]==-2) {
cout<<"? "<<i+1<<" "<<j<<endl,cin>>a[i][j],upd(i,j);
}
cout<<"!"<<endl;
for(int i=0;i<=n;++i,cout<<endl) for(int j=i+1;j<=n;++j) cout<<a[i][j]<<" ";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
*AA. [CF2190F] Xor Product (7.5)
手玩 \(S(x,x,k)\),假设 \(x\) 与 \(x+k-1\) 没有公共前缀,那么存在 \(d\) 使得 \(x<2^d\le x+k-1\)。
分成区间 \([x,2^d),[2^d,x+k)\),异或 \(2^{d}-1,2^d\) 后可以区间变成 \([0,2^d-x),[0,x+k-2^d)\)。
考虑 \(|\{i\oplus j\mid i\le a,j\le b\}|\),首先注意到该集合,因为每次 \(-1\) 都可以构造,只要求出最大值 \(g(a,b)\),而这显然是 \(a\operatorname{OR}b\) 再并上 \(a\operatorname{AND}b\) 的最高位 \(v\) 对应的 \(2^v-1\)。
那么选的两个数在同侧贡献就是 \(\max(g(2^d-x-1,2^d-x-1),g(x+k-1-2^d,x+k-1-2^d))+1\),异侧则是 \(g(2^d-x-1,x+k-1-2^d)\)。
同理对于 \(S(x,y,k)\),假设 \((x,x+k-1),(y,y+k-1)\) 都没有公共前缀,且 \(2^{d_x}\in (x,x+k),2^{d_y}\in (y,y+k)\),且 \(d_x< d_y\)。
记 \(a=2^{d_x}-x-1,b=x+k-1-2^{d_x},c=2^{d_y}-y-1,d=y+k-1-2^{d_y}\)。
那么分类讨论每类区间的贡献:\(g(a,c),g(b,c)\) 分别异或上 \(2^{d_y}-2^{d_x},2^{d_y}-2^{d_x}-1\)。
注意到 \(c+d=k-2\) 且 \(k\le 2^{d_x+1}\),所以 \(c,d<2^{d_x+1}\),如果 \(c<2^{d_x}\),则 \(g(a,c),g(b,c)<2^{d_x}\),对应的区间就是 \(2^{d_y}-2^{d_x}\) 的前后缀,必定不交,贡献就是 \(g(a,c)+g(b,c)+2\)。
如果 \(c\ge 2^{d_x}\),那么 \(g(a,c),g(b,c)\) 分别能覆盖 \([2^{d_y}-2^{d_x},2^{d_y}-2^{d_x+1}),(2^{d_y}-2^{d_x},2^{d_y}]\),贡献就是 \(2^{d_x+1}\)。
同理 \(g(a,d),g(b,d)\) 也有类似的性质,答案就是 \(\min(g(a,c)+g(b,c)+2,2^{d_x+1})+\min(g(a,d)+g(b,d)+2,2^{d_x+1})\).
此时枚举 \(c,d,d_y\) 即可,显然 \(d_y\) 越大 \(2^{d_x+1}\) 一项越大,且 \(d_y=d_x\) 显然不优,所以只要枚举 \(c\in[0,k-2],d=k-2-c\) 并钦定 \(d_y>d_x\) 即可。
用数位 dp 从高到低确定,维护一下 \(c+d\) 的进位即可。
时间复杂度 \(\mathcal O(\log V)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll f[2][16],g[2][16],pw[64];
void solve() {
ll X,K;
cin>>X>>K;
if(K==1) return cout<<"1\n",void();
ll h=__lg(X^(X+K-1));
X&=pw[h+1]-1;
ll A=pw[h]-X-1,B=X+K-1-pw[h];
cerr<<pw[h]<<" "<<A<<" "<<B<<"\n";
memset(f,-0x3f,sizeof(f)),f[0][0]=0;
for(int i=60;~i;--i) {
memset(g,-0x3f,sizeof(g));
int a=A>>i&1,b=B>>i&1;
for(int o:{0,1}) for(int c:{0,1}) for(int d:{0,1}){
int _=((K-2)>>i&1)^c^d;
if((_+c+d)/2!=o) continue;
for(int s=0;s<16;++s) if(f[o][s]>=0) {
ll z=f[o][s]; int t=s;
if(i>=h) {
if(!(t&3)&&c) z+=pw[i+1]-2,t|=3;
if(!(t&12)&&d) z+=pw[i+1]-2,t|=12;
}
if(!(t&1)&&a+c) z+=pw[i];
if(!(t&2)&&b+c) z+=pw[i];
if(!(t&4)&&a+d) z+=pw[i];
if(!(t&8)&&b+d) z+=pw[i];
if(!(t&1)&&a&&c) z+=pw[i]-1,t|=1;
if(!(t&2)&&b&&c) z+=pw[i]-1,t|=2;
if(!(t&4)&&a&&d) z+=pw[i]-1,t|=4;
if(!(t&8)&&b&&d) z+=pw[i]-1,t|=8;
g[_][t]=max(g[_][t],z);
}
}
memcpy(f,g,sizeof(g));
}
cout<<4+*max_element(f[0],f[0]+16)<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int i=0;i<64;++i) pw[i]=1ll<<i;
int _; cin>>_;
while(_--) solve();
return 0;
}
*AB. [CF2158F2] Distinct GCDs (7)
考虑手玩一下结构,取出 \(a\) 的值域,发现有多条 \(\gcd\) 相同的边时很难解决,因此我们假设这些数 \(\gcd\) 两两不同,这样只要选出欧拉路径即可。
那么我们要构造 \(100\) 个满足该条件的数,记 \(v_i=2^i3^{k-i}\) 可以构造 \(k+1\) 个数,且 \(\gcd(v_i,v_j)\) 能双射 \((i,j)\)。
那么定义 \(w_{i,j}=2^i3^{k-i}5^j7^{k-j}\),这样 \(\gcd(w_{x_1,y_1},w_{x_2,y_2})\) 能知道 \(\{x_1,x_2\},\{y_1,y_2\}\),但无法分辨 \((w_{x_1,y_1},w_{x_2,y_2}),(w_{x_1,y_2},w_{x_2,y_1})\)。
额外设定一维用来分辨,取 \(w_{i,j}=2^{i+j}3^{i}5^{9-i}7^j11^{9-j}\) 即可,其实也可以直接随机构造。
时间复杂度 \(\mathcal O(n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,q,f[105],d,e[5005];
ll w[12][20],a[105];
bool g[105][105];
void dfs(int x) { for(int i=1;i<=m;++i) if(g[i][x]) g[i][x]=g[x][i]=0,dfs(i),e[++d]=i; }
void solve() {
cin>>n,d=0,m=lower_bound(f+1,f+q+1,n)-f;
for(int i=1;i<=m;++i) for(int j=1;j<=m;++j) g[i][j]=1;
if(m%2==0) for(int i=3;i<m;i+=2) g[i-1][i]=g[i][i-1]=0;
dfs(1),e[++d]=1;
for(int i=1;i<=n;++i) cout<<a[e[i]]<<" \n"[i==n];
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int o:{2,3,5,7,11}) for(int i=w[o][0]=1;i<(o>2?10:20);++i) w[o][i]=w[o][i-1]*o;
for(int i=0;i<10;++i) for(int j=0;j<10;++j) a[++q]=w[2][i+j]*w[3][i]*w[5][9-i]*w[7][j]*w[11][9-j];
for(int i=1;i<=q;++i) f[i]=i*(i+1)/2+1-(i&1?0:i/2-1);
int _; cin>>_;
while(_--) solve();
return 0;
}

浙公网安备 33010602011771号