2024 Noip 做题记录(三)


By DaiRuiChen007



Round #9 - 2024.9.23

A. [P10849] Level

Problem Link

题目大意

给定若干人和空位,等级 1n,其中等级为 i 的人和空位分别有 bi,ai 个,给每个人匹配一个位置,如果一个等级为 i 的人匹配了一个等级为 j 的位置,会产生 sgn(ij) 的收益,最大化收益。

数据范围:n106

思路分析

考虑贪心,显然优先最大化 j<i 的匹配数量,可以根据调整法证明这样一定不劣。

然后尽可能多地保留 j=i 的匹配即可,我们从小到大扫描 i,每个 ij[1,i) 范围内尽可能多地匹配,优先匹配,根据朴素的贪心思想,匹配等级较大的肯定更优,可以直接用栈维护。

时间复杂度:O(n)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5;
int n,a[MAXN],b[MAXN],s[MAXN];
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=1;i<=n;++i) scanf("%d",&b[i]);
	int w=0;
	for(int i=1,j=0;i<=n;++i) {
		while(b[i]&&j) {
			int x=min(b[i],a[s[j]]);
			w+=x,b[i]-=x,a[s[j]]-=x;
			if(!a[s[j]]) --j;
		}
		s[++j]=i;
	}
	for(int i=1;i<=n;++i) w-=max(0,b[i]-a[i]);
	printf("%d\n",w);
	return 0;
}



B. [P10538] Route

Problem Link

题目大意

n 个点的城市,m 条线路有起点、终点、开始时间、结束时间、费用,还有 q 个限制表示 [l,r] 时间段内,可以在某条线路上免费吃饭,或者在某个城市 uwu 的代价吃饭,求 1n 的最短路(所有限制都要满足)。

数据范围:n,m,q105

思路分析

直接建图最短路较为困难,考虑 dp,用 fe 表示走到 e 的最短花费。

按每条路径的开始时间或结束时间排序,那么转移一定是从前到后的,对于每条边 i,设其起点为 x,开始时间为 s

我们就要在所有以 x 为终点的边 j 中求出一条,设其结束时间为 t,那么 ji 的转移系数就是 fj 加上 wx 倍的 (s,t) 范围内限制个数。

暴力转移复杂度是平方的,考虑优化。

我们发现转移的代价函数是区间内限制数个数,这显然满足四边形不等式,故有决策单调性。

考虑二分优化,每次求出一条新的边 ifi 后,就在 i 终点对应的数据结构上插入 fi 这个决策,在值域上二分出最小的 x 使得 ix 的最优决策,这是简单的。

查询一条边的 fi 就在其起点的数据结构上二分出开始时间所属的决策。

二分的时候要做一个静态二维数点,主席树维护即可。

时间复杂度 O(mlog2V)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef vector<int> vi;
const int MAXN=1e5+5,V=1e9+5;
const ll inf=1e18;
struct SegmentTree {
	int tot,sum[MAXN*32],ls[MAXN*32],rs[MAXN*32];
	void ins(int u,int l,int r,int q,int &p) {
		sum[p=++tot]=sum[q]+1;
		if(l==r) return ;
		int mid=(l+r)>>1;
		if(u<=mid) ins(u,l,mid,ls[q],ls[p]),rs[p]=rs[q];
		else ins(u,mid+1,r,rs[q],rs[p]),ls[p]=ls[q];
	}
	int qry(int u,int l,int r,int p) {
		if(!p||u<=l) return sum[p];
		int mid=(l+r)>>1,s=0;
		if(u<=mid) s=qry(u,l,mid,ls[p]);
		return s+qry(u,mid+1,r,rs[p]);
	}
}	TR;
int n,m,q,rt[MAXN];
struct Seg {
	int l,r;
	bool operator <(const Seg &oth) const { return r<oth.r; }
}	o[MAXN];
struct Edge {
	int u,v,a,b,w;
}	e[MAXN];
int qry(int l,int r) {
	if(l>r) return 0;
	int i=upper_bound(o+1,o+q+1,Seg{l,r})-o-1;
	return TR.qry(l,1,V,rt[i]);
}
struct ds {
	int w;
	struct info {
		int l,r,x; ll d;
	};
	vector <info> q;
	ll f(const info&p,int x) {
		if(p.x>x) return inf;
		return p.d+1ll*w*qry(p.x+1,x-1);
	}
	#define tl (q.back())
	void ins(int x,ll d) {
		info z={0,0,x,d};
		while(q.size()&&f(tl,tl.l)>=f(z,tl.l)) q.pop_back();
		if(q.empty()) return q.push_back({x,V,x,d});
		int l=tl.l,r=tl.r,s=r+1;
		while(l<=r) {
			int mid=(l+r)>>1;
			if(f(tl,mid)>=f(z,mid)) s=mid,r=mid-1;
			else l=mid+1;
		}
		if(s<=V) q.back().r=s-1,q.push_back({s,V,x,d});
	}
	#undef tl
	ll calc(int x) {
		if(q.empty()) return -1;
		int l=0,r=q.size()-1,i=r;
		while(l<=r) {
			int mid=(l+r)>>1;
			if(x<=q[mid].r) i=mid,r=mid-1;
			else l=mid+1;
		}
		if(x<q[i].x) return -1;
		return f(q[i],x);
	}
}	dp[MAXN];
ll Main() {
	sort(o+1,o+q+1);
	for(int i=1;i<=q;++i) TR.ins(o[i].l,1,V,rt[i-1],rt[i]);
	sort(e+1,e+m+1,[&](auto s,auto t){ return s.b<t.b; });
	dp[1].ins(0,0);
	for(int i=1;i<=m;++i) {
		ll f=dp[e[i].u].calc(e[i].a);
		if(~f) dp[e[i].v].ins(e[i].b,f+e[i].w);
	}
	return dp[n].calc(V);
}
ll solve(int N,int M,int W,vi T,vi X,vi Y,vi A,vi B,vi C,vi L,vi R) {
	n=N,m=M,q=W;
	for(int i=1;i<=n;++i) dp[i].w=T[i-1];
	for(int i=0;i<m;++i) e[i+1]={X[i]+1,Y[i]+1,A[i],B[i],C[i]};
	for(int i=0;i<q;++i) o[i+1]={L[i],R[i]};
	return Main();
}



C. [P10833] Permutation

Problem Link

题目大意

给定长度为 n 的序列 a1an,求有多少区间 [l,r] 使得 alar1rl+1 的排列。

数据范围:n106

思路分析

考虑 CDQ 分治,我们用如下两个条件刻画一个好的区间:

  • 区间最大值等于区间长度。
  • 区间没有重复元素。

设当前分治区间中 [l,mid] 的长度为 i 的后缀最大值为 Li[mid+1,r] 的长度为 i 的前缀最大值为 Ri

那么第一个条件等于 i+j=max(Li,Rj),枚举 j 并钦定 RjLi,否则反过来类似处理,此时 i 的范围是 [l,mid] 的一段后缀,可以双指针求出。

我们只要判断 i=Rjj 时是否合法即可,先判定 RjLi,然后就要求区间内无重复。

如果两个重复的元素都在 [l,mid] 中,找出最靠右的一对相当于限制 i 的下界,同理两个元素都在 [mid+1,r] 中就是限制 j 的上界。

然后考虑 [midi+1,mid] 范围内不能出现和 [mid+1,mid+j] 范围内相同的数。

从小到大枚举 j,求出每个 amid+j 在左侧的首次出现,又转化成 i 的下界限制,最终可以 O(1) 检验。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+5;
int n,ans=0,a[MAXN],mx[MAXN],lp[MAXN],rp[MAXN];
void cdq(int l,int r) {
	if(l==r) return ans+=(a[l]==1),void();
	int mid=(l+r)>>1;
	cdq(l,mid),cdq(mid+1,r);
	mx[mid]=a[mid],mx[mid+1]=a[mid+1];
	for(int i=mid-1;i>=l;--i) mx[i]=max(mx[i+1],a[i]);
	for(int i=mid+2;i<=r;++i) mx[i]=max(mx[i-1],a[i]);
	int L=l,R=r;
	for(int i=mid;i>=l;--i) {
		if(lp[a[i]]) { L=i+1; break; }
		lp[a[i]]=i;
	}
	for(int i=mid+1;i<=r;++i) {
		if(rp[a[i]]) { R=i-1; break; }
		rp[a[i]]=i;
	}
	for(int i=mid+1,j=mid+1,p=L;i<=R;++i) {
		while(j>L&&mx[j-1]<=mx[i]) --j;
		if(lp[a[i]]) p=max(p,lp[a[i]]+1);
		if(1<=mx[i]-(i-mid)&&mx[i]-(i-mid)<=mid-max(j,p)+1) ++ans;
	}
	for(int i=mid,j=mid,p=R;i>=L;--i) {
		while(j<R&&mx[j+1]<mx[i]) ++j;
		if(rp[a[i]]) p=min(p,rp[a[i]]-1);
		if(1<=mx[i]-(mid+1-i)&&mx[i]-(mid+1-i)<=min(j,p)-mid) ++ans;
	}
	for(int i=l;i<=mid;++i) lp[a[i]]=0;
	for(int i=mid+1;i<=r;++i) rp[a[i]]=0;
}
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	cdq(1,n),printf("%d\n",ans);
	return 0;
}



*D. [P10004] Inverse

Problem Link

题目大意

给定 n,对于所有 x,y[0,n) 计数有多少 n 阶排列 p 满足 [pi<pi+1]=x,[pi1<pi+11]=y

数据范围:n500

思路分析

先考虑只有 x 的限制如何做,这就是欧拉数,可以二项式反演钦定 i 个位置填 < 号,那么原序列会被分成 ni 个连续段,每个段内元素严格递增。

回到这题,考虑二元二项式反演,钦定 p 上形成 i 个递增连续段,p1 上形成 j 个递增连续段,然后对两维分别二项式反演回去即可。

然后只需要考虑 p 上形成 i 个递增连续段,p1 上形成 j 个递增连续段的方案数。

观察 p1 上一个递增连续段的形态,如果该连续段下标区间为 [l,r],那么 p 上值在 [l,r] 内的元素下标递增。

那么对于 p1 上的一个递增连续段,我们把其对应的 [l,r] 依次插入 p 中的 i 个递增连续段内,只关心每个连续段内插了多少元素,容易发现每一种分配方式恰好对应一组 p

那么我们就是要计数有多少 i×j 矩阵 A 每行每列和非零,且总元素和为 n

这里再二维容斥一遍,即钦定 ab 列元素为 0,剩余的行列无限制,相当于直接插板,然后对每一维容斥回去即可。

时间复杂度 O(n3)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
namespace FastMod {
typedef unsigned long long ull;
typedef __uint128_t uLL;
ull b,m;
inline void init(ull B) { b=B,m=ull((uLL(1)<<64)/B); }
inline ull mod(ull a) {
	ull q=((uLL(m)*a)>>64),r=a-q*b;
	return r>=b?r-b:r;
}
};
#define o(x) FastMod::mod(x)
const int MAXN=505,MAXV=2.6e5+5;
int n,MOD;
ll C[MAXN][MAXN],fac[MAXV],ifac[MAXV];
ll g[MAXN][MAXN],f[MAXN][MAXN],h[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; }
signed main() {
	scanf("%d%d",&n,&MOD),FastMod::init(MOD);
	for(int i=fac[0]=1;i<MAXV;++i) fac[i]=fac[i-1]*i%MOD;
	ifac[MAXV-1]=ksm(fac[MAXV-1]);
	for(int i=MAXV-1;i;--i) ifac[i-1]=ifac[i]*i%MOD;
	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=1;i<=n;++i) for(int j=1;j<=n;++j) g[i][j]=fac[i*j+n-1]%MOD*ifac[i*j-1]%MOD*ifac[n]%MOD;
	for(int i=1;i<=n;++i) {
		memset(h,0,sizeof(h));
		for(int j=1;j<=n;++j) {
			for(int k=1;k<=i;++k) {
				h[j]=o(h[j]+((i-k)&1?MOD-C[i][k]:C[i][k])*g[k][j]);
			}
		}
		for(int j=1;j<=n;++j) {
			for(int k=0;k<=j;++k) {
				f[i][j]=o(f[i][j]+((j-k)&1?MOD-C[j][k]:C[j][k])*h[k]);
			}
		}
	}
	memset(g,0,sizeof(g));
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) g[n-i][n-j]=f[i][j];
	memset(f,0,sizeof(f));
	for(int i=0;i<n;++i) {
		memset(h,0,sizeof(h));
		for(int j=0;j<n;++j) {
			for(int k=i;k<n;++k) {
				h[j]=o(h[j]+((k-i)&1?MOD-C[k][i]:C[k][i])*g[k][j]);
			}
		}
		for(int j=0;j<n;++j) {
			for(int k=j;k<n;++k) {
				f[i][j]=o(f[i][j]+((k-j)&1?MOD-C[k][j]:C[k][j])*h[k]);
			}
		}
	}
	for(int i=0;i<n;++i) {
		for(int j=0;j<n;++j) printf("%lld ",f[i][j]);
		puts("");
	}
	return 0;
}



*E. [P10005] Prefix

Problem Link

题目大意

定义一个长度为 n 的序列 a 的权值为 i=knsi1,其中 siai 的前缀和。

给定 n,m,k,求所有长度为 n 值域 [1,m] 且互不相同的序列的权值和。

数据范围:n,m100,k{1,2}

思路分析

k=1 开始,其代价函数为 si1,直接维护不可做,考虑设计组合意义并转化。

可以设计出如下的问题:

对于 i[1,n],颜色为 i 的球有 ai 个,将他们的排列随机打乱,要求第 i 种颜色的球的最后一次出现位置比第 i+1 种颜色的球的最后一次出现位置早。

考虑每种颜色依次插入,得到的总概率就是 i=1naisi

假设我们确定了所有 ai 构成的无序集合,那么对于一组球的排列,一定有唯一一组 ai 的排列与之对应使其符合条件(按每种颜色最后出现位置排序)。

因此确定所有 ai 构成的无序集合后,i=1naisi=1,那么这种情况的 si1=ai1

因此我们 dp 求出 1mn 个元素,选 k 权值为 1k 时的权值和。

然后考虑 k=2 的情况。

此时相当于给答案 ×a1

假如我们钦定一组 a1an,那么答案就是前一问排列合法的概率乘以 a1,先计算排列合法的概率。

此时 a1 一定是最后一次出现最靠前的颜色,这不好处理,考虑容斥,即钦定一个集合 B 中的元素最后一次出现比 a1 早,设他们对应的球数为 b1bt

求对应的方案数,先排列 b1bt,然后插入 a1,最后插入其他元素,记 SB=bi 得到:

(SBb1,,bk)(SB+a11SB)A!(SB+a1)!iBai!

化简得到原式等于:

(Aa1,a2,,an)a1SB+a1

注意到我们求的是概率,因此前面那个表示方案数的式子需要除掉。

因此一组 B 对答案的贡献就是 (1)|B|a1SB+a1,答案就是:

aa1×i11aiB(1)Ba1SB+a1

考虑直接 dp 维护,设 fi,j,s,0/1 表示考虑值域为 [1,i] 的数,选了 j 个元素进 aSB=s,是否选定 a1 的答案。

转移时枚举 i+1 是不放、当 a1、放入 B、放入 B 中的哪一种情况即可转移。

