2025 NOI 做题记录(二)
\(\text{By DaiRuichen007}\)
Round #65 - 20250326
A. [AT-CF17-F] Distribute Numbers
题目大意
选择一个 \(n\in[1000,2000]\),以及一个 \(k\),构造 \(n\) 个大小为 \(k\) 的 \([1,n]\) 子集,使得任意两个集合的交集大小为 \(1\),且每个元素出现 \(k\) 次。
思路分析
把有交的集合连边,那么相当于把 \(K_n\) 分解成 \(n\) 个 \(K_k\),考虑边数:\(\dfrac {n(n-1)}2=n\dfrac{k(k-1)}2\),则 \(n=k(k-1)+1\)。
不妨考虑第 \(n\) 个点所在的集合,那么会把 \([1,n-1]\) 分成 \(k\) 个组,不妨设为 $[1,k-1],[k,2(k-1)],\dots $。
那么剩下的每个集合在每个组中都恰好选出一个元素,且每个元素被选 \(k-1\) 次。
可以发现对于每对 \((i,j)\) 第一组的第 \(i\) 个点和第二组的第 \(j\) 个点恰在一个组中出现过。
那么一种合法的构造就是对于 \((i,j)\), 选出第 \(3\) 组的第 \((j+i)\bmod {k-1}\) 个点,第 \(4\) 组的 \((j+2i)\bmod(k-1)\) 个点。
那么两个组的交点就是 \(j_1+xi_1\equiv j_2+xi_2\pmod{k-1}\) 的解,容易发现 \(k-1\) 为质数时解唯一,取 \(k=38\) 即可。
代码呈现
#include<bits/stdc++.h>
using namespace std;
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int k=38,n=k*(k-1)+1;
cout<<n<<" "<<k<<"\n";
for(int i=0;i<k;++i) {
for(int j=1;j<k;++j) cout<<i*(k-1)+j<<" ";
cout<<n<<"\n";
}
for(int i=0;i<k-1;++i) for(int j=0;j<k-1;++j) {
cout<<i+1<<" ";
for(int t=1,u=j;t<k;++t,u=(u+i)%(k-1)) cout<<t*(k-1)+u+1<<" \n"[t==k-1];
}
return 0;
}
B. [AT-CF16-E] Water Distribution
题目大意
给定平面上 \(n\) 个点 \(p_1\sim p_n\),第 \(i\) 个点上有水量 \(w_i\),可以。
从处取 \(p_i\) 处取 \(v\) 容量的水运送到 \(p_j\),会使得 \(w_j\) 增加 \(\max(0,v-\mathrm{dis}(p_i,p_j))\),\(\mathrm{dis}\) 为欧几里得距离。
最大化 \(\min w_i\)。
数据范围:\(n\le 16\)。
思路分析
把所有的操作对应的边 \((i,j)\) 都写出来。
容易发现对于一个连通块,我们可以把他们内部的水量任意排列:取出一棵生成树,然后算出每条边的流量,容易发现一定有一种合法的操作顺序。
那么对于一个连通块 \(S\),我们会让内部的总水量 \(W\) 减少 \(T\),\(T\) 是 \(S\) 的平面最小生成树大小,最优方法一定是每个点平均水量,即对答案的贡献为 \(\dfrac{W-T}{|S|}\)。
然后对于不同的连通块,枚举子集合并即可。
时间复杂度 \(\mathcal O(3^n+n^22^n)\)。
代码呈现
#include<bits/stdc++.h>
#define ld long double
#define pc __builtin_popcount
using namespace std;
const ld inf=1e18;
int n,x[16],y[16],z[16];
ld d[16][16],c[1<<16],f[1<<16];
signed main() {
scanf("%d",&n);
for(int i=0;i<n;++i) scanf("%d%d%d",&x[i],&y[i],&z[i]);
for(int i=0;i<n;++i) for(int j=0;j<n;++j) {
d[i][j]=sqrt(1ll*(x[i]-x[j])*(x[i]-x[j])+1ll*(y[i]-y[j])*(y[i]-y[j]));
}
for(int s=0;s<(1<<n);++s) c[s]=inf;
for(int i=0;i<n;++i) c[1<<i]=0;
for(int s=1;s<(1<<n);++s) for(int i=0;i<n;++i) if(s>>i&1) {
for(int j=0;j<n;++j) if(!(s>>j&1)) {
c[s|1<<j]=min(c[s|1<<j],c[s]+d[i][j]);
}
}
for(int s=1;s<(1<<n);++s) {
ld w=0;
for(int i=0;i<n;++i) if(s>>i&1) w+=z[i];
if(w>c[s]) f[s]=(w-c[s])/pc(s);
for(int t=s&(s-1);t;t=(t-1)&s) f[s]=max(f[s],min(f[t],f[s^t]));
}
printf("%.20Lf\n",f[(1<<n)-1]);
return 0;
}
C. [CF983D] Arkady and Rectangles
题目大意
在无穷大二维网格上给出 \(n\) 个矩形,每个点的颜色是覆盖该点的矩形标号最大值,求有多少种颜色。
数据范围:\(n\le 10^5\)。
思路分析
首先肯定要扫描线,线段树套堆维护纵坐标每个区间上覆盖的矩形标号。
那么我们只要每次找出一个当前列可以被看到的颜色即可。
那么对线段树的每个节点维护其子树中可能被新看到的最大颜色。
然后对于每个节点 \([l,r]\),维护子树内可能被新看到的最大颜色 \(f\)。
转移时从左右子树取 \(\max f\),然后考虑当前节点上的最大颜色 \(c\),如果该 \(c\) 未看过则更新 \(f\),否则如果 \(f<c\) 则设 \(f=0\) 表示看不到任何新颜色。
但这个做法不一定正确,例如在其左子树中有一个颜色为 \(2\) 的矩形覆盖,右子树有一个颜色为 \(3\) 的矩形覆盖,且这两个颜色已看过,则在 \(c=1\) 时应该设 \(f=0\)。
因此设 \(g_i\) 表示覆盖 \(i\) 且已看过的颜色最大值,那么 \(c>\min g[l,r]\) 时才能更新 \(f\)。
那么同时维护 \(f,g\) 即可。
时间复杂度 \(\mathcal O(n\log n)\)。
代码呈现
#include<bits/stdc++.h>
using namespace std;
struct Heap {
priority_queue <int> qi,qo;
void ins(int x) { qi.push(x); }
void ers(int x) { qo.push(x); }
bool any() { return qi.size()>qo.size(); }
int top() {
while(qi.size()&&qo.size()&&qi.top()==qo.top()) qi.pop(),qo.pop();
return qi.top();
}
};
const int MAXN=1e5+5,MAXS=1<<19;
int n,m,L[MAXN],R[MAXN],st[MAXN*2];
bool vis[MAXN];
Heap tr[MAXS];
int mx[MAXS],mn[MAXS];
void psu(int p,bool o=1) {
mx[p]=o?max(mx[p<<1],mx[p<<1|1]):0;
mn[p]=o?min(mn[p<<1],mn[p<<1|1]):0;
if(tr[p].any()) {
int c=tr[p].top();
if(!vis[c]) mx[p]=max(mx[p],c);
else mn[p]=max(mn[p],c);
}
if(mx[p]<mn[p]) mx[p]=0;
}
void upd(int ul,int ur,int k,int o,int l=1,int r=m,int p=1) {
if(ul<=l&&r<=ur) {
if(o>0) tr[p].ins(k);
if(o<0) tr[p].ers(k);
psu(p,l<r);
return ;
}
int mid=(l+r)>>1;
if(ul<=mid) upd(ul,ur,k,o,l,mid,p<<1);
if(mid<ur) upd(ul,ur,k,o,mid+1,r,p<<1|1);
psu(p);
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
vector <array<int,2>> op;
for(int i=1,l,r;i<=n;++i) {
cin>>l>>L[i]>>r>>R[i],st[++m]=L[i],st[++m]=R[i];
op.push_back({l,i}),op.push_back({r,-i});
}
sort(st+1,st+m+1),m=unique(st+1,st+m+1)-st-1;
for(int i=1;i<=n;++i) {
L[i]=lower_bound(st+1,st+m+1,L[i])-st;
R[i]=lower_bound(st+1,st+m+1,R[i])-st;
}
int ans=0;
sort(op.begin(),op.end());
for(int i=0;i<2*n;++i) {
int x=abs(op[i][1]);
upd(L[x],R[x]-1,x,op[i][1]>0?1:-1);
if(i==2*n-1||op[i][0]<op[i+1][0]) {
while(mx[1]) x=mx[1],++ans,vis[x]=1,upd(L[x],R[x]-1,x,0);
}
}
cout<<ans+1<<"\n";
return 0;
}
*D. [AT-MJPC17-D] Oriented Tree
题目大意
给定 \(n\) 个点的树,给每条边定向使得对于每条路径 \(u\to v\),路径上和 \(u\to v\) 运动方向相反的边数最大值最小,求方案数。
数据范围:\(n\le 1000\)。
思路分析
首先考虑直径,设长度为 \(d\),则很显然答案 \(D\) 至少为 \(\lceil d/2\rceil\),构造很简单:按深度分层,奇数层的边向上偶数层的边向下。
首先我们需要用一个简洁的形式刻画 \(u\to v\) 权值:设 \(h_u\) 表示根到 \(u\) 路径上两种方向的边的个数,则权值为 \(\dfrac 12(|h_u-h_v|+\mathrm{dis}(u,v))\)。
则 \(|h_u-h_v|\le 2D-\mathrm{dis}(u,v)\),不妨设直径长度为 \(2D\),那么将直径中点定为根,则所有叶子 \(h_u\) 相同,不妨设都 \(=0\)。
那么此时一组合法的 \(h\) 恰好对应一组答案。
进一步刻画:如果 \(v\) 为某个和 \(u\) 属于根的不同子树的叶子,则 \(|h_u|\le D-\mathrm{dis}(rt,u)=D-dep_u\)。
容易证明该限制充分:\(|h_u-h_v|\le |h_u|+|h_v|\le D-dep_u+D-dep_v\le 2D-\mathrm{dis}(u,v)\)。
那么我们只要找到合法的 \(\{h\}\) 数组满足 \(|h_u|\le D-dep_u\),且任意相邻的 \((u,v)\) 都有 \(|h_u-h_v|=1\),简单 dp 即可。
如果 \(d\) 为奇数,那么设直径中点为 \((s,t)\),将这棵树分为左右两侧。
则两侧叶子的 \(h\) 不奇偶,那么任意两个叶子的 \(h\) 之差均为 \(1\)。
那么显然至少有一侧的叶子权值相同,设为左侧,则左侧 \(|h_u|\le D-dep_u\),右侧 \(|h_u|\le D+1-dep _u\)。
注意特判左右两侧叶子权值都相同的情况,会重复计数,即此时右侧点需满足 \(|h_u\pm 1|\le D_1-dep_u\)。
设 \(f_{u,i}\) 表示 \(h_u=i\) 的方案数 dp 即可。
时间复杂度 \(\mathcal O(n^2)\)。
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1025,MOD=1e9+7,W=505;
vector <int> G[MAXN];
int n,dep[MAXN],fa[MAXN];
void dfs1(int u) { for(int v:G[u]) if(v^fa[u]) fa[v]=u,dep[v]=dep[u]+1,dfs1(v); }
ll f[MAXN][MAXN];
void dfs2(int u,int D,int o) {
for(int v:G[u]) if(v^fa[u]) fa[v]=u,dep[v]=dep[u]+1,dfs2(v,D,o);
int L=dep[u]-D+W+o,R=D-dep[u]+W+o;
for(int i=L;i<=R;++i) {
f[u][i]=1;
for(int v:G[u]) if(v^fa[u]) f[u][i]=f[u][i]*(f[v][i-1]+f[v][i+1])%MOD;
}
}
ll sol(int s,int t,int D,bool op) {
memset(f,0,sizeof(f));
fa[s]=t,fa[t]=s,dep[s]=dep[t]=0;
if(!op) dfs2(s,D+1,0),dfs2(t,D,0);
else dfs2(s,D,0),dfs2(t,D,1);
ll ans=0;
for(int i=1;i<2*W;++i) ans=(ans+f[s][i]*(f[t][i-1]+f[t][i+1]))%MOD;
return ans;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
int s,t;
dfs1(1),s=max_element(dep+1,dep+n+1)-dep;
dep[s]=fa[s]=0,dfs1(s),t=max_element(dep+1,dep+n+1)-dep;
if(dep[t]%2==0) {
int rt=t,D=dep[t]/2;
for(int i=0;i<D;++i) rt=fa[rt];
dep[rt]=fa[rt]=0,dfs2(rt,D,0);
ll ans=0;
for(int i=1;i<2*W;++i) ans=(ans+f[rt][i])%MOD;
cout<<ans<<"\n";
} else {
int D=dep[t]/2;
for(int i=0;i<D;++i) t=fa[t];
s=fa[t];
ll ans=(sol(s,t,D,0)+sol(t,s,D,0)-sol(s,t,D,1)-sol(t,s,D,1))%MOD;
cout<<(ans+MOD)%MOD<<"\n";
}
return 0;
}
*E. [AGC065D] Not Intersect
题目大意
求圆上 \(n\) 个等分点连 \(m\) 条不相交弦的方案数。
数据范围:\(n\le 10^7\)。
思路分析
首先 \(m\) 最大值一定是三角剖分时取到,即 \(m\le 2n-3\)。
首先连接相邻点的弦无意义,只考虑连接非相邻点的弦。
从 \((1,n)\) 处断环为链,每条弦都唯一对应 \([1,n]\) 的子区间,只要这些区间两两包含或无交即可。
一种区间是否合法的检验方法是:从左往右扫描右端点 \(r\),对于每个区间 \([l,r]\),不能存在 \(x\le l\le y<r\) 的区间 \([x,y]\)。
那么用栈维护所有合法的 \(l\),每次加入 \([l,r]\) 就把 \(l+1\sim r-1\) 中的元素都弹出即可。
具体来说:不把 \(1\) 加入栈,且每次先加入 \(r\),再弹出 \(l+1\sim r-1\) 的元素,最后加入虚拟区间 \([1,n]\),这样一个序列合法当且仅当任何时候栈大小 \(>0\),且最终栈大小 \(=1\)。
容易发现任何一种栈的大小的变化序列和一组连边方案双射。
设有 \(k-1\) 条弦,则只要考虑有 \(n-1\) 个 \(+1\) 和 \(k\) 个负数,和为 \(1\) 且每个前缀和都 \(>0\) 的序列个数。
这是经典的 Raney 定理:对于每个序列,其所有循环移位中恰有一个合法。
这是显然的,合法循环移位一定是所有取到前缀和最小值的点中下标最大的一个。
那么方案数先给所有负数插板分配权值,再求所有整数位置。
设 \(i\) 为连接相邻点的弦个数,\(k=m-i+1\),则答案为:
时间复杂度 \(\mathcal O(n)\)。
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e7+5,MOD=1e9+7;
ll n,m,fac[MAXN],ifac[MAXN],inv[MAXN];
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 C(int x,int y) { return y<0||y>x?0:fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
signed main() {
inv[1]=1;
for(int i=2;i<MAXN;++i) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
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;
scanf("%lld%lld",&n,&m);
if(m>max(0ll,2*n-3)) return puts("0"),0;
if(n<=2) return puts("1"),0;
ll s=0;
for(int i=0;i<=n&&i<=m;++i) if(m-i<=n-3) {
int k=m-i+1;
s=(s+C(n,i)*C(n-3,k-1)%MOD*C(n-1+k,k)%MOD*inv[n-1+k])%MOD;
}
printf("%lld\n",s);
return 0;
}
*F. [UOJ823] 铁轨回收
题目大意
给定长度为 \(n\) 的序列 \(a,b\),进行如下操作:
- 对于 \(i=1\sim n-1\),令 \(j\) 为 \([i+1,n]\) 中的随机数,令 \(a_j\gets \min(b_j,a_i+a_j)\)。
求最终 \(a_n\) 的期望。
数据范围:\(n\le 50,a_i\le b_i\le 30\)。
思路分析
首先所有 \(i\to j\) 连边形成有根树,设 \(c_i\) 表示操作 \(1\sim i-1\) 后 \(a_i\) 的值。
考虑从后往前 dp,枚举 \(c_i\):
- 如果 \(c_i<b_i\),那么要求 \(i\) 的所有儿子 \(\sum c=b_i-a_i\)。
- 否则我们求假设该点 \(c_i=b_i\),减去实际 \(c_i<b_i\) 的方案数。
那么从后往前,考虑处理完 \([i+1,n]\) 的点会产生什么状态:
- 首先有一些点对儿子的 \(c\) 之和有限制,我们只关心所有限制 \(\sum c=v\) 的 \(v\) 构成的可重集 \(S\)。
- 而剩下的点就是无限制的点,只关心其个数 \(k\)。
设该状态为 \(f(i,S,k)\),考虑转移:
- 如果该点父亲为 \(k\) 个无限制点之一,那么我们也不关心该点的 \(c_i\),转移到 \(f(i-1,S,k+1)\),系数为 \(k\)。
- 如果 \(c_i<b_i\),那么选出 \(x\in S\),转移到 \(f(i-1,S\setminus x\cup \{x-c_i,c_i-a_i\},k)\)。
- 否则如果该点无限制,选出 \(x\in S\),转移到 \(f(i-1,S\setminus x\cup \{x-b_i\},k+1)\)。
- 然后容斥掉实际 \(c_i<b_i\),选出 \(x\in S\),转移到 \(f(i-1,S\setminus x\cup\{x-b_i,c_i-a_i\},k)\),系数为 \(-1\)。
可以发现 \(S\) 中元素总和单调不增,因此 \(S\) 种类数为 \(\pi(B_n)\) 级别。
注意到对于不同的 \(c_n\),我们的 dp 过程完全一致,只有初始值不同,那么转置原理倒过来就只要 dp 一次。
注意 \(S\) 中 \(=0\) 的元素不需要记录,且本质不同的 \(x\) 值只有 \(\sqrt{B_n}\) 级别。
时间复杂度 \(\mathcal O(n^2B_n^{1.5}\pi(B_n))\)。
代码呈现
#include<bits/stdc++.h>
#define ll long long
#define stat array<int,32>
using namespace std;
const int MAXS=30005,MOD=998244353;
ll ksm(ll a,ll b=MOD-2,ll p=MOD) {
ll ret=1;
for(;b;a=a*a%p,b=b>>1) if(b&1) ret=ret*a%p;
return ret;
}
map <stat,int> idx;
stat sta[MAXS];
int n,V,m=0,a[55],b[55],sa[55],sts[MAXS],stc[MAXS];
int ins[MAXS][35],ers[MAXS][35];
void dfs(int i,int s,int c,stat S) {
if(i>V) return sta[++m]=S,sts[m]=s,stc[m]=c,idx[S]=m,void();
for(int j=0;s+i*j<=V&&c+j<=n;++j) {
S[i]=j,dfs(i+1,s+i*j,c+j,S);
}
}
int dp[55][55][MAXS];
void DP() {
for(int j=0;j<=n;++j) dp[0][j][1]=1;
for(int i=1;i<n;++i) for(int j=0;j<=n-i;++j) {
int *f0=dp[i-1][j],*f1=dp[i-1][j+1];
for(int s=1;s<=m;++s) {
if(stc[s]>i||stc[s]+j>n-i||sts[s]>sa[i]) continue;
stat &S=sta[s];
ll val=1ll*j*f1[s];
for(int x=a[i];x<=V;++x) {
int t=ers[s][x],c=x?S[x]:n-i-j-stc[s];
ll tmp=0; if(!c) continue;
for(int y=a[i];y<=x&&y<b[i];++y) {
tmp+=f0[ins[ins[t][x-y]][y-a[i]]];
}
if(x>=b[i]) {
t=ins[t][x-b[i]];
for(int y=a[i];y<b[i];++y) {
tmp-=f0[ins[t][y-a[i]]];
}
tmp+=f1[t];
}
val+=tmp*c;
}
val%=MOD;
if(val<0) val+=MOD;
dp[i][j][s]=val;
}
}
}
signed main() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d%d",&a[i],&b[i]),sa[i]=sa[i-1]+a[i];
stat S; S.fill(0),V=b[n];
dfs(1,0,0,S);
for(int i=1;i<=m;++i) {
S=sta[i];
for(int j=1;j<=V;++j) {
if(S[j]) --S[j],ers[i][j]=idx[S],++S[j];
if(stc[i]<n&&sts[i]+j<=V) ++S[j],ins[i][j]=idx[S],--S[j];
}
ers[i][0]=ins[i][0]=i;
}
DP();
ll inv=ksm(dp[n-1][1][1]),tot=1;
for(int i=0;i<V;++i) {
if(i<a[n]) {
printf("0 ");
continue;
}
S.fill(0);
if(i>a[n]) S[i-a[n]]=1;
ll ans=dp[n-1][0][idx[S]]*inv%MOD;
tot=(tot+MOD-ans)%MOD;
printf("%lld ",ans);
}
printf("%lld\n",tot);
return 0;
}
Round #66 - 20250327
A. [AT-HTC20-D] Manga Market
题目大意
给定 \(n\) 个商店,在相邻商店间移动的耗时为 \(1\),在某个商店购物的耗时为 \(a_it+b_i\),\(t\) 为此前总耗时。
求 \(m\) 个时刻内最多在多少个不同的商店购物。
数据范围:\(n\le 10^5,m\le 10^9\)。
思路分析
可以发现每个商店会让 \(t\gets t+1+(t+1)a_i+b_i\),设 \(c_i=a_i+b_i+1\),则 \(t\gets (a_i+1)t+c_i\)。
如果 \(a_i>0\),很显然 \(t\) 至少翻倍,至多选 \(\log m\) 个元素。
对于这些元素之间的顺序,显然可以 Exchange Argument,\(i\) 比 \(j\) 优当且仅当 \((a_j+1)((a_i+1)t+c_i)+c_j<(a_i+1)((a_j+1)t+c_j)+c_i\),化简得到 \(a_jc_i<a_ic_j\)。
因此排序后暴力 dp,\(f_{i,j}\) 表示 \([1,i]\) 中选 \(j\) 个元素的最小代价。
最后 \(a_i=0\) 的元素二分求能选几个即可。
时间复杂度 \(\mathcal O(n\log m)\)。
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,inf=1e9+7;
struct info {
ll a,b;
inline bool operator <(const info &o) {
if(!a&&!o.a) return b<o.b;
if(!a||!o.a) return !o.a;
return a*o.b>b*o.a;
}
} a[MAXN];
ll f[32],c[MAXN];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,z;
cin>>n>>z;
for(int i=1;i<=n;++i) cin>>a[i].a>>a[i].b,a[i].b+=a[i].a+1;
sort(a+1,a+n+1);
for(int i=0;i<32;++i) f[i]=i?inf:0;
int h=0;
while(h<n&&a[h+1].a) ++h;
for(int i=1;i<=h;++i) {
for(int j=30;~j;--j) {
f[j+1]=min(f[j+1],f[j]*(a[i].a+1)+a[i].b);
}
}
for(int i=1;i<=n-h;++i) c[i]=c[i-1]+a[i+h].b;
int s=0;
for(int i=0;i<32;++i) if(f[i]<=z) {
s=max(s,int(upper_bound(c+1,c+n-h+1,z-f[i])-c-1+i));
}
cout<<s<<" ";
return 0;
}
B. [QOJ6170] 凸多边形正则划分问题
题目大意
对于给定的 \(n,k\),求凸 \(nk-2(n-1)\) 边形的 \(k\) 角剖分方案数。
数据范围:\(n\le 1.1\times 10^7,k\le 200\)。
思路分析
首先设 \(f_n\) 为答案,\(F=\sum f_ix^i\),考虑转移:对于当前多边形的第一条边,枚举包含该边的 \(k\) 边形,会把多边形剩余部分划分为 \(k-1\) 部分,且每部分的 \(\sum n\) 恰好等于 \(n-1\)。
那么递推式就是 \(F=1+xF^{k-1}\),拉格朗日反演得到 \([x^n]F=\dfrac 1n[x^{n-1}]\left(\dfrac{x}{F^{<-1>}}\right)^n\)。
带入 \(F^{<-1>}=\dfrac{x-1}{x^{k-1}}\),化简得到 \([x^n]F=\dfrac 1n[x^{n-1-kn}](x-1)^{-n}\)。
可以用广义二项式定理 \((a+b)^{r}=\sum_{k=0}^{\infty} a^{i}b^{r-i}\dfrac{r^{\underline i}}{i!}\),其中 \(r\) 为任意实数。
则 \(a=-1,b=x,i=(k-2)n+1\) 得到答案为 \(\dfrac 1n(-1)^i\dfrac{(-n)^{\underline i}}{i!}=\dfrac 1n\dfrac{n^{\overline i}}{i!}=\dfrac{(n+i-1)!}{n!i!}\)。
时间复杂度 \(\mathcal O(n)\)。
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1.1e6+5,MOD=1e9+7;
ll inv[MAXN];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
inv[1]=1;
for(int i=2;i<MAXN;++i) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
inv[0]=1;
for(int i=1;i<MAXN;++i) inv[i]=inv[i-1]*inv[i]%MOD;
int n,k; ll s=0;
while(cin>>n>>k) {
ll z=1;
for(int i=(k-2)*n+2;i<=(k-1)*n;++i) z=z*i%MOD;
s=(s+z*inv[n])%MOD;
}
cout<<s<<"\n";
return 0;
}
C. [P6377] Termites
题目大意
给定 \(a_1\sim a_n\),两个人轮流操作,每次选择一个 \(a_i\) 满足 \(a_{i-1}=0\) 或 \(a_{i+1}=0\),然后将 \(a_i\) 加入自己的得分,并令 \(a_i\gets 0\)。
求两个人都在最大化自己得分的策略下最终得分。
数据范围:\(n\le 10^6\)。
思路分析
原题相当于给出若干个双端队列,每次取开头或结尾。
注意到如果 \(a_{i-1}<a_i>a_{i+1}\),那么先手取走 \(i-1\) 后后手必定取走 \(i\),如果在后手其他位置操作,则先手也在这里操作更优秀。
并且先手取走 \(a_{i-1}\) 肯定是为了获得 \(a_{i+1}\) 的收益,否则在其他位置操作更优。
那么这三个元素可以合并为 \(a_{i-1}+a_{i+1}-a_i\),操作后每个双端队列都是单谷的,每次取最大值显然是最优解。
但对于序列两端的队列,只能从一侧取数。
如果 \(a_1>a_2\),很显然会最后取走 \(a_2\),那么不断操作开头和结尾都会变成单调序列,这样就能排序贪心了。
用链表维护序列。
时间复杂度 \(\mathcal O(n)\)。
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
ll a[MAXN],S,w,b[MAXN];
int n,m,l[MAXN],r[MAXN];
bool o[MAXN];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n,r[0]=1,l[n+1]=n;
for(int i=1;i<=n;++i) cin>>a[i],o[i]=!!a[i],S+=a[i],l[i]=i-1,r[i]=i+1;
for(int i=3;i<=n;++i) {
while(true) {
int x=l[i],y=l[x],z=l[y];
if(o[y]&&o[x]&&o[i]&&a[x]>=a[i]&&a[x]>=a[y]) {
a[i]+=a[y]-a[x],r[z]=i,l[i]=z;
} else break;
}
}
int s=r[0],t=l[n+1]; ll z=0;
for(;o[s]&&o[r[s]]&&a[r[s]]<=a[s];s=r[r[s]]) z+=a[r[s]]-a[s];
for(;o[t]&&o[l[t]]&&a[t]>=a[l[t]];t=l[l[t]]) z+=a[l[t]]-a[t];
for(int i=s;i<=t;i=r[i]) if(o[i]) b[++m]=a[i];
sort(b+1,b+m+1,greater<>()),b[++m]=z;
for(int i=1;i<=m;++i) w+=(i&1?1:-1)*b[i];
cout<<(S+w)/2<<" "<<(S-w)/2<<"\n";
return 0;
}
D. [QOJ5092] 森林游戏
题目大意
给定一棵 \(n\) 个点的有根树,有点权 \(a_u\),先后手轮流操作,每次操作可以选择一个祖先都被选择过的 \(u\),将 \(a_u\) 加入自己的得分。
求两个人都在最大化自己得分的策略下最终得分。
数据范围:\(n\le 2\times 10^5\)。
思路分析
从树是若干条链开始分析,对于一条链 $a_1,a_2,a_3,\dots $,如果 \(a_1<a_2\),那么取走 \(a_1\) 后后手必然取 \(a_2\),否则如果其他位置更优,先手必然会先取对应位置。
同理下一步一定是先手取 \(a_3\),否则在其他位置操作更优的话先手不会取 \(a_1\)。
那么我们把 \(a_1\sim a_3\) 合并成 \(a_3+a_1-a_2\),不断操作后每条链权值递减。
此时最优解一定是从大到小依次取每个点,那么这等价于把所有点从大到小排成一条链。
因此我们可以把每棵树树都等价成一条单调的链:先递归操作每个子树,然后把子树归并,最后在开头加入 \(a_{rt}\) 并用上述方法调整。
如果链长为 \(2\) 且 \(a_1<a_2\),那么一定会在最后操作这两个点,直接给答案的差加上 \((-1)^n(a_1-a_2)\)
用可并堆或启发式合并维护所有链。
时间复杂度 \(\mathcal O(n\log^2n)\)。
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
vector <int> G[MAXN];
priority_queue <ll> f[MAXN];
int n; ll a[MAXN],ans=0;
void dfs(int u,int fz) {
for(int v:G[u]) if(v^fz) {
dfs(v,u);
if(f[v].size()>f[u].size()) f[u].swap(f[v]);
while(f[v].size()) f[u].push(f[v].top()),f[v].pop();
}
int o=-1;
while(f[u].size()) {
if(o<0&&f[u].top()<=a[u]) break;
a[u]+=f[u].top()*o,f[u].pop(),o=-o;
}
if(o>0) ans+=n&1?-a[u]:a[u];
else f[u].push(a[u]);
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i],ans+=a[i];
for(int i=1,u,v;i<n;++i) cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
dfs(1,0);
for(int o=1;f[1].size();o=-o) ans+=o*f[1].top(),f[1].pop();
cout<<ans/2<<"\n";
return 0;
}
*E. [CF1662J] Training Camp
题目大意
给定 \(n\times n\) 矩阵 \(A\),保证每行每列都是 \(1\sim n\) 排列,选出 \(n\) 个格子使得每行每列恰一个。
对于任意未被选的 \(A_{i,j}\),设第 \(i\) 行,第 \(j\) 列选出的元素为 \(x_i,y_j\),则 \((y_j-A_{i,j})(x_i-A_{i,j})>0\)。
每个点有 01 权值 \(C\),最大化选出的格子的 \(\sum C_{i,j}\)。
数据范围:\(n\le 128\)。
思路分析
首先这个限制可以看成 \(x_i<A_{i,j}\implies y_j<A_{i,j}\)。
可以想到切糕模型,但在这里 \(x,y\) 不独立。
那么可以想到把 \(2n\) 条链拼起来:即对于每一行每一列,\(A\) 值相邻的点直接连流量为 \(\infty\) 的有向边(权值小到大)
我们要每行每列删掉一个点使得不存在 \(A=1\) 的点到 \(A=n\) 的点的路径。
此时如果 \(x_i<A_{i,j}<y_j\),则在第 \(j\) 列从 \(1\) 走到 \(A_{i,j}\) 后,在第 \(i\) 行走到 \(n\) 就连通了。
但此时我们对于任意一条路径 \(P_1\sim P_n\),只要 \(P_{i},P_{i+1}\) 同行或同列,我们都钦定这样的路径不连通,而我们实际只需要禁止恰有一次“拐弯”的路径。
可以证明如果这样的 \(P\) 存在,则 \(A\) 一定不合法。
否则考虑 \(P_1=(i,j),P_2=(i,k)\),合法路径一定有 \(x_1>1\implies x_i>2\implies y_k>2\),因此设 \(P_v=(i_v,j_v)\),归纳得出 \(x_{i_v},y_{j_v}>v\),那么 \(v=n\) 显然矛盾。
\(A=1,A=n\) 的点建立虚拟源汇,只要在建出的图上选出 \(n\) 个点使得 \(S\to T\) 路径不存在。
那么拆点后设权值为 \(W-C_{i,j}\) 求最小割即可,其中 \(W\) 是充分大的数。
数据范围:\(\mathcal O(\mathrm{Flow}(n^2,n^2))\)。
代码呈现
#include<bits/stdc++.h>
using namespace std;
namespace F {
const int MAXV=5e4+5,MAXE=5e5+5,inf=1e9;
int hd[MAXV],dep[MAXV],cur[MAXV],ec=1,S,T;
struct Edge { int v,f,e; } G[MAXE];
void add(int u,int v,int f) { G[++ec]={v,f,hd[u]},hd[u]=ec; }
int link(int u,int v,int f) { return add(v,u,0),add(u,v,f),ec; }
bool bfs() {
memset(dep,-1,sizeof(dep));
memcpy(cur,hd,sizeof(cur));
queue <int> Q; Q.push(S),dep[S]=0;
while(Q.size()) {
int u=Q.front(); Q.pop();
for(int i=hd[u];i;i=G[i].e) if(G[i].f) {
int v=G[i].v;
if(dep[v]==-1) dep[v]=dep[u]+1,Q.push(v);
}
}
return ~dep[T];
}
int dfs(int u,int f) {
if(u==T) return f;
int r=f;
for(int i=cur[u];i&&r;i=G[i].e) {
int v=G[cur[u]=i].v;
if(dep[v]==dep[u]+1&&G[i].f) {
int g=dfs(v,min(r,G[i].f));
if(!g) dep[v]=-1;
G[i].f-=g,G[i^1].f+=g,r-=g;
}
}
return f-r;
}
int dinic() {
int f=0;
while(bfs()) f+=dfs(S,inf);
return f;
}
}
using F::link;
const int MAXN=135,inf=1e9,Q=1e5;
int a[MAXN][MAXN],r[MAXN][MAXN],c[MAXN][MAXN],w[MAXN][MAXN];
signed main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) {
scanf("%d",&a[i][j]),r[i][a[i][j]]=j,c[j][a[i][j]]=i;
}
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) scanf("%d",&w[i][j]);
auto id=[&](int i,int j,int x) { return (i-1)*n+j+x*n*n; };
int s=F::S=2*n*n+1,t=F::T=s+1;
for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) {
int v=a[i][j];
if(v==1) link(s,id(i,j,0),inf);
if(v==n) link(id(i,j,1),t,inf);
link(id(i,j,0),id(i,j,1),Q+1-w[i][j]);
if(v<n) {
link(id(i,j,1),id(i,r[i][v+1],0),inf);
link(id(i,j,1),id(c[j][v+1],j,0),inf);
}
}
printf("%d\n",n-(F::dinic()-Q*n));
return 0;
}
*F. [AT-WTF19-C] Triangular Lamps
题目大意
给定无穷大二维坐标系,初始只有一个点是黑色的,每次可以选择一个位置 \((x,y)\),然后翻转 \((x,y),(x,y+1),(x+1,y)\) 的颜色。
已知最终状态有 \(n\) 个点是黑色的,求初始为黑色的点 \((x_0,y_0)\)。
数据范围:\(n\le 10^4\)。
思路分析
设 \(V\) 为充分大的值。
我们可以尝试把每个点的贡献投射到 \(x=-V\) 上,即对黑格子 \((x,y)\),翻转 \((x,y),(x-1,y),(x-1,y+1)\)。
则 \((x,y)\) 对 \(x=-V\) 的贡献是杨辉三角 \(\bmod 2\) 的结果,对 \((-V,i)\) 的贡献为 \([i-y\subseteq x+V]\)。
那么我们求出 \(x=-V\) 直线上第一个和最后一个黑色格子就能求出答案。
假如 \(y_0\) 已知,则第一个黑色格子就是 \((-V,y_0)\),需要找最后一个黑色格子。
那么可以倍增:对于每个 \(2^i\),询问 \((-V,y_0+2^i)\) 的颜色,如果是黑色就令 \(y_0\gets y_0+2^i\)。
进一步,如果我们知道 \(x=-V\) 上的某个黑格子 \((-V,y)\),那么从大到小考虑每个 \(2^i\),询问 \((-V,y\pm 2^i)\) 的颜色即可知道第一个和最后一个黑格子。
这是因为最大的 \(i\) 使得 \((-V,y+2^i)\) 合法的数一定是 \(y_0+x_0-y\) 的最高位,因此每个合法的 \(i\) 都是 \(y_0+x_0-y\) 中的二进制位。
每次求颜色时暴力考虑每个初始点的贡献,这部分复杂度 \(\mathcal O(n\log V)\)。
那么问题变成求一个黑色的 \((-V,y)\)。
可以考虑经典分治:如果 \([l,r]\) 中有奇数个黑色的点,那么 \([l,mid],[mid+1,r]\) 中恰有一侧有奇数个黑色的点,递归到叶子就找到了 \(y\)。
但初始 \([-V,V]\) 中有偶数个黑色的点。
可以发现对于至少有一个 \(r\) 满足,\([-V,V]\) 中 \(y\bmod 3=r\) 的黑色点有奇数个。
这是因为 \(x=x_0\) 时每个 \(r\) 对应的黑点数为 \((1,0,0)\),不断操作得到 \((1,1,0),(1,0,1),(0,1,0),\dots\) 构成循环。
依然枚举每个初始为黑色的点对所求值的贡献,我们要求 \(\sum_{i=l}^r [i\bmod 3=z][i\subseteq S]\),数位 dp 即可。
时间复杂度 \(\mathcal O(n\log^2V)\)。
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e4+5;
const ll inf=1e17;
int n;
ll X[MAXN],Y[MAXN];
bool F(ll mx,ll S,int z) {
if(mx<0) return 0;
static bool f[64][2][3];
memset(f,0,sizeof(f));
f[63][0][0]=1;
for(int i=62;~i;--i) for(int j:{0,1}) for(int k:{0,1,2}) {
for(int c=0;c<=(S>>i&1)&&(j||c<=(mx>>i&1));++c) f[i][j||c<(mx>>i&1)][(k*2+c)%3]^=f[i+1][j][k];
}
return f[0][0][z]^f[0][1][z];
}
bool dp(ll l,ll r,int z) { //%3=r
bool s=0;
for(int i=1;i<=n;++i) s^=F(r-Y[i],X[i]+inf,(z+3-Y[i]%3)%3)^F(l-1-Y[i],X[i]+inf,(z+3-Y[i]%3)%3);
return s;
}
bool q(ll z) {
bool s=0;
for(int i=1;i<=n;++i) if(z>=Y[i]) s^=((z-Y[i])&(X[i]+inf))==z-Y[i];
return s;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i) cin>>X[i]>>Y[i];
ll l=-inf,r=3*inf;
int c=0;
while(!dp(l,r,c)) ++c;
while(l<r) {
ll mid=(l+r)>>1;
if(dp(l,mid,c)) r=mid;
else l=mid+1;
}
ll x=l,y=l;
for(int k=62;~k;--k) {
if(q(x-(1ll<<k))) x-=1ll<<k;
if(q(y+(1ll<<k))) y+=1ll<<k;
}
cout<<y-x-inf<<" "<<x<<"\n";
return 0;
}
Round #67 - 20250402
A. [QOJ5573] Holiday Regifting
题目大意
给定 \(n\) 个点,每秒会向第一个点加入一个礼物,如果某个点的礼物达到 \(c_u\) 个,那么就会清空这个点的礼物,并向他的每个后继加入一个礼物。
求多少秒后所有点首次都没有礼物。
数据范围:\(n\le 10^4,m\le 3\times 10^4\),\(m\) 为后继总数。
思路分析
考虑维护前缀 \([1,i-1]\) 的周期,以及一个周期后 \([i,n]\) 的每个点 \(j\) 会有 \(a_j\) 个礼物。
那么加入第 \(i\) 个点就会把周期重复 \(\dfrac{c_i}{\gcd(a_i,c_i)}\) 轮,然后更新 \(a\) 即可。
时间复杂度 \(\mathcal O(nm)\)。
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e4+5,MOD=998244353;
ll a[MAXN],c[MAXN],ans;
vector <int> G[MAXN];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int n,m; cin>>n>>m;
for(int i=1;i<=n;++i) cin>>c[i];
for(int i=1,u,v;i<=m;++i) cin>>u>>v,G[u].push_back(v);
ans=1,a[1]=1;
for(int i=1;i<=n;++i) {
ll z=c[i]/__gcd(a[i],c[i]); ans=ans*z%MOD;
for(int u=i;u<=n;++u) a[u]*=z;
for(int u=i;u<=n;++u) {
for(int v:G[u]) a[v]+=a[u]/c[u];
a[u]%=c[u];
}
}
cout<<ans<<"\n";
return 0;
}
B. [QOJ8329] Excuse
题目大意
给定 \(n\) 个随机变量,每个随机变量取值为 \(x\) 的概率为 \(2^{-x-1}\),求这些变量的 \(\mathrm{mex}\) 的期望。
数据范围:\(n\le 10^5\)。
思路分析
设 \(f_n\) 为答案,那么可以考虑枚举有 \(i\) 个数 \(>0\),然后把这些数 \(-1\) 变成子问题,\(\mathrm{mex}\) 恰好变大 \(1\)。
那么得到 \(f_n=\sum_i 2^{-n}\binom ni(f_i+1)\),注意所有数都 \(>0\) 时 \(\mathrm{mex}=0\),因此 \(i\in[0,n)\)。
用分治 NTT 优化转移即可。
时间复杂度 \(\mathcal O(n\log^2n)\)。
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353,N=1<<19,G=3,i2=(MOD+1)/2;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
int ksm(int a,int b=MOD-2) { int s=1; for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) s=1ll*s*a%MOD; return s; }
namespace P {
void poly_init() {
inv[1]=1;
for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[0]=ifac[0]=1;
for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
for(int k=1;k<=N;k<<=1) {
int x=ksm(G,(MOD-1)/k); w[k]=1;
for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y; }
void ntt(int *f,bool idft,int n) {
for(int i=0;i<n;++i) {
rev[i]=(rev[i>>1]>>1);
if(i&1) rev[i]|=n>>1;
}
for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
for(int k=2,x,y;k<=n;k<<=1) {
for(int i=0;i<n;i+=k) {
for(int j=i;j<i+k/2;++j) {
x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
}
}
}
if(idft) {
reverse(f+1,f+n);
for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
}
}
void poly_mul(const int *f,const int *g,int *h,int n,int m) {
static int a[N],b[N];
for(int i=0;i<n;++i) a[i]=f[i];
for(int i=0;i<m;++i) b[i]=g[i];
int len=plen(n+m-1);
ntt(a,0,len),ntt(b,0,len);
for(int i=0;i<len;++i) h[i]=1ll*a[i]*b[i]%MOD;
ntt(h,1,len);
memset(a,0,sizeof(int)*len);
memset(b,0,sizeof(int)*len);
}
}
int f[N],a[N],b[N],c[N];
void cdq(int l,int r) {
if(l==r) return f[l]=1ll*f[l]*fac[l]%MOD*ksm(2,MOD-1-l)%MOD,void();
int mid=(l+r)>>1;
cdq(l,mid);
for(int i=l;i<=mid;++i) a[i-l]=1ll*(f[i]+1)*ifac[i]%MOD;
for(int i=1;i<=r-l;++i) b[i]=ifac[i];
P::poly_mul(a,b,c,mid-l+1,r-l+1);
for(int i=mid+1;i<=r;++i) f[i]=(f[i]+c[i-l])%MOD;
cdq(mid+1,r);
}
signed main() {
P::poly_init();
int n;
scanf("%d",&n);
cdq(0,n);
printf("%d\n",f[n]);
return 0;
}
C. [CF1326F] Wise men
题目大意
给定 \(n\) 个点的无向图,定义一个排列 \(p\) 的权值为长度为 \(n-1\) 的字符串 \(v(p)\):\(v(p)_i\) 表示 \(p_i,p_{i+1}\) 之间是否有边。
对每个 \(s\) 求出有多少 \(p\) 满足 \(v(p)=s\)。
数据范围:\(n\le 18\)。
思路分析
同时限定有些边存在,有些边不存在是较困难的,首先可以子集反演去掉一侧限制。
即计算钦定 \(s\) 中对应的 \(p_i,p_{i+1}\) 之间有边,其他边不作限定的情况,然后子集反演即可。
此时对于一个 \(s\),相当于把 \(p\) 分成若干条链,每条链中相邻两个点有边。
很显然只要链长的多重集相同,这两个 \(s\) 对应的方案数也相同。
那么本质不同的 \(s\) 只要 \(\pi(n)\) 个,可以暴力枚举。
然后就要求把原图划分为长度为 \(a_1,a_2,\dots,a_q\) 的链的方案数。
对于每个 \(k\),求图中所有长度为 \(k\) 的链对应的点集,构成的集合幂级数 \(F_k\)。
只要求出 \([x^U]\prod F_{a_i}\),可以先对每个 \(F\) FWT 并动态维护乘积,最后 \(\mathcal O(2^n)\) 求 IFWT 后的 \(x^U\) 单点系数。
时间复杂度 \(\mathcal O(2^n\pi(n)+n2^n)\)。
代码呈现
#include<bits/stdc++.h>
#define ll long long
#define pc __builtin_popcount
using namespace std;
int n,G[18][18];
ll h[1<<18][18],f[19][1<<18],pr[19][1<<18],rs[1<<18];
map <vector<int>,ll> dp;
vector <int> stk;
void dfs(int m,int k,int o) {
if(!m) {
ll z=0;
for(int s=0;s<(1<<n);++s) z+=((n-pc(s))&1?-1:1)*pr[o][s];
return dp[stk]=z,void();
}
for(int i=k;i<=m;++i) {
stk.push_back(i);
for(int s=0;s<(1<<n);++s) pr[o+1][s]=pr[o][s]*f[i][s];
dfs(m-i,i,o+1);
stk.pop_back();
}
}
signed main() {
scanf("%d",&n);
for(int i=0;i<n;++i) for(int j=0;j<n;++j) scanf("%1d",&G[i][j]);
for(int i=0;i<n;++i) h[1<<i][i]=1;
for(int s=0;s<(1<<n);++s) for(int i=0;i<n;++i) if(s>>i&1) {
f[pc(s)][s]+=h[s][i];
for(int j=0;j<n;++j) if(!(s>>j&1)&&G[i][j]) h[s|1<<j][j]+=h[s][i];
}
for(int i=0;i<=n;++i) {
for(int k=0;k<n;++k) for(int s=0;s<(1<<n);++s) if(s>>k&1) f[i][s]+=f[i][s^(1<<k)];
}
for(int s=0;s<(1<<n);++s) pr[0][s]=1;
dfs(n,1,0);
for(int s=0;s<(1<<(n-1));++s) {
vector <int> o;
for(int i=0,j=-1;i<n;++i) if(!(s>>i&1)) o.push_back(i-j),j=i;
sort(o.begin(),o.end()),rs[s]=dp[o];
}
for(int k=0;k<n-1;++k) for(int s=0;s<(1<<(n-1));++s) if(s>>k&1) rs[s^(1<<k)]-=rs[s];
for(int s=0;s<(1<<(n-1));++s) printf("%lld ",rs[s]); puts("");
return 0;
}
*D. [CF1336E] Chiori and Doll Pickin
题目大意
给定 \(n\) 个 \([0,2^m)\) 之间的数 \(a_1\sim a_n\),对于每个 \(i\in[0,m]\),求有多少 \(a\) 的子序列异或和 \(\mathrm{popcount}=i\)。
数据范围:\(n\le 2\times 10^5,m\le 53\)。
思路分析
首先子集异或和肯定建立线性基,设其大小为 \(q\),对于线性基中每个能被表示出来的数 \(x\),恰好有 \(2^{n-q}\) 个子集的异或和 \(=x\),这是显然的。
因此 \(q\) 较小的时候 \(\mathcal O(2^q)\) 爆搜所有元素即可,我们只要对 \(q\) 较大的情况构造做法。
设线性基为 \(b_1\sim b_q\),用集合幂级数表示线性基的每个元素:\([x^s]F=\prod (1+x^{b_i})\)。
考虑 \([x^S]\mathrm{FWT}(F)\),注意到 \([x^S]\mathrm{FWT}(1+x^{b_i})=1+(-1)^{|S\cap b_i|}\)。
因此 \([x^S]\mathrm{FWT}(F)=2^n\) 当且仅当 \(\forall i\),\(|S\cap b_i|\equiv 0\pmod 2\),否则 \([x^S]\mathrm{FWT}(F)=0\)。
假设我们得到了 \(\mathrm{FWT}(F)\),如何求答案 \(f_i\)。
注意到 \([x^S]\mathrm{FWT}(F)\) 对每个 \(f_i\) 的贡献只和 \(|S|\) 有关,枚举 \(|S\cap T|\),然后用组合数计算对应的方案数即可。
因此只要对每个 \(|S|\) 求 \(\sum [x^S]\mathrm{FWT}(F)\)。
可以证明合法的 \(S\) 只有 \(2^{m-q}\) 个,对 \(b\) 高斯消元,设线性基中的位为 \(d_1\sim d_q\)。
那么枚举 \(S\) 所有不在线性基中的位 \(S'\),如果 \(|b_i\cap S'|\) 为奇数,则 \(d_i\in S\)。
那么这样就能得到所有的 \(S\),即枚举每个不在线性基的位 \(k\),如果 \(k\in S'\) 则把所有 \(k\in b_i\) 的 \(d_i\) 也翻转。
容易发现这也是一个线性基,且大小为 \(m-q\),爆搜即可。
时间复杂度 \(\mathcal O(nm+2^{m/2}+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,q=0;
ll a[55],f[55],C[55][55],g[55];
bool ins(ll x) {
for(int i=m-1;~i;--i) if(x>>i&1) {
if(!a[i]) return a[i]=x,true;
x^=a[i];
}
return false;
}
vector <ll> e;
void dfs(int i,ll s) {
if(i==(int)e.size()) return ++f[__builtin_popcountll(s)],void();
dfs(i+1,s),dfs(i+1,s^e[i]);
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=0;i<=m;++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(ll i=1,x;i<=n;++i) cin>>x,q+=ins(x);
if(q*2<=m) {
for(int i=0;i<m;++i) if(a[i]) e.push_back(a[i]);
dfs(0,0);
} else {
for(int i=0;i<m;++i) if(a[i]) for(int j=i+1;j<m;++j) if(a[j]>>i&1) a[j]^=a[i];
for(int i=0;i<m;++i) if(!a[i]) {
ll s=1ll<<i;
for(int j=0;j<m;++j) if(a[j]>>i&1) s|=1ll<<j;
e.push_back(s);
}
dfs(0,0);
for(int i=0;i<=m;++i) {
ll w=f[i]*ksm(2,q)%MOD;
for(int j=0;j<=m;++j) for(int k=0;k<=min(i,j);++k) {
g[j]=(g[j]+(k&1?-1:1)*C[i][k]*C[m-i][j-k]%MOD*w)%MOD;
}
}
for(int i=0;i<=m;++i) f[i]=(g[i]+MOD)*ksm(2,MOD-1-m)%MOD;
}
for(int i=0;i<=m;++i) cout<<f[i]*ksm(2,n-q)%MOD<<" \n"[i==m];
return 0;
}
*E. [Xmas22-F] Fast as Fast as Ryser
题目大意
给定 \(n\) 个点的完全图,每条边有边权,对于 \(k=0\sim n/2\),求每个大小为 \(k\) 的匹配的边权乘积之和。
数据范围:\(n\le 40\)。
思路分析
不妨假设 \(n\) 为偶数(否则可以加入一个边权全 \(0\) 的虚拟点)。
注意到一个子图是匹配的条件是所有点度数 \(\in\{0,1\}\),此时这个图很不连通,不太理想。
不妨把所有 \(2i,2i+1\) 连边,那么此时所有点度数 \(\in\{1,2\}\),则匹配对应新图的环和链。
且匹配的边数为 \(\dfrac n2\) 减去链的个数。
那么此时每个链和环中 \((2i,2i+1)\) 都是成对出现。
可以 dp 求出 \(g_s,h_s\) 表示 \(s\) 中的点构成链或环的方案数,可以 dp 求出。
\(g_s\) 只要在 dp 的时候记录起点即可,\(h_s\) 可以直接钦定起点为环上最小值,则每次经过的点都要 \(>\min(s)\)。
根据集合幂级数的组合意义,我们可以得到答案为 \([x^U]\dfrac{g^k}{k!}\mathrm{exp}(h)\)。
对每个 \(k\) 直接计算的复杂度为 \(\mathcal O(n^32^n)\)。
显然要把多个 \(k\) 的计算过程统一,然后一次性计算。
可以用想到用倍增计算:从小到大考虑每个 \(i\),然后处理最大值为 \(i\) 的环或链的贡献。
设 \(f_k\) 表示已经加入 \(k\) 个环的方案数,那么转移就是 \(f_k\times g\to f_{k+1}\) 或 \(f_{k}\times h\to f_k\),注意这里我们只考虑 \(g/h\) 中最高位 \(=i\) 的项。
此时复杂度 \(\sum i^32^i=\mathcal O(n^32^n)\),但注意到 \(\sum i^2(n-i)2^n=\mathcal O(n^22^n)\)。
因此倒过来做这个过程,即从 \(i=n,S=U\) 开始,每次删除最大值为 \(i\) 的环或链,只要把子集卷积的过程也倒过来即可。
时间复杂度 \(\mathcal O(n^22^n)\)。
代码呈现
#include<bits/stdc++.h>
#define ull unsigned long long
#define uLL unsigned __int128
using namespace std;
void fmt(ull *f,int n) {
for(int s=1;s<(1<<n);s<<=1) for(int i=0;i<(1<<n);i+=s<<1) for(int j=i;j<i+s;++j) f[j+s]+=f[j];
}
void fwt(ull *f,int n) {
for(int s=1;s<(1<<n);s<<=1) for(int i=0;i<(1<<n);i+=s<<1) for(int j=i;j<i+s;++j) f[j]+=f[j+s];
}
void ifwt(ull *f,int n) {
for(int s=1;s<(1<<n);s<<=1) for(int i=0;i<(1<<n);i+=s<<1) for(int j=i;j<i+s;++j) f[j]-=f[j+s];
}
int n,m,o,pc[1<<20];
ull E[40][40],g[1<<20],h[1<<20];
uLL dp1[1<<20][40];
ull dp2[1<<20][40];
ull G[21][1<<20],H[21][1<<20];
ull f[21][1<<20],a[21][1<<20],b[21][1<<20];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=0;i<n;++i) for(int j=0;j<n;++j) cin>>E[i][j];
o=n&1,n+=o,m=n/2;
for(int s=1;s<(1<<m);++s) pc[s]=pc[s>>1]+(s&1);
for(int i=0;i<n;++i) dp1[1<<(i/2)][i]=1;
for(int s=0;s<(1<<m);++s) for(int i=0;i<n;++i) if(s>>(i/2)&1) {
for(int j=0;j<n;++j) if(!(s>>(j/2)&1)) dp1[s|1<<(j/2)][j^1]+=dp1[s][i]*E[i][j];
}
for(int s=0;s<(1<<m);++s) {
uLL z=0;
for(int i=0;i<n;++i) z+=dp1[s][i];
g[s]=z/2;
}
for(int i=0;i<m;++i) dp2[1<<i][i*2]=1;
for(int s=0;s<(1<<m);++s) for(int i=0;i<n;++i) if(s>>(i/2)&1) {
for(int j=0;j<n;++j) if(!(s>>(j/2)&1)&&(s&((1<<(j/2))-1))) dp2[s|1<<(j/2)][j^1]+=dp2[s][i]*E[i][j];
}
for(int s=1;s<(1<<m);++s) {
int ed=2*__builtin_ctz(s)+1;
for(int i=0;i<n;++i) h[s]+=dp2[s][i]*E[i][ed];
}
for(int s=0;s<(1<<m);++s) G[pc[s]][s]=g[s],H[pc[s]][s]=h[s];
for(int i=0;i<=m;++i) fmt(G[i],m),fmt(H[i],m);
cerr<<clock()<<"\n";
f[0][(1<<m)-1]=1;
for(int i=m-1;~i;--i) for(int j=0;j<=m-i-1;++j) {
for(int k=0;k<=i;++k) for(int s=0;s<(1<<i);++s) a[k][s]=0;
for(int s=0;s<(1<<i);++s) a[pc[s]][s]=f[j][s|1<<i];
for(int k=0;k<=i;++k) ifwt(a[k],i);
for(int k=0;k<=i;++k) for(int s=0;s<(1<<i);++s) b[k][s]=0;
for(int k=0;k<=i;++k) for(int t=k;t<=i;++t) {
for(int s=0;s<(1<<i);++s) b[k][s]+=a[t][s]*(G[t-k+1][s|1<<i]-G[t-k+1][s]);
}
for(int k=0;k<=i;++k) fwt(b[k],i);
for(int s=0;s<(1<<i);++s) f[j+1][s]+=b[pc[s]][s];
for(int k=0;k<=i;++k) for(int s=0;s<(1<<i);++s) b[k][s]=0;
for(int k=0;k<=i;++k) for(int t=k;t<=i;++t) {
for(int s=0;s<(1<<i);++s) b[k][s]+=a[t][s]*(H[t-k+1][s|1<<i]-H[t-k+1][s]);
}
for(int k=0;k<=i;++k) fwt(b[k],i);
for(int s=0;s<(1<<i);++s) f[j][s]+=b[pc[s]][s];
}
for(int i=m;i>=o;--i) cout<<f[i][0]<<" \n"[i==0];
return 0;
}
Round #68 - 20250403
A. [QOJ4217] Graph Coloring
题目大意
给定 \(n\) 个点的竞赛图,给每条边染 \(0\sim 13\) 的颜色,使得任意 \(u\to v,v\to w\) 的两条边不同色。
数据范围:\(n\le 3000\)。
思路分析
我们考虑刻画合法染色方案,那么定义 \(S_u\) 为 \(u\) 的入边颜色集合,则限制为 \(u\) 的出边颜色 \(\not\in S_u\)。
对于一条 \(u\to v\) 的边,只要该边颜色 \(\in S_v\setminus S_u\) 即可。
那么我们只要选出 \(n\) 个 \([0,13]\) 的子集使得他们两两不包含即可。
经典构造就是选出所有 \(\mathrm{popcount}=7\) 的集合,此时可能的集合个数 \(\binom{14}7=3432>n\),满足条件。
时间复杂度 \(\mathcal O(n^2)\)。
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3005;
int n,a[MAXN];
char G[MAXN][MAXN];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=2;i<=n;++i) for(int j=1;j<i;++j) cin>>G[i][j];
for(int s=0,i=0;i<=n;++s) if(__builtin_popcount(s)==7) a[++i]=s;
for(int i=2;i<=n;++i,cout<<"\n") for(int j=1;j<i;++j) {
int x=a[i],y=a[j]; if(G[i][j]=='1') swap(x,y);
cout<<char('a'+__builtin_ctz(x^(x&y)));
}
return 0;
}
B. [AT-CF17-I] Full Tournament
题目大意
给定 \(2^n\) 个为编号 \(1\sim 2^n\) 的人,他们要进行淘汰赛:
- 首先把所有人从左到右排成一排 \(p_1\sim p_{2^n}\),然后相邻两个元素比较,编号较小的胜出。
- 然后把所有胜者和败者分开,依然按 \(p\) 中的顺序排列后进行两个 \(2^{n-1}\) 人的子问题。
已知某些人的排名,构造一组合法的 \(p\)。
数据范围:\(n\le 18\)。
思路分析
我们考虑怎样的排名序列是合法的,设 \(a_i\) 表示排名为 \(i\) 的人的编号。
如果 \(n=1\),那么 \(a_1<a_2\),如果 \(n=2\),那么 \(\min(a_1,a_2)<\min(a_3,a_4),\max(a_1,a_2)<\max(a_3,a_4)\),即 \(a_1<a_3,a_2<a_4\)。
那么不断推广可以得到:\(a_i<a_{i+2^j}\),其中 \(j\not\in i\),也即 \(s\subseteq t\implies a_s\le a_t\)。
对于一组合法的 \(a\),我们可以给出如下构造:\(a_i\) 和 \(a_{i+2^{n-1}}\) 在第一轮当对手,剩余元素递归 \(n-1\) 的子问题。
那么这样构造,最终 \(a_i\) 会放到 \(p_{\mathrm{bitrev}(i)}\),其中 \(\mathrm{bitrev}\) 表示二进制逐位翻转的结果。
那么求出合法的 \(a\) 然后蝴蝶变换即可。
高维前缀和算出每个 \(a_i\) 的范围,然后贪心匹配即可。
时间复杂度 \(\mathcal O(n2^n)\)。
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=(1<<18)+5;
int n,a[MAXN],rv[MAXN],L[MAXN],R[MAXN];
vector <int> f[MAXN];
signed main() {
cin>>n;
for(int i=0;i<(1<<n);++i) {
cin>>a[i];
if(a[i]) L[i]=R[i]=a[i];
else L[i]=1,R[i]=1<<n;
}
for(int k=0;k<n;++k) for(int s=0;s<(1<<n);++s) if(s>>k&1) {
L[s]=max(L[s],L[s^(1<<k)]),R[s^(1<<k)]=min(R[s^(1<<k)],R[s]);
}
set <int> S;
for(int i=0;i<(1<<n);++i) {
if(L[i]>R[i]) return cout<<"NO\n",0;
f[R[i]].push_back(i);
}
for(int i=1;i<=(1<<n);++i) {
S.insert(i);
for(int j:f[i]) {
auto it=S.lower_bound(L[j]);
if(it==S.end()) return cout<<"NO\n",0;
a[j]=*it,S.erase(it);
}
}
for(int i=0;i<(1<<n);++i) rv[i]=rv[i>>1]>>1|(i&1)<<(n-1);
cout<<"YES\n";
for(int i=0;i<(1<<n);++i) cout<<a[rv[i]]<<" "; cout<<"\n";
return 0;
}
C. [QOJ7932] AND-OR closure
题目大意
给定 \(n\) 个 \([0,2^m)\) 的元素 \(A\),求最小的 \(|B|\) 使得 \(A\subseteq B\),且 \(\forall x,y\in B\implies x\operatorname{AND}y,x\operatorname{OR}y\in B\)。
数据范围:\(n\le 2\times 10^5,m\le 40\)。
思路分析
注意到 $x\operatorname{AND}(y\operatorname{OR}z)=(x\operatorname{AND}y)\operatorname{OR}(x\operatorname{AND}z) $,所以我们先 \(\operatorname{AND}\) 出一些极小的元素,再考虑他们能 \(\operatorname{OR}\) 出什么东西。
考虑什么样的非空元素 \(s\) 是极小的:很显然如果 \(s,a_i\) 交非空,则 \(s\subseteq a_i\)。
那么这样的 \(s\) 只有 \(\mathcal O(m)\) 个,即对于每个 \(x\),求 \(v_x=\mathrm{AND}_{x\in a_i} a_i\)。
可以证明对于某个 \(t=a_i\operatorname{AND}a_j\),一定有 \(t=\mathrm{OR}_{x\in t} v_x\)。
因此我们只要对 \(v_x\) 求能 \(\mathrm{OR}\) 出哪些数。
注意到 \(x\) 向 \(v_x\) 中的所有位连边,缩点后能得到一个闭合的 DAG。
那么我们就要对 DAG 中的闭合子图个数计数,拓扑排序后 Meet-In-Middle。
枚举左半部分点集(设他们对右半部分的贡献为 \(s\)),FWT 预处理出右半部分有多少闭合子图包含 \(s\)。
时间复杂度 \(\mathcal O(nm+m2^{m/2})\)。
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int B=40;
int n,m,dsu[B],cl[B],ord[B],deg[B];
ll to[B],e[B],a[B],f[1<<20];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
signed main() {
cin>>n;
ll rs=(1ll<<B)-1,vis=0;
for(int i=0;i<B;++i) to[i]=(1ll<<B)-1;
for(int i=1;i<=n;++i) {
ll x; cin>>x,rs&=x,vis|=x;
for(int j=0;j<B;++j) if(x>>j&1) to[j]&=x;
}
iota(dsu,dsu+B,0);
for(int i=0;i<B;++i) if(vis>>i&1) for(int j=i+1;j<B;++j) if(vis>>j&1) {
if((to[i]>>j&1)&&(to[j]>>i&1)) dsu[find(i)]=find(j);
}
for(int i=0;i<B;++i) if((vis>>i&1)&&dsu[i]==i) cl[i]=m++;
for(int i=0;i<B;++i) if(vis>>i&1) for(int j=0;j<B;++j) if(vis>>j&1) {
if(to[i]>>j&1) e[cl[find(i)]]|=1ll<<cl[find(j)];
}
for(int i=0;i<m;++i) {
e[i]&=~(1ll<<i);
for(int j=0;j<m;++j) if(e[i]>>j&1) ++deg[j];
}
queue <int> Q;
for(int i=0;i<m;++i) if(!deg[i]) Q.push(i);
for(int i=0;i<m;++i) {
int u=Q.front(); Q.pop(),ord[u]=i;
for(int v=0;v<m;++v) if(e[u]>>v&1) {
if(!--deg[v]) Q.push(v);
}
}
for(int i=0;i<m;++i) for(int j=0;j<m;++j) if(e[i]>>j&1) a[ord[i]]|=1ll<<ord[j];
for(int i=0;i<m;++i) a[i]|=1ll<<i;
int L=m/2,R=m-m/2;
for(int s=0;s<(1<<R);++s) {
ll t=0;
for(int i=0;i<R;++i) if(s>>i&1) t|=a[i+L]>>L;
if(s==t) ++f[s];
}
for(int i=0;i<R;++i) for(int s=0;s<(1<<R);++s) if(s>>i&1) f[s^(1<<i)]+=f[s];
ll ans=0;
for(int s=0;s<(1<<L);++s) {
ll t=0;
for(int i=0;i<L;++i) if(s>>i&1) t|=a[i];
if((t&((1<<L)-1))==s) ans+=f[t>>L];
}
if(rs) --ans; //no empty
cout<<ans<<"\n";
return 0;
}
*D. [LOJ6703] 小 Q 的序列
题目大意
定义序列 \(c\) 的权值为 \(\prod(c_i+i)\),求 \(a_1\sim a_n\) 所有子序列的权值和。
数据范围:\(n\le 10^5\)。
思路分析
首先可以 dp,\(f_{i,j}\) 表示 \([1,i]\) 中选 \(j\) 个的方案数,那么 \(f_{i,j}=f_{i-1,j}+(a_i+j)f_{i-1,j-1}\)。
用生成函数描述就是 \(F_i=(1+a_ix+x+x^2\Delta)F_{i-1}\),形式太复杂。
如果我们用 \(f_{i,j}\) 表示 \([1,i]\) 中有 \(j\) 个元素没选,那么 \(f_{i,j}=f_{i-1,j-1}+(a_i+i-j)f_{i-1,j}\)。
生成函数得到 \(F_i=(x+a_i+i-x\Delta)F_{i-1}\),其中 \(x\Delta F_{i-1}\) 表示 \(xF'_{i-1}\),不妨假设该算子有结合律。
那么答案就是 \(\prod (a_i+i+x-x\Delta)\) 的各项系数之和。
不妨带入 \(b_i=a_i+i,y=x-x\Delta\),得到 \(\prod (a_i+y)=\sum h_iy^i\)。
那么我们需要计算 \(G_i=(x-x\Delta)^i\) 各项系数之和。
观察各项系数:\(g_{i,j}=g_{i-1,j-1}-jg_{i-1,j}\),可以类比第二类斯特林数 \(s_{i,j}=s_{i-1,j-1}+js_{i-1,j}\)。
那么 \(g_{i,j}\) 的组合意义就是 \(i\) 个数分成 \(j\) 个集合,一个大小为 \(k\) 的集合系数为 \((-1)^{k-1}\)。
那么单个集合的系数就是 \(C=\sum^\infty_{i=1}\dfrac{(-1)^{i-1}}{i!}\),而 \(\sum_jg_{i,j}\) 就是 \([x^i]\exp(C)\),使用多项式 \(\exp\) 即可。
分治 NTT 算出 \(h_i\) 后答案就是 \(\sum h_i[x^i]\exp(C)\)。
时间复杂度 \(\mathcal O(n\log^2n)\)。
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353,N=1<<19,G=3;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
int ksm(int a,int b=MOD-2) {
int ret=1;
for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) ret=1ll*ret*a%MOD;
return ret;
}
namespace P {
void poly_init() {
inv[1]=1;
for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[0]=ifac[0]=1;
for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
for(int k=1;k<=N;k<<=1) {
int x=ksm(G,(MOD-1)/k); w[k]=1;
for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y; }
void ntt(int *f,bool idft,int n) {
for(int i=0;i<n;++i) {
rev[i]=(rev[i>>1]>>1);
if(i&1) rev[i]|=n>>1;
}
for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
for(int k=2,x,y;k<=n;k<<=1) {
for(int i=0;i<n;i+=k) {
for(int j=i;j<i+k/2;++j) {
x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
}
}
}
if(idft) {
reverse(f+1,f+n);
for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
}
}
void poly_mul(const int *f,const int *g,int *h,int n,int m) {
static int a[N],b[N];
for(int i=0;i<n;++i) a[i]=f[i];
for(int i=0;i<m;++i) b[i]=g[i];
int len=plen(n+m-1);
ntt(a,0,len),ntt(b,0,len);
for(int i=0;i<len;++i) h[i]=1ll*a[i]*b[i]%MOD;
ntt(h,1,len);
memset(a,0,sizeof(int)*len);
memset(b,0,sizeof(int)*len);
}
void poly_inv(const int *f,int *g,int n) {
static int a[N];
g[0]=ksm(f[0]);
int k=2;
for(;k<(n<<1);k<<=1) {
for(int i=0;i<k;++i) a[i]=f[i];
ntt(g,0,k<<1),ntt(a,0,k<<1);
for(int i=0;i<(k<<1);++i) {
g[i]=(2-1ll*a[i]*g[i]%MOD)*g[i]%MOD;
if(g[i]<0) g[i]+=MOD;
}
ntt(g,1,k<<1);
memset(g+k,0,sizeof(int)*k);
}
memset(g+n,0,sizeof(int)*(k-n));
memset(a,0,sizeof(int)*k);
}
void poly_ln(const int *f,int *g,int n) {
static int a[N],b[N];
poly_inv(f,a,n);
for(int i=1;i<n;++i) b[i-1]=1ll*i*f[i]%MOD;
int m=plen(n<<1);
ntt(a,0,m),ntt(b,0,m);
for(int i=0;i<m;++i) a[i]=1ll*a[i]*b[i]%MOD;
ntt(a,1,m);
g[0]=0;
for(int i=1;i<n;++i) g[i]=1ll*a[i-1]*inv[i]%MOD;
memset(g+n,0,sizeof(int)*(m-n));
memset(a,0,sizeof(int)*m);
memset(b,0,sizeof(int)*m);
}
void poly_exp(const int *f,int *g,int n) {
static int a[N];
g[0]=1;
int k=2;
for(;k<(n<<1);k<<=1) {
poly_ln(g,a,k);
for(int i=0;i<k;++i) a[i]=(f[i]+MOD-a[i])%MOD;
++a[0];
ntt(a,0,k<<1),ntt(g,0,k<<1);
for(int i=0;i<(k<<1);++i) g[i]=1ll*g[i]*a[i]%MOD;
ntt(g,1,k<<1);
memset(g+k,0,sizeof(int)*k);
}
memset(g+n,0,sizeof(int)*(k-n));
memset(a,0,sizeof(int)*k);
}
}
int n,f[N],g[N],h[N];
vector <int> p[N];
void cdq(int l,int r) {
if(l==r) return ;
int mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
int x=p[l].size(),y=p[mid+1].size();
for(int i=0;i<x;++i) f[i]=p[l][i];
for(int i=0;i<y;++i) g[i]=p[mid+1][i];
P::poly_mul(f,g,h,x,y);
p[l]=vector<int>(h,h+x+y-1);
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
P::poly_init(),cin>>n;
for(int i=1,z;i<=n;++i) cin>>z,p[i]={(z+i)%MOD,1};
cdq(1,n);
memset(f,0,sizeof(f)),memset(g,0,sizeof(g));
for(int i=1;i<=n;++i) f[i]=i&1?ifac[i]:MOD-ifac[i];
P::poly_exp(f,g,n+1);
int ans=0;
for(int i=0;i<=n;++i) ans=(ans+1ll*g[i]*fac[i]%MOD*p[1][i])%MOD;
cout<<(ans+MOD-1)%MOD<<"\n";
return 0;
}
*E. [QOJ7980] 区间切割
题目大意
给定 \(n\) 个 \([1,m]\) 的子区间 \([l_i,r_i]\),依次进行 \(m\) 个操作 \((x,L,R)\):表示对于 \(i\in[L,R]\),把 \([l_i,r_i]\) 变成 \([l_i,x],[x,r_i]\) 中较长的一个(等长选第一个),求最终的每个区间。
数据范围:\(n\le 10^5,m\le 10^6\),\(x\) 构成 \(1\sim m\) 排列。
思路分析
首先如果数据随机,那么对于每个子区间,每次操作有 \(\dfrac 13\) 概率落在区间中间 \(\dfrac 13\) 的位置,此时区间至少缩短 \(\dfrac 13\),因此有效操作只有期望 \(\mathcal O(\log m)\) 次。
那么对 \(i\) 扫描线,线段树动态维护每个 \(x\) 被操作的时间 \(t_x\),每次选出 \([l,r]\) 中 \(t\) 最小的操作进行即可。
对于一般的情况,落在区间两侧的操作次数可能很多,注意到落在左 \(\dfrac 13\) 的区间总是删掉左边,落在右 \(\dfrac 13\) 的区间总是删掉右边。
那么线段树求出第一个落在中 \(\dfrac 13\) 的操作 \(t_i\),然后线段树二分出左 \(\dfrac 13\) 最大的 \(<t_i\) 的操作,右 \(\dfrac 13\) 最小的 \(>t_i\) 的操作。
时间复杂度 \(\mathcal O(m\log m+n\log^2m)\)。
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5,MAXM=1e6+5,N=1<<20;
int n,m,_,ql[MAXN],qr[MAXN],a[MAXM],tr[N<<1];
void upd(int x,int v) {
for(tr[x+=N]=v,x>>=1;x;x>>=1) tr[x]=min(tr[x<<1],tr[x<<1|1]);
}
int qry(int l,int r) {
int s=m+1;
for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) {
if(~l&1) s=min(s,tr[l^1]);
if(r&1) s=min(s,tr[r^1]);
}
return s;
}
int qL(int x,int k) {
for(x+=N;x^1;x>>=1) if((x&1)&&tr[x^1]<k) { x^=1; break; }
if(x==1) return 0;
while(x<N) x<<=1,x|=tr[x|1]<k;
return x-N;
}
int qR(int x,int k) {
for(x+=N;x^1;x>>=1) if(!(x&1)&&(tr[x^1]<k)) { x^=1; break; }
if(x==1) return m+1;
while(x<N) x<<=1,x|=tr[x]>=k;
return x-N;
}
vector <array<int,2>> op[MAXN];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m>>_,fill(tr,tr+(N<<1),m+1);
for(int i=1;i<=n;++i) cin>>ql[i]>>qr[i];
for(int i=1,l,r,x;i<=m;++i) cin>>x>>l>>r,a[i]=x,op[l].push_back({x,i}),op[r+1].push_back({x,m+1});
for(int i=1;i<=n;++i) {
for(auto o:op[i]) upd(o[0],o[1]);
int l=ql[i],r=qr[i];
while(r-l+1>=3) {
int x=(2*l+r+2)/3,y=(l+2*r)/3,t=qry(x,y);
l=max(l,qL(x,t)),r=min(r,qR(y,t));
if(t>m) break;
2*a[t]>=l+r?r=a[t]:l=a[t];
}
cout<<l<<" "<<r<<"\n";
}
return 0;
}

浙公网安备 33010602011771号