2026 NOI 做题记录(一)
\(\text{By DaiRuiChen007}\)
A. [CF2063F2] Counting Is Not Fun (1)
对括号序列建树,加入一对括号相当于删掉其到父亲的边,每个连通块要构成合法括号串。
时光倒流用并查集维护。
时间复杂度 \(\mathcal O(n\alpha(n))\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=6e5+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; }
ll fac[MAXN],ifac[MAXN],w[MAXN],iw[MAXN],f[MAXN];
int n,a[MAXN],fa[MAXN],siz[MAXN],dsu[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void solve() {
cin>>n;
for(int i=0;i<=n;++i) {
w[i]=fac[2*i]*ifac[i]%MOD*ifac[i+1]%MOD;
iw[i]=ifac[2*i]*fac[i]%MOD*fac[i+1]%MOD;
}
for(int i=1,x,y;i<=n;++i) cin>>x>>y,a[x]=i,a[y]=-i;
for(int i=1,u=0;i<=2*n;++i) {
if(a[i]>0) fa[a[i]]=u,u=a[i];
else u=fa[u];
}
for(int i=0;i<=n;++i) dsu[i]=i,siz[i]=0;
ll s=f[n]=1;
for(int i=n;i;--i) {
int o=find(fa[i]);
s=s*iw[siz[o]]%MOD*iw[siz[i]]%MOD;
siz[o]+=siz[i]+1,dsu[i]=o;
s=s*w[siz[o]]%MOD,f[i-1]=s;
}
for(int i=0;i<=n;++i) cout<<f[i]<<" \n"[i==n];
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int i=fac[0]=1;i<MAXN;++i) fac[i]=fac[i-1]*i%MOD;
ifac[MAXN-1]=ksm(fac[MAXN-1]);
for(int i=MAXN-1;i;--i) ifac[i-1]=ifac[i]*i%MOD;
int _; cin>>_;
while(_--) solve();
return 0;
}
B. [CF2066D2] Club of Young Aircraft Builders (4)
首先颜色 \(n\) 必定出现 \(c\) 次,因此只要每种颜色最后一次出现合法即可。
考虑如何判定,从前往后扫描,动态维护最大的 \(q\) 使得颜色 \(q\) 不能再加入。
计数的时候每个位置必定填 \(>q\) 的数,此时不确定其颜色,而是等 \(>q\) 的数达到 \(c\) 个后,选择若干个位置颜色为 \(q+1\),然后 \(q\gets q+1\)。
dp 设 \(f_{i,j,k}\) 表示 \(a[1,i],q=j\),有 \(k\) 个 \(>q\) 的操作,注意在同一个位置可能有多个 \(j\) 被填满。
时间复杂度 \(\mathcal O(nmc)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e4+5,MOD=1e9+7;
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; }
int n,m,c,a[MAXN],ct[105],vis[105],fac[MAXN],ifac[MAXN],f[105][105];
int C(int x,int y) { return y<0||y>x?0:1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
void solve() {
cin>>n>>c>>m;
for(int i=1;i<=n;++i) ct[i]=vis[i]=0;
for(int i=1;i<=m;++i) {
cin>>a[i];
if(a[i]) ++vis[a[i]];
}
memset(f,0,sizeof(f)),f[1][0]=1;
int tot=0;
for(int i=1;i<=m;++i) {
if(a[i]) ++tot,++ct[a[i]];
for(int j=1;j<=n;++j) {
for(int k=c;k;--k) f[j][k]=f[j][k-1];
f[j][0]=0;
}
for(int j=1,cur=tot;j<=n-(i<m);cur-=ct[j++]) if(f[j][c]&&ct[j]==vis[j]) {
for(int x=0;x<=c-cur;++x) {
f[j+1][c-x-ct[j]]=(f[j+1][c-x-ct[j]]+1ll*f[j][c]*C(c-cur,x))%MOD;
}
}
}
cout<<f[n+1][0]<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int i=fac[0]=1;i<MAXN;++i) fac[i]=1ll*i*fac[i-1]%MOD;
ifac[MAXN-1]=ksm(fac[MAXN-1]);
for(int i=MAXN-1;i;--i) ifac[i-1]=1ll*i*ifac[i]%MOD;
int _; cin>>_;
while(_--) solve();
return 0;
}
C. [CF2068E] Porto Vs. Benfica (3)
假设选出的路径已知,那么在 \(u\) 时肯定封堵 \(u\to n\) 最短路的下一条边,建立最短路树,不难证明只会经过一条非树边,那么计算 \(u\) 封堵后的距离 \(w_u\) 是平凡的。
然后考虑答案,设路径为 \(e_0\sim e_k\),则代价为 \(\max i+w_{e_i}\),不妨从后往前刻画代价,则 \(f_u=\min_v\max(w_u,1+vf_v)\),Dijkstra 转移即可。
时间复杂度 \(\mathcal O(m\log m)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5,inf=1e9;
vector <int> G[MAXN];
int n,m,d[MAXN],w[MAXN],f[MAXN],fa[MAXN],dsu[MAXN];
bool vis[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i) d[i]=w[i]=f[i]=inf,dsu[i]=i;
for(int i=1,u,v;i<=m;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
queue <int> Q; Q.push(n),d[n]=0;
while(Q.size()) {
int u=Q.front(); Q.pop();
for(int v:G[u]) if(d[v]>d[u]+1) fa[v]=u,d[v]=d[u]+1,Q.push(v);
}
vector <array<int,3>> E;
for(int u=1;u<=n;++u) for(int v:G[u]) if(u<v&&fa[u]!=v&&fa[v]!=u) E.push_back({d[u]+d[v]+1,u,v});
sort(E.begin(),E.end());
for(auto e:E) {
for(int x=find(e[1]),y=find(e[2]);x!=y;dsu[x]=fa[x],x=find(x)) {
if(d[x]<d[y]) swap(x,y);
w[x]=e[0]-d[x];
}
}
priority_queue <array<int,2>,vector<array<int,2>>,greater<array<int,2>>> q;
q.push({f[n]=0,n});
while(q.size()) {
int u=q.top()[1]; q.pop();
if(vis[u]) continue; vis[u]=true;
for(int v:G[u]) if(f[v]>max(f[u]+1,w[v])) q.push({f[v]=max(f[u]+1,w[v]),v});
}
cout<<(f[1]>=inf?-1:f[1])<<"\n";
return 0;
}
D. [CF2077E] Another Folding Strip (3)
首先刻画一次操作能更新的下标集:首先相邻两个下标必须不同奇偶,然后一定可以构造:只要每次把中间的格子反复折叠成一格即可。
然后考虑操作次数,观察可以发现是最大的 \(a_l-a_{l+1}+a_{l+2}-\cdots +a_r\),设 \(b_i=\sum_{j\le i} (-1)^ja_j\),可以证明答案就是 \(|\max b_r-b_{l-1}|=\max b_i-\min b_i\),单调栈维护所有区间最值之和。
时间复杂度 \(\mathcal O(n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=998244353;
int st[MAXN];
ll a[MAXN];
void solve() {
int n; cin>>n;
for(int i=1;i<=n;++i) cin>>a[i],a[i]*=i&1?-1:1,a[i]+=a[i-1];
ll s=0;
for(int o:{0,1}) {
st[0]=-1; ll w=0;
for(int i=0,tp=0;i<=n;++i) {
for(;tp&&a[st[tp]]<a[i];--tp) w=(w-a[st[tp]]%MOD*(st[tp]-st[tp-1]))%MOD;
w=(w+a[i]%MOD*(i-st[tp]))%MOD,st[++tp]=i,s=(s+w)%MOD;
}
for(int i=0;i<=n;++i) a[i]*=-1;
}
cout<<(s+MOD)%MOD<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
E. [CF2084F] Skyscape (2.5)
首先每次操作 \(a_r\) 必定是最小值,否则 \(a'\) 不是排列。
考虑如何判定一组 \(b\),首先找到 \(a_x=b_1\),那么 \(a_x\) 必定是 \(a[1,x]\) 的最小值,否则放不到开头。
显然直接把 \(a_x\) 和 \(b_1\) 删掉继续判断不劣。
那么对于 \(x<y,a_x<a_y\),要求 \(a_x\) 在 \(b\) 中比 \(a_y\) 早出现,相当于拓扑序限制。
确定了一部分 \(b\) 就变成了 Constraited Topological Sort 问题,只要求出 \([l_i,r_i]\) 即可。
那么 \(l_{a_y}=1+\max_{x<y,a_x<a_y} l_{a_x}\),直接树状数组即可维护,\(r\) 同理。
时间复杂度 \(\mathcal O(n\log n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int n,a[MAXN],b[MAXN],l[MAXN],r[MAXN],f[MAXN],id[MAXN];
void solve() {
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i],l[i]=1,r[i]=n,id[i]=i;
for(int i=1;i<=n;++i) {
cin>>b[i];
if(b[i]) l[b[i]]=r[b[i]]=i;
}
fill(f+1,f+n+1,0);
for(int i=1;i<=n;++i) {
for(int x=a[i];x;x&=x-1) l[a[i]]=max(l[a[i]],f[x]+1);
for(int x=a[i];x<=n;x+=x&-x) f[x]=max(f[x],l[a[i]]);
}
fill(f+1,f+n+1,n+1);
for(int i=n;i>=1;--i) {
for(int x=a[i];x<=n;x+=x&-x) r[a[i]]=min(r[a[i]],f[x]-1);
for(int x=a[i];x;x&=x-1) f[x]=min(f[x],r[a[i]]);
}
sort(id+1,id+n+1,[&](int x,int y){ return r[x]>r[y]; });
priority_queue <array<int,2>> Q;
for(int i=n,j=1;i>=1;--i) {
for(;j<=n&&r[id[j]]==i;++j) Q.push({l[id[j]],id[j]});
if(Q.empty()||i<Q.top()[0]) return cout<<"-1\n",void();
b[i]=Q.top()[1],Q.pop();
}
for(int i=1;i<=n;++i) cout<<b[i]<<" \n"[i==n];
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
F. [CF2092F] Andryusha and CCB (2)
设总美感值为 \(c\),分成 \(k\) 段时,要求有 \((k-1)-c\bmod k\) 个分割线在同色位置之间。
注意到这种分割线数量可以任意减少,因此只要最大化其数量,贪心地优先放这种即可。
算答案就枚举每段美感值,然后枚举段数,此时合法前缀在一个区间内,左右端点容易维护,复杂度调和。
时间复杂度 \(\mathcal O(n\log n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5;
int n,m,a[MAXN],f[MAXN];
char s[MAXN];
void solve() {
cin>>n,m=0;
for(int i=1;i<=n;++i) cin>>s[i],f[i]=0;
for(int i=2;i<=n;++i) if(s[i]!=s[i-1]) a[++m]=i;
for(int k=1;k<=m;++k) {
int l=k,r=k;
for(;l<=m;l+=k,r+=k+1) {
++f[a[l]]; if(r<m) --f[a[r+1]];
if(a[l+1]-a[l]==1) ++l;
}
}
for(int i=1;i<=n;++i) f[i]+=f[i-1]+(i==1||s[i]==s[i-1]);
for(int i=1;i<=n;++i) cout<<f[i]<<" \n"[i==n];
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
G. [CF2030G2] The Destruction of the Universe (3)
首先考虑计算答案,容易发现最终交会在所有 \(l_i,r_i\) 的中位数上。
枚举对应的位置 \(k\),并判定哪些区间集合合法。
设 \(r_i=k,l_i=k\) 的区间分别有 \(x,y\) 个,则 \(l_i>k\) 的区间数减去 \(r_i<k\) 的区间数只差应该 \(\in(-x,y]\)。
枚举这个差值,方案数用范德蒙德卷积计算。
求距离之和相当于钦定左边或右边的一个区间,计算剩余的方案数,维护左右所有区间到 \(k\) 距离之和即可。
时间复杂度 \(\mathcal O(n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+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,cl[MAXN],cr[MAXN],at[MAXN],el[MAXN],er[MAXN];
ll pw[MAXN],fac[MAXN],ifac[MAXN],sl[MAXN],sr[MAXN];
ll C(int x,int y) { return y<0||y>x?0:fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
void solve() {
cin>>n;
for(int i=0;i<=n+1;++i) cl[i]=cr[i]=sl[i]=sr[i]=at[i]=el[i]=er[i]=0;
for(int i=1,l,r;i<=n;++i) {
cin>>l>>r;
++cl[r],sl[r]+=r,++cr[l],sr[l]+=l;
if(l==r) ++at[l];
else ++er[l],++el[r];
}
for(int i=1;i<=n;++i) cl[i]+=cl[i-1],sl[i]+=sl[i-1];
for(int i=n;i>=1;--i) cr[i]+=cr[i+1],sr[i]+=sr[i+1];
ll ans=0;
for(int i=1;i<=n;++i) if(at[i]||el[i]||er[i]) {
int x=el[i],y=er[i],k=at[i],l=cl[i-1],r=cr[i+1];
ll zl=0,zr=0;
for(int j=x+k,s=0;j>=1;--j) {
s=(s+pw[y]*C(x+k,j))%MOD;
zl=(zl+s*C(l+r-1,l+j))%MOD;
zr=(zr+s*C(l+r-1,l+j-1))%MOD;
}
for(int j=y+k-1,s=0;j>=0;--j) {
s=(s+pw[x]*C(y+k,j+1))%MOD;
zl=(zl+s*C(l+r-1,r+j-1))%MOD;
zr=(zr+s*C(l+r-1,r+j))%MOD;
}
zl=zl*pw[n-x-y-l-r-k]%MOD,zr=zr*pw[n-x-y-l-r-k]%MOD;
ans=(ans+(1ll*l*i-sl[i-1])%MOD*zl)%MOD;
ans=(ans+(sr[i+1]-1ll*r*i)%MOD*zr)%MOD;
}
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int i=fac[0]=pw[0]=1;i<MAXN;++i) fac[i]=fac[i-1]*i%MOD,pw[i]=pw[i-1]*2%MOD;
ifac[MAXN-1]=ksm(fac[MAXN-1]);
for(int i=MAXN-1;i;--i) ifac[i-1]=ifac[i]*i%MOD;
int _; cin>>_;
while(_--) solve();
return 0;
}
H. [CF2056F2] Xor of Median (3)
首先只考虑重排数为奇数的序列,对应所有的 \(cnt\) 构成 \(n\) 的拆分。
注意到这些序列都有绝对众数,且绝对众数就是最大值。
设共有 \(k\) 种颜色,那么对应的贡献就是 \(\begin{Bmatrix}\mathrm{popcount}(n)\\k\end{Bmatrix}\sum_x x\times \binom{x}{k-1}\)。
斯特林数 \(\bmod 2\) 直接打表找规律,计算 \([0,m-1]\) 所有 \(k-1\) 超集的异或和可以数位 dp。
时间复杂度 \(\mathcal O(k\log m)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef array<int,2> pii;
int n,m;
bool qry(int x,int y) {
if(y<=0||y>x) return x==0&&y==0;
return (((y-1)/2)&(x-y))==0;
}
pii dp[32];
array<int,2> dfs(int i,int s,bool o) {
if(i<0) return {1,0};
if(!o&&~dp[i][0]) return dp[i];
array<int,2> f={0,0};
for(int c=s>>i&1;c<=(o?(m>>i&1):1);++c) {
auto g=dfs(i-1,s,o&&(c==(m>>i&1)));
f[0]^=g[0],f[1]^=g[1]^(g[0]*c<<i);
}
if(!o) dp[i]=f;
return f;
}
void solve() {
string _;
cin>>n>>m>>_,n=count(_.begin(),_.end(),'1'),--m;
int s=0;
for(int i=0;i<n;++i) if(qry(n,i+1)) {
memset(dp,-1,sizeof(dp)),s^=dfs(29,i,true)[1];
}
cout<<s<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
I. [CF2068D] Morse Code (3.5)
考虑如何判定一组深度系数是否合法,那么从大到小,每次把深度 \((\max ,\max-1)\) 的点合成一个深度为 \(\max-2\) 的点即可。
那么 \(f_{i,j,k}\) 表示确定了 \(i\) 个系数,当前 \(j\) 个 \(\max-1\) 和 \(k\) 个 \(\max\),转移时先逐个加入若干深度 \(\max-1\) 的叶子,然后转移到 \((k,j-k)\) 即可。
注意新建一层的时候要加上已加入点的权值之和,即排序后的前 \(i\) 小元素和。
时间复杂度 \(\mathcal O(n^3)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=205,inf=1e9;
int n,a[MAXN],id[MAXN],s[MAXN],b[MAXN],f[MAXN][MAXN][MAXN];
void chkmin(int &x,const int &y) { x=y<x?y:x; }
vector <int> h[MAXN];
int ls[MAXN<<1],rs[MAXN<<1];
string e[MAXN<<1];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i) {
double d; cin>>d,a[i]=round(d*10000),id[i]=i;
}
sort(id+1,id+n+1,[&](int x,int y){ return a[x]<a[y]; });
for(int i=1;i<=n;++i) s[i]=s[i-1]+a[id[i]];
memset(f,0x3f,sizeof(f));
f[0][0][0]=0;
for(int i=0;i<=n;++i) for(int x=i;~x;--x) for(int y=i-x;~y;--y) {
if(f[i][x][y]>=inf) continue;
if(i<n) chkmin(f[i+1][x+1][y],f[i][x][y]);
if(y<=x) chkmin(f[i][y][x-y],f[i][x][y]+s[i]);
}
int m=0,ct=n;
for(int q=n,x=1,y=0;q;++m) {
while(q&&x&&f[q][x][y]==f[q-1][x-1][y]) --q,--x,++b[m];
y+=x,swap(x,y);
}
for(int i=m,c=0;i>=1;--i) for(int j=0;j<b[i];++j) h[i].push_back(id[++c]);
for(int i=m;i>=2;--i) {
while(h[i].size()&&h[i-1].size()) {
ls[++ct]=h[i-1].back(),rs[ct]=h[i].back();
h[i-2].push_back(ct),h[i].pop_back(),h[i-1].pop_back();
}
}
for(int i=ct;i>n;--i) e[ls[i]]=e[i]+'.',e[rs[i]]=e[i]+'-';
for(int i=1;i<=n;++i) cout<<e[i]<<"\n";
return 0;
}
J. [CF2077D] Maximum Polygon (5)
首先任意 \(\log V\) 个元素总能找到三角形,所以序列开头以及序列最大值都只有 \(\log V\) 种可能。
先暴力确定最大可能的开头,然后枚举序列最大值,然后我们要加入若干元素使得序列总和合法。
先假设开头是最大值,如果不限制总和,那么只要把所有后缀最大值加入即可。
如果此时不够满足要求,就在最后两个后缀最大值中间递归此过程:先加入所有后缀最大值,然后继续递归。
但是这样贪心没有考虑后选的元素对前面元素的贡献,正确做法是从前往后考虑每个区间,假设后面的区间全部选满时,求出该区间的最优解,然后递归下一个区间。
如果开头不是最大值,那么就先处理中间一段,再处理最大值后面的,实现时有一点细节。
求这些区间对应的树状结构只要每个点向后面第一个比自己大的点连边即可。
时间复杂度 \(\mathcal O(n\log V)\)。
更优的做法是只枚举最大值,然后最大字典序序列可以简单贪心。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,a[MAXN],id[MAXN],b[MAXN],st[MAXN];
bool f[MAXN];
vector <int> G[MAXN];
ll s[MAXN];
vector<int> gen(int hd,int mp) {
int m=0,tp=0,p=0,mx=a[mp]; ll z=mx;
for(int i=hd;i<=n;++i) if(a[i]<=mx) b[++m]=a[i],p=(i==mp?m:p);
if(accumulate(b+1,b+m+1,0ll)<=2*mx) return vector<int>();
for(int i=1;i<=m;++i) G[i].clear(),f[i]=false,s[i]=b[i];
f[1]=f[p]=true;
auto link=[&](int u,int v) {
s[u]+=s[v],G[u].push_back(v);
};
function<ll(int,ll)> dfs=[&](int u,ll q) {
reverse(G[u].begin(),G[u].end());
ll w=0,t=0;
for(int v:G[u]) t+=s[v];
for(int v:G[u]) {
t-=s[v];
if(q-w>=0||u==p) w+=b[v],f[v]=true;
w+=dfs(v,q-w-t);
}
return w;
};
if(p>1) {
ll t=accumulate(b+p+1,b+m+1,0ll); z-=b[1];
for(int i=2;i<p;++i) {
while(tp&&b[st[tp]]<b[i]) link(i,st[tp--]);
st[++tp]=i;
}
while(tp) link(1,st[tp--]);
z-=dfs(1,z-t);
}
for(int i=p+1;i<=m;++i) {
while(tp&&b[st[tp]]<b[i]) link(i,st[tp--]);
st[++tp]=i;
}
for(int i=tp;i;--i) link(p,st[i]);
dfs(p,z);
vector <int> o;
for(int i=1;i<=m;++i) if(f[i]) o.push_back(b[i]);
return o;
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i],id[i]=i;
stable_sort(id+1,id+n+1,[&](int x,int y){ return a[x]>a[y]; });
int p,q;
for(p=3;p<=n;++p) {
ll z=0;
for(int i=p;i;--i) {
if(p-i>1&&z>a[id[i]]) goto fl1;
z+=a[id[i]];
}
}
return cout<<"-1\n\n",void();
fl1:;
for(q=1;q<=p;++q) {
ll z=0; int c=0;
for(int i=n;i>q;--i) if(id[i]>id[q]) ++c,z+=a[id[i]];
if(c>1&&z>a[id[q]]) goto fl2;
++c,z+=a[id[q]];
for(int i=q-1;i;--i) if(id[i]>id[q]) {
if(c>1&&z>a[id[i]]) goto fl2;
++c,z+=a[id[i]];
}
}
fl2:;
vector <int> ans=gen(id[q],id[q]);
for(int i=q-1;i;--i) if(id[i]>=id[q]) {
ans=max(ans,gen(id[q],id[i]));
}
cout<<ans.size()<<"\n";
for(int i:ans) cout<<i<<" "; cout<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
K. [CF2096G] Wonderful Guessing Game (5.5)
首先如果没有题目的限制则只要简单三分即可。
求出每个元素作为答案时每次询问的回答串,如果两个串海明距离 \(=1\) 则无法辨别。
可以用一次额外询问确定之,每个点对应的颜色为串中元素和 \(\bmod 3\),可以证明能找到两个出现次数相等的元素。
时间复杂度 \(\mathcal O(n\log n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
string a[MAXN];
void sol(int l,int r) {
if(r-l<=1) return ;
int x=(r-l+1)/3;
for(int i=l;i<l+x;++i) a[i]+='L';
for(int i=l+x;i<l+2*x;++i) a[i]+='R';
for(int i=l+2*x;i<r;++i) a[i]+='N';
sol(l,l+x),sol(l+x,l+2*x),sol(l+2*x,r);
}
void solve() {
int n; cin>>n;
for(int i=1;i<=n;++i) a[i].clear();
sol(1,n+1);
int k=0;
for(int i=1;i<=n;++i) k=max(k,(int)a[i].size());
vector <int> s[3];
for(int i=1;i<=n;++i) {
a[i].resize(k,'N'); int c=0;
for(char o:a[i]) c=(c+(o=='L'?1:(o=='R'?2:0)))%3;
s[c].push_back(i);
}
int cl=0,cr=1;
if(s[0].size()==s[2].size()) cr=2;
else if(s[1].size()==s[2].size()) cl=2;
for(int i:s[cl]) a[i]+='L';
for(int i:s[cr]) a[i]+='R';
for(int i:s[(6-cl-cr)%3]) a[i]+='N';
cout<<k+1<<endl;
for(int c=0;c<=k;++c) {
deque <int> q;
for(int i=1;i<=n;++i) {
if(a[i][c]=='L') q.push_front(i);
if(a[i][c]=='R') q.push_back(i);
}
cout<<q.size()<<" ";
for(int i:q) cout<<i<<" ";
cout<<endl;
}
string z; cin>>z;
for(int i=1;i<=n;++i) {
for(int j=0;j<=k;++j) if(z[j]!='?'&&z[j]!=a[i][j]) goto fl;
return cout<<i<<endl,void();
fl:;
}
}
signed main() {
int _; cin>>_;
while(_--) solve();
return 0;
}
L. [CF2096H] Wonderful XOR Problem (6)
FWT 后只关心 \(\sum_{i=l}^r(-1)^{|i\cdot s|}\),先把 \([l,r]\) 拆成 \(\mathcal O(\log V)\) 个 \([u,u+2^k)\),其中 \(\mathrm{lowbit}(u)\ge k\)。
考虑 \([u,u+2^k)\) 范围内所有数的贡献,如果 \(\mathrm{lowbit}(s)< k\),则 \(i\) 与 \(i\oplus2^{\mathrm{lowbit}(s)}\) 的贡献会抵消,否则贡献就是 \(2^k\times (-1)^{|i\cdot u|}\)。
那么枚举 \(d=\mathrm{lowbit}(s)\),只要考虑 \(k\le d\) 的所有 \((u,k)\),并且这些元素的低 \(d+1\) 位贡献全部可以计算,转成 \(m'=m-(d+1)\) 的问题。
由于 \([l,r]\) 分解出的 \((u,k)\) 在 \(u\) 递增时 \(k\) 严格单增,因此 \(k\le d\) 的这些元素构成了两个长度 \(<2^{d+1}\) 的区间,则 \(\lfloor u/2^{d+1}\rfloor\) 只有至多两种取值。
简单处理一下把问题变成求 \(\prod_s(a_s+b_sx^{s})\),乘法为异或卷积。
这个问题比较经典,考虑分治:最高位的取值只和右侧元素个数的奇偶性有关,因此我们分别维护异或和为 \(x\),且选了奇数或偶数个元素的方案数,合并时两两卷积。
注意到每次合并都要 FWT 和 IFWT 可以简单优化把重复过程去掉,做到 \(\mathcal O(m'2^{m'})\)。
时间复杂度 \(\mathcal O(nm^2+m2^m)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef array<int,2> pii;
const int MAXN=2e5+5,MOD=998244353,i2=(MOD+1)/2;
int w[1<<18];
pii f[1<<18];
vector <pii> a[MAXN];
void dfs(int d,int z,int l,int r,bool x,bool y,auto&o) {
if(l<=z&&z+(1ll<<(d+1))-1<=r) return o.push_back({z,d+1});
for(int i=(x?l>>d&1:0);i<=(y?r>>d&1:1);++i) {
dfs(d-1,z|i<<d,l,r,x&&i==(l>>d&1),y&&i==(r>>d&1),o);
}
}
void solve() {
int n,m;
cin>>n>>m,fill(w,w+(1<<m),0),w[0]=1;
for(int i=1,l,r;i<=n;++i) {
cin>>l>>r,a[i].clear(),dfs(m-1,0,l,r,1,1,a[i]),w[0]=1ll*w[0]*(r-l+1)%MOD;
}
for(int k=0;k<m;++k) {
int d=m-k-1;
for(int s=0;s<(1<<d);++s) f[s]={1,0};
for(int i=1;i<=n;++i) {
map <int,int> z;
for(auto q:a[i]) if(q[1]<=k) {
z[q[0]>>(k+1)]+=(q[0]>>k&1?-1:1)*(1<<q[1]);
}
if(z.empty()) goto em;
int t=z.begin()->first,x=z.begin()->second+MOD,j=0,y=0;
if(z.size()==2) j=z.rbegin()->first^t,y=z.rbegin()->second+MOD;
int l=(1ll*f[j][0]*x+1ll*f[j][1]*y)%MOD,r=(1ll*f[j][0]*y+1ll*f[j][1]*x)%MOD;
f[j]={l,r},swap(f[t][0],f[t][1]);
}
for(int p=1;p<(1<<d);p<<=1) for(int i=0;i<(1<<d);i+=p<<1) for(int j=i;j<i+p;++j) {
const pii x=f[j],y=f[j+p];
int l0=1ll*x[0]*y[0]%MOD,l1=1ll*x[1]*y[0]%MOD,r0=1ll*x[1]*y[1]%MOD,r1=1ll*x[0]*y[1]%MOD;
f[j]={(l0+r0)%MOD,(l1+r1)%MOD},f[j+p]={(l0+MOD-r0)%MOD,(l1+MOD-r1)%MOD};
}
for(int s=0;s<(1<<d);++s) w[(s<<1|1)<<k]=(f[s][0]+f[s][1])%MOD;
em:;
}
for(int p=1;p<(1<<m);p<<=1) for(int i=0;i<(1<<m);i+=p<<1) for(int j=i;j<i+p;++j) {
int x=w[j],y=w[j+p]; w[j]=1ll*i2*(x+y)%MOD,w[j+p]=1ll*i2*(x+MOD-y)%MOD;
}
int ans=0;
for(int s=0,p=1;s<(1<<m);++s,p=p*2%MOD) ans^=1ll*w[s]*p%MOD;
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
M. [CF2101E] Kia Bakes a Cake (1.5)
显然答案是 \(\mathcal O(\log n)\) 级别,因此容易设计 dp:\(f_{u,x}\) 表示从 \(u\) 出发走 \(x\) 步,第一步长度的最大值。
转移为 \(f_{u,x}=\max\{\mathrm{dis}(u,v)\mid f_{v,x-1}\ge 2\mathrm{dis}(u,v)\}\),点分治优化逐层转移。
时间复杂度 \(\mathcal O(n\log ^3n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=7e4+5,inf=1e9;
vector <int> G[MAXN],e[MAXN];
bool vis[MAXN],a[MAXN];
int siz[MAXN],cur[MAXN],b[MAXN],cl[18][MAXN],di[18][MAXN];
int n,f[MAXN],g[MAXN],z[MAXN];
array<int,2> t[MAXN];
void calc(int u) {
int *c=cl[b[u]],*d=di[b[u]];
d[u]=c[u]=0; if(a[u]) e[u].push_back(u);
for(int v:G[u]) if(!vis[v]) {
function<void(int,int)> dfs2=[&](int x,int fz) {
siz[x]=1; if(a[x]) e[u].push_back(x);
for(int y:G[x]) if(!vis[y]&&y!=fz) {
d[y]=d[x]+1,c[y]=c[x],dfs2(y,x),siz[x]+=siz[y];
}
};
c[v]=v,d[v]=1,dfs2(v,u);
}
sort(e[u].begin(),e[u].end(),[&](int x,int y){ return d[x]<d[y]; });
}
void dfs1(int u) {
vis[u]=true,calc(u);
for(int v:G[u]) if(!vis[v]) {
int rt=0;
function<void(int,int)> dfs3=[&](int x,int fz) {
cur[x]=siz[v]-siz[x];
for(int y:G[x]) if(!vis[y]&&y!=fz) dfs3(y,x),cur[x]=max(cur[x],siz[y]);
if(!rt||cur[rt]>cur[x]) rt=x;
};
dfs3(v,u),b[rt]=b[u]+1,dfs1(rt);
}
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) {
char c; cin>>c,a[i]=c-'0';
z[i]=-1,g[i]=vis[i]=0,f[i]=a[i]?inf:0;
G[i].clear(),e[i].clear();
}
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
b[1]=0,dfs1(1);
for(int h=1;h<=18;++h) {
for(int u=1;u<=n;++u) if(e[u].size()) {
int *d=di[b[u]],*c=cl[b[u]],s=e[u].size();
for(int i=0,x;i<s;++i) x=e[u][i],t[i]={f[x]-2*d[x],x};
int x=0,y=0; d[0]=-inf,c[0]=-1;
auto add=[&](int o) {
if(d[o]>d[x]) {
if(c[x]==c[o]) x=o;
else y=x,x=o;
} else if(d[o]>d[y]&&c[x]!=c[o]) y=o;
};
sort(t,t+s);
for(int i=s-1,j=s-1;~i;--i) {
int p=e[u][i];
while(~j&&t[j][0]>=2*d[p]) add(t[j--][1]);
g[p]=max(g[p],d[c[x]==c[p]?y:x]+d[p]);
}
}
for(int i=1;i<=n;++i) if(f[i]&&!g[i]) z[i]=h;
for(int i=1;i<=n;++i) f[i]=g[i],g[i]=0;
if(*max_element(f+1,f+n+1)==0) break;
}
for(int i=1;i<=n;++i) cout<<z[i]<<" \n"[i==n];
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
N. [CF2118F] Shifts and Swaps (3.5)
首先猜测答案合法当且仅当每个 \(\{v,v+1\}\) 构成的子序列都循环同构。
具体来说先找到 \(a,b\) 中哪些 \(1\) 匹配可以使得 \(1,2\) 构成的子序列相等,进一步求出哪些 \(2\) 匹配使得 \(1,2\) 构成的子序列相等,再判断这些匹配是否能让 \(2,3\) 构成的子序列相等,以此类推。
判断时用哈希维护。
时间复杂度 \(\mathcal O(n)\)。
代码:
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=5e5+5,B=6737151;
int n,m,a[MAXN],b[MAXN];
vector <int> x[MAXN],y[MAXN];
ull pw[MAXN];
bool ok[MAXN],s[MAXN],t[MAXN];
void solve() {
cin>>n>>m;
for(int i=1;i<=m;++i) x[i].clear(),y[i].clear();
for(int i=1;i<=n;++i) {
cin>>a[i];
if(a[i]>1) x[a[i]-1].push_back(i);
if(a[i]<m) x[a[i]].push_back(i);
}
for(int i=1;i<=n;++i) {
cin>>b[i],ok[i]=(b[i]==1);
if(b[i]>1) y[b[i]-1].push_back(i);
if(b[i]<m) y[b[i]].push_back(i);
}
for(int i=1;i<m;++i) if(x[i].size()!=y[i].size()) return cout<<"NO\n",void();
for(int v=1,h=find(a+1,a+n+1,1)-a;v<m;++v) {
rotate(x[v].begin(),find(x[v].begin(),x[v].end(),h),x[v].end());
int k=x[v].size();
for(int i=0;i<k;++i) s[i]=a[x[v][i]]-v,t[i]=b[y[v][i]]-v;
ull S=0,T=0;
for(int i=0;i<k;++i) S=S*B+s[i],T=T*B+t[i];
bool fl=false;
for(int i=0;i<k;++i) {
if(!t[i]&&(ok[y[v][i]]&=(S==T))) {
fl=true;
for(int j=i;;j=(j+1)%k) if(t[j]) { ok[y[v][j]]=true; break; }
}
T=T*B+(1-pw[k])*t[i];
}
if(!fl) return cout<<"NO\n",void();
h=x[v][find(s,s+k,1)-s];
}
cout<<"YES\n";
}
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]*B;
int _; cin>>_;
while(_--) solve();
return 0;
}
O. [CF2124G] Maximise Sum (4.5)
其次 \(i\) 必须是前缀最小值,设前一个、后一个前缀最小值分别是 \(x,y\),那么对应的贡献可以表示为 \(\sum_{k=i}^{\min(y,j)-1}\min(a_j,b_k-a_i)\),其中 \(b_k\) 表示 \(a[1,j]\) 中的次小值。
如果 \(j\in[i,x)\) 容易用树状数组维护,但 \(j\ge x\) 的枚举量太大,首先如果存在 \(j'>j,a_{j'}>a_j\),那么 \(j\) 肯定不优,所以只要考虑所有后缀最大值即可。
注意到 \(\max b_j\le a_{y}\),那么 \(a_j>a_y-a_i\) 的 \(j\) 只要考虑第一个即可,则此时需要枚举的 \(j\) 总数是 \(\mathcal O(a_y-a_i)\) 级别的,则均摊枚举量 \(\mathcal O(V)\),可以通过。
时间复杂度 \(\mathcal O(n\log n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5,inf=1e9;
int n,m,k,a[MAXN],b[MAXN],c[MAXN];
ll w[MAXN],f[MAXN];
struct FenwickTree {
ll tr[MAXN],s;
int st[MAXN],tp;
void add(int x,ll v) { for(st[++tp]=x;x<=n;x+=x&-x) tr[x]+=v; }
ll qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
void init() { while(tp) for(int x=st[tp--];x<=n;x+=x&-x) tr[x]=0; }
} T1,T2;
void solve() {
cin>>n,m=k=0,w[n+1]=0,fill(f,f+n,0);
for(int i=1,x=n+1;i<=n;++i) {
cin>>a[i];
if(a[i]<x) b[++m]=i,x=a[i];
w[i]=x;
}
for(int i=n,x=-1;i;--i) {
if(a[i]>x) c[++k]=i,x=a[i];
w[i]+=w[i+1];
}
b[m+1]=n+1,c[k+1]=b[0]=0,a[0]=inf,f[0]=w[1];
for(int i=1;i<=m;++i) {
int ct=0;
for(int j=b[i],v=min(n,a[b[i-1]]-a[b[i]]);j<b[i+1];++j) {
if(j>b[i]) {
f[j-b[i]]=max(f[j-b[i]],w[1]-w[j]+T2.qry(a[j])+1ll*a[j]*(ct-T1.qry(a[j])));
v=min(v,a[j]-a[b[i]]);
}
if(v) T1.add(v,1),T2.add(v,v),++ct;
}
for(int o=1;o<=k&&c[o]>=b[i+1];++o) {
int j=c[o];
f[j-b[i]]=max(f[j-b[i]],w[1]-w[j]+T2.qry(a[j])+1ll*a[j]*(ct-T1.qry(a[j])));
if(a[j]>=a[b[i-1]]-a[b[i]]) break;
}
T1.init(),T2.init();
}
for(int i=n-1;i;--i) f[i-1]=max(f[i-1],f[i]);
for(int i=0;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;
}
P. [CF2125F] Timofey and Docker (1.5)
猜测最小代价关于子串数量是凸函数,那么代价最小的一定是当前出现次数左右首个被覆盖最多的位置,然后 wqs 二分,检验的时候 \(f_{i,j}\) 表示前 \(i\) 个数 \(\mathrm{lcp}=j\) 的代价。
时间复杂度 \(\mathcal O(n\log V)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5,inf=1e9;
const ll INF=1e18;
typedef array<ll,2> pii;
string s,o="docker";
int n,m,a[MAXN];
pii chk(ll w) {
array<pii,6> f,g;
f.fill({INF,0}),f[0]={0,0};
for(int i=0;i<n;++i) {
g.fill({INF,0});
for(int j=0;j<6;++j) {
g[0]=min(g[0],pii{f[j][0]+(s[i]==o[j]||s[i]=='d'),f[j][1]});
g[1]=min(g[1],pii{f[j][0]+(s[i]!='d'),f[j][1]});
if(j<5) g[j+1]=min(g[j+1],pii{f[j][0]+(s[i]!=o[j]),f[j][1]});
else g[0]=min(g[0],pii{f[j][0]+(s[i]!=o[j])+w,f[j][1]+1});
}
f.swap(g);
}
return *min_element(f.begin(),f.end());
}
int qry(int k) {
ll l=-inf,r=inf,z=inf;
while(l<=r) {
ll mid=(l+r)>>1;
if(chk(mid)[1]<=k) z=mid,r=mid-1;
else l=mid+1;
}
return chk(z)[0]-k*z;
}
void solve() {
cin>>s>>m,n=s.size();
int ct=0,mx=n/6;
for(int i=0;i<n-5;++i) ct+=(s.substr(i,6)==o);
fill(a,a+mx+1,0);
for(int i=1,l,r;i<=m;++i) {
cin>>l>>r,r=min(r,mx);
if(l<=r) ++a[l],--a[r+1];
}
for(int i=1;i<=mx;++i) a[i]+=a[i-1];
int w=*max_element(a,a+mx+1);
if(a[ct]==w) return cout<<"0\n",void();
int ans=inf;
for(int i=ct-1;i>=0;--i) if(a[i]==w) { ans=min(ans,qry(i)); break; }
for(int i=ct+1;i<=mx;++i) if(a[i]==w) { ans=min(ans,qry(i)); break; }
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
Q. [CF2127H] 23 Rises Again (2.5)
首先 \(K_4\) 中每个点被 \(6\) 个简单环包含,因此原图是广义串并联图。
那么对于每个簇,我们只要记录两个端点度数分别为 \(\{0,1,2\}\) 时的答案,缩一度点的时候直接找相邻的另一条边合并即可。
时间复杂度 \(\mathcal O(m\log m)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=35;
struct info {
int f[3][3];
info() { memset(f,-0x3f,sizeof(f)); }
inline friend info operator +(const info &u,const info &v) {
info w;
for(int x:{0,1,2}) for(int y:{0,1,2}) if(x+y<=2) for(int i:{0,1,2}) for(int j:{0,1,2}) {
w.f[i][j]=max(w.f[i][j],u.f[x][i]+v.f[y][j]);
}
return w;
}
inline friend info operator *(const info &u,const info &v) {
info w;
for(int x:{0,1,2}) for(int y:{0,1,2}) if(x+y<=2) for(int i:{0,1,2}) for(int j:{0,1,2}) if(i+j<=2) {
w.f[x+y][i+j]=max(w.f[x+y][i+j],u.f[x][i]+v.f[y][j]);
}
return w;
}
info rev() {
info t;
for(int i:{0,1,2}) for(int j:{0,1,2}) t.f[i][j]=f[j][i];
return t;
}
} E;
map <int,info> G[MAXN];
void link(int u,int v,info z) {
if(G[u].count(v)) G[u][v]=G[u][v]*z,G[v][u]=G[v][u]*z.rev();
else G[u][v]=z,G[v][u]=z.rev();
}
int n,m;
void solve() {
cin>>n>>m;
for(int i=1;i<=n;++i) G[i].clear();
for(int i=1,u,v;i<=m;++i) cin>>u>>v,link(u,v,E);
queue <int> Q;
for(int i=1;i<=n;++i) if(G[i].size()<=2) Q.push(i);
while(Q.size()) {
int u=Q.front(); Q.pop();
if(G[u].empty()) continue;
if(G[u].size()==2) {
auto l=*G[u].begin(),r=*G[u].rbegin();
int x=l.first,y=r.first;
G[x].erase(u),G[y].erase(u),link(x,y,l.second+r.second),G[u].clear();
if(G[x].size()<=2) Q.push(x);
if(G[y].size()<=2) Q.push(y);
} else {
int x=G[u].begin()->first; info z=G[u].begin()->second;
if(G[x].size()==1) {
int s=0;
for(int i:{0,1,2}) for(int j:{0,1,2}) s=max(s,z.f[i][j]);
cout<<s<<"\n";
return ;
}
G[x].erase(u),G[u].clear();
info &w=G[x].begin()->second,q;
for(int k:{0,1,2}) {
int s=0;
for(int i:{0,1,2}) s=max(s,z.f[i][k]);
for(int i:{0,1,2}) if(i+k<=2) for(int j:{0,1,2}) q.f[i+k][j]=max(q.f[i+k][j],s+w.f[i][j]);
}
w=q,G[G[x].begin()->first][x]=w.rev();
if(G[x].size()<=2) Q.push(x);
}
}
}
signed main() {
E.f[0][0]=0,E.f[1][1]=1;
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
R. [CF2128F] Strict Triangle (4.5)
注意到最优解一定是 \(1\to n\) 最短路全部填 \(l\),其他边全部填 \(r\)。
考虑如何判定一条路径是最优的,首先可以证明 \(1\to k\to n\) 的最短路和这条路径的交一定是一段前缀和一段后缀。
枚举所有可能前后缀的端点 \((u,v)\),必须要求 \(\mathrm{dis}(k,u)+\mathrm{dis}(k,v)>\mathrm{dis}(u,v)\)。
判定一条路径合法时可以维护对 \(\mathrm{dis}(k,v)\) 限制的最大值 \(x\),转移为 \(x\gets \max(x+w,-\mathrm{dis}(k,v))\)。
则 dp 记录 \(f_u\) 表示 \(1\to u\) 的最小 \(x\) 值,\(f_v=\min_u(\max(f_u+w(u,v),-\mathrm{dis}(k,v)))\),用 Dijkstra 维护转移即可。
时间复杂度 \(\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 v,l,r; };
vector <Edge> G[MAXN];
ll h[MAXN],f[MAXN];
bool vis[MAXN];
void solve() {
int n,m,o;
cin>>n>>m>>o;
for(int i=1;i<=n;++i) G[i].clear();
for(int i=1,u,v,l,r;i<=m;++i) cin>>u>>v>>l>>r,G[u].push_back({v,l,r}),G[v].push_back({u,l,r});
for(int i=1;i<=n;++i) h[i]=inf,vis[i]=false;
priority_queue <array<ll,2>,vector<array<ll,2>>,greater<>> Q;
Q.push({h[o]=0,o});
while(Q.size()) {
int u=Q.top()[1]; Q.pop();
if(vis[u]) continue; vis[u]=true;
for(auto e:G[u]) if(h[e.v]>h[u]+e.r) Q.push({h[e.v]=h[u]+e.r,e.v});
}
for(int i=1;i<=n;++i) f[i]=inf,vis[i]=false;
Q.push({f[1]=-h[1],1});
while(Q.size()) {
int u=Q.top()[1]; Q.pop();
if(vis[u]) continue;
vis[u]=true,f[u]=max(f[u],-h[u]);
if(h[u]<=f[u]) { f[u]=inf; continue; }
for(auto e:G[u]) if(f[e.v]>f[u]+e.l) Q.push({f[e.v]=f[u]+e.l,e.v});
}
cout<<(f[n]<h[n]?"YES\n":"NO\n");
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
*S. [CF2053G] Naive String Splits (8)
考虑如何校验一组字符串 \((x,y)\),其中 \(|x|\le |y|\),一个朴素的想法就是从前往后能放 \(y\) 就放 \(y\),否则放 \(x\)。
这个做法在 \(x\) 不是 \(y\) 周期时是正确的,因为此时的 \(y\) 一定不是任意 \(x^k+y\) 的前缀。
否则我们需要进一步考虑,如果 \(x\) 是 \(y\) 的整周期,那么预处理 \(s,t\) 的最小整周期即可简单判定。
否则设 \(x\) 的最小整周期为 \(a\),且 \(y=a^{t}+b,\),\(b\) 是 \(a\) 的真前缀,且 \(a+b\ne b+a\)。
考虑加强填 \(y\) 的条件,首先如果当前串前缀不为 \(y+x\),那么填 \(y\) 后不能再填 \(y\) 或 \(x\),除非 \(t=y\)。
那么在串前缀为 \(y+x\) 时填入 \(y\),否则如果填 \(x^k+y\),考虑 \(y\) 末尾的 \(b\) 以及后面 \(x\) 开头的 \(a\),其对应位置在 \(x^k+y\) 上一定是 \(a+b\) 或者 \(a+a\)(其前缀也是 \(a+b\)),由于 \(b+a\ne a+b\),因此必定不能填入 \(x^k+y\)。
如果 \(x\) 是 \(y\) 的非整周期,则只有 \(t\) 开头为 \(y+x\) 或 \(t=y\) 时填 \(y\),否则填 \(x\)。
用哈希判断,则填入 \(\mathcal O\left(\dfrac{m}{|x|}\right)\) 个串后就判定完成,因此复杂度均摊调和。
时间复杂度 \(\mathcal O(m\log m)\)。
代码:
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=5e6+5;
mt19937_64 rnd(time(0));
ull B,hc[26],pw[MAXN],hs[MAXN],ht[MAXN];
char s[MAXN],t[MAXN];
ull vs(int l,int r) { return hs[r]-hs[l-1]*pw[r-l+1]; }
ull vt(int l,int r) { return ht[r]-ht[l-1]*pw[r-l+1]; }
void solve() {
int n,m,p;
cin>>n>>m;
for(int i=1;i<=n;++i) cin>>s[i],hs[i]=hs[i-1]*B+hc[s[i]-'a'];
for(int i=1;i<=m;++i) cin>>t[i],ht[i]=ht[i-1]*B+hc[t[i]-'a'];
for(p=1;p<n;++p) if(n%p==0&&vs(1,n-p)==vs(p+1,n)) break;
auto qry=[&](int k) {
if(k%p==0) {
if(m%p||vt(1,m-p)!=vt(p+1,m)||vt(1,p)!=vs(1,p)) return false;
int x=max(k,n-k);
for(int i=0;i<=m/x;++i) if((m-i*x)%(n-x)==0) return true;
return false;
}
ull vx=vs(1,k),vy=vs(k+1,n);
if(k>n-k) swap(vx,vy);
int x=min(k,n-k),y=n-x;
bool fl=0;
if(k>n-k) fl=(vs(1,k-x)==vs(x+1,k)&&vs(1,x)==vx);
else fl=(vs(k+1,n-x)==vs(k+x+1,n)&&vs(k+1,k+x)==vx);
if(fl) {
for(int i=1;i<=m;) {
if(i+y-1==m&&vt(i,i+y-1)==vy) i+=y;
else if(i+x+y-1<=m&&vt(i,i+y-1)==vy&&vt(i+y,i+y+x-1)==vx) i+=y;
else if(i+x-1<=m&&vt(i,i+x-1)==vx) i+=x;
else return false;
}
} else {
for(int i=1;i<=m;) {
if(i+y-1<=m&&vt(i,i+y-1)==vy) i+=y;
else if(i+x-1<=m&&vt(i,i+x-1)==vx) i+=x;
else return false;
}
}
return true;
};
string ans;
for(int i=1;i<n;++i) ans+="01"[qry(i)];
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
B=rnd()|1;
for(int c=0;c<26;++c) hc[c]=rnd()|1;
for(int i=pw[0]=1;i<MAXN;++i) pw[i]=pw[i-1]*B;
int _; cin>>_;
while(_--) solve();
return 0;
}
T. [CF2061F2] Kevin and Binary String (4.5)
考虑确定 \(t\) 的情况:那么首先 \(t\) 开头的 \(0\) 一定 \(s\) 开头的若干段 \(0\),然后这些段中间的 \(1\) 段就会连起来放到后面,重复这个过程即可。
根据上述过程,我们可以知道合法的方案一定是把 \(s\) 中的段划分成若干个区间,每个区间间隔地变成 \(00\cdots 011\cdots 1\) 或 \(11\cdots 100\cdots 0\),并且操作次数就是每段长度除以二下取整。
考虑如何判断一个区间 \([i,j]\) 可以变成 \(00\cdots 011\cdots 1\),首先知道 \(i\) 就知道 \(0\) 串的开头,那么可以向后填若干个 \(0\) 的 \(j\) 是一个区间,可以二分。同理已知 \(j\) 可以向前填若干个 \(1\) 的 \(i\) 也是一个区间。
那么转移形如 \(f_{j,c}=\max \{f_{i,1-c}+(j-i)\bmod 2\mid j\le p_i,i\ge q_j\}\),容易用数据结构优化转移。
时间复杂度 \(\mathcal O(n\log n)\) 或 \(\mathcal O(n\log^2n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=4e5+5,inf=1e9;
int n,m,f[MAXN][2],a[MAXN],b[MAXN],c[MAXN][2],d[MAXN][2],L[MAXN][2],R[MAXN][2];
string s,t;
struct FenwickTree {
int tr[MAXN],s,k;
void init(int o) { k=o,fill(tr+1,tr+k+1,-inf); }
void upd(int x,int v) { for(;x;x&=x-1) tr[x]=max(tr[x],v); }
int qry(int x) { for(s=-inf;x<=k;x+=x&-x) s=max(s,tr[x]); return s; }
} T[2];
basic_string <int> e[MAXN];
void cdq(int l,int r) {
if(l==r) return ;
int mid=(l+r)>>1;
cdq(l,mid);
for(int o:{0,1}) {
T[0].init(mid-l+1),T[1].init(mid-l+1);
for(int i=l;i<=mid;++i) if(L[i][o]>mid) e[min(r,L[i][o])].push_back(i);
for(int i=r;i>mid;--i) {
for(int x:e[i]) T[x&1].upd(x-l+1,f[x][o]);
if(R[i][o]<=mid) for(int h:{0,1}) {
f[i][o^1]=max(f[i][o^1],T[h].qry(max(l,R[i][o])-l+1)+((i&1)^h));
}
e[i].clear();
}
}
cdq(mid+1,r);
}
void solve() {
cin>>s>>t,n=s.size(),s=" "+s,t=" "+t,m=0;
for(int i=1,j=0;i<=n;++i) if(i==n||s[i]!=s[i+1]) ++m,a[m]=i-j,b[m]=s[i]-'0',j=i;
for(int i=1;i<=m;++i) for(int o:{0,1}) c[i][o]=c[i-1][o]+(b[i]==o?a[i]:0);
for(int i=1;i<=n;++i) for(int o:{0,1}) d[i][o]=d[i-1][o]+(t[i]=='0'+o);
for(int i=1;i<=n;++i) f[i][0]=f[i][1]=-inf;
for(int i=0;i<=m;++i) {
int u=c[i][0]+c[i][1];
L[i][0]=L[i][1]=R[i][0]=R[i][1]=i;
for(int o:{0,1}) {
for(int l=i+1,r=m;l<=r;) {
int j=(l+r)>>1;
if(d[u+c[j][o^1]-c[i][o^1]][o]==d[u][o]) L[i][o]=j,l=j+1;
else r=j-1;
}
for(int l=0,r=i-1;l<=r;) {
int j=(l+r)>>1;
if(d[u+c[j][o]-c[i][o]][o^1]==d[u][o^1]) R[i][o]=j,r=j-1;
else l=j+1;
}
}
}
cdq(0,m);
int z=max(f[m][0],f[m][1]);
cout<<(z<0?-1:(m-z)/2)<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
*U. [CF2062H] Galaxy Generator (7.5)
首先考虑求单个 \(f\):从上往下扫描线,取出每行最左最右的点,如果构成的区间和之前的某个区间有相交,那么就能把这两行连在一起,并且把区间并起来。
直接 \(dp_{i,s}\) 记录前 \(i\) 行,\(s\) 中的列未被覆盖,但是这样不好计算连通块数量。
考虑每个连通块的形状:每个连通块对应的都是一个连续的若干行和若干列,即一个子矩形,且任意两个连通块之间没有公共行列。
如果能够对于每个子矩形算出内部的点恰好能生成该矩形的方案数 \(f_{l,r,u,d}\),那么处理 \(dp_{i,s}\) 的转移比较平凡,只要分讨第 \(i\) 行是否有点,如果有的话,枚举所在矩形的左右和下边界即可。
计算 \(f_{l,r,u,d}\) 采用容斥,如果构成多个连通块或连通块 \(\ne [l,r]\times[u,d]\),那么枚举最靠上的一个矩形 \([l',r']\times [u',d']\)。
剩下部分的方案数就是 \([r'+1,r]\) 这些行,只有 \([u,u')\cup(d',d]\) 的列未被覆盖,求方案数,这里的转移和 \(dp_{i,s}\) 一致。
时间复杂度 \(\mathcal O(n^8+2^nn^5)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
int n,a[16][16],pw[256],f[16][16][16][16],g[16][16][1<<14],dp[16][1<<14];
void solve() {
cin>>n;
for(int i=pw[0]=1;i<=n*n;++i) pw[i]=pw[i-1]*2%MOD;
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) {
char c; cin>>c,a[i][j]=c-'0';
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
}
memset(f,0,sizeof(f)),memset(g,0,sizeof(g)),memset(dp,0,sizeof(dp));
for(int i=0;i<=n;++i) for(int s=0;s<(1<<n);++s) g[i+1][i][s]=1;
for(int l=n;l>=1;--l) for(int r=l;r<=n;++r) {
for(int x=n;x>=1;--x) for(int y=x,o=0;y<=n;++y) {
o|=1<<(y-1);
int &z=f[l][r][x][y]=pw[a[r][y]-a[r][x-1]-a[l-1][y]+a[l-1][x-1]]-1;
for(int L=l;L<=r;++L) for(int R=L;R<=r;++R) {
for(int X=x;X<=y;++X) for(int Y=X,s=o;Y<=y;++Y) {
if(R-L==r-l&&Y-X==y-x) continue;
s^=1<<(Y-1),z=(z+1ll*(MOD-f[L][R][X][Y])*g[R+1][r][s])%MOD;
}
}
}
memcpy(g[l][r],g[l+1][r],sizeof(g[l][r]));
for(int x=1;x<=n;++x) for(int y=x,o=0;y<=n;++y) {
o|=1<<(y-1);
for(int s=o;s<(1<<n);s=(s+1)|o) for(int d=l;d<=r;++d) {
g[l][r][s]=(g[l][r][s]+1ll*f[l][d][x][y]*g[d+1][r][s^o])%MOD;
}
}
}
for(int i=1;i<=n;++i) {
memcpy(dp[i],dp[i-1],sizeof(dp[i]));
for(int x=1;x<=n;++x) for(int y=x,o=0;y<=n;++y) {
o|=1<<(y-1);
for(int s=o;s<(1<<n);s=(s+1)|o) for(int d=i;d>=1;--d) {
dp[i][s]=(dp[i][s]+1ll*f[d][i][x][y]*(dp[d-1][s^o]+g[1][d-1][s^o]))%MOD;
}
}
}
cout<<dp[n][(1<<n)-1]<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
*V. [CF2066F] Curse (9)
记 MSS 表示和最大的子段,注意到两个 MSS 有交,则他们的并也是 MSS,因此取出所有极长 MSS,他们两两不交。
显然第一步操作肯定会选择一个极长的 MSS,否则替换成包含其的极长 MSS 不劣。
不妨假设我们把这些 MSS 都变成了 \([-\infty]\),然后递归求出剩余的 MSS 直到序列全是 \(-\infty\)。
此时我们把原序列划分成若干段,一个很强的观察是:操作段 \([l,r]\) 后,新的分段中不存在越过 \((l-1,l)\) 或 \((r,r+1)\) 的段。
具体来说可以假设存在段 \([x,y]\) 使得 \(x<l\le y\),则 \(a[x,l)\) 的和非负,那么原序列中 \([l,r]\) 段应该拓展到 \([x,r]\)。
所以每个段之间是相对独立的,因此答案一定是每个段分别变成 \(b\) 的一个子区间。
考虑怎样的分配方式是合法的:首先被操作过的段权值是从大到小的一个前缀,取出和最小的一个段,设为 \(x\)。
首先和 \(<x\) 的段必须匹配完全相等的子段,而和 \(\ge x\) 的段首次操作必须变成 MSS \(\le x\) 的段,且在此之后的操作也要修改成 MSS \(\le x\) 的段,除非是最后一次操作,否则会影响后续的操作无法进行。
那么必要条件是和 \(\ge x\) 的子段至多匹配一个 MSS \(>x\) 的段。
如果这个匹配 MSS \(>x\) 的段原本的和恰好是 \(x\),那么从大到小依次操作每个段即可。
否则我们可以把和 \(>x\) 的段全部变成 \([x]\),然后再进行此操作。
那么枚举 \(x\) 设 \(f_{i,j,0/1}\) 表示前 \(i\) 个段匹配 \(b[1,j]\),容易把转移优化到 \(\mathcal O(nm)\)。
时间复杂度 \(\mathcal O(n^2m)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=505;
int n,m,a[MAXN],b[MAXN],s[MAXN],w[MAXN][MAXN],e[MAXN][MAXN][2];
struct seg { int l,r; };
vector <seg> S;
bitset <MAXN> f[2],g[2];
void dfs(int l,int r) {
if(l>r) return ;
int u=0,v=0;
for(int x=l;x<=r;++x) for(int y=r;y>=x;--y) {
if(!u||s[y]-s[x-1]>s[v]-s[u-1]) u=x,v=y;
}
dfs(l,u-1),S.push_back({u,v}),dfs(v+1,r);
}
void solve() {
cin>>n>>m,S.clear();
for(int i=1;i<=n;++i) cin>>a[i],s[i]=s[i-1]+a[i];
for(int i=1;i<=m;++i) cin>>b[i];
dfs(1,n);
for(int i=1;i<=m;++i) {
for(int j=i+1,z=w[i][i]=b[i];j<=m;++j) {
w[i][j]=max(w[i][j-1],z=max(z,0)+b[j]);
}
}
for(auto t:S) {
int x=s[t.r]-s[t.l-1],q=0;
f[0].reset(),f[1].reset(),f[0].set(1);
for(auto z:S) {
int l=z.l,r=z.r;
g[0].reset(),g[1].reset();
if(s[r]-s[l-1]<x) {
for(int i=1;i-l+r<=m;++i) {
for(int j=l;j<=r;++j) if(a[j]!=b[i+j-l]) goto fl;
for(auto o:{0,1}) if(f[o][i]) g[o].set(i-l+r+1),e[q][i-l+r+1][o]=i;
fl:;
}
} else {
for(int i=1;i<=m;++i) if(f[0][i]) {
for(int j=i+1;j<=m+1;++j) g[1].set(j),e[q][j][1]=-i;
break;
}
for(int i=1,j=0,h[2]={0,0};i<=m;++i) {
for(j=max(j,i-1);j<m&&w[i][j+1]<=x;++j);
for(int o:{0,1}) if(f[o][i]&&j+1>h[o]) {
for(int k=max(i+1,h[o]+1);k<=j+1;++k) g[o].set(k),e[q][k][o]=i;
h[o]=j+1;
}
}
}
swap(f[0],g[0]),swap(f[1],g[1]),++q;
}
if(!f[0][m+1]&&!f[1][m+1]) continue;
vector <seg> T(q);
for(int i=q-1,c=f[1][m+1],p=m+1,o;~i;--i) {
o=e[i][p][c],T[i]={abs(o),p-1},c&=o>0,p=abs(o);
}
vector <int> len(q),sg;
for(int i=0;i<q;++i) {
len[i]=S[i].r-S[i].l+1;
if(s[S[i].r]-s[S[i].l-1]>=x) sg.push_back(i);
}
cout<<sg.size()*2<<"\n";
auto id=[&](int p) { return 1+accumulate(len.begin(),len.begin()+p,0); };
sort(sg.begin(),sg.end(),[&](int i,int j){ return s[S[i].r]-s[S[i].l-1]>s[S[j].r]-s[S[j].l-1]; });
for(int i:sg) {
cout<<id(i)<<" "<<id(i)+len[i]-1<<" 1\n"<<x<<"\n",len[i]=1;
}
sort(sg.begin(),sg.end(),[&](int i,int j){ return w[T[i].l][T[i].r]<w[T[j].l][T[j].r]; });
for(int i:sg) {
int l=T[i].l,r=T[i].r;
cout<<id(i)<<" "<<id(i)+len[i]-1<<" "<<r-l+1<<"\n",len[i]=r-l+1;
for(int j=l;j<=r;++j) cout<<b[j]<<" \n"[j==r];
}
return ;
}
cout<<"-1\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
W. [CF2077F] AND x OR (3.5)
刻画好的序列对:首先 \(a=b\) 时必定是好的,否则考虑最后一次操作,必定有 \(b_i\subseteq x\subseteq b_j\)。
因此 \(b\) 中必定有两个成包含关系的数,可以此时用如下方法构造:\(\forall k\ne i,j\),操作 \((k,i,0),(i,k,b_k)\) 使得 \(a_k=b_k\),最后剩下 \(a_i,a_j\),操作 \((i,j,0),(j,i,0),(j,ib_i),(i,j,b_j)\) 即可。
\(a=b\) 情况平凡,只要求最小的 \(x+y\) 使得 \(b_i+x\subseteq b_j+y\)。
考虑最短路:第一层起点向 \(b_i\) 连边,\(u\to u+1\) 连边,第二层 \(u\) 向超集连边,第三层 \(u\to u-1\) 连边,\(b_i\) 向终点连边。
很显然答案就是图上最短路,要求起点终点不是同一个 \(b_i\)。
只要维护到每个点的最短路,以及颜色不同的次短路,第二次转移很显然可以 FWT 维护。
时间复杂度 \(\mathcal O(m\log m)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e6+5,inf=1e9;
struct info {
int v1,c1,v2,c2;
inline info add() { return {v1+1,c1,v2+1,c2}; }
inline friend void operator +=(info &u,info v) {
if(u.v1>v.v1) swap(u,v);
if(v.c1!=u.c1&&v.v1<u.v2) u.v2=v.v1,u.c2=v.c1;
if(v.c2!=u.c1&&v.v2<u.v2) u.v2=v.v2,u.c2=v.c2;
}
} f[1<<21];
int n,m,a[MAXN],b[MAXN];
void solve() {
cin>>n>>m,m=1<<(__lg(m)+1);
for(int i=0;i<m;++i) f[i]={inf,0,inf,0};
ll ans=0;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=n;++i) {
cin>>b[i],ans+=abs(a[i]-b[i]);
if(!f[b[i]].c1) f[b[i]].c1=i,f[b[i]].v1=0;
else if(!f[b[i]].c2) f[b[i]].c2=i,f[b[i]].v2=0;
}
for(int i=1;i<m;++i) f[i]+=f[i-1].add();
for(int k=1;k<m;k<<=1) for(int i=0;i<m;++i) if(i&k) f[i]+=f[i^k];
for(int i=m-1;i;--i) f[i-1]+=f[i].add();
for(int i=1;i<=n;++i) {
ans=min(ans,(ll)(f[b[i]].c1==i?f[b[i]].v2:f[b[i]].v1));
}
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
X. [CF2081E] Quantifier (4.5)
首先可以感受到整个过程就是先把每个点尽可能往下放,然后自下而上地归并。
很显然从上往下的过程是唯一的,只要贪心找到最深的边即可。
然后考虑归并:比较麻烦的一点是儿子上开头的点和父亲上结尾的点如果同色,那么这些点之间可以混合,其他情况基本按照树上的拓扑序进行。
那么我们只要记录 \(f_{u,i,c}\) 表 \(u\) 子树内合并完成后,开头的颜色 \(c\) 以及长度 \(i\),合并时就枚举段的开头长度即可。
此时处理 \(f_{u,i,c}\times f_{v,j,1-c}\) 的情况需要枚举新的开头长度,无法接受。
注意到如果新的开头颜色为 \(c\),那么不关心 \(j\),所以记 \(f_{u,0,c}=\sum_{i>0} f_{u,i,c}\),然后就只要处理同色的转移了,复杂度可以接受。
时间复杂度 \(\mathcal O(n^2)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5005,MOD=998244353;
int fac[MAXN],ifac[MAXN];
int ksm(int a,int b=MOD-2) { int s=1; for(;b;b>>=1,a=1ll*a*a%MOD) if(b&1) s=1ll*s*a%MOD; return s; }
int C(int x,int y) { return y<0||y>x?0:1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
void mul(vector<int>&f,vector<int>&g,int sa,int sb) {
int sf=f.size(),sg=g.size();
vector <int> q(sf+sg-1);
for(int i=0;i<sf&&i<sa;++i) if(f[i]) {
for(int j=sg-1,w=0;~j;--j) {
w=(w+g[j])%MOD,q[i+j]=(q[i+j]+1ll*f[i]*w%MOD*C(i+j,i)%MOD*C(sa-i-1+sb-j,sb-j))%MOD;
}
}
for(int i=0;i<sg&&i<sb;++i) if(g[i]) {
for(int j=sf-1,w=0;~j;--j) {
w=(w+f[j])%MOD,q[i+j]=(q[i+j]+1ll*g[i]*w%MOD*C(i+j,i)%MOD*C(sb-i-1+sa-j,sa-j))%MOD;
}
}
if(sa<sf&&sb<sg) q[sa+sb]=(q[sa+sb]+1ll*f[sa]*g[sb]%MOD*C(sa+sb,sa))%MOD;
f.swap(q),vector<int>().swap(g);
}
vector <int> f[MAXN][2],a[MAXN],G[MAXN];
int n,m,c[MAXN],d[MAXN],fa[MAXN],siz[MAXN];
bool vis[MAXN][2];
void dfs(int u) {
f[u][0]=f[u][1]={1},siz[u]=0;
for(int v:G[u]) {
dfs(v);
for(int o:{0,1}) mul(f[u][o],f[v][o],siz[u],siz[v]);
siz[u]+=siz[v];
}
for(int o:a[u]) {
auto &g=f[u][o]; int s=0;
g.insert(g.begin(),0);
for(int i=1;i<(int)g.size();++i) g[i]=1ll*g[i]*i%MOD,s=(s+g[i])%MOD;
f[u][o^1]={s},++siz[u];
}
}
void solve() {
cin>>n>>m;
for(int i=0;i<=n;++i) a[i].clear(),G[i].clear(),vis[i][0]=vis[i][1]=0;
for(int i=1;i<=n;++i) cin>>fa[i],G[fa[i]].push_back(i);
for(int i=1;i<=m;++i) cin>>c[i];
for(int i=1;i<=m;++i) cin>>d[i];
for(int i=m;i;--i) {
int x=d[i];
for(int u=d[i];u;u=fa[u]) if(vis[u][c[i]^1]) x=u;
a[x].push_back(c[i]),vis[x][c[i]]=true;
}
dfs(0);
cout<<(f[0][0][0]+f[0][1][0])%MOD<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int i=fac[0]=1;i<MAXN;++i) fac[i]=1ll*i*fac[i-1]%MOD;
ifac[MAXN-1]=ksm(fac[MAXN-1]);
for(int i=MAXN-1;i;--i) ifac[i-1]=1ll*i*ifac[i]%MOD;
int _; cin>>_;
while(_--) solve();
return 0;
}
Y. [CF2084G2] Wish Upon a Satellite (4.5)
首先分析这个游戏,先二分转成 \(0,1\),注意到先手无法主动删除 \(1\),后手无法主动删除 \(0\),因此序列两头的元素几乎不会变。
具体来说 \(n\) 是奇数时只要两头有一个 \(1\),那么先手每次操作都可以避开这个 \(1\),否则后手最后一个操作前避开两头的 \(0\) 即可。
\(n\) 是偶数反过来,要求两头都是 \(1\),先手最后一个操作前避开两头的 \(1\) 即可。
那么 \(n\) 是奇数时答案为 \(\max(a_1,a_n)\),否则为 \(\min(a_1,a_n)\)。
然后考虑插入 dp,\(f_{i,j}\) 表示 \(1\sim i\) 中有 \(j\) 个插在奇数位置上。
那么写出转移,简单变形一下,可以把转移写成如下两种操作:
- \(f_{j}\gets f_j+w_i(j)\),其中 \(w_i(x)\) 是一个一次函数。
- \(f_j\gets \min(f_j,f_{j-1}+q_i(j))\),此时 \(q_i(x)\) 也是一次函数。
考虑维护 \(f\) 的差分数组 \(d_j=f_j-f_{j-1}\),第一个操作相当于全局加,第二个操作就是找到第一个 \(d_j<q_i(j)\) 的位置插入一个 \(q_i(j)\)。
那么用平衡树维护之,二分的时候动态维护当前点的排名即可。
时间复杂度 \(\mathcal O(n\log n)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5;
const ll inf=1e18;
mt19937 rnd(time(0));
int n,a[MAXN];
ll f[MAXN],tg[MAXN];
int pri[MAXN],siz[MAXN],ls[MAXN],rs[MAXN];
void psu(int p) { siz[p]=siz[ls[p]]+siz[rs[p]]+1; }
void adt(int p,ll k) { if(p) f[p]+=k,tg[p]+=k; }
void psd(int p) { adt(ls[p],tg[p]),adt(rs[p],tg[p]),tg[p]=0; }
void split(int p,ll k,ll b,int &x,int &y) {
if(!p) return x=y=0,void();
ll w=b+(siz[ls[p]]+1)*k; psd(p);
if(f[p]>=w) x=p,split(rs[p],k,b+k*(siz[ls[p]]+1),rs[x],y),psu(x);
else y=p,split(ls[p],k,b,x,ls[y]),psu(y);
}
int merge(int x,int y) {
if(!x||!y) return x|y;
psd(x),psd(y);
if(pri[x]<pri[y]) return rs[x]=merge(rs[x],y),psu(x),x;
else return ls[y]=merge(x,ls[y]),psu(y),y;
}
void dfs(int x,vector<ll>&o) {
if(!x) return ;
psd(x),dfs(ls[x],o),o.push_back(f[x]),dfs(rs[x],o);
}
void solve() {
cin>>n;
for(int i=1;i<=n;++i) f[i]=tg[i]=siz[i]=ls[i]=rs[i]=a[i]=0,pri[i]=rnd();
for(int i=1,x;i<=n;++i) cin>>x,a[x]=i;
int q=0,rt=0; ll z=0;
for(int i=1,h=0;i<=n;++i) {
if(!a[i]) {
z+=1ll*i*((n+1)/2+i-2*q),adt(rt,-2*i);
int x,y;
split(rt,4*i,1ll*i*(4*q-(n&1)-2*i-2),x,y);
siz[++h]=1,f[h]=1ll*i*(4*(q+siz[x]+1)-(n&1)-2*i-2);
adt(y,4*i),rt=merge(x,merge(h,y));
} else if(a[i]&1) {
++q,z+=1ll*i*(n/2-i+2*q),adt(rt,2*i);
} else {
z+=1ll*i*((n+1)/2+i-2*q),adt(rt,-2*i);
}
}
vector <ll> o; dfs(rt,o);
for(int i=0;i<(n+1)/2-q;++i) z+=o[i];
cout<<z<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _; cin>>_;
while(_--) solve();
return 0;
}
Z. [CF2101F] Shoo Shatters the Sunshine (4.5)
考虑把根定为蓝色点的直径中点,则答案就是最深的蓝色点加上最深的红色点。
枚举根从下到上逐层 dp,记录是否加入过蓝色点和红色点,转移系数简单容斥一下即可计算。
时间复杂度 \(\mathcal O(n^2)\)。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=6005,MOD=998244353;
vector <int> G[MAXN],a[MAXN];
ll p2[MAXN],p3[MAXN];
int b[MAXN];
void solve() {
int n; cin>>n;
for(int i=1;i<2*n;++i) G[i].clear();
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(n+i),G[v].push_back(n+i),G[n+i]={u,v};
ll ans=0;
for(int rt=1;rt<2*n;++rt) {
int m=0;
for(int u:G[rt]) {
int k=0;
function<void(int,int,int)> dfs=[&](int x,int fz,int d) {
if(d>k) k=d,a[d].push_back(0);
if(x<=n) ++a[d].back(),++b[d];
for(int y:G[x]) if(y^fz) dfs(y,x,d+1);
};
dfs(u,rt,1),m=max(m,k);
}
array<ll,6> f={0,0,0,0,0,0},g;
for(int i=m;i>=1;--i) if(b[i]) {
int c=b[i];
g[0]=(f[0]*p2[c]+(p2[c]-1))%MOD;
g[3]=(f[3]*p2[c]+(p2[c]-1)*i)%MOD;
ll w1=p2[c]-1,w2=p3[c]+MOD-p2[c];
for(int x:a[i]) {
w1=(w1-(p2[x]-1))%MOD;
w2=(w2-(p3[x]-p2[x])*p2[c-x])%MOD;
}
w1=(w1+MOD)%MOD,w2=(w2+MOD)%MOD;
g[1]=(f[1]*p2[c]+w1)%MOD;
g[4]=(f[4]*p2[c]+w1*i)%MOD;
g[2]=(f[2]*p3[c]+f[0]*w2+f[1]*(p3[c]+MOD-p2[c])+(w2+MOD-w1))%MOD;
g[5]=(f[5]*p3[c]+f[3]*w2+f[4]*(p3[c]+MOD-p2[c])+(f[0]*w2+f[1]*(p3[c]+MOD-p2[c])+(w2+MOD-w1)*2)%MOD*i)%MOD;
f.swap(g);
}
if(rt<=n) ans=(ans+f[5]*3+f[3]+f[4])%MOD;
else ans=(ans+f[5])%MOD;
for(int i=1;i<=m;++i) a[i].clear(),b[i]=0;
}
ans=(MOD+1)/2*ans%MOD;
cout<<ans<<"\n";
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int i=p2[0]=p3[0]=1;i<MAXN;++i) p2[i]=p2[i-1]*2%MOD,p3[i]=p3[i-1]*3%MOD;
int _; cin>>_;
while(_--) solve();
return 0;
}

浙公网安备 33010602011771号