时间复杂度 O(m4)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=105,MAXS=1e4+5;
int n,m,k,MOD;
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 inv[MAXS];
namespace k1 {
ll f[MAXN];
void main() {
	f[0]=1;
	for(int i=1;i<=m;++i) for(int j=n;j;--j) f[j]=(f[j]+f[j-1]*inv[i])%MOD;
	printf("%lld\n",f[n]);
}
}
namespace k2 {
int up[MAXN];
ll f[MAXN][MAXS][2],g[MAXN][MAXS][2];
void add(ll &x,ll y) { x=(x+y)%MOD; }
void main() {
	for(int i=1;i<=m;++i) up[i]=(i<=n)?i*(i+1)/2:up[i-1]+n;
	f[0][0][0]=1;
	for(int i=1;i<=m;++i) {
		memcpy(g,f,sizeof(g));
		for(int j=0;j<n;++j) for(int s=0;s<=up[i-1];++s) for(int o:{0,1}) {
			ll &w=f[j][s][o]; if(!w) continue;
			add(g[j+1][s][o],inv[i]*w); //choose in A
			add(g[j+1][s+i][o],(MOD-inv[i])*w); //choose in B
			if(!o) add(g[j][s+i][1],i*w); //choose as a[1]
		}
		memcpy(f,g,sizeof(f));
	}
	ll ans=0;
	for(int s=0;s<=up[m];++s) ans=(ans+f[n-1][s][1]*inv[s])%MOD;
	printf("%lld\n",ans);
}
}
signed main() {
	scanf("%d%d%d%d",&n,&m,&k,&MOD);
	for(int i=1;i<MAXS;++i) inv[i]=ksm(i);
	k==1?(k1::main()):(k2::main());
	return 0;
}




Round #10 - 2024.9.24

A. [P10524] Shift

Problem Link

题目大意

给定长度为 2n 的序列 a,求一个循环移位最大化 aiANDi,aiORi,aii

数据范围:n20

思路分析

考虑暴力枚举循环移位的大小,即每次将所有元素左移一位,动态维护三个答案。

用支持全局 +1 的 Trie,从高到低位维护,每次循环移位相当于交换 O(n) 对左右兄弟。

我们在深度为 d 的节点维护第 d 位的贡献,push up 的时候只需要数左右子树有多少个 ai 的第 d 位为 1,这是简单的。

时间复杂度 O(n2n)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1<<21;
int n,a[MAXN],d[MAXN],tg[MAXN],f[MAXN];
ll X[MAXN],Y[MAXN],Z[MAXN];
void ins(int p,int u,int x) {
	if(p>1) f[p]+=x>>(d[p]-1)&1;
	if(d[p]==n) return ;
	int c=u>>d[p]&1;
	ins(p<<1|c,u,x);
}
void psu(int p) {
	int k=d[p],s=1<<(n-k-1),ls=p<<1|tg[p],rs=ls^1;
	X[p]=X[ls]+X[rs]+((ll)(f[ls]+s-f[rs])<<k);
	Y[p]=Y[ls]+Y[rs]+((ll)f[rs]<<k);
	Z[p]=Z[ls]+Z[rs]+((ll)(f[ls]+s)<<k);
}
void rot(int p) {
	if(d[p]==n) return ;
	rot(p<<1|tg[p]),tg[p]^=1,psu(p);
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=2;i<(2<<n);++i) d[i]=d[i>>1]+1;
	for(int i=0;i<(1<<n);++i) cin>>a[i],ins(1,i,a[i]);
	for(int i=(1<<n)-1;i;--i) psu(i);
	ll sx=X[1],sy=Y[1],sz=Z[1];
	for(int i=1;i<(1<<n);++i) rot(1),sx=max(sx,X[1]),sy=max(sy,Y[1]),sz=max(sz,Z[1]);
	cout<<sx<<" "<<sy<<" "<<sz<<"\n";
	return 0;
}



B. [P10332] Order

Problem Link

题目大意

给定一棵 n 个点的树,每次可以将和已选集合相邻的一个点 u 加入集合(初始必须先加入根),有 1pu 的概率直接结束,否则会获得 wu 的收益并继续,最大化收益期望。

数据范围:n105

思路分析

给每个 w 乘上 p,到期望收益就是 i=1nwjj<ipj

如果无拓扑序限制,这是经典的 Exchange Argument 问题,按 wi+piwj<wj+pjwi 排序,即按 pi1wi<pj1wj 排序。

在树上就是树上 Exchange Argument,每次找到 pi1wi 最小的点 x 并和父亲 y 合并,得到新的 (px×py,wy+pywx),用优先队列维护。

由于使用懒惰删除,因此要维护每个点当前的合并次数以判断是否是最新版本。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
#define ld long double
using namespace std;
const int MAXN=1e5+5;
struct info {
	ld p,w; int id,s;
	friend bool operator <(const info &u,const info &v) {
		return u.w+u.p*v.w<v.w+v.p*u.w;
	}
}	a[MAXN];
int n,fa[MAXN],dsu[MAXN];
int find(int x) { return x^dsu[x]?dsu[x]=find(dsu[x]):x; }
signed main() {
	scanf("%d",&n);
	priority_queue <info> Q;
	for(int i=1;i<=n;++i) scanf("%Lf",&a[i].w),a[i].id=i,a[i].s=1;
	for(int i=1;i<=n;++i) scanf("%Lf",&a[i].p),a[i].w*=a[i].p;
	for(int i=2;i<=n;++i) scanf("%d",&fa[i]),Q.push(a[i]);
	iota(dsu+1,dsu+n+1,1);
	while(Q.size()) {
		auto z=Q.top(); int u=z.id; Q.pop();
		if(z.s!=a[u].s||dsu[u]!=u) continue;
		int x=find(fa[u]);
		a[x].w+=a[x].p*a[u].w,dsu[u]=x;
		a[x].p*=a[u].p,a[x].s+=a[u].s;
		if(x>1) Q.push(a[x]);
	}
	printf("%.18Lf\n",a[1].w);
	return 0;
}



*C. [P10042] Iterate

Problem Link

题目大意

给定 n×m 网格,每个格子填 02,有些还没填,你要给他们填入 02

一个网格会进行若干轮迭代,对于一个填 x 的格子,如果他四联通的格子中有 x1,那么迭代后这个格子也会变成 x1,在 mod3 意义下考虑。

一个网格是好的当且仅当其不会无限迭代,定义其权值为 (0,0) 最后一次迭代时共迭代了几次。

数据范围:n5,m50

思路分析

考虑把 mod3 去掉,设原矩阵为 ai,j,那么找到一个 bi,jai,j(mod3) 且相邻元素差 [1,1]

如果确定 b1,1 的值那么 ab 构成双射。

a 上的操作也会在 b 上进行,并且 a,b 的操作情况完全相同。

那么一个 b 的网格一定会停止,并且所有最小值所在位置不会迭代,每次操作就会把它周围的点权值变该点权值。

因此答案就是所有最小值中到 (0,0) 最小的曼哈顿距离。

因此只要 a 能找到一个对应的 b 就一定是好的矩阵,我们又发现找不到 b 的矩阵 a 一定包含一个形如 [xxyz] 的子矩阵,而这个子矩阵会无限减小,所以我们证明了好矩阵和存在 b 的矩阵一一对应。

对于每个 2×2 检验是否合法即可。

因此第一问可以简单 dp,fi,s 表示前 i 列的状态为 s 有多少种方案,转移枚举第 i+1 列轮廓线,时间复杂度 O(m6n)

然后考虑第二问,设 b1,1=0,我们要维护最小的 b,以及所有 b 取到最小值的位置到 (0,0) 的最小距离。

如果暴力记录状态还要记录 b0,i,这是不能接受的,但我们只关心 b 的最小值与 bj,i 的相对关系,因此可以记录 b0,ibmin 的值。

然后要维护到 (0,0) 的最小距离,我们发现这一维如果再记录 O(n+m) 的信息是无法接受的。

考虑类似刚才的技巧,设最小距离为 d,我们只关心 d 与某个 j+i 的关系,因此可以记录 di

di 时说明 >i 的列都不可能成为最小曼哈顿距离的点,因此只要记录 max(0,di) 即可,这样这一维的信息只有 O(n)

因此设计状态 fi,j,k,sj 表示 b0,ibmink 表示 max(0,di) 的方案数,再记录 gi,j,k,s 表示对应权值和。

