2026 NOI 做题记录(十四)
\(\text{By DaiRuiChen007}\)
A. [P11648] 2236 A.D. (4.5)
对 \(k\) 个位折半,我们要求动态维护集合 \(S\),每次单点修改或查询 \(w_x=\sum_{y\in S}a_{x\lor y}\)。
用 DSU 维护,每个点只有 \(k\) 个祖先会改变路径权值,加上 \(\log n\) 个轻边祖先,\(S\) 的修改和询问次数是 \(\mathcal O(n\log n)\) 的。
那么直接折半在插入删除时分别枚举 \(x\lor y\) 的前后 \(\frac k2\) 位即可。
时间复杂度 \(\mathcal O(n2^{k/2}(k+\log n))\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5,MOD=998244353;
int n,m,k,L,R,a[MAXN];
ll h[16],wl[128],wr[256],z[MAXN],f[256][128];
vector <int> G[MAXN];
int hson[MAXN],siz[MAXN],fa[MAXN],b[MAXN],dfn[MAXN],efn[MAXN],rk[MAXN],dcnt;
void dfs1(int u,int fz) {
if(fz) G[u].erase(find(G[u].begin(),G[u].end(),fz));
fa[u]=fz,siz[u]=1,dfn[u]=++dcnt,rk[dcnt]=u;
for(int v:G[u]) {
dfs1(v,u),siz[u]+=siz[v];
if(siz[v]>siz[hson[u]]) hson[u]=v;
}
efn[u]=dcnt;
}
int c[MAXN],st[MAXN],tp;
void dfs2(int u) {
z[u]=h[a[u]];
for(int v:G[u]) if(v^hson[u]) {
dfs2(v),z[u]=(z[u]+z[v])%MOD,b[v]=1<<a[v];
for(int i=dfn[v];i<=efn[v];++i) {
b[rk[i]]=(i==dfn[v]?0:b[fa[rk[i]]])|1<<a[rk[i]];
memset(f[b[rk[i]]>>k],0,sizeof(f[0]));
}
}
auto add=[&](int s,int o) {
int r=s>>k,l=s&L;
for(int x=0;x<=L;++x) f[r][x]+=o*wl[x|l];
};
auto qry=[&](int s) {
int r=s>>k,l=s&L; ll o=0;
for(int x=0;x<R;++x) o=(o+(f[x][l]%MOD+MOD)*wr[x|r])%MOD;
return o;
};
if(hson[u]) {
dfs2(hson[u]),z[u]=(z[u]+z[hson[u]])%MOD;
function<void(int,int)> dfs3=[&](int x,int s) {
if(s>>a[u]&1) return ;
if(!c[s]++) st[++tp]=s;
for(int y:G[x]) dfs3(y,s|1<<a[y]);
};
dfs3(hson[u],1<<a[hson[u]]);
for(;tp;c[st[tp--]]=0) add(st[tp],-c[st[tp]]),add(st[tp]|1<<a[u],c[st[tp]]);
}
z[u]=(z[u]+qry(1<<a[u]))%MOD,add(1<<a[u],1);
for(int v:G[u]) if(v^hson[u]) {
for(int i=dfn[v];i<=efn[v];++i) {
b[rk[i]]|=1<<a[u];
if(!c[b[rk[i]]]++) st[++tp]=b[rk[i]];
}
for(int i=1;i<=tp;++i) z[u]=(z[u]+qry(st[i])*c[st[i]])%MOD;
for(;tp;c[st[tp--]]=0) add(st[tp],c[st[tp]]);
}
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m,k=m/2,L=(1<<k)-1,R=1<<(m-k);
for(int i=0;i<m;++i) cin>>h[i];
for(int s=0;s<=L;++s) {
wl[s]=1;
for(int i=0;i<k;++i) if(s>>i&1) wl[s]=wl[s]*h[i]%MOD;
}
for(int s=0;s<R;++s) {
wr[s]=1;
for(int i=0;i<m-k;++i) if(s>>i&1) wr[s]=wr[s]*h[i+k]%MOD;
}
for(int i=1;i<=n;++i) cin>>a[i],--a[i];
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
dfs1(1,0),dfs2(1);
for(int i=1;i<=n;++i) cout<<z[i]<<" \n"[i==n];
return 0;
}
*B. [P14652] 这里是,终末停滞委员会 (8)
首先这是一个分配问题,容易想到最小割建模,但正常的最小割只有 \(u\in S,u\in T\) 两种情况。
那么需要拆点,按 \(x_u,y_u\in S\) 的状态来区分三种状态,设 \(x_u\in S,y_u\in T\) 为 A,\(x_u\in T,y_u\in S\) 为 B,\(x_u,y_u\in S\) 为 C。
那么对于二类边,双向连接 \((x_u,y_v),(x_v,y_u)\),权值为 \(1\),分讨每种情况可以证明一定合法。
对于一类边,提前加上 \(2\) 的答案,双向连接 \((x_u,y_u),(x_v,y_v),(x_u,y_v),(x_v,y_u)\),权值为 \(1\)。
可以证明 \(x_u,y_u\in T\) 的情况不如把所有连通的这样的点都调整成 \(x_u,y_u\in S\)。
那么我们只要求字典序前 \(k\) 小的最小割。
根据最小割的经典结论,我们对残量网络缩点,只要 \(S\) 是缩点后的一个闭合子图即可。
那么每次我们把 \(u\) 放入 \(S\) 就把残量网络上后继放入 \(S\),否则把前驱放入 \(T\)。
快速判断每个 \(u\) 能否被放入需要预处理出每对 \(x_u,y_u\) 在残量网络上是否可达,分块 bitset 维护。
时间复杂度 \(\mathcal O(m\sqrt m+\frac{nm}\omega+(n+m)k)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5,MAXE=1e7+5,inf=1e9,B=4096;
struct Edge { int v,f,e; } G[MAXE];
int S,T,ec=1,hd[MAXN],cur[MAXN],d[MAXN];
void link(int u,int v,int w) {
G[++ec]={v,w,hd[u]},hd[u]=ec;
G[++ec]={u,0,hd[v]},hd[v]=ec;
}
bool bfs() {
memset(d,-1,sizeof(d)),memcpy(cur,hd,sizeof(hd));
queue <int> Q; d[S]=0,Q.push(S);
while(Q.size()) {
int u=Q.front(); Q.pop();
for(int i=hd[u];i;i=G[i].e) if(G[i].f&&d[G[i].v]<0) d[G[i].v]=d[u]+1,Q.push(G[i].v);
}
return ~d[T];
}
int dfs(int u,int f) {
if(u==T) return f;
int r=f;
for(int &i=cur[u];i;i=G[i].e) if(d[G[i].v]==d[u]+1&&G[i].f) {
int g=dfs(G[i].v,min(G[i].f,r));
if(!g) d[G[i].v]=-1;
r-=g,G[i].f-=g,G[i^1].f+=g;
if(!r) return f;
}
return f-r;
}
int n,m1,m2,q,dfn[MAXN],low[MAXN],dcnt,st[MAXN],tp,bl[MAXN],scnt,vis[MAXN];
bool ins[MAXN],ok[MAXN];
vector <int> E[MAXN];
void tarjan(int u) {
dfn[u]=low[u]=++dcnt,ins[st[++tp]=u]=1;
for(int i=hd[u];i;i=G[i].e) if(G[i].f) {
int v=G[i].v;
if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]) for(++scnt;ins[u];ins[st[tp--]]=0) bl[st[tp]]=scnt;
}
bitset <B> f[MAXN];
void upd(int u,int c) {
if(~vis[u]) return ;vis[u]=c,st[++tp]=u;
for(int i=hd[u];i;i=G[i].e) if(bl[u]==bl[G[i].v]||G[i^c].f) upd(G[i].v,c);
}
string w;
void solve(int i) {
if(!q) exit(0);
if(i>n) return cout<<w<<"\n",--q,void();
int h=tp;
auto rb=[&]() { for(;tp>h;vis[st[tp--]]=-1); };
if(!ok[i]&&vis[i]!=0&&vis[i+n]!=1) w[i-1]='A',upd(i,1),upd(i+n,0),solve(i+1),rb();
if(!ok[i+n]&&vis[i]!=1&&vis[i+n]!=0) w[i-1]='B',upd(i,0),upd(i+n,1),solve(i+1),rb();
if(vis[i]!=1&&vis[i+n]!=1) w[i-1]='C',upd(i,0),upd(i+n,0),solve(i+1),rb();
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m1>>m2>>q,S=2*n+1,T=S+1;
for(int i=1;i<=n;++i) {
char o; cin>>o;
if(o=='A') link(i,T,inf),link(S,i+n,inf);
if(o=='B') link(S,i,inf),link(i+n,T,inf);
if(o=='C') link(S,i,inf),link(S,i+n,inf);
}
for(int i=1,u,v;i<=m1+m2;++i) {
cin>>u>>v,link(u+n,v,1),link(u,v+n,1),link(v+n,u,1),link(v,u+n,1);
if(i<=m1) link(u,v,1),link(u+n,v+n,1),link(v,u,1),link(v+n,u+n,1);
}
int z=2*m1;
while(bfs()) z-=dfs(S,inf);
cout<<z<<"\n";
for(int i=1;i<=T;++i) if(!dfn[i]) tarjan(i);
for(int u=1;u<=T;++u) for(int i=hd[u];i;i=G[i].e) if(G[i].f&&bl[u]!=bl[G[i].v]) E[bl[u]].push_back(bl[G[i].v]);
for(int l=1,r;l<=2*n;l=r+1) {
r=min(2*n,l+B-1);
for(int i=1;i<=scnt;++i) f[i].reset();
for(int i=l;i<=r;++i) f[bl[i]][i-l]=1;
for(int u=1;u<=scnt;++u) for(int v:E[u]) f[u]|=f[v];
for(int i=l;i<=r;++i) ok[i]=f[bl[i>n?i-n:i+n]][i-l];
}
memset(vis,-1,sizeof(vis)),upd(S,0),upd(T,1);
w=string(n,'A'),solve(1);
return 0;
}
*C. [P13758] 夏终 (7)
从链的情况开始,刻画最终的 MST,肯定是选择原图上若干条边构成一些区间,然后把每个区间的最小值向 \(\min b_i\) 连接。
那么每个区间的权值可以看成当前的 \(x=b_0+\min b_i\) 加上区间最小的 \(b\),经典做法就是维护答案关于 \(x\) 的凸壳,合并左右区间的时候矩乘闵和。
我们现在要支持单点修改 \(b\) 动态维护整体凸壳。
考虑分块,对每个块建立线段树,然后维护查询的 \(x\) 对应矩阵每个位置上的取值,然后把每个块上的数值权值乘起来。
设块长为 \(B\) 修改复杂度 \(\mathcal O(B)\),查询复杂度 \(\mathcal O(\frac nB\log B)\),瓶颈在二分凸壳,可以离线对询问基数排序后双指针做到线性。
对于一般的 MST,注意到我们只要保证生成过程中任意时刻点集连通性不变即可,因此可以按重构树 dfn 序把树变为等价的序列问题。
时间复杂度 \(\mathcal O(q\sqrt {n\log n})\),精细实现可以做到 \(\mathcal O(q\sqrt n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef basic_string<ll> vec;
const int MAXN=2e5+5,B=512;
const ll inf=1e15;
void mul(const vec&u,const vec&v,ll *w) {
int i=0,j=0,l=u.size()-1,r=v.size()-1;
for(w[0]=u[0]+v[0];i<l||j<r;) {
if(j==r||(i<l&&u[i+1]-u[i]<v[j+1]-v[j])) w[i+j+1]=w[i+j]+u[i+1]-u[i],++i;
else w[i+j+1]=w[i+j]+v[j+1]-v[j],++j;
}
}
void sol(const vec&xl,const vec&xr,const vec&yl,const vec&yr,vec&z) {
static ll x[B+5],y[B+5];
mul(xl,xr,x),mul(yl,yr,y),z.resize(xl.size()+xr.size()-1);
for(int i=0;i<(int)z.size();++i) z[i]=min(x[i],y[i]);
}
ll qry(const vec&x,ll z) {
int p=0,m=x.size()-1;
if(m) for(int k=1<<__lg(m);k;k>>=1) if(p+k<=m&&x[p+k-1]-x[p+k]>z) p+=k;
return x[p]+p*z;
}
vec f[B<<1][2][2];
int _,n,m,q,dsu[MAXN],ed[MAXN],nx[MAXN],id[MAXN],ps[MAXN];
ll a0[MAXN],a[MAXN],b[MAXN],dp[MAXN][2],mn[MAXN],vl[MAXN],tw[MAXN];
int find(int x) { return x^dsu[x]?dsu[x]=find(dsu[x]):x; }
void ini(int p,int x) {
f[p][0][0]={b[x],a[x]};
f[p][0][1]={a[x]+b[x],inf};
f[p][1][0]={inf,0};
f[p][1][1]={b[x],inf};
}
void psu(int p) { for(int l:{0,1}) for(int r:{0,1}) sol(f[p<<1][l][0],f[p<<1|1][0][r],f[p<<1][l][1],f[p<<1|1][1][r],f[p][l][r]); }
void init(int l,int r,int p) {
if(l==r) return ini(p,l);
int mid=(l+r)>>1;
init(l,mid,p<<1),init(mid+1,r,p<<1|1);
psu(p);
}
void upd(int x,int l,int r,int p) {
if(l==r) return ini(p,l);
int mid=(l+r)>>1;
x<=mid?upd(x,l,mid,p<<1):upd(x,mid+1,r,p<<1|1);
psu(p);
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>_>>n>>m>>q>>a[0],a0[0]=a[0];
multiset <ll> A;
for(int i=1;i<=n;++i) cin>>a0[i],A.insert(a0[i]);
vector <array<ll,3>> E(m);
for(auto&e:E) cin>>e[1]>>e[2]>>e[0];
for(int i=1;i<=n;++i) dsu[i]=ed[i]=i,E.push_back({inf,1,i});
sort(E.begin(),E.end());
for(auto e:E) {
int x=find(e[1]),y=find(e[2]);
if(x!=y) nx[ed[x]]=y,tw[ed[x]]=e[0],dsu[y]=x,ed[x]=ed[y];
}
for(int u=find(1),i=1;i<=n;++i,u=nx[u]) id[u]=i,b[i]=tw[u];
for(int i=1;i<=n;++i) a[id[i]]=a0[i];
memcpy(a0,a,sizeof(a));
for(int i=1;i<=q;++i) {
cin>>ps[i]>>vl[i],ps[i]=id[ps[i]];
if(ps[i]) A.erase(A.find(a[ps[i]]));
a[ps[i]]=vl[i];
if(ps[i]) A.insert(a[ps[i]]);
mn[i]=*A.begin()+a[0],dp[i][0]=-*A.begin(),dp[i][1]=inf;
}
for(int l=1,r;l<=n;l=r+1) {
memcpy(a,a0,sizeof(a));
r=min(n,l+B-1),init(l,r,1);
for(int i=1;i<=q;++i) {
if(l<=ps[i]&&ps[i]<=r) a[ps[i]]=vl[i],upd(ps[i],l,r,1);
ll z0=min(dp[i][0]+qry(f[1][0][0],mn[i]),dp[i][1]+qry(f[1][1][0],mn[i]));
ll z1=min(dp[i][0]+qry(f[1][0][1],mn[i]),dp[i][1]+qry(f[1][1][1],mn[i]));
dp[i][0]=z0,dp[i][1]=z1;
}
}
for(int i=1;i<=q;++i) cout<<dp[i][1]<<"\n";
return 0;
}
*D. [P11066] 电梯 (9)
我们的思路大致是在同一个时间按某种顺序把所有电梯都激活,这样才能较自由地安排每个电梯。
一种显然的方法就是从右往左激活每个电梯,且目标点都在当前电梯右侧。
那么这种操作能直接处理所有 \(p_i>i\) 的点,如果 \(p_i\le i\) 则移动到 \(i+1\) 即可。
此时过了一秒后我们会让位置 \(1\) 空出,且 \(p_i\le i\) 的点都已经停止,类似地从左往右把 \(p_i\le i\) 移动到 \(p_i\) 上即可。
问题是 \(p_i=i+1\) 的点在一秒后会停下并阻止对 \(p_i\le i\) 的点的还原过程。
我们要让这样的点不存在,一个观察是 \(p_i=i+1\) 的点随意与任何一个 \(p_j\) 交换后 \(p_i,p_j\) 一定会合法。
那么我们把序列两两匹配,如果一对匹配中有这样的点就交换它们,用刚才的构造实现该过程。
我们的要求是目标操作不能交换相邻元素,且操作次数和移动距离有关,所以隔 \(2\) 匹配是最优的。
由于 \(m\) 一定合法,那么可以通过删掉该元素把 \(m\) 调整为偶数,再按 \(m\bmod 4\) 分类,四个元素间隔匹配即可即可,\(m=2\) 的情况特判,\(m\bmod 4=2\) 的剩余情况把最后六个元素间隔匹配。
精细讨论可以证明操作次数不超过 \(5n\)。
时间复杂度 \(\mathcal O(n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef basic_string<int> vec;
const int MAXN=5e4+5;
int n,m,a[MAXN],b[MAXN];
vec z;
void sol(int *p) {
int l=0,r=0;
for(int i=m;i;--i) {
if(p[i]>i) z+=p[i];
else z+=i+1;
r=max(r,p[i]-i),l=max(l,i-p[i]);
}
z+=0;
for(int i=1;i<=m;++i) if(p[i]<=i) z+=p[i];
z+=vec(max(r-1,l+1),0);
}
void solve() {
z.clear();
for(int i=1;i<=m;++i) cin>>a[i],b[i]=i;
if(m==2) return cout<<(a[1]==1?"0\n\n":"7\n3 3 0 1 0 2 0\n"),void();
if(m==3) {
if(a[1]==2||a[2]==3) z={4,3,3,0,1,0,2,3,0},swap(a[1],a[2]);
} else {
for(int i=1,k=m-(m&1);i<=k;i+=4) {
if(i+5==k) {
for(int j:{0,1,2}) if(a[i+j]==i+j+1||a[i+j+3]==i+j+4) swap(a[i+j],a[i+j+3]),swap(b[i+j],b[i+j+3]);
break;
}
for(int j:{0,1}) if(a[i+j]==i+j+1||a[i+j+2]==i+j+3) swap(a[i+j],a[i+j+2]),swap(b[i+j],b[i+j+2]);
}
sol(b);
}
sol(a),cout<<z.size()<<"\n";
for(int x:z) cout<<x<<" "; cout<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
for(int q,o;_--;) {
cin>>q>>n>>m>>o;
while(q--) solve();
}
return 0;
}
E. [P12152] 催眠术 (4.5)
首先序列是好的当且仅当可以分成 \(k\) 段,每段值域 \([1,m]\)。
那么 dp 的时候维护 \(pre_i\),以及当前分出了几个 \([1,m]\) 的段,以及当前段的情况。
\(f_{i,j,c,p,q}\) 表示 \(pre_i=j\),当前已分出 \(c\) 个段,当前段已出现的元素有 \(p\) 个和段中匹配的某个 \(a_x\) 相同,\(q\) 个没有。
转移的时候考虑加入一个 \(a\) 中元素,则要从 \(p\) 或 \(q\) 或外界转移,是否从 \(p\) 中转移可以通过 \((j,p)\) 判断,否则我们向 \(q\) 中加入元素的时候不确定具体颜色,直到此时被取出时再确定。
然后用 \(g_{i,j,c,p,q}\) 表示当前过程中填入若干个 \(\not\in a\) 的元素的过程,注意这里的 \(p\) 已经包含了 \(a_{j+1}\) 的颜色,但我们要时刻注意填出的元素总数并不能超过 \(m\),这和被钦定的 \(a_{j+1}\) 是否为新元素有关,所以新开一维记录。
还要一些其他的状态处理每个段的末尾,以及 \(pre_i=m\) 的部分。
注意到 \(jp,cq\le mk\le n\),所以状态数 \(\mathcal O(n^3)\)。
时间复杂度 \(\mathcal O(n^3)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=1e9+7;
int n,m,k,a[405],w[405][405],fac[405],rk[405][405],ans=0,sf[405];
int f[2][805][805],g[2][805][805][2],h[2][805][405],e[2][805]; //g: a[j+1] new?
int id(int x,int y) { return x*(k+1)+y; }
inline void add(int &x,const ll &y) { x=(x+y)%MOD; }
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m>>k;
if(m*k>n) return cout<<"0\n",0;
for(int i=1;i<=m;++i) cin>>a[i];
for(int i=fac[0]=1;i<=n;++i) fac[i]=1ll*fac[i-1]*i%MOD;
for(int i=1;i<=n;++i) for(int j=0;j<=m;++j) cin>>w[i][j];
for(int i=1;i<=m;++i) for(int j=i,c=0;j>=1;--j) if(!rk[i][a[j]]) rk[i][a[j]]=++c;
sf[n+1]=1;
for(int i=n;i;--i) sf[i]=1ll*sf[i+1]*w[i][m]%MOD*k%MOD;
f[0][id(0,0)][id(0,0)]=1;
for(int i=1,o=0,z;i<=n;++i,o^=1) {
for(int j=0;j<=m&&j<=i;++j) for(int c=0;c<=i/k&&c<=j;++c) for(int p=0;p<=k;++p) {
for(int q=0;p+q<=k;++q) {
z=0,swap(z,f[o][id(j,p)][id(c,q)]);
if(z) { //a[j+1] type
if(rk[j][a[j+1]]&&rk[j][a[j+1]]<=p) { //in P
add(g[o][id(j,p)][id(c,q)][1],z);
add(h[o][id(j,p+q)][c],1ll*z*fac[k-p]);
} else {
if(q) add(g[o][id(j,p+1)][id(c,q-1)][1],1ll*z*q); //in Q
add(g[o][id(j,p+1)][id(c,q)][0],z); //new
add(h[o][id(j,p+q)][c],1ll*z*fac[k-p-1]%MOD*q); //must in Q
}
}
for(int x:{0,1}) {
z=0,swap(z,g[o][id(j,p)][id(c,q)][x]);
if(!z) continue;
if(p+q+x<k) add(g[o^1][id(j,p)][id(c,q+1)][x],1ll*z*w[i][j]);
add(g[o^1][id(j,p)][id(c,q)][x],1ll*z*w[i][j]%MOD*(p+q-1));
if(j<m-1) {
if(p+q<k) add(f[o^1][id(j+1,p)][id(c,q)],1ll*z*w[i][j+1]);
else add(f[o^1][id(j+1,0)][id(c+1,0)],1ll*z*w[i][j+1]%MOD*fac[q]);
} else {
if(p+q<k) add(e[o^1][id(c,p+q)],1ll*z*w[i][m]%MOD*fac[k-p]);
else add(e[o^1][id(c+1,0)],1ll*z*w[i][m]%MOD*fac[k-p]%MOD*fac[c<m-1?k:0]);
}
}
}
z=0,swap(z,h[o][id(j,p)][c]);
if(z) {
add(h[o^1][id(j,p)][c],1ll*z*w[i][j]%MOD*(p-1));
if(p<k-1) add(h[o^1][id(j,p+1)][c],1ll*z*w[i][j]);
else add(f[o^1][id(j,0)][id(c+1,0)],1ll*z*w[i][j]);
}
}
for(int c=0;c<=i/k&&c<=m;++c) for(int p=0;p<=k;++p) {
z=0,swap(z,e[o][id(c,p)]);
if(!z) continue;
if(c==m) add(ans,1ll*z*sf[i]);
else {
add(e[o^1][id(c,p)],1ll*z*w[i][m]%MOD*p);
if(p<k-1) add(e[o^1][id(c,p+1)],1ll*z*w[i][m]);
else add(e[o^1][id(c+1,0)],1ll*z*w[i][m]%MOD*fac[c<m-1?k:0]);
}
}
}
add(ans,e[n&1][id(m,0)]);
cout<<ans<<"\n";
return 0;
}
F. [P14256] 平局 (3)
判定序列时考虑差分数组,操作只有 \([0]\to [],[+1,-1]\to [],[+1,+1]\to [-1],[-1,-1]\to [+1]\) 四种,其中前两种增加答案。
变成类似括号匹配的问题,简单维护,首先前缀中如果剩 \(+1\),则可以和 \(-1\) 匹配,否则可以把 \(-1\) 保留等待下一个 \(-1\) 生成 \(+1\)。
那么栈的结构一定是栈底 \(\le 1\) 个 \(-1\),然后若干个 \(+1\),最终栈里剩下每三个 \(+1\) 可以增加一次答案。
dp 记录栈结构以及上一个元素即可。
时间复杂度 \(\mathcal O(n^2)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3005,MOD=1e9+7;
struct info { int x,y; } f[2][MAXN][2][3];
int n,a[MAXN],ans=0;
inline void add(int&x,const int&y) { x=x+y>=MOD?x+y-MOD:x+y; }
inline void add(info&u,const info&v,int o) { add(u.x,v.x),add(u.y,v.y); if(o) add(u.y,v.x); }
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i) { char o; cin>>o,a[i]=o-'0'; }
for(int c:{0,1,2}) if(a[1]>>c&1) f[1][0][0][c]={1,0};
for(int i=2,o=1;i<=n+1;++i,o^=1) {
for(int j=0;j<i;++j) for(int c:{0,1}) for(int x:{0,1,2}) {
info &z=f[o][j][c][x];
if(!z.x&&!z.y) continue;
if(i>n) { ans=(ans+z.y+1ll*(j/3)*z.x)%MOD; continue; }
for(int y:{0,1,2}) if(a[i]>>y&1) {
if(x==y) add(f[o^1][j][c][y],z,1);
else if(y==(x+2)%3) {
add(f[o^1][j+1][c][y],z,0);
} else {
if(j) add(f[o^1][j-1][c][y],z,1);
else if(c) add(f[o^1][1][0][y],z,0);
else add(f[o^1][0][1][y],z,0);
}
}
z.x=z.y=0;
}
}
cout<<ans<<"\n";
return 0;
}
*G. [P13560] 交换换 (8)
首先我们的要求就是 \(\forall u\in S\),对 \((i,i+u)\) 连边后,所有 \(i,p_i\) 连通。
考虑 \(k=1\) 的情况,我们二分 \(\min S\),并令 \(S=[x,n-1]\),此时图会把 \([1,n-x]\cup[x+1,n]\) 全部连通,容易快速算出最大合法的 \(w=\min S\)。
注意到 \(w\ge \frac n2\),而我们能通过一些转化使得点数变为 \(n-w\),则这样递归的复杂度为 \(\mathcal O(n)\)。
注意到对于一个已经合法的集合,会有 \(2^{n-1}\) 个合法序列,因此我们搜索到的序列会很少。
朴素可以通过大部分测试点,一个优化方法是对 \(n\le 60\) 的情况爆搜连通块等价类并 dp,更优的做法直接对搜索过程中的并查集记忆化即可。
代码:
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int MAXN=1e6+5;
typedef basic_string<int> vec;
mt19937_64 rnd(time(0));
ull hw[MAXN],hc[MAXN];
struct DSU {
int n; vec fa;
DSU(int N){ n=N,fa=vec(n,0),iota(fa.begin(),fa.end(),0); }
int find(int x) { return fa[x]^x?fa[x]=find(fa[x]):fa[x]; }
void link(int x,int y) { if(~x&&~y) fa[find(x)]=find(y); }
void add(int c) { for(int i=c;i<n;++i) link(i-c,i); }
int operator ()(int x) { return find(x); }
ull qhs(ull z) {
vec a(n,-1);
for(int i=0,c=0;i<n;++i) if(fa[i]==i) a[i]=c++;
for(int i=0;i<n;++i) z=z*hw[i]+hc[a[find(i)]];
return z;
}
void shk(int m) {
if(n==m) return ;
vec a(n,-1),b(m,-1);
for(int i=0;i<m;++i) {
if(a[find(i)]<0) a[find(i)]=i;
b[i]=a[find(i)];
}
n=m,fa.swap(b);
}
};
int st[MAXN],tp; ll k;
unordered_map<ull,ll> f;
ll solve(int n,DSU&F,DSU&Q) {
ull hs=Q.qhs(F.qhs(n));
if(f.count(hs)&&f[hs]<k) return k-=f[hs],f[hs];
bool ok=1;
for(int i=0;i<n;++i) ok&=F(i)==F(Q(i));
if(ok) {
if(n<64&&k>(1ll<<(n-1))) return k-=1ll<<(n-1),f[hs]=1ll<<(n-1);
for(int i=63,o=st[tp];~i;--i) if((k-1)>>i&1) st[++tp]=o+n-1-i;
for(int i=1;i<=tp;++i) cout<<st[i]<<" \n"[i==tp];
exit(0);
}
int h=n;
vec c(n,0);
for(int i=0;i<n;++i) c[F(i)]=max({c[F(i)],n-i-1,i});
for(int i=0;i<n;++i) if(F(Q(i))!=F(i)) h=min({h,c[F(i)],c[F(Q(i))]});
ll z=0;
for(++tp;h;h--) {
st[tp]=st[tp-1]+h; int m=max(h,n-h); DSU nF(m),nQ(m);
auto p=[&](int x) { return x>=m?x-h:x; };
for(int i=0;i<n;++i) nF.link(p(i),p(F(i))),nQ.link(p(i),p(Q(i)));
nF.add(h),nQ.add(h),nF.shk(n-h),nQ.shk(n-h),z+=solve(n-h,nF,nQ);
}
st[tp--]=0;
return f[hs]=z;
}
signed main() {
for(int i=0;i<MAXN;++i) hw[i]=rnd()|1,hc[i]=rnd();
int n; cin>>n>>k;
DSU F(n),Q(n); bool o=1;
for(int i=0,x;i<n;++i) cin>>x,Q.link(i,--x),o&=x==i;
k+=o,solve(n,F,Q);
cout<<"-1\n";
return 0;
}
H. [P12050] 三叶虫树 (4)
首先枚举端点集合后确定答案,这是经典问题,每条边取到两侧点数较小值,考虑将端点的重心定根,则最优解是所有点对跨越重心匹配。
因此答案可以写成每个点到重心的距离之和。
那么降序维护该过程,线段树每次取出距离重心最近的点并删除,每次删点只会对重心产生 \(\mathcal O(1)\) 的变化,即只有 \(\mathcal O(1)\) 个点所属子树被修改,简单处理向上或向下的过程可以做到单 \(\log\)。
时间复杂度 \(\mathcal O(n\log n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
namespace IO {
int ow,olim=(1<<21)-100;
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<21];
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read() {
int x=0; char c=gc();
while(!isdigit(c)) c=gc();
while(isdigit(c)) x=x*10+(c^48),c=gc();
return x;
}
void flush() {
fwrite(obuf,ow,1,stdout),ow=0;
}
void write(ll x) {
if(!x) obuf[ow++]='0';
else {
int t=ow;
for(;x;x/=10) obuf[ow++]=(x%10)^48;
reverse(obuf+t,obuf+ow);
}
if(ow>=olim) flush();
}
void putc(char c) {
obuf[ow++]=c;
if(ow>=olim) flush();
}
void putstr(const string &s) {
for(auto &c:s) obuf[ow++]=c;
if(ow>=olim) flush();
}
#undef gc
}
const int MAXN=1e6+5;
const ll inf=1e18;
struct Edge { int v,w,e; } G[MAXN*2];
int n,rt,siz[MAXN],cur[MAXN],dfn[MAXN],efn[MAXN],dcnt,hd[MAXN],ec;
int rk[MAXN],hson[MAXN],top[MAXN],fa[MAXN],di[MAXN];
ll d[MAXN],ans=0,f[MAXN];
struct BIT {
int tr[MAXN];
void add(int x,int v) { for(;x<=n;x+=x&-x) tr[x]+=v; }
int qy(int x) { int s=0; for(;x;x&=x-1) s+=tr[x]; return s; }
int fd(int s) { int x=0; for(int k=1<<19;k;k>>=1) if(x+k<=n&&s>tr[x+k]) s-=tr[x+=k]; return rk[x+1]; }
} B;
struct Segt {
ll ad[1<<21|5];
array<ll,2> tr[1<<21|5];
void adt(int p,ll k) { ad[p]+=k,tr[p][0]+=k; }
void psu(int p) { tr[p]=min(tr[p<<1],tr[p<<1|1]),tr[p][0]+=ad[p]; }
void init(int l=1,int r=n,int p=1) {
if(l==r) return tr[p]={d[rk[l]],rk[l]},void();
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,ll k,int l=1,int r=n,int p=1) {
if(ul<=l&&r<=ur) return adt(p,k);
int mid=(l+r)>>1;
if(ul<=mid) add(ul,ur,k,l,mid,p<<1);
if(mid<ur) add(ul,ur,k,mid+1,r,p<<1|1);
psu(p);
}
} T;
void dfs1(int u,int fz) {
siz[u]=1;
for(int i=hd[u];i;i=G[i].e) if(G[i].v^fz) dfs1(G[i].v,u),siz[u]+=siz[G[i].v],cur[u]=max(cur[u],siz[G[i].v]);
cur[u]=max(cur[u],n-siz[u]);
if(!rt||cur[u]<cur[rt]) rt=u;
}
void dfs2(int u,int fz) {
siz[u]=1,dfn[u]=++dcnt,rk[dcnt]=u,fa[u]=fz,di[u]=di[fz]+1;
for(int i=hd[u];i;i=G[i].e) if(G[i].v^fz) {
d[G[i].v]=d[u]+G[i].w,dfs2(G[i].v,u),siz[u]+=siz[G[i].v];
if(siz[G[i].v]>siz[hson[u]]) hson[u]=G[i].v;
}
efn[u]=dcnt;
}
void dfs3(int u,int tp) {
top[u]=tp; if(hson[u]) dfs3(hson[u],tp);
for(int i=hd[u];i;i=G[i].e) if(G[i].v!=fa[u]&&G[i].v!=hson[u]) dfs3(G[i].v,G[i].v);
}
int LCA(int x,int y) {
for(;top[x]^top[y];x=fa[top[x]]) if(di[top[x]]<di[top[y]]) swap(x,y);
return di[x]<di[y]?x:y;
}
int in(int u,int v) {
for(;di[fa[top[v]]]>di[u];v=fa[top[v]]);
return fa[top[v]]==u?top[v]:hson[u];
}
signed main() {
n=IO::read();
for(int i=1,u,v,w;i<n;++i) {
u=IO::read(),v=IO::read(),w=IO::read();
G[++ec]={v,w,hd[u]},hd[u]=ec,G[++ec]={u,w,hd[v]},hd[v]=ec;
}
dfs1(1,0),dfs2(rt,0),dfs3(rt,rt);
for(int i=1;i<=n;++i) B.add(dfn[i],1),ans+=d[i];
T.init(),f[n/2]=ans;
for(int i=n-1;i>=2;--i) {
int x=T.tr[1][1];
ans-=T.tr[1][0],B.add(dfn[x],-1),T.add(dfn[x],dfn[x],inf);
int l=B.qy(dfn[rt]-1),r=B.qy(efn[rt]);
if((r-l)*2<i) {
int u=LCA(B.fd(l),rt),v=LCA(B.fd(r+1),rt);
if(d[u]<d[v]) swap(u,v);
ans-=d[rt]-d[u],T.add(1,n,d[u]-d[rt]),T.add(dfn[rt],efn[rt],2*(d[rt]-d[u]));
rt=u,l=B.qy(dfn[rt]-1),r=B.qy(efn[rt]);
}
if(l<r) {
int y=in(rt,B.fd((l+r+1)/2)),L=B.qy(dfn[y]-1),R=B.qy(efn[y]);
if((R-L)*2>i) {
int u=LCA(B.fd(L+1),B.fd(R));
ans-=d[u]-d[rt],T.add(1,n,d[u]-d[rt]),T.add(dfn[u],efn[u],2*(d[rt]-d[u])),rt=u;
}
}
f[i/2]=ans;
}
for(int i=1;i<=n/2;++i) IO::write(f[i]),IO::putc(" \n"[i==n/2]);
IO::flush();
return 0;
}
I. [P13497] 墓碑密码 (2)
设答案为关于出现次数为奇数的元素个数 \(y\) 的形式幂级数,所求即为 \(\sum [x^{t_i}]\prod (1+yx^{s_i})\),考虑 FWT 后的数组 \(f\),\(f_x=(1+y)^{|S|-w_x}(1-y)^{w_x}\),其中 \(w_x=\sum |x\cap s_i|\bmod 2\)。
最终要求 \(\sum_{t_i}\sum_xf_x(-1)^{|t_i\cap x|}\),所以压位计算每个 \(x\) 对应的 \(\sum |x\cap s_i|\bmod 2,\sum |x\cap t_i|\bmod 2\) 即可求出 \(F(y)\),求答案是平凡的。
时间复杂度 \(\mathcal O(V+qn)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
#define pc __builtin_popcountll
#define u128 unsigned __int128
using namespace std;
const int h=14,MOD=1e9+7;
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,m,q,a[135],b[135];
ll f[135],w[135],C[135][135],pr[270],ip[270];
u128 fl[1<<h],gl[1<<h],fr[1<<h],gr[1<<h];
int ct(u128 s) { return pc(s>>64)+pc(s&(-1ull)); }
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=0;i<n;++i) cin>>a[i];
for(int i=0;i<m;++i) cin>>b[i];
for(int s=0;s<(1<<h);++s) {
for(int i=n-1;~i;--i) {
fl[s]=fl[s]<<1|(pc(s&a[i])&1);
fr[s]=fr[s]<<1|(pc((s<<h)&a[i])&1);
}
for(int i=m-1;~i;--i) {
gl[s]=gl[s]<<1|(pc(s&b[i])&1);
gr[s]=gr[s]<<1|(pc((s<<h)&b[i])&1);
}
}
for(int s=0;s<(1<<h);++s) for(int t=0;t<(1<<h);++t) {
w[ct(fl[s]^fr[t])]+=m-2*ct(gl[s]^gr[t]);
}
for(int i=0;i<=n;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
for(int i=0;i<=n;++i) {
w[i]=(w[i]%MOD+MOD)*ksm(1<<(2*h))%MOD;
for(int j=0;j<=i;++j) for(int k=0;k<=n-i;++k) {
f[j+k]=(f[j+k]+(j&1?MOD-w[i]:w[i])*C[n-i][k]%MOD*C[i][j])%MOD;
}
}
cin>>q;
ll iv=1;
for(int i=1;i<=n;++i) iv=iv*ksm(i)%MOD;
for(int z,o,t,ans;q--;) {
cin>>z,pr[0]=1,o=max(0,z-n)/2,ans=0,t=z/2-o+n;
for(int i=1;i<=t;++i) pr[i]=pr[i-1]*(o+i)%MOD;
ip[t]=ksm(pr[t]);
for(int i=t;i;--i) ip[i-1]=ip[i]*(o+i)%MOD;
for(int i=0;i<=n&&i<=z;++i) ans=(ans+pr[(z-i)/2-o+n]*ip[(z-i)/2-o]%MOD*f[i])%MOD;
cout<<ans*iv%MOD<<"\n";
}
return 0;
}
J. [P13699] Doomed Doom (4.5)
首先每个假设每个字符串已经没有相邻的相同元素。
从 \(n=2\) 的情况出发,此时两个串一定表示成 \(AS^aA^{-1},AS^bA^{-1}\),其中 \(S^{-k}\) 表示 \(\mathrm{rev}(S)^k\)。
需要满足 \(a,b\ne 0\) 且 \(S\) 的开头结尾不同或 \(|S|=a=b=1\)。
可以证明如果 \(n>2\) 则每个串都要表示成这个形状,否则我们能得到 \(TS^aT^{-1}=S^b\),观察开头结尾字符即可导出矛盾。
那么简单计数即可。
注意可以加入一些空串,且非空串 $\le 1 $ 必定合法。
时间复杂度 \(\mathcal O(m^2\log m)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4505,MOD=998244353,i2=(MOD+1)/2;
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,m;
ll f[MAXN],g[MAXN];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m,f[0]=1;
for(int i=1;i<=m;++i) {
memset(g,0,sizeof(g)),g[0]=f[1]*3%MOD;
for(int j=1;j<=i;++j) g[j]=(f[j-1]+f[j+1]*2)%MOD;
memcpy(f,g,sizeof(f));
}
ll ans=((ksm(3,m)+MOD-f[0])*n+f[0])%MOD*ksm(f[0],n-1)%MOD;
memset(g,0,sizeof(g)),g[0]=3;
for(int i=2;i<=m;++i) g[i]=(g[i-1]+g[i-2]*2)%MOD;
for(int i=1;i<=m;++i) for(int j=2*i;j<=m;j+=i) g[j]=(g[j]+MOD-g[i])%MOD;
for(int i=0;2*i+1<=m;++i) {
ans=(ans+ksm(2,i)*3*ksm(f[2*i+1],n))%MOD;
}
for(int i=2;i<=m;++i) {
for(int j=0;2*j+i<=m;++j) {
ll s=0;
for(int k=2*j+i;k<=m;k+=i) s=(s+f[k]*2)%MOD;
ll z=(ksm((s+f[0])%MOD,n)+(s*n+f[0])%MOD*(MOD-ksm(f[0],n-1)))%MOD;
ans=(ans+z*i2%MOD*g[i]%MOD*ksm(2,max(0,j-1)))%MOD;
}
}
cout<<ans*ksm(3,MOD-1-n*m)%MOD<<"\n";
return 0;
}
K. [P14135] Miserable EXperience (3.5)
首先从上到下操作每层的点使得它们所有点点权和本层最小值相同,这些操作是必须进行的。
然后对于某个层 \(d\),可以用一次操作令 \(a_d\gets a_d-1\),或者用 \(sz_d\) 让 \(a[d,n]\) 减一。
从下往上贪心,每当 \(\min a[d,n]\) 减少的时候就弹出后缀中最优秀的操作。
容易发现上述过程可以用长剖优化。
时间复杂度 \(\mathcal O(n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
vector <int> G[MAXN];
int n,fa[MAXN],d[MAXN],hson[MAXN],h[MAXN],dcnt;
ll a[MAXN],w0[MAXN],sm[MAXN],f[MAXN];
ll mn[MAXN*2],sz[MAXN*2],tg[MAXN*2],vl[MAXN*2],up[MAXN*2],dp[MAXN*2];
void dfs1(int u) {
d[u]=1;
for(int v:G[u]) {
dfs1(v);
if(d[v]+1>d[u]) d[u]=d[v]+1,hson[u]=v;
}
}
void dfs2(int u) {
h[u]=++dcnt;
if(hson[u]) dfs2(hson[u]);
else up[++dcnt]=1e18;
for(int v:G[u]) if(v^hson[u]) dfs2(v);
}
void adt(int u,int i,ll x) {
tg[h[u]+i]+=x,mn[h[u]+i]+=x,up[h[u]+i]+=x;
}
void dfs3(int u) {
for(int v:G[u]) dfs3(v),w0[u]+=w0[v];
for(int v:G[u]) if(f[v]<0) return f[u]=-1,void();
mn[h[u]]=a[u],sz[h[u]]=1,sm[u]=sm[hson[u]]+a[u];
int e=0;
for(int v:G[u]) if(v^hson[u]) {
ll dx=0,dy=0;
for(int i=1;i<=d[v];++i) {
adt(u,i+1,tg[h[u]+i]),tg[h[u]+i]=0;
adt(v,i,tg[h[v]+i-1]),tg[h[v]+i-1]=0;
ll x=mn[h[u]+i]-dx,y=mn[h[v]+i-1]-dy;
if(x<0||y<0) return f[u]=-1,void();
if(x<y) w0[u]+=(y-x)*sz[h[v]+i-1],dy+=y-x;
else w0[u]+=(x-y)*sz[h[u]+i],dx+=x-y;
sm[u]+=min(x,y)-mn[h[u]+i],mn[h[u]+i]=min(x,y),sz[h[u]+i]+=sz[h[v]+i-1];
}
sm[u]-=(d[u]-d[v]-1)*dx,adt(u,d[v]+1,-dx),e=max(e,d[v]);
for(int i=d[v];~i;--i) up[h[u]+i]=min(mn[h[u]+i],up[h[u]+i+1]);
if(up[h[u]]<0) return f[u]=-1,void();
}
f[u]=w0[u]+sm[u];
for(int i=0;i<=e;++i) adt(u,i+1,tg[h[u]+i]),tg[h[u]+i]=0;
for(int i=e;~i;--i) {
vl[h[u]+i]=max(vl[h[u]+i+1],d[u]-i-sz[h[u]+i]);
up[h[u]+i]=min(up[h[u]+i+1],mn[h[u]+i]);
dp[h[u]+i]=dp[h[u]+i+1]+(up[h[u]+i+1]-up[h[u]+i])*vl[h[u]+i+1];
}
f[u]-=dp[h[u]]+vl[h[u]]*up[h[u]];
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
string O;
cin>>n>>O;
for(int i=2,j=0;i<=n;++i) {
for(;O[i+j-2]^'1';++j);
G[fa[i]=j].push_back(i);
}
cin>>O;
for(int i=1,j=0;i<=n;++i) for(int x:{4,3,2,1,0}) a[i]|=(O[j++]-33)<<(x*6);
dfs1(1),dfs2(1),dfs3(1);
ll ans=0;
for(int i=1;i<=n;++i) ans^=f[i]+1;
cout<<ans<<"\n";
return 0;
}
L. [P13542] Two avenues (3)
首先建立 dfs 树以及切边等价类,删掉两条割边的情况平凡,考虑删掉切边等价类里的两条边。
如果是一条树边和非树边,则只要计算跨过该树边的路径数。
如果是两条树边,则计算分别跨过两条树边的路径数,减去两倍的同时跨过两条树边的路径数。
由于同一个切边等价类的边在 dfs 树上处于一条链中,可以发现对应的权值函数满足四边形不等式,所以可以决策单调性分治快速计算,具体算权值可以用线段树合并或主席树。
时间复杂度 \(\mathcal O((n+q)\log^2 n)\)。
代码:
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=5e5+5,MAXS=5e7+5;
struct Segt {
int tr[MAXS],ls[MAXS],rs[MAXS],tot;
void init() { for(int i=1;i<=tot;++i) tr[i]=ls[i]=rs[i]=0; tot=0; }
void ins(int x,int l,int r,int &p) {
++tr[p?p:(p=++tot)];
if(l==r) return ;
int mid=(l+r)>>1;
x<=mid?ins(x,l,mid,ls[p]):ins(x,mid+1,r,rs[p]);
}
int merge(int l,int r,int q,int p) {
if(!p||!q) return p|q;
int u=++tot; tr[u]=tr[p]+tr[q];
if(l==r) return u;
int mid=(l+r)>>1;
ls[u]=merge(l,mid,ls[q],ls[p]),rs[u]=merge(mid+1,r,rs[q],rs[p]);
return u;
}
int qry(int ul,int ur,int l,int r,int p) {
if(ul<=l&&r<=ur) return tr[p];
int mid=(l+r)>>1,s=0;
if(ul<=mid) s+=qry(ul,ur,l,mid,ls[p]);
if(mid<ur) s+=qry(ul,ur,mid+1,r,rs[p]);
return s;
}
} T;
mt19937_64 rnd(time(0));
ull hs[MAXN],hw[MAXN];
unordered_map <ull,vector<int>> F;
int _,ty,n,m,q,d[MAXN],U[MAXN],V[MAXN],fa[MAXN];
struct Edge { int v,i; };
vector <Edge> G[MAXN];
int dfn[MAXN],st[20][MAXN],dcnt;
int cmp(int x,int y) { return dfn[x]<dfn[y]?x:y; }
int LCA(int x,int y) {
if(x==y) return x;
int l=min(dfn[x],dfn[y])+1,r=max(dfn[x],dfn[y]),k=__lg(r-l+1);
return cmp(st[k][l],st[k][r-(1<<k)+1]);
}
void dfs1(int u,int fz) {
fa[u]=fz,d[u]=d[fz]+1,dfn[u]=++dcnt,st[0][dcnt]=fz;
for(auto e:G[u]) if(e.v^fz) {
if(!d[e.v]) dfs1(e.v,u),hs[u]^=hs[e.v];
else if(d[e.v]<d[u]) hw[e.i]=rnd(),hs[u]^=hw[e.i],hs[e.v]^=hw[e.i];
}
if(u>1) F[hs[u]].push_back(u);
}
array<int,5>z;
int rt[MAXN],f[MAXN],b[MAXN];
void dfs2(int u) {
for(auto e:G[u]) if(d[e.v]==d[u]+1) dfs2(e.v),f[u]+=f[e.v],rt[u]=T.merge(1,n,rt[e.v],rt[u]);
}
void cdq(int l,int r,int L,int R) {
if(l>r) return ;
int v=-1,o=0,p=(l+r)>>1;
for(int i=L;i<p&&i<=R;++i) {
int w=f[b[i]]+f[b[p]]-2*T.qry(1,d[b[i]]-1,1,n,rt[b[p]]);
if(w>v) v=w,o=i;
}
z=max(z,array<int,5>{v,fa[b[o]],b[o],fa[b[p]],b[p]});
cdq(l,p-1,L,o),cdq(p+1,r,o,R);
}
void solve() {
cin>>n>>m;
for(int i=1;i<=m;++i) cin>>U[i]>>V[i],G[U[i]].push_back({V[i],i}),G[V[i]].push_back({U[i],i});
dfs1(1,0),z={0,U[1],V[1],U[2],V[2]};
for(int k=1;k<20;++k) for(int i=1;i+(1<<k)-1<=n;++i) {
st[k][i]=cmp(st[k-1][i],st[k-1][i+(1<<(k-1))]);
}
cin>>q;
for(int i=1,u,v,w;i<=q;++i) {
cin>>u>>v,w=LCA(u,v),++f[u],++f[v],f[w]-=2;
T.ins(d[w],1,n,rt[u]),T.ins(d[w],1,n,rt[v]);
}
dfs2(1);
vector <array<int,2>> bg;
for(int i=2;i<=n;++i) bg.push_back({hs[i]?0:f[i],i});
sort(bg.begin(),bg.end(),greater<>());
z=max(z,array<int,5>{bg[0][0]+bg[1][0],fa[bg[0][1]],bg[0][1],fa[bg[1][1]],bg[1][1]});
for(int i=1;i<=m;++i) if(hw[i]&&F.count(hw[i])) for(int x:F[hw[i]]) {
z=max(z,array<int,5>{f[x],U[i],V[i],x,fa[x]});
}
for(auto&it:F) if(it.first) {
int tp=0; for(int u:it.second) b[++tp]=u;
sort(b+1,b+tp+1,[&](int x,int y){ return d[x]<d[y]; });
if(tp>1) cdq(2,tp,1,tp-1);
}
cout<<z[0]<<"\n"<<z[1]<<" "<<z[2]<<"\n"<<z[3]<<" "<<z[4]<<"\n";
dcnt=0,z.fill(0),F.clear(),T.init();
for(int i=1;i<=n;++i) b[i]=d[i]=dfn[i]=f[i]=fa[i]=rt[i]=0,G[i].clear(),hs[i]=0;
for(int i=1;i<=m;++i) hw[i]=U[i]=V[i]=0;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>_>>ty;
while(_--) solve();
return 0;
}
*M. [P13835] 返夏 (8.5)
根据矩阵树定理计算答案,我们每次要计算 \(\det(A)\to \det(A+B)\) 的类似过程。
根据 Determinant Lemma,\(\det(A+uv^T)=\det(I_m+v^TA^{-1}u)\det(A)\)。
但我们无法在每次修改后动态维护 \(A^{-1}\)。
考虑从初始情况开始,注意到 \(1\sim n\) 的链对应的矩阵树定理得到的矩阵 \(A\) 求逆很简单,模拟消元过程可知 \(A^{-1}_{i,j}=n-1-\max(i,j)\)。
我们把 \((1,n)\) 看成一条新加的边,
此时矩阵 \(u=v\) 且为 \((n-1)\times (m+1)\) 矩阵,\(u_{u_i,i}=1,u_{v_i,i}=-1\)。
那么只要动态维护 \(\det(I_m,v^T+A^{-1}u)\) 即可,那么我们要解决的就是动态地给对称矩阵 \(C\) 加入一行一列并维护 \(\det(C)\)。
考虑手动维护消元的过程,\(\begin{bmatrix}C^{-1}&0\\-a^TC^{-1}&1\end{bmatrix}\times\begin{bmatrix}C&a\\a^T&b\end{bmatrix}=\begin{bmatrix}I&a\\0&b-a^TC^{-1}a\end{bmatrix}\)。
记 \(w=b-a^TC^{-1}a\),则 \(\det(C')=\det(C)\times w\)。
进一步消元得到 \(C'^{-1}=\begin{bmatrix}C^{-1}-\frac 1wC^{-1}aa^TC^{-1}&-\frac 1wC^{-1}a\\-\frac 1wa^TC^{-1}&\frac 1w\end{bmatrix}\),直接暴力维护即可。
时间复杂度 \(\mathcal O(m^3)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int 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,m,u[805],v[805];
ll z,C[805][805],D[805][805],a[805],b[805];
int wc(int x,int y) { return max(0,min(v[x],v[y])-max(u[x],u[y])); }
signed main( ){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m,u[1]=1,v[1]=n,C[1][1]=z=n,D[1][1]=ksm(n);
for(int k=2;k<=m+1;++k) {
cin>>u[k]>>v[k];
if(v[k]<u[k]) swap(u[k],v[k]);
ll w=C[k][k]=v[k]-u[k]+1;
for(int i=1;i<k;++i) C[i][k]=C[k][i]=a[i]=wc(i,k),b[i]=0;
for(int i=1;i<k;++i) for(int j=1;j<k;++j) b[i]=(b[i]+D[i][j]*a[j])%MOD;
for(int i=1;i<k;++i) w=(w+a[i]*(MOD-b[i]))%MOD;
z=z*w%MOD,D[k][k]=w=ksm(w),cout<<z<<"\n";
for(int i=1;i<k;++i) for(int j=1;j<k;++j) D[i][j]=(D[i][j]+b[i]*b[j]%MOD*w)%MOD;
for(int i=1;i<k;++i) D[i][k]=D[k][i]=(MOD-b[i])*w%MOD;
}
return 0;
}
N. [P12011] 春开,意遥遥 (3)
观察发现操作是异或卷积,那么 FWT 后观察所有的 \((x+y,y-x)\),乘法变成对应位置相乘。
进一步只要维护 \(w=\prod \dfrac{x+y}{y-x}\) 即可,如果有 \((x+y)(y-x)=0\) 的元素,则区间答案必定是 \(0\)。
注意 \(p=2\) 的情况要特判。
那么我们要求的就是 \(w_1\sim w_k\) 乘积能生成多少数,求 \(p\) 的原根 \(g\) 以及离散对数 \(h_1\sim h_k\),答案为 \(\dfrac{p-1}{\gcd(h_i,p-1)}\)。
求所有 \(p_i\) 无法接受,发现答案实际上是 \(\mathrm{lcm}(\delta_p(w_i))\),而求解只要试除法容易做到 \(\mathcal O(\log^2p)\)。
区间 \(\mathrm{lcm}\) 之和由于每个左端点处 \(\mathrm{gcd}\) 只会变化 \(\mathcal O(\log p)\) 次,因此可以直接暴力维护。
时间复杂度 \(\mathcal O(n\log^2p)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
#define LL __int128
using namespace std;
const int MAXN=1e5+5,MOD=1e9+7;
int n; ll p,m; vector <ll> fc;
LL ksm(LL a,LL b) { LL s=1; for(;b;a=a*a%p,b>>=1) if(b&1) s=s*a%p; return s; }
void PR(ll x) {
for(ll i=2;i<=x/i;++i) if(x%i==0) {
fc.push_back(i);
for(;x%i==0;x/=i);
}
if(x>1) fc.push_back(x);
}
ll a[MAXN];
ll qry(ll x) {
ll w=m;
for(ll k:fc) for(;w%k==0;w/=k) if(ksm(x,w/k)!=1) break;
return w;
}
ll lcm(ll x,ll y) { return x&&y?x/__gcd(x,y)*y:0; }
array<ll,2> f[MAXN];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>p,m=p-1,PR(m);
if(p==2) {
ll s=1ll*n*(n+1)/2;
for(int i=1,l=0,r=0,x,y;i<=n;++i) {
cin>>x>>y;
if(x==y) l=i;
else if(x&&!y) r=i;
s+=max(0,r-l);
}
return cout<<s%MOD<<"\n",0;
}
for(int i=1;i<=n;++i) {
ll x,y,l,r; cin>>x>>y,l=(x+y)%p,r=(y+p-x)%p;
if(l&&r) a[i]=qry(l*ksm(r,p-2)%p);
}
ll s=0; f[0][0]=-1;
for(int i=1,h=0;i<=n;++i) {
if(!a[i]) f[h=1]={0,i};
else {
int k=0;
for(int j=1;j<=h;++j) {
ll z=lcm(f[j][0],a[i]);
if(f[k][0]!=z) f[++k]={z,f[j][1]};
else f[k][1]=f[j][1];
}
if(f[k][0]!=a[i]) f[++k]={a[i],i};
else f[k][1]=i;
h=k;
}
for(int j=1;j<=h;++j) s=(s+max(1ll,f[j][0])%MOD*(f[j][1]-f[j-1][1]))%MOD;
}
cout<<s<<"\n";
return 0;
}
O. [P10121] 保险丝 (4)
答案难以快速维护,考虑观察暴力的性质。
部分分提示不存在二度点时搜到的点数很少,容易发现最劣情况为满二叉树,此时搜索此时也是 \(\mathcal O(n\log n)\) 级别的。
因此对每个点子树内的 \(\ge 3\) 度点建虚树并暴力搜索统计,不在虚树上的二度点只要提前二维数点求出总点数就行。
时间复杂度 \(\mathcal O(n\log n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef basic_string<int> vec;
const int MAXN=1e6+5,MOD=994007158;
vec G[MAXN],W[MAXN],D[MAXN],E[MAXN];
int n,fa[MAXN],d[MAXN],dep[MAXN],dfn[MAXN],efn[MAXN],dcnt,rk[MAXN],st[20][MAXN];
ll fb[MAXN];
void dfs1(int u) {
dfn[u]=++dcnt,st[0][dcnt]=fa[u],dep[u]=dep[fa[u]]+1,rk[dcnt]=u;
for(int v:G[u]) dfs1(v),d[u]=d[u]?min(d[u],d[v]+1):d[v]+1;
efn[u]=dcnt;
}
void dfs2(int u,int x) {
W[max(1,dep[u]-x)]+=u,D[dep[u]]+=u;
for(int v:G[u]) dfs2(v,min(d[v],x));
}
int cmp(int x,int y) { return dfn[x]<dfn[y]?x:y; }
int LCA(int x,int y) {
if(x==y) return x;
int l=min(dfn[x],dfn[y])+1,r=max(dfn[x],dfn[y]),k=__lg(r-l+1);
return cmp(st[k][l],st[k][r-(1<<k)+1]);
}
ll f[MAXN],g[MAXN];
struct BIT {
int tr[MAXN],s;
void add(int x) { for(;x<=n;x+=x&-x) ++tr[x]; }
int qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
} T;
int dsu[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void dfs4(int u,int rt,int c) {
g[u]=fb[G[u].size()+(u>1)];
for(int v:E[u]) dfs4(v,rt,dep[v]-dep[u]),g[u]=g[u]*g[v]%MOD;
f[rt]=(f[rt]+(g[u]-1)*c)%MOD;
}
void solve(int u) {
vec V{u};
for(int i=find(dfn[u]+1);i<=efn[u];i=find(i+1)) V.push_back(rk[i]);
for(int i=1,m=V.size();i<m;++i) V.push_back(LCA(V[i-1],V[i]));
sort(V.begin(),V.end(),[&](int x,int y){ return dfn[x]<dfn[y]; });
V.erase(unique(V.begin(),V.end()),V.end());
for(int i=1;i<(int)V.size();++i) E[LCA(V[i-1],V[i])].push_back(V[i]);
dfs4(u,u,1);
for(int x:V) E[x].clear(),g[x]=0;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=2;i<=n;++i) cin>>fa[i],G[fa[i]]+=i;
fb[1]=fb[2]=1;
for(int i=3;i<=n;++i) fb[i]=(fb[i-1]+fb[i-2])%MOD;
dfs1(1),dfs2(1,d[1]);
for(int k=1;k<20;++k) for(int i=1;i+(1<<k)-1<=n;++i) {
st[k][i]=min(st[k-1][i],st[k-1][i+(1<<(k-1))]);
}
for(int i=1;i<=n;++i) {
for(int u:W[i]) T.add(dfn[u]);
for(int u:D[i]) f[u]=T.qry(efn[u])-T.qry(dfn[u]-1);
}
iota(dsu+1,dsu+n+2,1);
for(int i=1;i<=n;++i) if(G[i].size()<2) dsu[dfn[i]]=dfn[i]+1;
for(int i=n;i>=1;--i) {
for(int u:D[i]) solve(u);
for(int u:W[i]) dsu[dfn[u]]=dfn[u]+1;
}
ll ans=0;
for(int i=1;i<=n;++i) ans^=(f[i]+MOD)%MOD;
cout<<ans<<"\n";
return 0;
}
P. [P13242] 你的名字 (2.5)
后缀排序后,所有 \(t[l,r]\) 出现的位置都是一个区间,我们只要算出这个区间中每个 \(s_i\) 的 \(\sum \min a[l,i]\)。
那么前缀最小值问题可以考虑单侧递归线段树,强制在线套上主席树,注意到单侧递归时不会改被递归的点信息,因此空间复杂度不变。
时间复杂度 \(\mathcal O((n+m+q)\log^2 n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
struct SA {
static const int MAXN=6e5+5;
char s[MAXN];
int n,m,sa[MAXN],rk[MAXN],ct[MAXN],t[MAXN],f[20][MAXN];
void init() {
m=27,memset(ct,0,(m+1)<<2),s[n+1]=0;
for(int i=1;i<=n;++i) rk[i]=s[i]-'a'+1,++ct[rk[i]];
for(int i=1;i<=m;++i) ct[i]+=ct[i-1];
for(int i=n;i;--i) sa[ct[rk[i]]--]=i;
for(int k=1;;k<<=1) {
for(int i=1;i<=k;++i) t[i]=i+n-k;
for(int i=1,h=k;i<=n;++i) if(sa[i]>k) t[++h]=sa[i]-k;
memset(ct,0,(m+1)<<2);
for(int i=1;i<=n;++i) ++ct[rk[i]];
for(int i=1;i<=m;++i) ct[i]+=ct[i-1];
for(int i=n;i;--i) sa[ct[rk[t[i]]]--]=t[i];
memcpy(t,rk,(n+1)<<2),m=0,t[n+1]=0;
for(int i=1;i<=n;++i) {
rk[sa[i]]=m+=(i==1||t[sa[i]]!=t[sa[i-1]]||t[min(n+1,sa[i]+k)]!=t[min(n+1,sa[i-1]+k)]);
}
if(m==n) break;
}
for(int i=1,k=0;i<=n;++i) {
for(k=max(k-1,0);s[i+k]==s[sa[rk[i]-1]+k];++k);
f[0][rk[i]]=k;
}
for(int k=1;k<20;++k) for(int i=1;i+(1<<k)-1<=n;++i) {
f[k][i]=min(f[k-1][i],f[k-1][i+(1<<(k-1))]);
}
}
int lcp(int x,int y) {
if(x==y) return n-x+1;
int l=min(rk[x],rk[y])+1,r=max(rk[x],rk[y]),k=__lg(r-l+1);
return min(f[k][l],f[k][r-(1<<k)+1]);
}
} S;
int _,n,q,O,OL,OR,a[MAXN],bl[MAXN*3],rt[MAXN*3],mn[20][MAXN];
int qmn(int l,int r) {
int k=__lg(r-l+1);
return min(mn[k][l],mn[k][r-(1<<k)+1]);
}
struct Segt {
static const int MAXS=MAXN*100;
int ls[MAXS],rs[MAXS],tot,ct[MAXS];
ll f[MAXS];
void init(int l,int r,int &p) {
p=++tot;
if(l==r) return ;
int mid=(l+r)>>1;
init(l,mid,ls[p]),init(mid+1,r,rs[p]);
}
ll ask(int w,int l,int r,int p) {
if(l==r) return 1ll*ct[p]*min(w,a[l]);
int mid=(l+r)>>1;
if(w<=qmn(l,mid)) return 1ll*ct[ls[p]]*w+ask(w,mid+1,r,rs[p]);
return ask(w,l,mid,ls[p])+f[p]-f[ls[p]];
}
void psu(int l,int r,int p) {
int mid=(l+r)>>1;
f[p]=f[ls[p]]+ask(qmn(l,mid),mid+1,r,rs[p]);
}
void ins(int x,int l,int r,int o,int &p) {
ct[p=++tot]=ct[o]+1;
if(l==r) return f[p]=f[o]+a[x],void();
int mid=(l+r)>>1;
if(x<=mid) ins(x,l,mid,ls[o],ls[p]),rs[p]=rs[o];
else ins(x,mid+1,r,rs[o],rs[p]),ls[p]=ls[o];
psu(l,r,p);
}
int z; ll s;
void qry(int ul,int ur,int l,int r,int p) {
if(ul<=l&&r<=ur) return s+=ask(z,l,r,p),z=min(z,qmn(l,r)),void();
int mid=(l+r)>>1;
if(ul<=mid) qry(ul,ur,l,mid,ls[p]);
if(mid<ur) qry(ul,ur,mid+1,r,rs[p]);
}
ll qry(int l,int r,int p) {
return z=a[l],s=0,qry(l,r,1,n,p),s;
}
} T;
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>_>>n>>q>>O>>OL>>OR;
string str;
for(int i=1;i<=n;++i) {
cin>>str;
for(auto c:str) S.s[++S.n]=c,bl[S.n]=i;
S.s[++S.n]='{';
}
cin>>str;
int hd=S.n,m=str.size();
for(auto c:str) S.s[++S.n]=c;
for(int i=1;i<=n;++i) cin>>a[i],mn[0][i]=a[i];
for(int k=1;k<20;++k) for(int i=1;i+(1<<k)-1<=n;++i) {
mn[k][i]=min(mn[k-1][i],mn[k-1][i+(1<<(k-1))]);
}
S.init(),T.init(1,n,rt[0]);
for(int i=1;i<=S.n;++i) {
rt[i]=rt[i-1];
if(bl[S.sa[i]]) T.ins(bl[S.sa[i]],1,n,rt[i],rt[i]);
}
for(ll l1,r1,l2,r2,lst=0;q--;) {
cin>>l1>>r1>>l2>>r2;
l1=(l1+lst-1)%n+1,r1=(r1+lst-1)%n+1,l2=(l2+lst-1)%m+1,r2=(r2+lst-1)%m+1;
if(l1>r1) swap(l1,r1);
if(l2>r2) swap(l2,r2);
l1=(~OL?OL:l1),r1=(~OR?OR:r1);
int l=S.rk[hd+l2],r=l;
for(int k=1<<19;k;k>>=1) {
if(l>k&&S.lcp(hd+l2,S.sa[l-k])>=r2-l2+1) l-=k;
if(r+k<=S.n&&S.lcp(hd+l2,S.sa[r+k])>=r2-l2+1) r+=k;
}
cout<<(lst=T.qry(l1,r1,rt[r])-T.qry(l1,r1,rt[l-1]))<<"\n",lst*=O;
}
return 0;
}
*Q. [P14033] 子集乘积 (9)
首先我们无法分别 \(a,\overline a\),所以不妨钦定 \(a_1=0\)。
不妨假设我们每次询问 \(S\) 能得到 \(S\) 中 \(1\) 的个数,可以通过询问 \(S,S\cup\{1\}\) 实现。
那么我们尝试分治。
把待确定集合 \(Q\) 分成相等的两个部分 \(L,R\),对其分别构造还原方案,则对于 \(L,R\) 的第 \(i\) 次询问,我们询问 \(L_i\cup R_i,L_i\cup (R\setminus R_i)\) 就能得到询问 \(L_i,R_i\) 的答案。
注意到任何时候这两次询问同奇偶,那么不妨第二次询问 \(L_i\cup (R\setminus R_i)\cup\{x\}\),那么如果两次询问不同奇偶s说明 \(a_x=1\),否则 \(a_x=0\)。
还需要一次额外的询问求 \(R\) 内部 \(1\) 个数,然后可以倒推 \(L\) 内部元素个数。
用 \(f_n\) 表示确定 \(n\) 个元素的最小代价,则转移为 \(f_{2i+f_i}\gets 2f_i+1\)。
但此时一次查询需要交互两次,考虑优化。
我们只要确定 \(0,1\) 中哪种元素更多,那么我们尝试利用一些已知的元素,具体来说把所有已知的 \(a_i=x\) 的元素取出来,设有 \(c\) 个,则只要初始的 \(|S|\le c\),那么我们只要一次交互就能确定。
显然 \(c\) 至少是已知元素的一半,所以设 \(g_i\) 表示得到 \(i\) 个元素的最小代价,转移为 \(g_{i+j}\gets g_i+f_j(j\le\lceil i/2\rceil)\),此时算出 \(g_n\) 还差 \(\mathcal O(1)\) 次。
\(f\) 转移的时候可以类似三等分 \(Q\),然后用三次询问解出三个值以及两个其他位置,转移为 \(f_{3i+2f_i}\gets 3f_i+2\),此时 \(g_n\) 恰好满足题意。
时间复杂度 \(\mathcal O(n^2)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef basic_string<int> vec;
const int MAXN=1005;
int a[MAXN],f[MAXN],g[MAXN],pf[MAXN],of[MAXN],pg[MAXN];
vec mov(vec S,int d) { for(int &x:S) x+=d; return S; }
vec rev(const vec&S,int n) {
static bitset<MAXN> qy;
qy.reset(); for(int u:S) qy[u]=1;
vec T;
for(int i=1;i<=n;++i) if(!qy[i]) T+=i;
return T;
}
vector<vec> dp[MAXN];
void build(int n) {
if(dp[n].size()||n==1) return ;
auto add=[&](const vec&S) { dp[n].push_back(S); };
if(of[n]==1) {
build(n-1),dp[n]=dp[n-1],add({n});
return ;
} else if(of[n]==2) {
int m=pf[n];
build(m);
for(int i=0;i<f[m];++i) {
vec X=dp[m][i],Y=rev(X,m);
add(X+mov(X,m)),add(X+mov(Y,m));
if(2*m+1+i<=n) dp[n].back()+=2*m+1+i;
}
add({});
for(int i=1;i<=m;++i) dp[n].back()+=m+i;
} else {
int m=pf[n];
build(m);
for(int i=0;i<f[m];++i) {
vec X=dp[m][i],Y=rev(X,m);
add(X+mov(X,m)+mov(X,2*m));
add(X+mov(Y,m)+mov(X,2*m));
if(3*m+1+2*i<=n) dp[n].back()+=3*m+1+2*i;
add(X+mov(X,m)+mov(Y,2*m));
if(3*m+2+2*i<=n) dp[n].back()+=3*m+2+2*i;
}
add({});
for(int i=1;i<=m;++i) dp[n].back()+=m+i;
add({});
for(int i=1;i<=m;++i) dp[n].back()+=2*m+i;
}
}
void calc(int l,int r,vec&Z,int ct) {
if(ct==r-l||ct==0) {
for(int i=l+1;i<=r;++i) a[i]=!!ct;
return ;
}
int n=r-l;
if(of[n]==1) {
a[r]=Z.back(),Z.pop_back();
return calc(l,r-1,Z,ct-a[r]);
} else if(of[n]==2) {
int m=pf[n],rc=Z.back(),lc=ct-rc; Z.pop_back();
vec LZ,RZ;
for(int i=0;i<f[m];++i) {
int u=Z[2*i],v=Z[2*i+1]-rc;
if(l+2*m+1+i<=r) a[l+2*m+1+i]=(u+v)&1,v-=(u+v)&1;
LZ+=(u+v)/2,RZ+=(u-v)/2;
}
for(int i=l+2*m+1;i<=r;++i) lc-=a[i];
calc(l,l+m,LZ,lc),calc(l+m,l+2*m,RZ,rc);
} else {
int m=pf[n],rc=Z.back(); Z.pop_back();
int mc=Z.back(),lc=ct-rc-mc; Z.pop_back();
vec LZ,MZ,RZ;
for(int i=0;i<f[m];++i) {
int u=Z[3*i],v=Z[3*i+1]-mc,w=Z[3*i+2]-rc;
if(l+3*m+1+2*i<=r) a[l+3*m+1+2*i]=(u+v)&1,v-=(u+v)&1;
if(l+3*m+2+2*i<=r) a[l+3*m+2+2*i]=(u+w)&1,w-=(u+w)&1;
v=(u-v)/2,w=(u-w)/2,LZ+=u-v-w,MZ+=v,RZ+=w;
}
for(int i=l+3*m+1;i<=r;++i) lc-=a[i];
calc(l,l+m,LZ,lc),calc(l+m,l+2*m,MZ,mc),calc(l+2*m,l+3*m,RZ,rc);
}
}
int n,m;
int qry(const vec &S) {
static bitset<MAXN> qy; qy.reset();
for(int u:S) qy[u]=1;
int c=count(a+1,a+m+1,1),o=1;
if(m-c>c) o=0,c=m-c;
for(int i=1;i<=m;++i) if(a[i]==o) qy[i]=1;
cout<<"? "; for(int i=1;i<=n;++i) cout<<qy[i]; cout<<endl;
int p,s=c+S.size(); cin>>p;
if(!p) return o?s-c:0;
for(int x=1;x<=s/2;++x) if(x*(s-x)==p) return o?s-x-c:x;
return -1;
}
void solve() {
cin>>n,a[m=1]=0;
vec sz;
for(int t=n;t>1;t-=pg[t]) sz+=pg[t];
reverse(sz.begin(),sz.end());
for(int k:sz) {
build(k);
vec Z,O;
for(auto&S:dp[k]) Z+=qry(mov(S,m));
for(int i=1;i<=k;++i) O+=m+i;
int sb=qry(O);
calc(m,m+k,Z,sb),m+=k;
}
cout<<"! "; for(int i=1;i<=n;++i) cout<<a[i]; cout<<endl;
int o; cin>>o; if(o) return ;
cout<<"! "; for(int i=1;i<=n;++i) cout<<1-a[i]; cout<<endl;
cin>>o,memset(a,0,sizeof(a));
}
signed main() {
const int N=1000;
memset(f,0x3f,sizeof(f)),memset(g,0x3f,sizeof(g)),f[1]=g[1]=0;
for(int i=1;i<=N;++i) {
if(f[i+1]>f[i]+1) f[i+1]=f[i]+1,of[i+1]=1;
for(int j=i+1;j<=min(N,2*i+f[i]);++j) if(f[j]>2*f[i]+1) f[j]=2*f[i]+1,of[j]=2,pf[j]=i;
for(int j=i+1;j<=min(N,3*i+2*f[i]);++j) if(f[j]>3*f[i]+2) f[j]=3*f[i]+2,of[j]=3,pf[j]=i;
}
for(int i=1;i<=N;++i) {
for(int j=1;i+j<=N&&j<=(i+1)/2;++j) if(g[i+j]>g[i]+f[j]+1) g[i+j]=g[i]+f[j]+1,pg[i+j]=j;
}
int _; cin>>_;
while(_--) solve();
return 0;
}
*R. [P12264] 咏叹调调律 (8.5)
尝试把消除问题和括号匹配建立联系,\(B,C\) 分别对应 \(-2,+1\),而 \(A\) 可以对应 \(+2\) 或 \(-1\),容易发现一定是一个前缀为 \(+2\)。
那么序列合法的必要条件是括号化后得到一个合法括号序列,且合法序列唯一对应一个合法括号序列。
但该条件并不充分,因为一个 \(A_L/B\) 对应的两个左括号不能被分进两个不同的子序列。
那么按照优先匹配这些自由度更低的括号得到贪心顺序:\(A_LB>A_LA_RA_R=CCB>CA_R\)。
那么栈中维护若干 \(A_L,C\),如果加入一个 \(A_R\),那么优先和 \(A_L\) 匹配。
如果再加入一个 \(B\),那么可能会会拆开一个 \(A_LA_R\) 变成 \(A_LB+CA_R\)。
这要求这个 \(A_LA_R\) 生成的时候至少存在一个 \(C\)。
注意到当有 \(A_LA_R\) 时加入一个 \(A_R\) 则最优策略就是直接匹配 \(A_LA_RA_R\),因为如果这两个 \(A_R\) 分别匹配 \(CA_R\),那么把占据的两个 \(C\) 拿出来肯定优于一个 \(A_L\)。
所以只要栈中记录 \(A_L,C\) 个数,以及当前有没有填 \(A_R\),是否有 \(A_LA_R\),以及生成时是否有 \(C\) 供调整。
时间复杂度 \(\mathcal O(n^3)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=505,MOD=998244353;
inline void add(int&x,const int&y) { x=x+y>=MOD?x+y-MOD:x+y; }
int n,P,Q,R,F[2][MAXN][MAXN][4];//ArB>AlArAr/CCB>CAr
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>P>>Q>>R;
auto &f=F[0],&g=F[1];
f[0][0][0]=1;
for(int o=1,z;o<=n;++o) {
for(int i=0;i<=o;++i) for(int j=0;i+j<=o;++j) for(int x:{0,1,2,3}) {
z=0,swap(f[i][j][x],z);
if(!z) continue;
int za=1ll*z*P%MOD,zb=1ll*z*Q%MOD,zc=1ll*z*R%MOD;
if(!x) add(g[i+1][j][0],za);
if(x==2) add(g[i][j][1],za);
else if(x==3) add(g[i][j+1][1],za); //swap C out
else if(i&&j) add(g[i-1][j-1][3],za);
else if(i) add(g[i-1][0][2],za);
else if(j) add(g[0][j-1][1],za);
if(i) add(g[i-1][j][x],zb);
else if(x==3) add(g[i][j][1],zb);
else if(j>=2) add(g[i][j-2][x],zb);
add(g[i][j+1][x],zc);
}
swap(f,g);
cout<<(f[0][0][0]+f[0][0][1])%MOD<<" \n"[o==n];
}
return 0;
}

浙公网安备 33010602011771号