时间复杂度 O(nm(n+m)6n)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=60,MAXS=255,MOD=998244353;
const int pw[6]={1,3,9,27,81,243},d[3][3]={{0,1,-1},{-1,0,1},{1,-1,0}};
int n,m,Q,a[MAXN][5];
bool ok[55][MAXS],trs[MAXS][MAXS];
int v[MAXS],p[MAXS]; //min prefix, pos
int f1[MAXS][MAXN][5],f2[MAXS][MAXN][5];
//(last color,cur min-a[i,0],mindis-i) {wys ans}
int g1[MAXS][MAXN][5],g2[MAXS][MAXN][5];
int dg(int s,int i) { return s/pw[i]%3; }
void add(int &x,int y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
void add(int &x,long long y) { x=(x+y)%MOD; }
signed main() {
	ios::sync_with_stdio(false);
	cin>>n>>m,Q=pw[n];
	for(int i=0;i<n;++i) for(int j=0;j<m;++j) cin>>a[j][i];
	for(int i=0;i<m;++i) for(int s=0;s<Q;++s) {
		ok[i][s]=true;
		for(int j=0;j<n;++j) ok[i][s]&=(a[i][j]==3||a[i][j]==dg(s,j));
	}
	for(int s=0;s<Q;++s) {
		v[s]=p[s]=0;
		for(int i=1,k=0;i<n;++i) {
			k+=d[dg(s,i-1)][dg(s,i)];
			if(k<v[s]) v[s]=k,p[s]=i;
		}
		for(int t=0;t<Q;++t) {
			trs[s][t]=true;
			for(int i=1;i<n;++i) {
				int x=dg(s,i-1),y=dg(t,i-1),z=dg(t,i),w=dg(s,i);
				trs[s][t]&=!(d[x][y]+d[y][z]+d[z][w]+d[w][x]);
			}
		}
	}
	for(int s=0;s<Q;++s) if(ok[0][s]) f1[s][-v[s]][p[s]]=1,f2[s][-v[s]][p[s]]=p[s];
	for(int i=1;i<m;++i) {
		memset(g1,0,sizeof(g1)),memset(g2,0,sizeof(g2));
		for(int s=0;s<Q;++s) for(int j=0;j<=i+n;++j) for(int k=0;k<n;++k) for(int t=0;t<Q;++t) {
			if(!ok[i][t]||!trs[s][t]) continue;
			int w=(d[t%3][s%3]-j)-v[t],z=f1[s][j][k];
			if(!z) continue;
			if(w<0||(w==0&&k-1<=p[t])) { //remain min
				add(g1[t][j-d[t%3][s%3]][max(k-1,0)],z);
				add(g2[t][j-d[t%3][s%3]][max(k-1,0)],f2[s][j][k]);
			} else { //min is cur
				add(g1[t][-v[t]][p[t]],z);
				add(g2[t][-v[t]][p[t]],1ll*z*(i+p[t]));
			}
		}
		memcpy(f1,g1,sizeof(f1)),memcpy(f2,g2,sizeof(f2));
	}
	int s1=0,s2=0;
	for(int s=0;s<Q;++s) for(int j=0;j<=m+n;++j) for(int k=0;k<n;++k) {
		add(s1,f1[s][j][k]),add(s2,f2[s][j][k]);
	}
	printf("%d %d\n",s1,s2);
	return 0;
}



D. [P10010] Optimize

Problem Link

题目大意

给定 a1an,b1bn,选出一个子集 S 最大化 iSai×iSbi

数据范围:n3000,数据随机。

思路分析

由于数据随机,因此考虑一些随机化算法,随机一组初始态出发开始爬山,每次翻转一个点的状态,如果更优就转移。

但这样显然难以通过,利用数据随机的性质,我们发现此时 |S| 应该很接近 n2,因此从一组 |S|=n2 的解出发开始爬山即可。

代码呈现

#include<bits/stdc++.h>
#define ll long long
#define LL __int128
using namespace std;
const int MAXN=3005;
mt19937 rnd(998244353);
int n,id[MAXN];
ll a[MAXN],b[MAXN];
bool s[MAXN];
void solve() {
	for(int i=1;i<=n;++i) cin>>a[i]>>b[i],id[i]=i;
	LL Z=0;
	for(int o=32;o;--o) {
		shuffle(id+1,id+n+1,rnd);
		LL A=0,B=0;
		for(int i=1;i<=n/2;++i) s[i]=0,A+=a[i];
		for(int i=n;i>n/2;--i) s[i]=1,B+=b[i];
		LL X=A*B;
		for(int q=62500;q;--q) {
			int x=rnd()%n+1;
			if(s[x]) {
				if(X<=(A+a[x])*(B-b[x])) s[x]^=1,A+=a[x],B-=b[x],X=A*B;
			} else {
				if(X<=(A-a[x])*(B+b[x])) s[x]^=1,A-=a[x],B+=b[x],X=A*B;
			}
		}
		Z=max(Z,X);
	}
	string z;
	while(Z) z+=Z%10+'0',Z/=10;
	reverse(z.begin(),z.end());
	cout<<z<<"\n";
}
signed main() {
	ios::sync_with_stdio(false);
	int T; ll A,B; cin>>T>>n>>A>>B;
	while(T--) solve();
	return 0;
}



*E. [P10001] Coupon

Problem Link

题目大意

给定 n 个物品,你要按顺序购买每个物品,初始有 m 个优惠券。

对于第 i 个物品,你要花费总计 ai 个金币或优惠券,其中优惠券至多用 bi 张,并且你每付出 c 个金币就会获得一张优惠券(向下取整)。

最小化花费的金币总数。

数据范围:n106

思路分析

假设第 i 次购买时花费了 xi 张优惠券,那么前 i 次操作后剩余的优惠券 si=m+j=1iajxjcxj,唯一的限制就是 i[1,n+1]:xisi1

考虑如何贪心解决这个问题。

我们分析优惠券从 0 开始不断增加的过程:

  • 使用的前 aimodc 张优惠券:使用任意多张都不会减少获得的优惠券,因此贪心使用尽可能多的这种优惠券。
  • 接下来每使用 c 张优惠券,都会使得后续剩余优惠券数量额外 1
  • 对于剩余的最后 <c 张优惠券,还会使得后续剩余优惠券数量 1

容易发现所有的操作中,第一种操作显然最优,然后是第二种和第三种。

那么我们先进行所有的第一种操作,容易发现在哪个位置操作仅仅相当于改变 m,无后效性,因此可以随意操作,不妨从前往后依次取,即 xi=min(aimodc,bi,si1)

然后我们要进行一些第二种操作,即在某个位置连续使用 c 张优惠券。

我们发现这种情况下收益相同,使用的位置显然越靠后后效性越小,因此从后往前贪心取尽可能多的 xi 即可。

那么此时第 i 个位置会使用 min(bixic,si1xic,mini<jn+1sj1xjc+1)c 张优惠券。

最后第三种情况,我们只要考虑每个点剩余使用的优惠券数 [0,c) 的剩余情况。

根据贪心,我们依然要优先做收益最高的操作,即可以使用优惠券数最多的操作。

ti=si1xi 在第 i 个位置最多可以使用的优惠券数量就是 min(bixi,ti,mini<jn+1tj1)

观察这个结构的性质,我们发现 di=min(ti,mini<jn+1tj1) 具有单调性,随着 i 的增加而递增,而且在整个贪心过程中其单调性始终存在。

那么考虑模拟这个过程,我们从大往小枚举 w=c10,求出 min(di,bixi)=w 的所有位置然后操作。

考虑如何维护这些位置,首先这些位置显然满足 bixiw,而 bixi 是定值,因此可以离线,在 w=bixi 时插入操作 i,那么我们只要取出所有 diw 的操作即可。

di 从后往前递减,因此 diwi 一定是 1n 的一段后缀,用堆维护所有被插入的操作 i 中的最大值,判断是否有 diw 即可。

容易发现我们只要处理等于某个 bixi 的所有 w,中间的 w 不需要考虑,只要大于下一个 bixi 的所有 di 都操作即可。

我们可以用线段树动态维护 di,只要区间加区间最小值即可。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e6+5;
int n,id[MAXN];
ll c,a[MAXN],b[MAXN],s[MAXN],x[MAXN],up[MAXN];
struct SegmentTree {
	ll tr[MAXN<<2],tg[MAXN<<2];
	void psu(int p) { tr[p]=min(tr[p<<1],tr[p<<1|1]); }
	void adt(int p,int k) { tr[p]+=k,tg[p]+=k; }
	void psd(int p) { adt(p<<1,tg[p]),adt(p<<1|1,tg[p]),tg[p]=0; }
	void init(int l=1,int r=n+1,int p=1) {
		tr[p]=tg[p]=0;
		if(l==r) return tr[p]=s[l-1]-x[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,int k,int l=1,int r=n+1,int p=1) {
		if(ul<=l&&r<=ur) return adt(p,k);
		int mid=(l+r)>>1; psd(p);
		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);
	}
	ll qry(int ul,int ur,int l=1,int r=n+1,int p=1) {
		if(ul<=l&&r<=ur) return tr[p];
		int mid=(l+r)>>1; psd(p);
		if(ur<=mid) return qry(ul,ur,l,mid,p<<1);
		if(mid<ul) return qry(ul,ur,mid+1,r,p<<1|1);
		return min(qry(ul,ur,l,mid,p<<1),qry(ul,ur,mid+1,r,p<<1|1));
	}
}	T;
void solve() {
	scanf("%d%lld%lld",&n,&s[0],&c);
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
	for(int i=1;i<=n;++i) scanf("%lld",&b[i]);
	for(int i=1;i<=n;++i) {
		ll w=min({a[i]%c,b[i],s[i-1]});
		x[i]=w,s[i]=s[i-1]-x[i]+(a[i]-x[i])/c;
	}
	ll lim=s[n];
	for(int i=n;i>=1;--i) {
		ll w=min({(b[i]-x[i])/c,(s[i-1]-x[i])/c,lim/(c+1)});
		x[i]+=c*w,lim=min(lim-(c+1)*w,s[i-1]-x[i]);
	}
	for(int i=1;i<=n;++i) s[i]=s[i-1]-x[i]+(a[i]-x[i])/c;
	x[n+1]=id[n+1]=0,T.init();
	for(int i=1;i<=n;++i) id[i]=i,up[i]=min(c-1,b[i]-x[i]);
	sort(id+1,id+n+1,[&](int i,int j){ return up[i]>up[j]; });
	priority_queue <int> Q;
	for(int i=1,j;i<=n;i=j) {
		for(j=i;j<=n&&up[id[j]]==up[id[i]];++j) Q.push(id[j]);
		while(Q.size()) {
			int u=Q.top();
			ll z=min({up[u],T.qry(u,u),T.qry(u+1,n+1)-1});
			if(z>up[id[j]]) {
				Q.pop(),x[u]+=z,T.add(u,u,-z),T.add(u+1,n+1,-z-1);
			} else break;
		}
	}
	ll ans=0;
	for(int i=1;i<=n;++i) ans+=a[i]-x[i];
	printf("%lld\n",ans);
}
signed main() {
	int o; scanf("%d",&o);
	while(o--) solve();
	return 0;
}



*F. [P10062] Latin

Problem Link

题目大意

给定 n×n 矩阵的前 RC 列元素,构造一个完整矩阵使得每一行每一列都是 1n 排列。

数据范围:n500

思路分析

先考虑 C=n 的情况,此时相当于已经知道若干完整的行。

此时一定有解,可以依次构造每一行,建立二分图,左部点是这一行的所有位置,右部点是 1n 的值,如果一个位置可以填某个值,那么就在其间连一条边。

此时所有点的度数都是 nR,那么这张图是一张正则二分图,根据 Hall 定理证明其完美匹配总是存在,那么就证明了此时矩阵总是存在。

考虑如何构造,如果不对每一行分别求匹配,那么我们就是要把这张正则二分图分解成 nR 个完美匹配。

事实上,这就是二分图最小边染色,下面给出做法:

对于任意一张二分图,其最小边染色定义为用最少的颜色给每条边染色,且每个点的所有出边两两异色。

事实上最小边染色的大小就是所有顶点的最大度数,这显然是一个下界,现给出构造性证明。

考虑每次加入一条边 (u,v),如果 u,v 的出边中的未选用元素集有公共元素,直接设为这种颜色。

否则任选一种 u 的出边中未选用的颜色 xv 的出边种未选用的颜色 y

找到 vx 颜色出边对应的端点 w,然后将 (v,w) 设成颜色 y,然后 (u,v) 设成颜色 x

此时 w 的出边中可能出现两个 y,把另外一个设成颜色 x,可以证明在二分图上这样的调整过程总会停止。

关于此构造的正确性与时间复杂度的证明:

该构造过程只更改了原颜色为 xy 的颜色的边,所以暂时不考虑图上其它的边。

此时,每个点的度数不超过 2,所以此时图由若干链与环构成,且所有的链或环上边的颜色是 xy 交替出现的。

又因为 u 一开始没有颜色 x 的邻边,v 一开始没有颜色 y 的邻边,所以 u,v 均是孤立点或链的一端。

假设 u,v 是同一条链的两端,则该链 u 端的边的颜色为 yv 端的颜色为 x,由于链上颜色交替出现,所以该链的长度(边数)是偶数;同时,由于 u,v 在二分图的两侧,所以该链的长度是奇数。推出矛盾,所以 u,v 是两条不同的链的端点。

容易发现,这个构造只会扫描 u 所在的链,因此这个构造一定会结束,而且只会扫到 O(n) 条边。

然后考虑 C<n 的情况,那么对前 R 列分别建点求出类似的二分图,左部点度数均为 nC

如果右部点中存在度数 >nC 的点,说明这种元素要在 R×(nC) 区域中出现 >nC 次,显然无解。

否则类似地求出一组二分图边染色即可。

时间复杂度 O(n3)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=505;
struct bip {
	int X,Y,g[MAXN<<1][MAXN];
	void init(int x,int y) {
		X=x,Y=y;
		for(int i=1;i<=X+Y;++i) memset(g[i],0,sizeof(g[i]));
	}
	void add(int u,int v) {
		v+=X;
		int x=1,y=1;
		while(g[u][x]) ++x;
		while(g[v][y]) ++y;
		g[u][x]=v,g[v][y]=u;
		if(x==y) return ;
		for(int w=v,z=y;w;w=g[w][z],z^=x^y) swap(g[w][x],g[w][y]);
	}
}	G;
int d[MAXN],a[MAXN][MAXN];
bool vis[MAXN];
void solve() {
	int n,R,C;
	cin>>n>>R>>C;
	for(int i=1;i<=n;++i) d[i]=R;
	for(int i=1;i<=R;++i) for(int j=1;j<=C;++j) cin>>a[i][j],--d[a[i][j]];
	for(int i=1;i<=n;++i) if(d[i]>n-C) return cout<<"No\n",void();
	G.init(R,n);
	for(int i=1;i<=R;++i) {
		memset(vis,false,sizeof(vis));
		for(int j=1;j<=C;++j) vis[a[i][j]]=true;
		for(int j=1;j<=n;++j) if(!vis[j]) G.add(i,j);
	}
	for(int i=1;i<=R;++i) {
		for(int j=1;j<=n-C;++j) a[i][j+C]=G.g[i][j]-R;
	}
	G.init(n,n);
	for(int i=1;i<=n;++i) {
		memset(vis,false,sizeof(vis));
		for(int j=1;j<=R;++j) vis[a[j][i]]=true;
		for(int j=1;j<=n;++j) if(!vis[j]) G.add(i,j);
	}
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n-R;++j) a[j+R][i]=G.g[i][j]-n;
	}
	cout<<"Yes\n";
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) cout<<a[i][j]<<" \n"[j==n];
}
signed main() {
	ios::sync_with_stdio(false);
	int T; cin>>T;
	while(T--) solve();
	return 0;
}



*G. [P10433] Stop

Problem Link

题目大意

给定 n 个点 m 条边的无向图,有一些节点是“停止节点”,现在有 k 枚棋子初始放在 a1ak

游戏会进行若干轮,每轮依次考虑第 1k 枚棋子,并将其在图上移动到某个“停止节点”停下(不能不动)。

对于每个 u[1,n],求把第 1 枚棋子移动到 u 上,所有棋子移动距离和的最小值。

数据范围:n,m,k5×105

思路分析

我们发现第 2k 枚棋子对答案的影响很小,且他们相互独立,这些棋子只要分别最小化移动距离即可。

假设我们钦定进行了 x 轮移动,我们就要求出每个棋子最小移动距离关于 x 的函数 f(x)

注意到这个棋子第一轮移动到某个“停止节点”后,接下来的每一轮都可以直接走到某个邻居再走回来,因此每轮至多走 2 步。

设这个棋子第一轮至少移动 d 步到“停止节点”,那么进行 x 轮移动的代价不超过 2x+d2

如果想要更优,那就必须要做到每轮移动只走一步,也就是走到两个相邻的“停止节点”上。

对于所有的走到相邻“停止节点”的路径,我们关心其长度 w 和轮数 t,那么 xt 时存在一种代价为 xt+w 的路径。

我们发现 x<t 的时候 xt+w 不可能小于 2x+d2,因此我们只关心 wt 最小的路径即可。

证明:只需要证明 w12t+d4,即 w2td3

考虑 (w,t) 对应路径,由于其终点是第一对相邻的“停止节点”,因此此前路径上的的每个“停止节点”的邻域都没有“停止节点”。

那么路径上这 t 个“停止节点”,在路径上经过的下一个点肯定不是停止节点,因此可以将这 2t 个点取出。

并且考虑 d 表示这条路径上第一次到达“停止节点”前的长度,那么我们在这条长度为 w 的路径上删去 2t 个点的贡献,并不影响 d 中点的贡献。

因此我们知道 dw2t,且 d 也对应一条走到“停止节点”的路径,故 dd,因此 w2td

求出 wt 最小的路径,可以看成给终止节点的权值设为 0,其他点权值设为 1 时的最短路,可以 01bfs。

容易发现其他时候肯定不优,此时我们表示出了 f(x)=min(2x+d2,xt+w),可以看成一个分两段的上凸壳。

因此对于每个 x,所有棋子的贡献总和是一个分段函数 F(x),且其段数 k,而且是上凸壳。

那么一个朴素的想法就是对于每个 u,x,求出 a1 走到 u 恰好经过 x 轮的最短路 du,x,用 du,x+F(x) 更新答案。

但这样做的复杂度难以接受,根据贪心的思想,我们自然想到求出经过轮数最小的一条路径,但这不对,因为我们可以略微增加经过的轮数,但使得路径长度减小。

但是这样路径长度减小量肯定是 n 的,因为原路径长度肯定不超过 n

那么我们发现轮数很大的情况并不优,因为每多走一轮,其他的 k1 个棋子至少都要动一步,因此每增加一轮,F(x) 至少增加 k1

那么 nk1 轮后,我们就不可能获得比最小轮数路径更优的决策。

因此我们可以分层图 bfs,时间复杂度 O(nmk)

那么 k 很大的情况得到解决,我们只要考虑 k 较小的情况。

我们枚举凸壳上的每条边,设其在 x[l,r]F(x)=px+b,那么这条边的斜率 p 就相当于:如果轮数落在这条边的横坐标区间内,那么可以把移动 x 轮的代价看成 p×x

我们枚举 p,然后把增加一轮(棋子经过“停止节点”)的代价额外 +p,在这种情况下求出最短路 du

观察最短路径此时经过的轮数,如果这个轮数恰好落在 [l,r] 内,那么这显然就是答案,用 du+b 更新 u 的答案即可。

如果这个轮数落在 [l,r] 外,那么很显然,du+b 不可能成为答案,因为此时 px+b>F(x),所以取 F(x) 实际对应的决策一定更优。

因此我们知道答案就是所有 p 对应的 du+b 的最小值。

由于凸包只有 O(k) 段,对每段跑 Dijkstra,可以做到 O(kmlogm) 的复杂度。

k 进行根号平衡即可。

时间复杂度 O(mnlogm)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e4+5,inf=1e9,LIM=225;
int n,m,st[MAXN],cl[MAXN];
vector <int> G[MAXN];
int w1[MAXN],w2[MAXN];
ll ans[MAXN];
void bfs1() { //ans from x[1] without stop
	memset(ans,0x3f,sizeof(ans));
	queue <int> q; q.push(st[1]),ans[st[1]]=0;
	while(q.size()) {
		int u=q.front(); q.pop();
		if(u!=st[1]&&cl[u]) continue;
		for(int v:G[u]) if(ans[v]>n) ans[v]=ans[u]+1,q.push(v);
	}
}
void bfs2() { //min dis to first stop
	fill(w2+1,w2+n+1,inf);
	queue <int> q;
	for(int i=1;i<=n;++i) if(cl[i]) {
		for(int j:G[i]) if(w2[j]==inf) w2[j]=1,q.push(j);
	}
	while(q.size()) {
		int u=q.front(); q.pop();
		for(int v:G[u]) if(w2[v]==inf) w2[v]=w2[u]+1,q.push(v);
	}
}
void bfs3() { //min dis to 2 connected stops
	deque <int> q;
	fill(w1+1,w1+n+1,inf);
	for(int i=1;i<=n;++i) if(cl[i]&&w2[i]==1) w1[i]=0,q.push_back(i);
	static bool vis[MAXN];
	fill(vis+1,vis+n+1,false);
	while(q.size()) {
		int u=q.front(); q.pop_front();
		if(vis[u]) continue; vis[u]=true;
		int d=w1[u]+1-cl[u];
		for(int v:G[u]) if(w1[v]>d) {
			w1[v]=d;
			if(cl[u]) q.push_front(v);
			else q.push_back(v);
		}
	}
}
namespace A {
int lim,rd[MAXN],d[MAXN][MAXN/LIM+5];
ll f[MAXN],sum=0;
void bfs4() { //min rounds to every vertex
	fill(rd+1,rd+n+1,inf);
	deque <int> q;
	q.push_back(st[1]),rd[st[1]]=0;
	static bool vis[MAXN];
	fill(vis+1,vis+n+1,false);
	while(q.size()) {
		int u=q.front(); q.pop_front();
		if(vis[u]) continue; vis[u]=true;
		int z=cl[u]&&u!=st[1];
		for(int v:G[u]) if(rd[v]>rd[u]+z) {
			rd[v]=rd[u]+z;
			z?q.push_back(v):q.push_front(v);
		}
	}
}
void bfs5() { //shortest path to vertex at round d0[u]+i
	for(int i=1;i<=n;++i) fill(d[i],d[i]+lim+5,inf);
	queue <array<int,2>> q;
	q.push({st[1],0}),d[st[1]][0]=0;
	while(q.size()) {
		int u=q.front()[0],i=q.front()[1]; q.pop();
		int z=cl[u]&&d[u][i]>0;
		for(int v:G[u]) {
			int j=i+rd[u]+z-rd[v];
			if(j<=lim&&d[v][j]==inf) d[v][j]=d[u][i]+1,q.push({v,j});
		}
	}
}
void main() {
	lim=n/(m-1),f[1]=2*m-2;
	for(int i=2;i<=m;++i) {
		ll s=w1[st[i]],t=w2[st[i]]-2; //x+s or 2x+t
		if(s!=inf) --f[s-t+1];
		sum+=t;
	}
	for(int i=1;i<=n;++i) f[i]+=f[i-1];
	for(int i=1;i<=n;++i) f[i]+=f[i-1];
	bfs4(),bfs5(),f[0]=-sum;
	for(int i=1;i<=n;++i) for(int j=0;j<=lim;++j) if(d[i][j]!=inf&&j+rd[i]<=n) {
		ans[i]=min(ans[i],d[i][j]+f[j+rd[i]]+sum);
	}
}
}
namespace B {
int x[MAXN]; //change slope
bool vis[MAXN],chk[MAXN]; //round>1
ll dis[MAXN];
void dijk(int k) {
	memset(dis,0x3f,sizeof(dis));
	memset(vis,false,sizeof(vis));
	memset(chk,false,sizeof(chk));
	priority_queue <array<ll,2>,vector<array<ll,2>>,greater<array<ll,2>>> q;
	q.push({dis[st[1]]=0,st[1]});
	while(q.size()) {
		int u=q.top()[1]; q.pop();
		if(vis[u]) continue; vis[u]=true;
		int z=(cl[u]&&u!=st[1])?k+1:1;
		for(int v:G[u]) if(dis[v]>dis[u]+z) {
			q.push({dis[v]=dis[u]+z,v});
			chk[v]=chk[u]|(z>1);
		}
	}
}
void main() {
	ll sum=0;
	for(int i=2;i<=m;++i) {
		int s=w1[st[i]],t=w2[st[i]]-2;
		x[i-1]=s-t+1,sum+=t;
	}
	x[0]=0,x[m]=n,sort(x,x+m+1);
	for(int i=0;i<m&&x[i]<n;++i) {
		if(i) sum+=x[i]-1;
		if(x[i+1]==x[i]) continue;
		dijk(2*m-2-i);
		for(int u=1;u<=n;++u) if(vis[u]&&chk[u]) ans[u]=min(ans[u],dis[u]+sum);
	}
}
}
signed main() {
	int E;
	scanf("%d%d%d",&n,&E,&m);
	for(int u,v;E--;) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	for(int i=1;i<=n;++i) scanf("%1d",&cl[i]);
	for(int i=1;i<=m;++i) scanf("%d",&st[i]);
	bfs1(),bfs2(),bfs3();
	if(m>LIM) A::main();
	else B::main();
	for(int i=1;i<=n;++i) printf("%lld\n",ans[i]);
	return 0;
}



*H. [P10591] Connect

Problem Link

题目大意

给定 mn 个点的无向图,求有多少个 1m 的子集满足将这些编号的图的边集异或起来,得到的图连通。

数据范围:n10,m60

思路分析

考虑容斥,钦定得到的图可以分成 i 个连通块,设这样的方案数为 gi,再设得到的图恰好有 j 个连通块的方案数为 fj

那么我们能得到:

gi=ji{ji}fj

其中 {ji} 为第二类斯特林数,根据斯特林反演得到:

fj=ij(1)ij[ji]gi

那么我们的答案就是 f1,带入 j=1 得到答案为 i(1)i1(i1)!gi

因此我们只要求出 gi,考虑枚举最终的连通块形态,即枚举把 1n 划分成 i 个集合的所有方案,对所有的 i,枚举量是贝尔数级别的。

对于每一种方案,就是要钦定集合之间的每条边都在偶数个图里出现过,如果把这些边看成 01 位,我们就是要数异或和为 0 的子集个数,可以线性基解决。

时间复杂度 O(Bell(n)n2m)

一些思考:我们注意到最终 gi 的容斥系数是 (1)i1(i1)!,这个系数在“集合不相等容斥”中似曾相识,他们之间是否有深层的关系?

事实上是有的,我们在“集合不相等容斥”里的系数实际上是:i 个点的完全图,对于每一种连通边集 E(1)|E| 的和,我们考虑证明这个值等于 (1)i1(i1)!

我们对 E 连通边集的限制施加这个容斥,那么 gx 就是分成 x 个点集后,选出的边集 E 一定在每个点集内部连边,(1)|E| 的和。

我们发现只要 E 可选的边 1 条,其贡献总是会正负抵消,因此 gx 只在 x=i 时非零,且此时恰有 gx=1,那么我们就得到了这个容斥系数就是 (1)i1(i1)!

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,a[10],g[65][10][10];
ll X[65],fac[65],pw[65],ans=0;
struct bas {
	int cnt;
	ll b[65];
	void init() { cnt=0,memset(b,0,sizeof(b)); }
	void ins(ll x) {
		for(int i=45;~i;--i) if(x>>i&1) {
			if(!b[i]) return b[i]=x,++cnt,void();
			x^=b[i];
		}
	}
}	B;
void calc(int k) {
	B.init(),memset(X,0,sizeof(X));
	for(int i=0;i<n;++i) for(int j=i+1;j<n;++j) if(a[i]!=a[j]) {
		for(int o=0;o<m;++o) X[o]=X[o]<<1|g[o][i][j];
	}
	for(int o=0;o<m;++o) B.ins(X[o]);
	ans+=(k&1?1:-1)*fac[k-1]*pw[m-B.cnt];
}
void dfs(int i,int c) {
	if(i==n) return calc(c);
	for(a[i]=1;a[i]<=c+1;++a[i]) dfs(i+1,max(a[i],c));
}
signed main() {
	for(int i=fac[0]=pw[0]=1;i<64;++i) fac[i]=fac[i-1]*i,pw[i]=pw[i-1]*2;
	ios::sync_with_stdio(false);
	cin>>m;
	for(int o=0;o<m;++o) {
		string s; cin>>s;
		while(n*(n-1)/2!=(int)s.size()) ++n;
		for(int i=0,k=0;i<n;++i) for(int j=i+1;j<n;++j) g[o][i][j]=s[k++]-'0';
	}
	dfs(0,0);
	cout<<ans<<"\n";
	return 0;
}



*I. [P10064] Path

Problem Link

题目大意

给定 n 个点的树,在所有简单路径中选择若干条,要求任意两个点之间的路径都存在 2 条选出的路径覆盖其中的每条边,求方案数。

数据范围:n3000

思路分析

显然如果存在一条不合法路径,那么一定存在一条叶子到叶子的不合法路径。

因此只要所有叶子的路径合法。

我们取出每个叶子 u 能通过一条选出路径到达的所有点 Su,我们就是要要求所有 Su 交非空。

注意到 Su 的交如果非空,一定是连通块,因此我们可以使用点减边容斥,即计算每个点属于 Su 的交和每条边(两端点)属于 Su 的交的方案数。

先考虑如何计算 ufa(u) 的边被 Su 的交包含的方案数,发现不好维护,不妨进行容斥,钦定 u 子树内有多少个 Su 不包含这条边,可以算出子树外的叶子的 S 一定经过 u 方案数:

Ans=i=0lu(1)i(lui)2(solo)(sui)×2su(su1)/2×2si(so1)/2×(2sui1)lo

其中 su/so 表示 u 子树内、外的大小,lu,lo 表示 u 子树内、外的叶子个数,具体意义就是分讨每类边的连接情况。

然后考虑 uSu 的交包含的方案数,此时不能直接容斥,因为此时删除 u 后有 deg(u) 个连通块,我们只能钦定其中一个连通块中所有叶子的 Su

那么我们可以背包维护容斥系数,fi 表示 u 子树钦定 i 个叶子不过 u 的方案数,加上 v 子树的转移就是:

fi+jfi×(1)j(lvj)×2(Sui)(Svj)

u 子树外的叶子直接钦定 Su 必须过 u,算答案的式子和上面类似,由于此时卷积的上界是 su,因此复杂度是平方的。

时间复杂度 O(n2logP),瓶颈在计算答案时的快速幂,可以预处理优化掉。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3005,MAXV=5e6+5,MOD=998244353;
ll ksm(ll a,ll b) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll f[MAXN],g[MAXN],C[MAXN][MAXN],pw[MAXV],ans=0;
int n,sz[MAXN],sf[MAXN],lfc=0;
vector <int> G[MAXN];
void dfs(int u,int fz) {
	if(G[u].size()==1) return sz[u]=sf[u]=1,void();
	for(int v:G[u]) if(v^fz) dfs(v,u);
	memset(f,0,sizeof(f)),sz[u]=1,f[0]=1;
	int in=0;
	for(int v:G[u]) if(v^fz) {
		memset(g,0,sizeof(g));
		for(int i=0;i<=sf[v];++i) {
			ll w=i&1?MOD-C[sf[v]][i]:C[sf[v]][i];
			for(int j=0;j<=sf[u];++j) {
				g[i+j]=(g[i+j]+w*f[j]%MOD*pw[(sz[v]-i)*(sz[u]-j)])%MOD;
			}
		}
		memcpy(f,g,sizeof(f));
		in+=sz[v]*(sz[v]-1)/2;
		sf[u]+=sf[v],sz[u]+=sz[v];
	}
	int oz=n-sz[u],of=lfc-sf[u];
	in+=oz*(oz-1)/2;
	for(int i=0;i<=sf[u];++i) {
		ans=(ans+f[i]*pw[in+(oz-of)*(sz[u]-i)]%MOD*ksm(pw[sz[u]-i]-1,of))%MOD;
	}
	if(!fz) return ;
	in=oz*(oz-1)/2+sz[u]*(sz[u]-1)/2;
	for(int i=0;i<=sf[u];++i) {
		ll z=i&1?C[sf[u]][i]:MOD-C[sf[u]][i];
		ans=(ans+z*pw[in+(oz-of)*(sz[u]-i)]%MOD*ksm(pw[sz[u]-i]-1,of))%MOD;
	}
}
signed main() {
	scanf("%d",&n);
	for(int i=pw[0]=1;i<=n*(n-1)/2;++i) pw[i]=pw[i-1]*2%MOD;
	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=1,u,v;i<n;++i) {
		scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	}
	if(n==2) return puts("1"),0;
	int rt=0;
	for(int i=1;i<=n;++i) {
		if(G[i].size()==1) ++lfc;
		else if(!rt) rt=i;
	}
	dfs(rt,0);
	printf("%lld\n",ans);
	return 0;
}




Round #11 - 2024.9.26

A. [P9923] Button

Problem Link

题目大意

给定 n×n 网格上的 m 个点,将其中一些点(至少一个)染黑,使得每行每列黑色格子数奇偶性相同。

数据范围:n105,m5×105

思路分析

考虑将每行每列拆点建图,那么每个点可以看成两个点之间的一条边,那么我们就是要选出一个非空边集使得在这个边集中每个点的度数奇偶性都相同。

先考虑每个点度数都是偶数,那么选出的边很显然一定会形成若干个欧拉回路,由于度数为 0 也合法,因此只要某个连通块能任意选出一个环即可。

剩余的情况一定是森林,此时我们要求每个点度数均为偶数,自下而上构造,用每个点到父亲的边调整,能得到唯一的方案(或得不到方案)。

时间复杂度 O(n+m)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,m,fa[MAXN*2];
int hd[MAXN*2],ec=1,to[MAXN*10],lst[MAXN*10];
bool vis[MAXN*2];
void adde(int u,int v) { to[++ec]=v,lst[ec]=hd[u],hd[u]=ec; }
void dfs1(int u) {
	vis[u]=true;
	for(int i=hd[u];i;i=lst[i]) if(i!=fa[u]){
		int v=to[i];
		if(!vis[v]) fa[v]=i^1,dfs1(v);
		else {
			vector <int> wys{i};
			for(int x=u;x!=v;x=to[fa[x]]) wys.push_back(fa[x]);
			cout<<"TAK\n"<<wys.size()<<"\n";
			for(int z:wys) cout<<z/2<<" ";
			cout<<"\n";
			exit(0);
		}
	}
}
bool deg[MAXN*2];
vector <int> wys;
void dfs2(int u) {
	vis[u]=true;
	for(int i=hd[u];i;i=lst[i]) if(i^fa[u]) dfs2(to[i]);
	if(fa[u]&&!deg[u]) {
		deg[to[fa[u]]]^=1,wys.push_back(fa[u]),deg[u]^=1;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1,u,v;i<=m;++i) {
		cin>>u>>v,adde(u,v+n),adde(v+n,u);
	}
	for(int i=1;i<=2*n;++i) if(!vis[i]) dfs1(i);
	memset(vis,false,sizeof(vis));
	for(int i=1;i<=2*n;++i) if(!vis[i]) {
		dfs2(i);
		if(!deg[i]) return cout<<"NIE\n",0;
	}
	cout<<"TAK\n"<<wys.size()<<"\n";
	for(int z:wys) cout<<z/2<<" ";
	cout<<"\n";
	return 0;
}



B. [P10360] Swap

Problem Link

题目大意

给定长度为 n 的 01 序列 x1xnm 次操作 (ai,bi),如果 xai=1,xbi=0 就交换 xaixbi

一个序列是好的当且仅当最终得到的 x 序列中的 1 恰好构成一个区间。

对每个 k 求有多少恰有 k1 的 01 序列是好的,答案对 2 取模。

数据范围:n35,m1000

思路分析

朴素暴力就是对于每个 x,从前往后维护这个序列,但这样显然不能通过。

考虑减小枚举量,对于一个不被任何操作覆盖的 xi,我们不枚举其取值,只在某个 aj=i 的时候考虑。

对于一个操作 ai,bi,分类讨论两个元素当前的取值是否被确定:

  • 如果 xai,xbi 都被确定,那么直接模拟下一步操作。

  • 如果 xai,xbi 都未被确定,那么我们发现如果 xaixbi,那么转移到下一步操作的时候两个序列会得到相同的结果,因此这种序列对答案的贡献一定是偶数,可以忽略掉。

    因此只要枚举 xai=xbi=0/1 两种情况。

  • 如果仅有 xai 被确定(另一种对称),如果 xai=0,说明这次操作无效,可以跳过,否则我们发现:

    • 如果 xbi=1,那么交换后 xai=xbi=1
    • 如果 xbi=0,那么交换后 xai=0,xbi=1

    因此此时 xbi 恒为 1xai 任取 0/1,那么直接把 xbi 设为已知且值为 1xai 设为未知即可。

容易发现只有第二种情况时状态数翻倍,且此时一定消耗两个未知元素,其他操作不消耗未知元素,那么状态总数就是 O(2n/2) 的。

时间复杂度 O(2n/2(m+n2))

如果 xbi=0,那么交换后 xai=xbi=0

代码呈现

#include<bits/stdc++.h>
#define ll long long
#define q(s,i) (s>>i&1)
#define d(i) (1ll<<i)
using namespace std;
const int MAXN=1005;
int n,m,a[MAXN],b[MAXN];
bool ans[40];
void dfs(int i,ll vis,ll col) {
	if(i==m+1) {
		for(int l=0;l<n;++l) {
			int p=n;
			for(int r=l;r<n;++r) if(q(vis,r)&&!q(col,r)) {
				p=r; break;
			}
			for(int r=n-1;r>=l;--r) {
				if(r<p) ans[r-l+1]^=1;
				if(q(vis,r)&&q(col,r)) break;
			}
			if(q(vis,l)&&q(col,l)) break;
		}
		return ;
	}
	if(q(vis,a[i])&&q(vis,b[i])) {
		if(q(col,a[i])&&!q(col,b[i])) col^=d(a[i])^d(b[i]);
		dfs(i+1,vis,col);
	} else if(q(vis,a[i])) {
		if(!q(col,a[i])) dfs(i+1,vis,col); 
		else {
			vis^=d(a[i])^d(b[i]);
			col&=~d(a[i]);
			dfs(i+1,vis,col|d(b[i]));
		}
	} else if(q(vis,b[i])) {
		if(q(col,b[i])) dfs(i+1,vis,col);
		else {
			vis^=d(a[i])^d(b[i]);
			col&=~d(b[i]);
			dfs(i+1,vis,col&(~d(a[i])));
		}
	} else {
		vis|=d(a[i])^d(b[i]);
		dfs(i+1,vis,col);
		dfs(i+1,vis,col|d(a[i])|d(b[i]));
	}
}
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i) scanf("%d%d",&a[i],&b[i]),--a[i],--b[i];
	dfs(1,0,0);
	for(int i=1;i<=n;++i) printf("%d ",ans[i]); puts("");
	return 0;
}



C. [P10367] Flip

Problem Link

题目大意

给定 n 个点 m 条边的无向图,节点初始有黑白两种颜色,对于一条两端点同色的边,可以同时翻转两端点颜色,求一共能得到多少种不同的节点颜色组合。

数据范围:n2×105,m4×105

思路分析

从链的情况入手,我们发现如果 ai1ai+1,不妨设 ai=ai1,那么操作 (i1,i),(i,i+1) 就能交换 ai1,ai+1

因此我们能任意排列奇数位置的点和偶数位置的点,并且可以同时增加或减少两种位置上黑点的个数。

因此只要保证两种位置上黑点个数差相等,每种颜色序列都是可以的得到的。

不难将这个结论推广到连通二分图上。

对于一个奇环,我们发现根据上面的过程,可以任意排列整个环的颜色,因此对于一个有奇环的连通块,我们只要求黑点个数的奇偶性不变。

答案就是每个连通块答案的乘积。

时间复杂度 O(n+m)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,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; }
ll fac[MAXN],ifac[MAXN];
ll C(int x,int y) {
	if(x<0||y<0||y>x) return 0;
	return fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
int n,m,f[2],g[2],w[MAXN];
bool vis[MAXN],col[MAXN],flg;
vector <int> G[MAXN];
void dfs(int u,int c) {
	if(vis[u]) return flg&=(col[u]==c),void();
	vis[u]=true,col[u]=c,++f[c],g[c]+=w[u];
	for(int v:G[u]) dfs(v,c^1);
}
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=ifac[0]=fac[0]=1;i<=n;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
	for(int i=1;i<=n;++i) scanf("%d",&w[i]);
	for(int i=1,u,v;i<=m;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	ll ans=1;
	for(int u=1;u<=n;++u) if(!vis[u]) {
		f[0]=f[1]=g[0]=g[1]=0,flg=1,dfs(u,0);
		ll sum=0;
		if(flg) {
			for(int i=0;i<=f[1];++i) {
				sum=(sum+C(f[0],i+g[0]-g[1])*C(f[1],i))%MOD;
			}
		} else {
			int s=f[0]+f[1];
			for(int i=g[1]%2;i<=s;i+=2) {
				sum=(sum+C(s,i))%MOD;
			}
		}
		ans=ans*sum%MOD;
	}
	printf("%lld\n",ans);
	return 0;
}



D. [P10879] Modify

Problem Link

题目大意

给定 n 个点的树,其中第 i 个点的父亲在 [li,ri] 中均匀随机,m 次操作给 uv 路径边权 +w,求出每条 ufa(u) 的边的期望边权。

数据范围:n,m5000

思路分析

考虑如何刻画路径边权,考虑一个类似朴素 LCA 的过程,如果 u>v,那么 ufa(u) 的边一定被覆盖。

那么可以设计一个简单 dp,fi,j 表示从 (u,v) 开始跳到 (i,j) 的概率。

转移就是 fu,vrulu+1fluru,v,但是要时刻保证 u>v,因此可以看成 f 上的两个区间加,差分维护。

算答案就是把每个 w×fi,j 加进 i 的答案,对每条路径 dp 复杂度 O(n2m)

对每个 (u,v) 都 dp 一遍复杂度不能接受,但我们可以合并起点,即把每条路径看成令初始的 fu,vw,一遍 dp 即可算出答案。

时间复杂度 O(n2+m)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5005,MOD=998244353;
int n,m,l[MAXN],r[MAXN],f[MAXN][MAXN],inv[MAXN],g[MAXN];
int a[MAXN][MAXN],b[MAXN][MAXN];
void add(int &x,int y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
void sub(int &x,int y) { x=(x>=y)?x-y:x+MOD-y; }
signed main() {
	scanf("%d",&n),inv[1]=1;
	for(int i=2;i<=n;++i) scanf("%d%d",&l[i],&r[i]),inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
	scanf("%d",&m);
	for(int i=1,u,v,w;i<=m;++i) {
		scanf("%d%d%d",&u,&v,&w);
		add(f[max(u,v)][min(u,v)],w%MOD);
	}
	for(int j=n;j>=1;--j) {
		for(int i=n;i>j;--i) {
			add(a[i][j],a[i+1][j]),add(f[i][j],a[i][j]);
			add(b[i][j],b[i][j+1]),add(f[i][j],b[i][j]);
			add(g[i],f[i][j]);
			int z=1ll*f[i][j]*inv[r[i]-l[i]+1]%MOD;
			if(j<l[i]) add(a[r[i]][j],z),sub(a[l[i]-1][j],z);
			else if(r[i]<j) add(b[j][r[i]],z),sub(b[j][l[i]-1],z);
			else {
				add(a[r[i]][j],z),sub(a[j][j],z);
				add(b[j][j-1],z),sub(b[j][l[i]-1],z);
			}
		}
	}
	for(int i=2;i<=n;++i) printf("%d ",g[i]); puts("");
	return 0;
}



*E. [P9850] Subgraph

Problem Link

题目大意

给定 n 个点 m 条边的无向图,求 K4 子图个数与四元独立集子图个数之差。

数据范围:n105,m2×105

思路分析

考虑二项式反演,设 fi 表示钦定 i 条边的四元子图数量,答案就是 |f0f1+f2f3+f4f5|

f0f5 分别计算:

  • f0:任选四个点都满足:n(n1)(n2)(n3)24

  • f1:任选一条边并另选两个点:m×n(n1)2

  • f2:分讨选的两条边的形态:

    • 如果有公共顶点,那么枚举顶点并选剩下的一个点: (n3)udu(du1)2,其中 du 表示度数。
    • 如果没有公共顶点,任选两条边,容斥掉有公共顶点的情况:m(m1)2udu(du1)2
  • f3:分讨选的三条边的形态:

    • 如果三条边有公共顶点,枚举顶点:udu(du1)(du2)6
    • 如果是三元环,答案就是图中三元环个数 c3 乘以 (n3)
    • 如果是链,枚举中间的一条边,但我们要求另外两条边的另一个端点不重合,如果重合了就形成了三元环,且一个三元环被算三次:(u,v)E(du1)(dv1)3c3
  • f4:分讨选的四条边的形态:

    • 如果是四元环,答案就是图中四元环个数 c4
    • 如果是三元环外挂一条边,枚举外挂边的节点,答案就是:cu(du2),其中 cu 表示覆盖 u 的三元环个数。
  • f5:此时只能是两个三元环公用一条边:ece(ce1)2,其中 ce 表示过 e 这条边的三元环个数。

容易发现 c3,c4,cu,ce 都是好求的。

时间复杂度 O(mm)

代码呈现

#include<bits/stdc++.h> 
#define ll long long
using namespace std;
const int MAXN=1e5+5;
struct Edge { int v,id; };
vector <Edge> G[MAXN],E[MAXN];
int n,m,deg[MAXN],f[MAXN];
ll c3=0,c4=0,cv[MAXN],ce[MAXN*2];
bool cmp(int x,int y) { return deg[x]<deg[y]||(deg[x]==deg[y]&&x<y); }
ll f0,f1,f2,f3,f4,f5;
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;++i) {
		scanf("%d%d",&u,&v),G[u].push_back({v,i}),G[v].push_back({u,i});
		++deg[u],++deg[v];
	}
	for(int u=1;u<=n;++u) for(auto e:G[u]) if(cmp(u,e.v)) E[u].push_back(e);
	for(int u=1;u<=n;++u) {
		for(auto i:G[u]) for(auto j:E[i.v]) if(cmp(u,j.v)) c4+=f[j.v]++;
		for(auto i:G[u]) for(auto j:E[i.v]) f[j.v]=0;
	}
	for(int u=1;u<=n;++u) {
		for(auto i:E[u]) f[i.v]=i.id;
		for(auto i:E[u]) for(auto j:E[i.v]) if(f[j.v]) {
			++c3,++cv[u],++cv[i.v],++cv[j.v];
			++ce[i.id],++ce[j.id],++ce[f[j.v]];
		}
		for(auto i:E[u]) f[i.v]=0;
	}
	f0=(__int128)n*(n-1)*(n-2)*(n-3)/24;
	f1=1ll*m*(n-2)*(n-3)/2;
	f2=1ll*m*(m-1)/2;
	for(int i=1;i<=n;++i) f2+=1ll*deg[i]*(deg[i]-1)/2*(n-3-1);
	f3=c3*(n-3-3);
	for(int u=1;u<=n;++u) f3+=1ll*deg[u]*(deg[u]-1)*(deg[u]-2)/6;
	for(int u=1;u<=n;++u) for(auto e:E[u]) f3+=1ll*(deg[u]-1)*(deg[e.v]-1);
	f4=c4;
	for(int u=1;u<=n;++u) f4+=(deg[u]-2)*cv[u];
	for(int i=1;i<=m;++i) f5+=ce[i]*(ce[i]-1)/2;
	ll ans=f0-f1+f2-f3+f4-f5;
	printf("%lld\n",ans<0?-ans:ans);
	return 0;
}



F. [P9878] Pattern

Problem Link

题目大意

给定 n×m 网格,有一些位置已经被填 0/1,构造一种给整个网格填入 0/1 的方案,使得其不包含子矩阵 [0110][1001]

数据范围:n,m100

思路分析

先把网格图黑白间隔染色,把被染成黑色的点 0/1 翻转,那么我们就要让新网格不包含全 0 和全 12×2 子矩阵。

首先,如果有一个 2×2 子矩形中填入三个同色元素,那么剩下一个位置一定要填异色元素,填完之后可能又产生连锁反应,将另一个 2×2 子矩形中填入了三个同色元素,用一个 dfs 维护此过程。

对每个 2×2 子矩形 dfs 一遍,得到的新图不包含任何一个 2×2 子矩形有三个同色元素和一个空位。

接下来我们任选一个空位,随便填 0/1,然后 dfs 增广,可以证明不断这样构造就能得出解。

我们只要说明一次 dfs 中不可能得到 2×2 同色子矩形。

反证法设我们将一个子矩形 [x000]x 钦定为 0,那么这一定是通过右上角的全 1 矩形得到的。

也就是形如:[111x000] 的结构,由于 dfs 前没有 2×2 子矩形有三个同色元素和一个空位,因此这个结构中的三个 0 至少有一个是在这一轮确定的,简单分析发现不可能存在这种情况。

时间复杂度 O(nm)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;
int n,m,a[105][105];
void dfs(int i,int j) {
	if(i<1||j<1||i>=n||j>=m) return ;
	int s=a[i][j]+a[i+1][j]+a[i][j+1]+a[i+1][j+1];
	if(s!=3&&s!=-3) return ;
	s=-s/3;
	if(!a[i][j]) a[i][j]=s,dfs(i-1,j-1);
	else if(!a[i+1][j]) a[i+1][j]=s,dfs(i+1,j-1);
	else if(!a[i][j+1]) a[i][j+1]=s,dfs(i-1,j+1);
	else a[i+1][j+1]=s,dfs(i+1,j+1);
}
void solve() {
	cin>>n>>m;
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
		char c; cin>>c,a[i][j]=(c=='?'?0:(c=='B'?1:-1))*((i+j)&1?-1:1);
	}
	for(int i=1;i<n;++i) for(int j=1;j<m;++j) dfs(i,j);
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(!a[i][j]) {
		a[i][j]=1,dfs(i-1,j-1),dfs(i-1,j),dfs(i,j-1),dfs(i,j);
	}
	bool flg=1;
	for(int i=1;i<n;++i) for(int j=1;j<m;++j) {
		int s=a[i][j]+a[i+1][j]+a[i][j+1]+a[i+1][j+1];
		flg&=(s!=4&&s!=-4);
	}
	if(!flg) return cout<<"NO\n",void();
	cout<<"YES\n";
	for(int i=1;i<=n;++i,cout<<"\n") for(int j=1;j<=m;++j) {
		if((i+j)&1) a[i][j]*=-1;
		cout<<(a[i][j]>0?'B':'W');
	}
}
signed main() {
	ios::sync_with_stdio(false);
	int T; cin>>T;
	while(T--) solve();
	return 0;
}



*G. [P9924] Signal

Problem Link

题目大意

给定左右各 n 个点,m 条边二分图,给每个点构造长度为 n+1 的字符串(字符集 A,B,C),使得:

  • 两个字符串之间存在至少一位相等当且仅当对应的两个节点属于二分图同一侧,或者有边相连。
  • 字符串两两不同。

数据范围:n1000,mn2

思路分析

一个朴素的做法是对左部点 1n,给第 i 个串第 i 位填 C,其他填 A

对于每个右部点,如果其和左部第 i 个点有边,填 C,否则填 B

0 位左部点填 A,右部点填 B ,这样一个长度为 n+1 的串就能得到答案。

但此时我们无法保证每个右部点字符串两两不同,我们可以额外用 log2n 位对第 i 个右部点表示 i 的二进制表示(用 B,C 代替 0,1),且左部点这些位填 A

这样的的串长是 n+1+log2n 的,无法通过。

考虑什么样的右部点会得到相同的字符串,当且仅当他们的邻域相同,把这些点称为“等价类”。

注意到只有同一个等价类中的点才需要区分,因此我们可以直接传递其在等价类中排名的二进制表示,而不是 i 的二进制表示。

那么假设右部点最大的等价类大小是 Rs,那么我们只要 n+1+log2Rs 个位,但这不够,需要继续优化。

考虑把左部点的等价类也划分出来,容易发现一个等价类可以共用一个位置填 C,但我们要对左部等价类内的元素进行去重。

那么设左、右部点的等价类数量是 Lc,Rc,等价类大小最大为 Ls,Rs

那么花费位数为 Lc+log2Ls+log2Rs,在 Rc<Lc 的时候可以交换左右部点得到更优的次数。

综上我们得到的花费位数为 min(Lc,Rc)+log2Ls+log2Rs

分析一下这个东西的量级,注意到 LsnLC+1nmin(Lc,Rc)+1

因此设 x=nmin(Lc,Rc)+1,那么花费位数不超过 nx+1+2log2x

x2log2x 时花费位数 n+1

我们只要考虑 x<2log2x 的情况,发现此时一定有 x=3/x=5

不妨设 x=3,那么此时左右部点都是 n3 个大小为 1 的等价类和一个大小为 3 的等价类。

考虑左部大小为 3 的等价类 {x,y,z},我们需要 1 个位传递其连边信息,并且需要 2 个位区分标号。

如果我们对每个点分别传递连边信息,那么不需要区分标号,但是要 3 个位传递连边。

结合一下两种做法,第一位传递 x,y 的连边信息,第二位传递 y,z 的连边信息,此时两次传递即可令所有字符串不同。

x=5 的情况类似,对于五元等价类 {a,b,c,d,e},第一位传递 a,b,c,第二位传递 c,d,e,第三位传递 b,d 即可。

综上,我们得到了串长 n+1 时完整的构造方案。

时间复杂度 O(n2+m)

代码呈现

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=2005;
int n,m,lim,id[MAXN];
vector <int> G[MAXN],lq[MAXN],rq[MAXN];
string s[MAXN];
mt19937_64 rnd(time(0));
ull hs[MAXN],hv[MAXN];
bool vis[MAXN];
int lg(int x) { return __lg(x)+!!(x&(x-1)); }
void solve() {
	for(int i=0;i<2*n;++i) {
		G[i].clear(),s[i].clear();
		lq[i].clear(),rq[i].clear();
		vis[i]=false,hs[i]=0,id[i]=-1;
	}
	for(int i=1,u,v;i<=m;++i) {
		cin>>u>>v,--u,--v;
		G[u].push_back(v),hs[u]^=hv[v];
		G[v].push_back(u),hs[v]^=hv[u];
	}
	cout<<lim<<"\n";
	for(int i=0;i<2*n;++i) s[i]=string(lim,(i<n?'A':'B'));
	int lc=0,rc=0,ls=0,rs=0;
	for(int i=0;i<n;++i) if(id[i]<0) {
		for(int j=i;j<n;++j) if(hs[i]==hs[j]) {
			id[j]=lc,lq[lc].push_back(j);
		}
		ls=max(ls,(int)lq[lc++].size());
	}
	for(int i=n;i<2*n;++i) if(id[i]<0) {
		for(int j=i;j<2*n;++j) if(hs[i]==hs[j]) {
			id[j]=rc,rq[rc].push_back(j);
		}
		rs=max(rs,(int)rq[rc++].size());
	}
	int len=min(lc,rc)+lg(ls)+lg(rs);
	if(len>lim) {
		assert(ls==rs&&lc==n-ls+1&&rc==n-rs+1&&(ls==3||ls==5));
		int k=0,x=lg(ls),y=lg(rs);
		for(int i=0;i<lc;++i) if(lq[i].size()>1) k=i;
		if(ls==3) {
			//ls=rs=3,lc=rc=n-2
			for(int i=0;i<n;++i) if(id[i]!=k) s[i][id[i]]='C';
			int a=lq[k][0],b=lq[k][1],c=lq[k][2];
			s[a][k]=s[b][k]=s[b][lc]=s[c][lc]='C';
			for(int i=n;i<2*n;++i) for(int j:G[i]) {
				s[i][id[j]]='C';
				if(id[j]==k) s[i][lc]='C';
			}
		} else {
			//ls=rs=5,lc=rc=n-4
			for(int i=0;i<n;++i) if(id[i]!=k) s[i][id[i]]='C';
			int a=lq[k][0],b=lq[k][1],c=lq[k][2],d=lq[k][3],e=lq[k][4];
			s[a][k]=s[b][k]=s[c][k]='C';
			s[c][lc]=s[d][lc]=s[e][lc]='C';
			s[b][lc+1]=s[d][lc+1]='C';
			for(int i=n;i<2*n;++i) for(int j:G[i]) {
				s[i][id[j]]='C';
				if(id[j]==k) s[i][lc]=s[i][lc+1]='C';
			}
		}
		for(int o=0;o<rc;++o) for(int i=0;i<(int)rq[o].size();++i) {
			for(int j=0;j<y;++j) {
				s[rq[o][i]][j+lc+x-1]="BC"[i>>j&1];
			}
		}
	} else if(lc<rc) {
		for(int i=0;i<n;++i) s[i][id[i]]='C';
		for(int i=n;i<2*n;++i) {
			for(int j:G[i]) s[i][id[j]]='C';
		}
		int x=lg(ls),y=lg(rs);
		for(int o=0;o<lc;++o) for(int i=0;i<(int)lq[o].size();++i) {
			for(int j=0;j<x;++j) {
				s[lq[o][i]][j+lc]="AC"[i>>j&1];
			}
		}
		for(int o=0;o<rc;++o) for(int i=0;i<(int)rq[o].size();++i) {
			for(int j=0;j<y;++j) {
				s[rq[o][i]][j+lc+x]="BC"[i>>j&1];
			}
		}
	} else {
		for(int i=n;i<2*n;++i) s[i][id[i]]='C';
		for(int i=0;i<n;++i) {
			for(int j:G[i]) s[i][id[j]]='C';
		}
		int x=lg(ls),y=lg(rs);
		for(int o=0;o<lc;++o) for(int i=0;i<(int)lq[o].size();++i) {
			for(int j=0;j<x;++j) {
				s[lq[o][i]][j+rc]="AC"[i>>j&1];
			}
		}
		for(int o=0;o<rc;++o) for(int i=0;i<(int)rq[o].size();++i) {
			for(int j=0;j<y;++j) {
				s[rq[o][i]][j+rc+x]="BC"[i>>j&1];
			}
		}
	}
	for(int i=0;i<2*n;++i) cout<<s[i]<<"\n";
}
signed main() {
	for(int i=0;i<MAXN;++i) hv[i]=rnd();
	ios::sync_with_stdio(false);
	while(cin>>n>>m>>lim) solve();
	return 0;
}



*H. [P10197] Minimize

Problem Link

题目大意

给定长度为 n 的序列 a1an,给定 k 个位置不能交换,其他位置上的元素可以任意交换,最小化 i=1n1max(ai,ai+1)

数据范围:n300,k6

思路分析

k=0 的情况开始,注意到 max(ai,ai+1)=12(ai+ai+1+|aiai+1|),因此我们只要最小化 |aiai+1|,升序或降序排列即可。

回到一般的问题,我们设 a0=an+1= 且这两个位置不能交换,那么整个序列就被不能交换的点分成若干个区间 (li,ri),我们只要把可以交换的元素填入这些区间即可。

假设我们在区间 (li,ri) 中填的元素是 S,根据刚才的结论,显然 S 升序排列或降序排列最优,如果 ali<ari 那么升序排列,否则降序排列。

S 中元素的最大最小值分别为 maxi,mini,那么这一段区间的贡献就是 |alimini|+|arimaxi|+maximini

注意到关于 maxi 的贡献为 |arimaxi|+maxi 随着 maxi 变小递减,关于 mini 的贡献 |alimini|mini 随着 mini 变大递减。

因此可以通过调整法证明每个区间内的值域区间 [mini,maxi] 要么相离要么包含,否则可以把相交区间切成两部分,使得 maxi 变小 minj 变大。

那么就可以考虑在值域上 dp,设可以交换的元素排序后是 w1wm,那么设 fl,r,S 表示考虑 S 中的区间用 wlwr 中的元素填满的最小代价。

根据代价函数的性质进一步分析,我们发现:对于两个值域区间 [mini,maxi][minj,maxj][mini,maxi] 内部不可能有元素在 j 中,否则交换该元素和 mini 即可让 mini 变大。

S 中的区间大小总和为 sizS,那么我们只要在 rl+1=sizS 时考虑向 S 中插入一个区间,否则直接从 min(fl,r1,S,fl+1,r,S) 转移。

考虑插入区间的形式:

  • 合并两个值域区间,枚举 TS 后从 f(l,l+sizT1,T)+f(l+sizT,r,ST) 转移。
  • 插入一个区间 x
    • 特判 x 大小为 1 的情况,可以钦定这种情况最先考虑,只需要在 l=r 时计算,通过合并操作即可转移。
    • 否则一定插入 minx=wl,maxx=wr 的若干个元素,算出其转移代价后从 f(l1,r+1,S{x}) 转移。

时间复杂度 O(n(3k+k2k))

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int V=1e6+5;
int a[305],L[10],R[10],len[10],sz[1<<7],w[305],id[10],f[305][305][1<<7];
void chkmin(int &x,int y) { x=x<y?x:y; }
signed main() {
	int N,M,n=0,m=0,ans=0;
	scanf("%d%d",&N,&M);
	for(int i=1;i<=N;++i) scanf("%d",&a[i]),ans+=2*a[i];
	for(int i=1;i<=M;++i) scanf("%d",&id[i]);
	a[0]=a[N+1]=V,id[0]=0,id[M+1]=N+1,ans+=2*V;
	for(int i=0;i<=M;++i) {
		if(id[i+1]-id[i]==1) ans+=abs(a[id[i]]-a[id[i+1]]);
		else {
			L[m]=id[i],R[m]=id[i+1],len[m]=R[m]-L[m]-1;
			for(int j=L[m]+1;j<=R[m]-1;++j) w[++n]=a[j];
			if(a[L[m]]>a[R[m]]) swap(L[m],R[m]); ++m;
		}
	}
	sort(w+1,w+n+1);
	for(int s=0;s<(1<<m);++s) for(int i=0;i<m;++i) if(s>>i&1) sz[s]+=len[i];
	memset(f,0x3f,sizeof(f));
	for(int i=0;i<=n;++i) f[i+1][i][0]=0;
	for(int d=1;d<=n;++d) for(int l=1,r=d;r<=n;++l,++r) for(int s=0;s<(1<<m);++s) if(sz[s]<=d) {
		f[l][r][s]=min(f[l][r-1][s],f[l+1][r][s]);
		if(sz[s]<d) continue;
		for(int j=0;j<m;++j) if(s>>j&1) {
			int z=abs(a[L[j]]-w[l])+abs(a[R[j]]-w[r])+w[r]-w[l];
			if(len[j]==1&&d==1) {
				chkmin(f[l][r][s],z);
			} else if(len[j]>1) {
				chkmin(f[l][r][s],f[l+1][r-1][s^(1<<j)]+z);
			}
		}
		for(int t=(s-1)&s;t;t=(t-1)&s) {
			chkmin(f[l][r][s],f[l][l+sz[t]-1][t]+f[l+sz[t]][r][s^t]);
		}
	}
	printf("%d\n",(ans+f[1][n][(1<<m)-1])/2-2*V);
	return 0;
}




Round #12 - 2024.9.27

A. [P10819] Speed

Problem Link

题目大意

给定 n 个点 m 条边的图,初始每条边边权为 1,某次操作可以把一条边的边权从 1p 变成 1p+1,进行 k 次操作,最小化 dist(s1,t1)+dist(s2,t2)

数据范围:n,m5000,k109

思路分析

很显然我们只关心路径交和剩余边的数量。

首先两条路径的路径交肯定是连续的一段,否则把较长的一段调整成较短的即可。

预处理出全源最短路,我们只要枚举路径交的起点终点,就可以求出 fi 表示路径交长度为 i 时剩余边最少有几条。

对每个 (i,fi) 分别求解,把每个操作看成带来 2p(p+1)1p(p+1) 的收益。

查询时二分斜率,求出最大的 q 使得操作 q 的所有操作需要次数 k,然后分讨求答案即可。

时间复杂度 O(nm+nlogV)

代码呈现

#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
const int MAXN=5005;
const ll inf=2e18;
int n,m; ll k;
vector <int> G[MAXN];
int d[MAXN][MAXN],f[MAXN];
ll q1(ll x) {
	ll z=sqrt(x)+10;
	while(z*(z-1)>x) --z;
	return z;
}
ll q2(ll x) {
	ll z=sqrt(2*x)+10;
	while(z*(z-1)/2>x) --z;
	return z;
}
ld qry(ll x,ll z) {
	if(!x&&!z) return 0;
	ll l=1,r=inf,p=0;
	while(l<=r) {
		ll mid=(l+r)>>1;
		if((q2(mid)-1)*z+(q1(mid)-1)*x<=k) l=mid+1,p=mid;
		else r=mid-1;
	}
	ll s1=q1(p),s2=q2(p),q=k-(s2-1)*z-(s1-1)*x;
	ll c1=(s1+1)*s1,c2=(s2+1)*s2/2;
	if(c1<c2) {
		return (ld)q/(s1+1)+(ld)(x-q)/s1+(ld)z/s2*2;
	} else if(c2<c1||q<=z) {
		return (ld)x/s1+((ld)(z-q)/s2+(ld)q/(s2+1))*2;
	} else {
		q-=z;
		return (ld)q/(s1+1)+(ld)(x-q)/s1+(ld)z/(s2+1)*2;
	}
}
signed main() {
	scanf("%d%d%lld",&n,&m,&k);
	for(int i=1,u,v;i<=m;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	memset(d,0x0f,sizeof(d));
	int s1,t1,s2,t2;
	scanf("%d%d%d%d",&s1,&t1,&s2,&t2);
	for(int s=1;s<=n;++s) {
		queue <int> q;
		q.push(s),d[s][s]=0;
		while(q.size()) {
			int u=q.front(); q.pop();
			for(int v:G[u]) if(d[s][v]>d[s][u]+1) d[s][v]=d[s][u]+1,q.push(v);
		}
	}
	memset(f,0x3f,sizeof(f));
	f[0]=d[s1][t1]+d[s2][t2];
	for(int u=1;u<=n;++u) for(int v=1;v<=n;++v) if(d[u][v]<n) {
		f[d[u][v]]=min(f[d[u][v]],d[s1][u]+d[v][t1]+d[s2][u]+d[v][t2]);
		f[d[u][v]]=min(f[d[u][v]],d[s1][u]+d[v][t1]+d[s2][v]+d[u][t2]);
	}
	ld ans=inf;
	for(int i=0;i<n;++i) if(f[i]<=4*n) {
		ans=min(ans,qry(f[i],i));
	}
	printf("%.16Lf\n",ans);
	return 0;
}



B. [P10200] Xor

Problem Link

题目大意

给定 a1an,将所有数划分成 X,Y 两个子集,使得 X 中任意两个不同元素异或和 k1Y 中任意两个不同元素异或和 k2,求方案数。

数据范围:n2×105,ai,k1,k2<260

思路分析

根据经典结论,如果有 x<y<z,那么一定有 xz>min(xy,yz),可以分讨 highbit 分布情况证明。

因此我们把 ai 从小到大排序,只要 X,Y 的邻项异或和 k1,k2 即可。

fi,j 表示 dp 到 aiaiX,上一个在 Y 中的元素是 ajgi,j 类似定义。

考虑 fi,j 的转移,如果 aiai+1k1,那么 fi+1,j=fi,j,否则 fi+1,j=0

如果 ajai+1k2fi,j 可以转移到 gi+1,i,特殊处理 j=0 的情况。

因此我们只要维护数据结构支持整体清空,查询 ixkfi 的值,直接用 Trie 树维护懒标记和子树和即可。

时间复杂度 O(nlogV)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=1e9+7;
void add(int &x,int y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
namespace T {
struct Node {
	int s[2],f[2]; bool tg[2];
	Node() { s[0]=s[1]=f[0]=f[1]=tg[0]=tg[1]=0; }
}	tr[MAXN*64];
void adt(Node &p,int o) { p.tg[o]=true,p.f[o]=0; }
void psd(Node &p) {
	for(int o:{0,1}) if(p.tg[o]) {
		if(p.s[0]) adt(tr[p.s[0]],o);
		if(p.s[1]) adt(tr[p.s[1]],o);
		p.tg[o]=false;
	}
}
int tot=1;
void ins(ll z) {
	int p=1;
	for(int i=59;~i;--i) {
		int c=z>>i&1;
		if(!tr[p].s[c]) tr[p].s[c]=++tot;
		p=tr[p].s[c];
	}
}
void upd(ll z,int x,int o) {
	int p=1;
	for(int i=59;~i;--i) {
		psd(tr[p]),add(tr[p].f[o],x);
		p=tr[p].s[z>>i&1];
	}
	add(tr[p].f[o],x);
}
int qry(ll z,ll k,int o) {
	int p=1,x=0;
	for(int i=59;~i;--i) {
		psd(tr[p]);
		int q=tr[p].s[(z>>i&1)^1];
		if(k>>i&1) p=q;
		else add(x,tr[q].f[o]),p=tr[p].s[z>>i&1];
	}
	return x;
}
}
int n;
ll a[MAXN],X,Y;
signed main() {
	scanf("%d%lld%lld",&n,&X,&Y),--X,--Y;
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]),T::ins(a[i]);
	sort(a+1,a+n+1);
	ll cur=1ll<<60;
	for(int i=2;i<=n;++i) {
		int f=(T::qry(a[i],X,0)+(cur>Y))%MOD;
		int g=(T::qry(a[i],Y,1)+(cur>X))%MOD;
		if((a[i-1]^a[i])<=X) T::adt(T::tr[1],1);
		if((a[i-1]^a[i])<=Y) T::adt(T::tr[1],0);
		if(f) T::upd(a[i-1],f,1);
		if(g) T::upd(a[i-1],g,0);
		if(i>1) cur=min(cur,a[i-1]^a[i]);
	}
	int ans=(T::tr[1].f[0]+T::tr[1].f[1])%MOD;
	printf("%d\n",ans);
	return 0;
}



C. [P10207] Ball

Problem Link

题目大意

给定 n 个球,位置为 a1anq 次询问 x,y,t,要求从 x 走到 y,要走到每个球的位置上并拿起所有球,当你拿着 k 个球时移动速度为 1k+1,求是否能在 t 时间内到达。

数据范围:n,q,ai,x,y,t5×105

思路分析

考虑对 ai 去重,注意到如果有 m 个本质不同的 ai,那么代价至少为 m(m+1)2,因此 m 只有 O(t) 级别。

注意到一个球所在位置如果被经过多次,肯定是最后一次经过时拿上最优。

因此每条路径第一次拿起的球一定是 a1/an,并且任何时候拿的球都是一段后缀和一段前缀。

考虑区间 dp,设 fl,r,0/1,0/1 表示从 1/n 出发,已经拿走了 a1l1,ar+1n 范围内的球,当前在 l/r 的方案数。

转移是容易的,复杂度 O(m2)=O(t)

查询时枚举起点并找到最接近终点的节点判断答案即可。

时间复杂度 O(t+qlogn)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1005,MAXL=5e5+5,inf=1e9;
int n,m,q,cnt[MAXL],a[MAXL];
ll dp[MAXN][MAXN][2][2];
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),++cnt[a[i]];
	scanf("%d",&q);
	for(int i=1;i<=m;++i) cnt[i]+=cnt[i-1];
	sort(a+1,a+n+1),n=unique(a+1,a+n+1)-a-1;
	if(n>=1000) {
		while(q--) puts("No");
		return 0;
	}
	memset(dp,0x3f,sizeof(dp));
	dp[1][n][0][0]=dp[1][n][1][1]=0;
	for(int len=n-1;len;--len) for(int l=1,r=len;r<=n;++l,++r) {
		ll s=cnt[a[l]-1]+cnt[m]-cnt[a[r]]+1;
		for(int x:{0,1}) {
			dp[l][r][x][0]=min(dp[l-1][r][x][0]+s*(a[l]-a[l-1]),
							   dp[l][r+1][x][1]+s*(a[r+1]-a[l]));
			dp[l][r][x][1]=min(dp[l-1][r][x][0]+s*(a[r]-a[l-1]),
							   dp[l][r+1][x][1]+s*(a[r+1]-a[r]));
		}
	}
	for(int s,t,k;q--;) {
		scanf("%d%d%d",&s,&t,&k);
		ll ans=inf;
		if(t<=a[n]) {
			int i=lower_bound(a+1,a+n+1,t)-a;
			ans=min(ans,dp[i][i][0][0]+abs(s-a[1])+1ll*(cnt[m]+1)*abs(a[i]-t));
			ans=min(ans,dp[i][i][1][0]+abs(s-a[n])+1ll*(cnt[m]+1)*abs(a[i]-t));
		}
		if(a[1]<=t) {
			int i=upper_bound(a+1,a+n+1,t)-a-1;
			ans=min(ans,dp[i][i][0][0]+abs(s-a[1])+1ll*(cnt[m]+1)*abs(a[i]-t));
			ans=min(ans,dp[i][i][1][0]+abs(s-a[n])+1ll*(cnt[m]+1)*abs(a[i]-t));
		}
		puts(ans+cnt[m]<=k?"Yes":"No");
	}
	return 0;
}



D. [P10167] Replace

Problem Link

题目大意

给定长度为 n 的表达式,由数字和 ? 组成,定义 f(x) 表示把 ? 替换成乘号或者数字串 x,得到的每个表达式的值之和,给定 L,R,求 i=LRf(i)

数据范围:n2000,L,R1018

思路分析

考虑单个 f(x) 如何求,dp 设 fi 表示第 i 个问号之前的答案,枚举上一个乘号所在位置,fij<ifj×D(j,i),其中 D(j,i) 表示第 j,i 个问号之间的 ? 全填 x 的数字串权值。

注意到 x 位数一定的时候,每个区间对应的值都是关于 x 的一次函数,因此答案必然是关于 xO(n) 次函数,自然考虑拉格朗日插值。

我们可以对于每个位数 d,枚举 O(n)x,并算出 f(x),插值求出 f 前缀和在 L1,R 处的点值,注意位数必须是 d

此时 D(j,i) 可以被写成关于 i,j 的若干个项,维护前缀和即可 O(n) dp。

时间复杂度 O(n2logV)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2005,MOD=1e9+7,i10=700000005;
ll ksm(ll a,int b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
char s[MAXN];
int n,m,a[MAXN];
ll L,R,f[MAXN],z[MAXN],dg[MAXN],pw[MAXN],ipw[MAXN],ans,cof[MAXN];
ll eval(int l,int r) { return (dg[r]-dg[l-1]*pw[r]%MOD*ipw[l-1]%MOD+MOD)%MOD; }
ll eval(ll x) {
	if(x<0) return 0;
	if(x<=n) return z[x];
	ll S=0,prd=1;
	for(int i=0;i<=n;++i) prd=(x-i)%MOD*prd%MOD;
	for(int i=0;i<=n;++i) {
		S=(S+prd*ksm((x-i)%MOD)%MOD*cof[i]%MOD*z[i])%MOD;
	}
	return S;
}
void solve(ll sz) {
	ll bas=pow(10,sz),len=ksm(10,sz+1),inv=ksm(len);
	for(int q=0;q<=n&&q<bas*9;++q) {
		ll o=(bas+q)%MOD;
		dg[0]=0,pw[0]=ipw[0]=1;
		for(int i=1;i<=n;++i) {
			ll d,v,iv;
			if(s[i]=='?') d=len,iv=inv,v=o;
			else d=10,iv=i10,v=s[i]-'0';
			dg[i]=(dg[i-1]*d+v)%MOD;
			pw[i]=pw[i-1]*d%MOD;
			ipw[i]=ipw[i-1]*iv%MOD;
		}
		memset(f,0,sizeof(f)),f[0]=1;
		ll s1=1,s2=0;
		for(int i=1;i<=m;++i) {
			f[i]=(s1*dg[a[i]-1]+(MOD-s2)*pw[a[i]-1])%MOD;
			s1=(s1+f[i])%MOD;
			s2=(s2+f[i]*dg[a[i]]%MOD*ipw[a[i]])%MOD;
		}
		z[q]=f[m];
	}
	for(int i=1;i<=n;++i) z[i]=(z[i]+z[i-1])%MOD;
	if(R>=bas) ans=(ans+eval(min(10*bas-1,R)-bas))%MOD;
	if(L-1>=bas) ans=(ans+MOD-eval(min(10*bas-1,L-1)-bas))%MOD;
}
signed main() {
	scanf("%s%lld%lld",s+1,&L,&R),n=strlen(s+1);
	for(int i=0;i<=n;++i) {
		cof[i]=1;
		for(int j=0;j<=n;++j) if(i!=j) cof[i]=cof[i]*(i+MOD-j)%MOD;
		cof[i]=ksm(cof[i])%MOD;
	}
	for(int i=1;i<=n;++i) if(s[i]=='?') a[++m]=i;
	a[++m]=n+1;
	for(int i=0;i<18;++i) solve(i);
	printf("%lld\n",ans);
	return 0;
}



E. [P11106] Divide

Problem Link

题目大意

给定 n 阶排列 p,分成两个子序列 q,r,最大化 q 中 Local-Max 个数加上 r 中 Local-Min 个数。

数据范围:n2×105

思路分析

考虑 q,r 第一次值域相交的时刻 ai,那么在 (i,n] 范围内可以直接取 LIS 和 LDS,其他元素可以直接放到对面的序列里,不影响对面的 Local-Max 或 Local-Min。

ai 之前,q 一定取 <ai 所有数,r 一定取 >ai 所有数,可以用 std::set 对每个 x 维护 ai=x 时当前 q,r 对应的权值和。

然后枚举 ai 属于 q/r,如果属于 q,那么就取 a(i,n] 中值域 <minr[1,i)>minr[1,i) 的 LIS 和 LDS,倒序扫描线,树状数组维护。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,a[MAXN],pre[MAXN],suf[MAXN],f[MAXN],w[MAXN];
struct FenwickTree1 {
	int tr[MAXN],s;
	void init() { fill(tr,tr+n+1,0); }
	void add(int x,int v) { for(;x<=n;x+=x&-x) tr[x]=max(tr[x],v); }
	int qry(int x) { for(s=0,x=min(x,n);x;x&=x-1) s=max(s,tr[x]); return s; }
}	T1;
struct FenwickTree2 {
	int tr[MAXN],s;
	void init() { fill(tr,tr+n+1,0); }
	void add(int x,int v) { for(;x;x&=x-1) tr[x]=max(tr[x],v); }
	int qry(int x) { for(s=0,x=max(x,1);x<=n;x+=x&-x) s=max(s,tr[x]); return s; }
}	T2;
void solve() {
	scanf("%d",&n); int ans=0;
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	set <int> S{0,n+1}; f[0]=f[n+1]=0;
	for(int i=1;i<=n;++i) {
		auto it=--S.upper_bound(a[i]);
		w[i]=f[a[i]]=++f[*it],pre[i]=*it,suf[i]=*++it,S.insert(a[i]);
	}
	T1.init(),T2.init();
	for(int i=n;i>=1;--i) {
		ans=max(ans,w[i]+T1.qry(pre[i])+T2.qry(pre[i]));
		ans=max(ans,w[i]+T1.qry(suf[i])+T2.qry(suf[i]));
		T1.add(a[i],T1.qry(a[i])+1),T2.add(a[i],T2.qry(a[i])+1);
	}
	printf("%d\n",ans);
}
signed main() {
	int Q; scanf("%d",&Q);
	while(Q--) solve();
	return 0;
}



F. [P11111] Range

Problem Link

题目大意

给定 n 个点的树,每个点点权 ai 有一个限定范围 [li,ri]q 组询问给定 v,要求构造一组 ai 使得树上最大权独立集为 v

数据范围:n,q2×105

思路分析

考虑所有 ai=li 和所有 ai=ri 时的最大权独立集 vL,vR,容易发现 v<vLv>vR 一定无解。

然后考虑每次令某个 auau+1,容易发现此时最大权独立集大小增量 {0,1}

那么 v[vL,vR] 时一定有解,我们可以对每个 ai 依次从 li 增加到 ri

容易发现一定存在某个时刻 x 使得 aix 后每次增加 ai,最大独立集的大小也增加,即增加 ai 会导致最大独立集增加的时刻是一段后缀。

那么我们求出 a1i 变成 rxai+1nlx 时的最大权独立集 vi,找到第一个 vi>v,那么 a1i1rxai+1nlxai=ri(viv) 即为一组解。

如果按 1n 的顺序逐个修改,需要用全局平衡二叉树维护 ddp,但是如果按 dfn 序修改,可以简单换根 dp 求出。

时间复杂度 O(n+qlogn)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,q,dcnt;
vector <int> G[MAXN];
ll X,Y,M;
ll l[MAXN],r[MAXN],s[MAXN],hv[MAXN],rk[MAXN],p[MAXN],t[MAXN];
array<ll,2> f[MAXN],g[MAXN];
ll hsh(int i,ll z) { return (i*X+z*z%M*Y)%M; }
void dfs1(int u,int fz) {
	f[u]={0,l[u]},g[u]={0,r[u]};
	for(int v:G[u]) if(v^fz) {
		dfs1(v,u);
		f[u][0]+=max(f[v][0],f[v][1]);
		g[u][0]+=max(g[v][0],g[v][1]);
		f[u][1]+=f[v][0];
		g[u][1]+=g[v][0];
	}
}
void dfs2(int u,int fz,array<ll,2>o) {
	rk[++dcnt]=u,hv[dcnt]=hv[dcnt-1]^hsh(u,l[u])^hsh(u,r[u]);
	o[0]+=f[u][0],o[1]+=f[u][1]+r[u]-l[u],s[dcnt]=max(o[0],o[1]);
	for(int v:G[u]) if(v^fz) {
		o[0]-=max(f[v][0],f[v][1]),o[1]-=f[v][0];
		dfs2(v,u,{max(o[0],o[1]),o[0]});
		o[0]+=max(g[v][0],g[v][1]),o[1]+=g[v][0];
	}
}
void solve() {
	scanf("%d%d%lld%lld%lld",&n,&q,&X,&Y,&M);
	for(int i=1;i<=n;++i) G[i].clear();
	for(int i=1,u,v;i<n;++i) {
		scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	}
	for(int i=1;i<=n;++i) scanf("%lld%lld",&l[i],&r[i]);
	dfs1(1,0);
	s[0]=max(f[1][0],f[1][1]),hv[0]=0;
	for(int i=1;i<=n;++i) hv[0]^=hsh(i,l[i]);
	dcnt=0,dfs2(1,0,{0,0});
	for(int i=1;i<=q;++i) scanf("%lld",&p[i]);
	for(int i=1;i<=q;++i) {
		if(p[i]<s[0]||p[i]>s[n]) printf("-1 ");
		else {
			int j=lower_bound(s,s+n+1,p[i])-s,u=rk[j];
			ll k=s[j]-p[i],ans=hv[j]^hsh(u,r[u])^hsh(u,r[u]-k);
			printf("%lld ",ans);
		}
	}
	puts(""),fflush(stdout);
	while(true) {
		int o; scanf("%d",&o);
		if(!o) break;
		if(p[o]<s[0]||p[o]>s[n]) puts("-1");
		else {
			int j=lower_bound(s,s+n+1,p[o])-s,u=rk[j];
			for(int i=1;i<=j;++i) t[rk[i]]=r[rk[i]];
			for(int i=j+1;i<=n;++i) t[rk[i]]=l[rk[i]];
			t[u]-=s[j]-p[o];
			for(int i=1;i<=n;++i) printf("%lld ",t[i]);
			puts("");
		}
		fflush(stdout);
	}
}
signed main() {
	int T; scanf("%d",&T);
	while(T--) solve();
	return 0;
}



*G. [P9983] Cover

Problem Link

题目大意

给定一棵 n 个点的树,有一些点是黑色,q 次询问 x,求最小的 |S|,使得将距离 S 中点 x 的点全部染黑后,每个点的着色情况恰好和题目给出的一致。

数据范围:n105,q20

思路分析

先考虑所有点是黑色的情况。

从最深的点 u 开始考虑,如果 u 还未被覆盖,我们要选一个点既能覆盖 u,对外界的影响又尽可能大。

显然考虑 ux 级祖先 v,那么 v 子树内的点都会被覆盖,并且对 v 子树外的影响是最大的。

然后考虑一般的情况,设一个点 u 到未被染色的点的距离为 pu,那么我们只选操作 pu>x 的点。

还是从最深的点 u 开始考虑,那么我们就是要找到一个 v 使得 dist(u,v)x,pv>xu 尽可能浅。

这样的 v 并不好维护,因为 v 的信息和 LCA(u,v) 有关。

不妨尝试找最浅的 w=LCA(u,v)w 固定时,dist(v,w) 越浅越容易合法,同时此时 v 深度更浅,那么 v 就是子树内最浅的 pv>xv

那么设 disw 表示子树内这样的 v 对应的 dist(v,w),我们就要求 disw+depudepwxdepux+depwdisw

但是此时随着 w 的增加,depw 增加,但 disw 单调性未知。

但我们发现如果设 disw 为所有 pv>xv 中最小的 dist(v,w),即去掉子树内限制后,一定有 |diswdisfa(w)|1.

因此 wfaw 时的变化量一定 [2,0],因此 x+depwdisw 单调递减,可以倍增出最浅的合法的 w

不难发现此时 w 确实是 LCA(u,v),因为如果这样的 v 存在,那么 LCA(u,v) 一定合法,故 w 一定会跳到 LCA(u,v) 上。

v 只要在维护 disw 时记录其取值点即可,然后我们要支持覆盖距离 v 不超过 x 的所有点,并查询一个点是否被覆盖,直接点分树维护即可。

时间复杂度 O(nqlogn)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,lim,pri[MAXN],dep[MAXN],dfn[MAXN],dcnt,st[MAXN][20],up[MAXN][20];
char op[MAXN];
vector <int> G[MAXN];
void bfs1() {
	queue <int> q;
	memset(pri,0x3f,sizeof(pri));
	for(int i=1;i<=n;++i) if(op[i]=='0') pri[i]=0,q.push(i);
	while(q.size()) {
		int u=q.front(); q.pop();
		for(int v:G[u]) if(pri[v]>pri[u]+1) q.push(v),pri[v]=pri[u]+1;
	}
}
void dfs0(int u,int fz) {
	dfn[u]=++dcnt,dep[u]=dep[fz]+1,st[dcnt][0]=up[u][0]=fz;
	for(int k=1;k<20;++k) up[u][k]=up[up[u][k-1]][k-1];
	for(int v:G[u]) if(v^fz) dfs0(v,u);
}
int bit(int x) { return 1<<x; }
int cmp(int x,int y) { return dfn[x]<dfn[y]?x:y; }
int dist(int x,int y) {
	if(x==y) return 0;
	int l=min(dfn[x],dfn[y])+1,r=max(dfn[x],dfn[y]),k=__lg(r-l+1);
	return dep[x]+dep[y]-2*dep[cmp(st[l][k],st[r-bit(k)+1][k])];
}
int fa[MAXN],siz[MAXN],cur[MAXN];
bool vis[MAXN];
void dfs1(int u) {
	vis[u]=true;
	function<void(int,int)> dfs2=[&](int x,int fz) {
		siz[x]=1;
		for(int y:G[x]) if(y!=fz&&!vis[y]) dfs2(y,x),siz[x]+=siz[y];
	};
	dfs2(u,0);
	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(y!=fz&&!vis[y]) dfs3(y,x),cur[x]=max(cur[x],siz[y]);
			if(!rt||cur[rt]>cur[x]) rt=x;
		};
		dfs3(v,u),fa[rt]=u,dfs1(rt);
	}
}
int d[MAXN],ver[MAXN],rd[MAXN];
void bfs2() {
	queue <int> q; memset(d,0x3f,sizeof(d));
	for(int i=1;i<=n;++i) if(pri[i]>lim) d[i]=0,ver[i]=i,q.push(i);
	while(q.size()) {
		int u=q.front(); q.pop();
		for(int v:G[u]) if(d[v]>d[u]+1) ver[v]=ver[u],d[v]=d[u]+1,q.push(v);
	}
}
void upd(int x) { for(int u=x;u;u=fa[u]) rd[u]=max(rd[u],lim-dist(x,u)); }
bool qry(int x) { for(int u=x;u;u=fa[u]) if(rd[u]>=dist(u,x)) return true; return false; }
vector <int> ord;
void solve() {
	scanf("%d",&lim),bfs2();
	memset(rd,-0x3f,sizeof(rd));
	int ans=0;
	for(int u:ord) {
		if(qry(u)) continue;
		if(d[u]>lim) return puts("-1"),void();
		int v=u;
		for(int k=19;~k;--k) if(up[v][k]&&d[up[v][k]]+dep[u]-dep[up[v][k]]<=lim) v=up[v][k];
		upd(ver[v]),++ans;
	}
	printf("%d\n",ans);
}
signed main() {
	int T;
	scanf("%d%s",&n,op+1);
	for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
	bfs1(),dfs0(1,0);
	for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
		st[i][k]=cmp(st[i][k-1],st[i+bit(k-1)][k-1]);
	}
	for(int i=1;i<=n;++i) if(op[i]=='1') ord.push_back(i);
	sort(ord.begin(),ord.end(),[&](int x,int y){ return dep[x]>dep[y]; });
	dfs1(1),scanf("%d",&T);
	while(T--) solve();
	return 0;
}



*H. [P11107] Mex

Problem Link

题目大意

给定集合 S,定义 f(S) 表示将 S 中所有数异或上 mex(S)1 后得到的集合。

q 组询问给定 n,k,p,求有多少 S 满足:

  • S[0,2k)0S,|S|=n
  • 序列 S,f(S),f(f(S)), 最终收敛于 S0,且 mex(S0)=p

数据范围:k17,n,p2k,q2×105

思路分析

|S|<2k,考虑刻画 S 收敛时的极限 p

  • 如果 [0,2k1) 不全在 S 中,那么 S 的极限等于 S[0,2k1) 中的极限。
  • 否则 mex(S)1 至少是 2k11
    • 如果 2k1S 中,那么 mex(S)1 至少是 2k1,翻转一次后变成 S[2k1,2k) 中的极限。
    • 否则 mex(S)1=2k11
      • 如果 2k1S,那么翻转后 [0,2k1) 依然全满,2k1 依然不属于 S,此时极限就是 2k1
      • 否则翻转后变成 2k1S 的问题,答案是 S[2k1,2k) 范围内翻转低 k1 位后的极限。

容易发现此时 p 一定是 2 的某个幂。

考虑 dp 刻画,fi,j,c 表示考虑值域 [0,2i),极限 p=2j,当前 |S|=c 的集合数。

但我们无法处理翻转后的情况,注意到翻转后我们要处理的子问题就是一个钦定 2k1S 的子问题,其余限制和原问题相同,设对应的方案数为 gi,j,c

边界条件是 j=i1,此时对于所有 2i1c2i2,有:

fi,i1,c=gi,i1,c=(2i12c2i1)

考虑一般的情况,先处理第一种情况,即 [0,2i1) 的极限为 2j[2i1,2i) 可以任选的情况:

fi,j,c0+c1(2i1c1)fi1,j,c0gi,j,c0+c1(2i11c1)fi1,j,c0

可以用 NTT 优化,容易把对 f 的卷积省掉。

然后考虑剩余后面的情况,注意翻转的情况仅在 f 中考虑:

fi,j,k+2i1fi1,j,kgi,j,k+2i1gi1,j,kfi,j,k+2i1gi1,j,k

回答时特判 n=2k 后答案就是 fk,log2p,n

时间复杂度 O(k32k+q)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int N=1<<18;
namespace FastMod {
typedef unsigned long long ull;
typedef __uint128_t uLL;
ull b,m;
inline void init(ull B) { b=B,m=ull((uLL(1)<<64)/B); }
inline ull mod(ull a) {
	ull q=((uLL(m)*a)>>64),r=a-q*b;
	return r>=b?r-b:r;
}
}
#define o(x) FastMod::mod(x)
int MOD,G;
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;
}
void poly_init() {
	vector <int> fr;
	for(int i=2,z=MOD-1;i*i<=z;++i) if(z%i==0) fr.push_back(i),fr.push_back(z/i);
	for(G=2;;++G) {
		bool ok=true;
		for(int z:fr) if(ksm(G,z)==1) { ok=false; break; }
		if(ok) break;
	}
	inv[1]=1;
	for(int i=2;i<N;++i) inv[i]=o(1ll*(MOD-MOD/i)*inv[MOD%i]);
	fac[0]=ifac[0]=1;
	for(int i=1;i<N;++i) fac[i]=o(1ll*fac[i-1]*i),ifac[i]=o(1ll*ifac[i-1]*inv[i]);
	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]=o(1ll*x*w[i+k-1]);
	}
}
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=o(1ll*f[j+k/2]*w[k+j-i]);
				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]=o(1ll*f[i]*x);
	}
}
int f[18][18][N],g[18][18][N],a[N],b[N];
int C(int x,int y) { return 1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD; }
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
signed main() {
	ios::sync_with_stdio(false);
	cin>>MOD,FastMod::init(MOD),poly_init();
	for(int i=1;i<18;++i) {
		int s=1<<(i-1),len=s<<1;
		memset(a,0,sizeof(int)*len);
		for(int k=0;k<s;++k) a[k]=C(s-1,k);
		ntt(a,0,len);
		for(int j=0;j<i-1;++j) {
			int *F=f[i][j],*G=g[i][j];
			memset(b,0,sizeof(int)*len);
			for(int k=0;k<s;++k) b[k]=f[i-1][j][k];
			ntt(b,0,len);
			for(int k=0;k<len;++k) b[k]=o(1ll*a[k]*b[k]);
			ntt(b,1,len);
			for(int k=0;k<len;++k) add(G[k],b[k]),add(F[k],b[k]),add(F[k+1],b[k]);
			for(int k=0;k<s;++k) {
				add(F[k+s],f[i-1][j][k]),add(F[k+s],g[i-1][j][k]);
				add(G[k+s],g[i-1][j][k]);
			}
		}
		if(i==1) f[1][0][1]=g[1][0][1]=1;
		else for(int k=0;k<s-1;++k) f[i][i-1][k+s]=g[i][i-1][k+s]=C(s-2,k);
	}
	int T; cin>>T;
	for(int k,n,p;T--;) {
		cin>>k>>n>>p;
		if(n==(1<<k)) cout<<(p==n)<<"\n";
		else cout<<(p==(p&-p)?f[k][__lg(p)][n]:0)<<"\n";
	}
	return 0;
}



*I. [P9884] Ant

Problem Link

题目大意

给定 n 个洞口,每个洞口每秒至多进或出一只蚂蚁。

t 秒从洞口爬出的蚂蚁会在第 ai+t+1 秒到达冰箱,第 t 秒进入洞口的蚂蚁至少要在 tai1 秒前到达冰箱,求至少多少秒后 m 只蚂蚁各经过冰箱一次。

数据范围:n105,m1014

思路分析

考虑二分时间 T,那么对于一只蚂蚁从第 s 秒从 i 出发,第 t 秒到达 j,合法当且仅当 s+ai+1taj1

如果我们知道每个洞口每个时刻是出蚂蚁还是进蚂蚁,那么一对合法的起点终点可以看成一条边,最多完成的蚂蚁数量就是二分图上的最大匹配。

由于左部每个点连的都是右部一段严格递减的后缀,因此可以贪心匹配每个右部点。

我们只要解决每个洞口是出蚂蚁还是进蚂蚁的问题。

注意到我们可以倒转时间使得进、出蚂蚁的事件交换,根据对称性不难猜测一定是前一半时间出蚂蚁,后一半时间进蚂蚁最优。

那么左、右部点分别是 O(n) 个区间,求出极小的连续段之后可以快速维护上面的最大匹配过程。

唯一的细节是 Tmod2=1 时最中间的时刻是进蚂蚁还是出蚂蚁,不妨假设这个时刻分别进出 0.5 只蚂蚁,把所有权值 ×2 再处理即可。

注意到所有差分数组都是 ai 加减一个定值,预先对 ai 排序后只需要归并 O(1) 个有序数组。

时间复杂度 O(nlogm)

代码呈现

#include<bits/stdc++.h> 
#define ll long long
using namespace std;
const int MAXN=1e5+5;
const ll inf=2.5e14;
int n,a[MAXN];
ll m;
struct opr {
	ll x; int c,op;
	friend bool operator <(const opr &x,const opr &y) { return x.x<y.x; }
} s[MAXN*6];
bool chk(ll T) {
	int p=0;
	if(T&1) {
		for(int i=1;i<=n;++i) s[++p]={a[i]+1,2,0};
		for(int i=1;i<=n;++i) s[++p]={a[i]+T/2+1,-1,0};
		for(int i=1;i<=n;++i) s[++p]={a[i]+T/2+2,-1,0};
		for(int i=n;i>=1;--i) s[++p]={T/2-a[i],1,1};
		for(int i=n;i>=1;--i) s[++p]={T/2-a[i]+1,1,1};
		for(int i=n;i>=1;--i) s[++p]={T-a[i],-2,1};
		for(int o=1;o<6;++o) inplace_merge(s+1,s+o*n+1,s+(o+1)*n+1);
	} else {
		for(int i=1;i<=n;++i) s[++p]={a[i]+1,2,0};
		for(int i=1;i<=n;++i) s[++p]={a[i]+T/2+1,-2,0};
		for(int i=n;i>=1;--i) s[++p]={T/2-a[i],2,1};
		for(int i=n;i>=1;--i) s[++p]={T-a[i],-2,1};
		for(int o=1;o<4;++o) inplace_merge(s+1,s+o*n+1,s+(o+1)*n+1);
	}
	ll ans=0,tot=0,lx=0,rx=0;
	for(int i=1;i<p;++i) {
		(s[i].op?rx:lx)+=s[i].c;
		ll d=s[i+1].x-s[i].x;
		if(lx>=rx) ans+=rx*d,tot+=(lx-rx)*d;
		else {
			ans+=lx*d;
			ll w=min((rx-lx)*d,tot);
			tot-=w,ans+=w;
		}
	}
	return ans>=2*m;
}
void solve() {
	scanf("%d%lld",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	ll l=0,r=inf,z=inf;
	while(l<=r) {
		ll mid=(l+r)>>1;
		if(chk(mid)) z=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%lld\n",z);
}
signed main() {
	int T; scanf("%d",&T);
	while(T--) solve();
	return 0;
}
posted @   DaiRuiChen007  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示