杂题记 1

杂题记 1杂题记 2

写在前面:分解大致为 1520 题,题目难度高,大部分题个人认为的实际难度不低于洛谷的紫题。

CF1797F#

link 。题意自行看 link 中的翻译。提示:点权重构树。

solution

套路题。容易想到容斥,答案为 |A|+|B|2|AB|A,B 分别为满足第 1,2 个条件的 (u,v) 构成的集合。

考虑令 cv 表示 u 的个数满足  kuv 的路径上,uk。则修改的影响为:cicx+1,ansans+i1+ci2ci=ans+i1ci。因为容易推得 |B| 增加 i1|A|,|AB| 增加 ci

然后只需要求最初树上的 ci 和最初的 ans 即可。分别建从大到小和从小到大的点权重构树,然后 ci 就是深度 1|A|=i=1nci|B| 也是类似求法,最初 |AB| 通过 dfs 和二维数点即可。复杂度 O(nlogn)

详细题解

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=2e5+5;
int n,m,c[N<<1];LL ans;
struct Edge
{
	int tot,head[N];
	struct edge{int to,nex;}e[N<<1];
	inline void add(int u,int v)
	{
		e[++tot]={v,head[u]};head[u]=tot;
		e[++tot]={u,head[v]};head[v]=tot;
	}
}a;
struct Bit
{
	int a[N];
	inline int lb(int x){return x&-x;}
	inline void add(int wz,int x){for(;wz<=n;wz+=lb(wz)) a[wz]+=x;}
	inline int ask(int wz){int ans=0;for(;wz;wz-=lb(wz)) ans+=a[wz];return ans;}
}B;
struct Tree
{
	int fa[N],id[N],siz[N],d[N],tot;Edge e;
	inline void init(int n){for(int i=1;i<=n;i++) fa[i]=i;}
	inline int getf(int x){return x==fa[x]?x:fa[x]=getf(fa[x]);}
	inline void hb(int x,int y){if((y=getf(y))^x) fa[y]=x,e.add(x,y);}
	void dfs(int x,int f)
	{
		siz[x]=1;id[x]=++tot;d[x]=d[f]+1;
		for(int i=e.head[x];i;i=e.e[i].nex)
		{
			int to=e.e[i].to;
			if(to!=f) dfs(to,x),siz[x]+=siz[to];
		}
	}
}mx,mn;
void dfs1(int x,int f)
{
	ans-=2*(B.ask(mx.id[x]+mx.siz[x]-1)-B.ask(mx.id[x]-1));B.add(mx.id[x],1);
	for(int i=mn.e.head[x];i;i=mn.e.e[i].nex)
	{
		int to=mn.e.e[i].to;
		if(to!=f) dfs1(to,x);
	}B.add(mx.id[x],-1);
}
int main()
{
	scanf("%d",&n);mx.init(n);mn.init(n);
	for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),a.add(u,v);scanf("%d",&m);
	for(int i=1;i<=n;i++) for(int j=a.head[i];j;j=a.e[j].nex){int to=a.e[j].to;if(to<i) mx.hb(i,to);}
	for(int i=n;i;i--) for(int j=a.head[i];j;j=a.e[j].nex){int to=a.e[j].to;if(to>i) mn.hb(i,to);}
	mx.dfs(n,0);mn.dfs(1,0);
	for(int i=1;i<=n;i++) ans+=(c[i]=mn.d[i]-1)+mx.d[i]-1;
	dfs1(1,0);printf("%lld\n",ans);int x;
	for(int i=n+1;i<=n+m;i++) scanf("%d",&x),ans+=i-1,ans-=(c[i]=c[x]+1),printf("%lld\n",ans);
	return 0;
}

2021 Grand Prix of IMO D. Deleting#

简要题意:给定长为 n 的序列 1,2,,n,每次操作删去相邻两个数,对 i<j2ji 给定 cost(i,j) 表示这相邻两个数是 ij 时的代价,求删完的最大代价的最小值。n40002ncost(i,j)1n24 的排列。

link 。提示:区间 dp

solution

fl,r(l<r,lr(mod2)) 表示区间的最小代价,则有 fl,r=min(max(cost(l,r),fl+1,r1),mink=lr1(max(fl,k,fk+1,r)))。如果直接这样转移时 O(n3) 的。

考虑二分,然后套路的把 midcost 设为 0,否则设为 1,然后用这些 0,1dp 即可。显然可以用 bitset 优化。

mink=lr1(max(fl,k,fk+1,r)) 可以通过:如果 fl,k=0,则 f[l]&=f[k+1] 来转移。初始时所有 f 都是 1。复杂度 O(n3lognω)。进行一些优化之后足以通过。

注:有 O(n3ω) 的做法,可以看 linkCF 里的题解。

code
#include<bits/stdc++.h>
#define LL long long
#define u32 unsigned int
#define u64 unsigned long long 
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const u32 N=4005;
namespace IO
{
	const u32 _Pu=2e7+5,_d=32;
	char buf[_Pu],obuf[_Pu],*p1=buf+_Pu,*p2=buf+_Pu,*p3=obuf,*p4=obuf+_Pu-_d;
	inline void fin()
	{
		memmove(buf,p1,p2-p1);
		u32 rlen=fread(buf+(p2-p1),1,p1-buf,stdin);
		if(p1-rlen>buf) buf[p2-p1+rlen]=EOF;p1=buf;
	}
	inline void fout(){fwrite(obuf,p3-obuf,1,stdout),p3=obuf;}
	inline u32 rd()
	{
		if(p1+_d>p2) fin();u32 isne=0,x=0;
		for(;!isdigit(*p1);++p1) isne=(*p1=='-');x=(*p1++-'0');
	    for(;isdigit(*p1);++p1) x=x*10+(*p1-'0');
		if(isne) x=-x;return x;
	}
}
u32 n,a[N][N>>1],j;
struct BI
{
	u64 a[N/128+5];const u32 B=N/128+1;
	inline void reset(){for(u32 i=0;i<=B;i++) a[i]=0;}
	inline void set(){for(u32 i=0;i<=B;i++) a[i]=-1;}
	inline bool operator[](const u32& x){return (a[x>>6]>>(x&63))&1;}
	inline void fu(const u32 &x,bool y){if(((a[x>>6]>>(x&63))&1)!=y) a[x>>6]^=1ull<<(x&63);}
	inline void operator &=(const BI& x){for(u32 i=(j+1)>>7;i<=B;i++) a[i]&=x.a[i];}
}f[N];
inline bool chk(u32 x)
{
	for(u32 i=1;i<=n;i++) f[i].set();
	for(u32 i=n-1;i;i--)
		for(j=i+1;j<=n;j+=2)
		{
			if(a[i][(j-i)>>1]<=x&&(i+1==j||!f[i+1][(j-1)>>1])) f[i].fu(j>>1,0);
			if(j+1<=n&&!f[i][j>>1]) f[i]&=f[j+1];
		}return f[1][n>>1];
}
int main()
{
	n=IO::rd();u32 x,l=1,r=n*n>>2,mid;
	for(u32 i=1;i<=n;i++) for(u32 j=i+1;j<=n;j+=2) a[i][(j-i)>>1]=IO::rd();
	while(l<r) mid=(l+r)>>1,chk(mid)?l=mid+1:r=mid;printf("%u",l);
	return 0;
}

COCI2014-2015#1. Kamp#

link 。提示:不要想 dsdp 题。

solution

想到 dp 就能想到是换根 dp,令 fi 表示为以 i 为根的子树中从 i 开始把所有家在这个子树内的人送回家并回到 i 节点的最短路程,numx 表示 x 子树中人的个数。

有:fi=vsonifv+2×[numx>0]×w(i,v)

然后换根,令 gi 为对于整棵树从 i 开始送人最后回到 i 的最短距离,显然有 g1=f1

x>1fafagx=gfa+2×([m>numx][numx>0])×w(x,fa),自己算算贡献就能得到。

这两部分代码
void dfs1(int x,int fa)
{
	for(int i=head[x];i;i=e[i].nex)
	{
		int to=e[i].to;
		if(to!=fa) dfs1(to,x),num[to]&&(f[x]+=f[to]+2*e[i].w);
	}
}//换根 
void dfs2(int x,int fa)
{
	for(int i=head[x];i;i=e[i].nex)
	{
		int to=e[i].to;
		if(to!=fa) f[to]=f[x]+2*((m>num[to])-(num[to]>0))*e[i].w,dfs2(to,x);
	}
}

然后考虑 i 的答案,发现是 fi 减去 i 到所有人中的最大距离。

求出所有人中带权的直径,则 i 到所有人中的最大距离一定是 i 到直径的两个端点中的一个的距离。直接在求直径的过程中同时维护最大次大所在的节点就知道直径的端点了。总复杂度 O(n)。但是由于我求直径用 O(nlogn) 于是我没放代码。

CF521D. Shop#

link 。提示:考虑把三种操作都化为一种操作。

solution

根据提示以及答案的形式,显然要把三种操作到化为乘法操作。

考虑当只有一个数且 n=m 时怎么安排操作顺序。显然是赋值最大的,然后加,加完乘。

这时赋值操作就可以转换为一次加法操作,现在考虑只有加和乘的情况。

考虑有操作个数限制怎么办,加法要尽量优肯定是先加大的数再加小的数,乘法类似。

考虑提示,由于 加法要尽量优肯定是先加大的数再加小的数 ,于是加法的顺序是一定的,于是我们把 a+xa×(a+xa),这时由于顺序一定分母分子都为常数。

现在只需在所有乘法中贪心选 m 个最大的乘即可。

有个小问题,就是有没有可能 a+xa<a+x+ya+x(x>y),就是实际操作时会出现矛盾的情况(其中两次操作为 +x,+y)。

这是因为 a+xa=1+xa>1+ya+x(x>y,a<a+x)=a+x+ya+x

code
#include<bits/stdc++.h>
#define P pair<long double,int>
#define fi first
#define se second
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e5+5;
struct node{LL o,x,y;}q[N];
int n,m,k,a[N];P b[N];
vector<P>g[N],h;vector<int>ans;
inline bool cmp(P x,P y){return x>y;}
int main()
{
	scanf("%d%d%d",&n,&m,&k);for(int i=1;i<=n;i++) scanf("%d",&a[i]);int o,x,y;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&o,&x,&y);q[i]={o,x,y};
		if(o==1) b[x].fi<y&&(b[x]={y,i},1);
		else if(o==2) g[x].push_back({y,i});
		else h.push_back({y,i});
	}
	for(int i=1;i<=n;i++){if(b[i].fi>a[i]) g[i].push_back({b[i].fi-a[i],b[i].se});sort(g[i].begin(),g[i].end(),cmp);}
	for(int i=1;i<=n;i++)
	{
		LL s=a[i];
		for(P j:g[i]) h.push_back({1.0*(s+j.fi)/s,j.se}),s+=j.fi;
	}sort(h.begin(),h.end(),cmp);printf("%d\n",min((int)h.size(),k));
	for(int i=0;i<min((int)h.size(),k);i++) ans.push_back({h[i].se});
	for(int i:ans) if(q[i].o==1) printf("%d ",i);
	for(int i:ans) if(q[i].o==2) printf("%d ",i);
	for(int i:ans) if(q[i].o==3) printf("%d ",i);
	return 0;
}

AGC030C. Coloring Torus#

link 。如果看不懂洛谷的翻译可以看看原题的翻译。提示:考虑斜线,交题构造。

solution

显然能如下构造:

[111222nnn]

于是完成了 K500。下面处理 K>500

考虑这个模型(模型 1):

[121212343434n1nn1nn1n]

于是所有 4k 的数就可以做了,类似的,所有能表示为 a2×b,即有平方因子的数也可以做了。但这样没法扩展。

再考虑这个模型(模型 2):

[12n1n23n1n1n2n1]

这也是满足条件的。但是这样还是不行。

下面我们考虑把这两个结合起来。

把模型 2n=500,而后取其中包含 1Kn 这些数的斜线,并把它们中的第奇数个数都加上 n(即就是像模型 1 那样交题)。这样当 n 为偶数,K2n 时也满足条件。

我们举一个小一点的例子,比如 n=4,K=7

[5674234574164527]

n=6,K=10

[789105623456191056784561235678910612345]

大家类似这样构造就不难构造出 n=500,K>500 的情况。

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
int n;
int main()
{
	scanf("%d",&n);int x;
	if(n<=500){printf("%d\n",n);for(int i=1;i<=n;i++,puts("")) for(int j=1;j<=n;j++) printf("%d ",i);return 0;}
	else{puts("500");for(int i=1;i<=500;i++,puts("")) for(int j=1;j<=500;j++) x=(i+j-2)%500+1,printf("%d ",x+(x<=n-500&&(i&1))*500);}
	return 0;
}

CF1817C. Similar Polynomials#

link 。提示:求导,离散导数

solution

先亮复杂度:O(d)

先有一个做法:我们对 A,Bd1 次导,此时 A,B 就变为两个一次函数,此时 s 为两个一次函数零点的距离,易证这样是正确的。

但是知道点值还原会多项式就都是亚线性的了。于是我们想一些更高妙的做法。


离散求导:

f 是一个 deg=n 的多项式,且 f 的定义域为 [0,n]N

定义 f 的离散导数 Δf(x)=f(x+1)f(x)(0xn1),也记作 Δ1f(x)

这时候 Δf(x) 只有 n 个点值,为一个 deg=n1 的多项式。也就是说每一次离散求导,deg 减少 1

Δkf(x)=Δ(Δk1f(x))(kN,kn)


于是我们把上面的求导换成离散导数即可。

此时我们若求出 A,B 进行 d1 次离散求导的
0,1 项系数,设为 a1,b1,a2,b2,则两个一次函数为 y=(b1a1)x+a1,y=(b2a2)x+a2,求这两个函数的零点距离即可。

f(x)=i=0naixi,则对 A(x) 进行一次离散求导等价于对 a 做一次差分,并且舍掉最后一项。

我们又由差分与前缀和知道 [xm]ΔkA(x)=[xm+k]f(x)(1x)k,于是 [xm]Δd1f(x)=[xm+d1]f(x)(1x)d1

此时我们可以直接 O(d) 二项式展开求出 (1x)d1,然后求多项式乘法的某一项系数可以 O(deg),于是总复杂度 O(d)

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
namespace IO
{
	const int _Pu=2e7+5,_d=32;
	char buf[_Pu],obuf[_Pu],*p1=buf+_Pu,*p2=buf+_Pu,*p3=obuf,*p4=obuf+_Pu-_d;
	inline void fin()
	{
		memmove(buf,p1,p2-p1);
		int rlen=fread(buf+(p2-p1),1,p1-buf,stdin);
		if(p1-rlen>buf) buf[p2-p1+rlen]=EOF;p1=buf;
	}
	inline void fout(){fwrite(obuf,p3-obuf,1,stdout),p3=obuf;}
	inline int rd()
	{
		if(p1+_d>p2) fin();int isne=0,x=0;
		for(;!isdigit(*p1);++p1) isne=(*p1=='-');x=(*p1++-'0');
	    for(;isdigit(*p1);++p1) x=x*10+(*p1-'0');
		if(isne) x=-x;return x;
	}
	inline void wr(int x,char end='\n')
	{
		if(!x) return *p3++='0',*p3++=end,void();
		if(x<0) *p3++='-',x=-x;
		char sta[20],*top=sta;
		do{*top++=(x%10)+'0';x/=10;}while(x);
		do{*p3++=*--top;}while(top!=sta);(*p3++)=end;
	}
}
const int N=2500005,mod=1e9+7;
int n,jc[N],inv[N],a[N],b[N],c[N],k1,b1,k2,b2;
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline int C(int n,int m){return 1ll*jc[n]*inv[m]%mod*inv[n-m]%mod;}
int main()
{
	n=IO::rd();
	jc[0]=1;for(int i=1;i<=n;i++) jc[i]=1ll*jc[i-1]*i%mod;
	inv[n]=ksm(jc[n],mod-2);for(int i=n-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
	for(int i=0;i<=n;i++) a[i]=IO::rd();
	for(int i=0;i<=n;i++) b[i]=IO::rd();
	for(int i=0;i<n;i++) c[i]=((n-1-i)&1)?mod-C(n-1,i):C(n-1,i);
	for(int i=0;i<n;i++) b1=(b1+1ll*a[i]*c[n-1-i])%mod;
	for(int i=0;i<=n;i++) k1=(k1+1ll*a[i]*c[n-i])%mod;k1=(k1-b1+mod)%mod;
	for(int i=0;i<n;i++) b2=(b2+1ll*b[i]*c[n-1-i])%mod;
	for(int i=0;i<=n;i++) k2=(k2+1ll*b[i]*c[n-i])%mod;k2=(k2-b2+mod)%mod;
	int t=(1ll*ksm(k2,mod-2)*b2-1ll*ksm(k1,mod-2)*b1)%mod;IO::wr(t>=0?t:t+mod);
	return IO::fout(),0;
}

LG P3592. MYJ#

link 。提示:区间 dp

solution

容易想到状态,设 fi,j,k 表示只考虑区间 [i,j] 的洗车店且最小值为 k 的方案数,由于 k 很大于是离散化,下面为了方便按照不离散化来写状态转移方程。

fi,j,k=maxp=ij(maxKkfi,p1,K+maxKkfp+1,j,K+gp,k×k)

枚举 p 是枚举最小值的位置,其中若 i>j,fi,j,k0,这时方便最小值在边界的时候能直接加。

其中 gp,k 表示在 [i,j] 区间内且跨过 pck 的消费者数量。

显然 gp,k 对于每个 [i,j] 都重新求一遍(暴力加然后做前缀和)是 O(n3m) 的,但是 f 中还嵌套了一层 max,于是考虑后缀 max

Fi,j,k=maxKkfi,j,K,则 Fi,j,k=max(maxp=ij(Fi,p1,K+Fp+1,j,K+gp,k×k),Fi,j,k+1)

于是得到的答案就是 F1,n,1。复杂度 O(n3m)


但是我们还要输出方案。令 posi,j,k 表示红色部分取到最大值时 p 的值,令 prei,j,k 表示 fi,j,k(这里是原来的!)后缀最大值的 k 的位置。

pre 的求法:pre[i][j][k]=(f[i][j][k]>=f[i][j][k+1])?k:pre[i][j][k+1];f[i][j][k]=max(f[i][j][k],f[i][j][k+1]);

然后输出方案直接递归输出即可。

void dfs(int l,int r,int k)
{
	if(l>r) return;
	int wz=pos[l][r][k=pre[l][r][k]];
	ans[wz]=b[k];dfs(l,wz-1,k);dfs(wz+1,r,k);
}
code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=55,M=4e3+5,V=5e5+5;
struct node{int x,y,z;}a[M];
int n,m,len,b[M],to[V],f[N][N][M],g[N][M],pos[N][N][M],pre[N][N][M],ans[N];
void dfs(int l,int r,int k)
{
	if(l>r) return;
	int wz=pos[l][r][k=pre[l][r][k]];
	ans[wz]=b[k];dfs(l,wz-1,k);dfs(wz+1,r,k);
}
int main()
{
	scanf("%d%d",&n,&m);int x,y,z;
	for(int i=1;i<=m;i++) scanf("%d%d%d",&x,&y,&z),a[i]={x,y,z},b[i]=z;
	sort(b+1,b+1+m);len=unique(b+1,b+1+m)-b-1;
	for(int i=1;i<=len;i++) to[b[i]]=i;
	for(int i=n;i>=1;i--) for(int j=i;j<=n;j++)
	{
		memset(g,0,sizeof(g));
		for(int k=1;k<=m;k++) if(i<=a[k].x&&a[k].y<=j) for(int l=a[k].x;l<=a[k].y;l++) g[l][to[a[k].z]]++;
		for(int k=i;k<=j;k++) for(int l=len-1;l>=1;l--) g[k][l]+=g[k][l+1];
		for(int k=len;k>=1;k--)
		{
			for(int l=i;l<=j;l++)
			{
				int t=f[i][l-1][k]+f[l+1][j][k]+g[l][k]*b[k];
				if(f[i][j][k]<=t) f[i][j][k]=t,pos[i][j][k]=l;
			}
			pre[i][j][k]=(f[i][j][k]>=f[i][j][k+1])?k:pre[i][j][k+1];f[i][j][k]=max(f[i][j][k],f[i][j][k+1]);
		}
	}printf("%d\n",f[1][n][1]);
	dfs(1,n,1);for(int i=1;i<=n;i++) printf("%d ",ans[i]);
	return 0;
}

CF1299E. So Mean#

link 。提示:先想询问个数为 O(n2) 的做法,然后想 CRT

solution

兔群对打 duel 的题,想了一晚大体思路都对了,但是细节没对。

下面设数 i 的位置为 wi

首先容易想到先只取 n1 个数,考虑如果返回了 1,则 n1n(n+1)2x,由于 n2Z,于是 x1(modn1)x=1 or n

于是找出这两个返回值为 1 的,随便钦定一个位置为 1 另一个为 n 即可。

类似的,如果我们知道了 1,2,,k,nk+1,n1,n,那么我们>可以 n 次询问知道 k+1,nk 的位置,设为 fi,se

显然 k+1,nk 的奇偶性不同,于是我们询问 ? w[1] fi 即可知道 >fi 位置的奇偶性,进而推出 pfipse 就是另一个了。

这样询问次数是 O(n2) 的,在 n18 时可以通过。给个代码


考虑 CRT,我们想选取最优的模数集使得 lcm800,显然取 S={3,5,7,8}

按照上面的做法,我们可能会知道 A={1,2,,k,nk+1,,n} 集合中的数的位置。

我们要求  3p8,0i<p, k1A 中的数使得它们的和模 pi

发现取 k=5 是最小满足要求的,举个 p=8 的例子:

由于 {1,2,3,4,n4,n3,n2},{1,2,3,4,n4,n3,n1},{1,2,3,4,n4,n3,n},{1,2,3,4,n4,n2,n},{1,2,3,4,n4,n1,n},{1,2,3,4,n3,n1,n},{1,2,3,4,n2,n1,n},{1,2,3,5,n2,n1,n}

8 个集合两两差 1,于是它们两两模 8 不同,符合要求。其他 p 类似取。

对于还没有确定的位置 i,对于所有 pB,我们询问这些集合和 i,如果返回值为 1 则能确定 pip 的值。对于每个模数 p 只需询问 p1 次,如果还没有返回值为 1 的也能唯一确定余数。

最后做一下 CRT 即可,由于 a 是一个排列,于是 p 大约平均询问 p2 次。由于初始要询问 5n 次,于是总次数大约为 (3+5+7+82+5)×n=16.5n 次。

实际次数显然比这个大得多,但是足以通过。(魏老师算的是 17.7n,看不懂)


优化:模 8 的时候可以倍增。对于位置 i,先进行 n 次求模 2 的值。

若是奇数,则询问 ? i w[1] w[2] w[4] 即可知道模 41 还是 3

偶数则询问 ? i w[1] w[2] w[3] 即可。

然后再分类 0,1,2,3(mod4) 类似询问即可知道模 8,这样每个 i稳定 3 次询问出模 8

估计次数为 16n 次,测试数据中最大次数在 1200013000 之间。Ntokisq 能做到 14n 次,我就不会。

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=805;
int n,a[N],w[N],M[N][4],to[15][15][15][15];
vector<int>W[8];
#define Put(x,y) (w[a[x]=y]=x)
#define ff fflush(stdout)
inline void Q(int k)
{
	bool ok=0;int fi,se,o;
	for(int i=1;i<=n;i++) if(!a[i])
	{
		printf("? %d ",n-2*k+1);for(int j=1;j<=n;j++) if((j^i)&&!a[j]) printf("%d ",j);puts("");ff;scanf("%d",&o);
		if(o){if(!ok) fi=i,ok=1;else{se=i;break;}}
	}if(k==1) return Put(fi,1),Put(se,n),void();
	printf("? 2 %d %d\n",w[1],fi);ff;scanf("%d",&o);Put(((k&1)^o^1)?fi:se,k),Put(((k&1)^o^1)?se:fi,n-k+1);
}
inline void init(const int p)
{
	vector<int>T[8];
	if(p==8) T[0]={1,2,3,4,n-4,n-3,n-2},T[1]={1,2,3,4,n-4,n-3,n-1},T[2]={1,2,3,4,n-4,n-3,n},T[3]={1,2,3,4,n-4,n-2,n},
	T[4]={1,2,3,4,n-4,n-1,n},T[5]={1,2,3,4,n-3,n-1,n},T[6]={1,2,3,4,n-2,n-1,n},T[7]={1,2,3,5,n-2,n-1,n};
	if(p==7) T[0]={1,2,3,4,n-4,n-3},T[1]={1,2,3,4,n-4,n-2},T[2]={1,2,3,4,n-4,n-1},T[3]={1,2,3,4,n-4,n},
	T[4]={1,2,3,4,n-3,n},T[5]={1,2,3,4,n-2,n},T[6]={1,2,3,4,n-1,n};
	if(p==5) T[0]={2,3,4,5},T[1]={1,3,4,5},T[2]={1,2,4,5},T[3]={1,2,3,5},T[4]={1,2,3,4};
	if(p==3) T[0]={1,2},T[1]={1,3},T[2]={2,3};
	for(int i=0;i<p;i++) W[i].clear();
	for(int i=0;i<p;i++)
	{
		int s=0;for(int j:T[i]) s+=j;s%=p;
		for(int j:T[i]) W[s].push_back(w[j]);
	}
}
inline void QQ(const int x,int c)
{
	init(x);int o;
	for(int i=1;i<=n;i++) if(!a[i])
	{
		bool ok=0;
		for(int j=0;j<x-1;j++)
		{
			printf("? %d ",x);for(int k:W[j]) printf("%d ",k);printf("%d\n",i);ff;
			scanf("%d",&o);if(o){M[i][c]=(x-j)%x;ok=1;break;}
		}(!ok)&&(M[i][c]=1);
	}
}
inline void out()
{
	bool ok=(a[1]>n/2);printf("! ");
	for(int i=1;i<=n;i++) printf("%d ",ok?n+1-a[i]:a[i]);fflush(stdout);
}
int main()
{
	scanf("%d",&n);for(int i=1;i<=n;i++) to[i%3][i%5][i%7][i%8]=i;
	if(n==2) return printf("! 1 2"),fflush(stdout),0;
	if(n<=18)
	{
		for(int i=1;i<=n/2-1;i++) Q(i);int fi=0,se,o;
		for(int i=1;i<=n;i++) if(!a[i]) (!fi)?fi=i:se=i;
		printf("? 2 %d %d\n",w[1],fi);ff;scanf("%d",&o);int k=n/2;
		Put(((k&1)^o^1)?fi:se,k),Put(((k&1)^o^1)?se:fi,n-k+1);return out(),0;
	}
	for(int i=1;i<=5;i++) Q(i);init(8);
	for(int i=1,o;i<=n;i++) if(!a[i])
	{
		printf("? 2 %d %d\n",w[1],i);ff;scanf("%d",&o);
		if(o)
		{
			printf("? 4 %d %d %d %d\n",w[1],w[2],w[4],i);ff;scanf("%d",&o);
			if(o)//mod4=1
			{
				printf("? 8 ");for(int j:W[3]) printf("%d ",j);printf("%d\n",i);ff;
				scanf("%d",&o);M[i][3]=o?5:1;
			}
			else//mod 4=3
			{
				printf("? 8 ");for(int j:W[1]) printf("%d ",j);printf("%d\n",i);ff;
				scanf("%d",&o);M[i][3]=o?7:3;
			}
		}
		else
		{
			printf("? 4 %d %d %d %d\n",w[1],w[2],w[3],i);ff;scanf("%d",&o);
			if(o)//mod4=2
			{
				printf("? 8 ");for(int j:W[2]) printf("%d ",j);printf("%d\n",i);ff;
				scanf("%d",&o);M[i][3]=o?6:2;
			}
			else//mod 4=0
			{
				printf("? 8 ");for(int j:W[0]) printf("%d ",j);printf("%d\n",i);ff;
				scanf("%d",&o);M[i][3]=o?0:4;
			}
		}
	}
	QQ(3,0);QQ(5,1);QQ(7,2);
	for(int i=1;i<=n;i++) if(!a[i]) a[i]=to[M[i][0]][M[i][1]][M[i][2]][M[i][3]];out();
	return 0;
}

CF1849E. Max to the Right of Min#

link 。提示:单调栈,线段树,01 赋值。

solution

考虑对于每个右端点统计答案。假设在 r 时,数组 a 满足:若区间 [l,r] 满足条件,则 al=1,否则 al=0

我们考虑 rr+1 时对于数组 a 的影响。发现只有 ar+1 为最大/小值时才会发生影响。

i 前第一个比 ai 大的位置为 bi,第一个比 ai 小的位置为 cib,c 容易单调栈维护求出。

  • br+1<r(即 ar<ar+1),则 ar+1 只能当在它为最大值时造成影响。容易发现影响为:x[bi+1,r],ax1

  • 否则 ar>ar+1,类似的,ar+1 只能当在它为最小值时造成影响。影响为:x[ci+1,r],ax0

于是问题转变为区间赋值 01,区间和,线段树维护即可。复杂度 O(nlogn)

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e6+5;
int n,a[N],b[N],c[N],t1,t2;LL ans;
namespace SGT
{
	LL a[N<<2];int lt[N<<2];
	inline void pushup(int wz){a[wz]=a[wz<<1]+a[wz<<1|1];}
	inline void upd(int l,int r,int wz,int x){a[wz]=1ll*(r-l+1)*x;lt[wz]=x;}
	inline void pushdown(int l,int r,int wz)
	{
		int t=lt[wz],mid=(l+r)>>1;if(t==-1) return;
		upd(l,mid,wz<<1,t);upd(mid+1,r,wz<<1|1,t);lt[wz]=-1;
	}
	void updata(int l,int r,int wz,int L,int R,int x)
	{
		if(L<=l&&r<=R) return upd(l,r,wz,x);
		int mid=(l+r)>>1;pushdown(l,r,wz);
		if(L<=mid) updata(l,mid,wz<<1,L,R,x);
		if(mid<R) updata(mid+1,r,wz<<1|1,L,R,x);pushup(wz);
	}
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];b[1]=c[1]=t1=t2=1;
	for(int i=1;i<=(n<<2);i++) SGT::lt[i]=-1;
	for(int i=2;i<=n;i++)
	{
		while(t1&&a[b[t1]]<a[i]) t1--;
		while(t2&&a[c[t2]]>a[i]) t2--;int p1=b[t1]+1,p2=c[t2]+1;
		if(p1<=i-1) SGT::updata(1,n,1,p1,i-1,1);
		else SGT::updata(1,n,1,p2,i-1,0);
		ans+=SGT::a[1];b[++t1]=c[++t2]=i;
	}cout<<ans;
	return 0;
}

CF1613F. Tree Coloring#

link 。提示:容斥,钦定,NTT

solution

考虑钦定 k 个点不满足条件,即对于每个点 x,cx=cfx1,且 c 仍然是 1n 的排列,我们考虑对这个东西计数,设满足这条件的方案数为 fk

由于剩下 nk 个点能任意挑,于是系数为 (1)k(nk)!
容斥一下答案为:i=0n(1)i(ni)!fi

si 表示点 i儿子数量。由于 c 仍然是 1n 的排列,于是 k 个点的分布一定满足:每个节点至多只有一个儿子被选中。于是 fk=[xk]i=1n(1+six)(1)kfk=[xk]i=1n(1six)

于是我们只需求多项式 i=1n(1six) 即可,注意有特殊条件 i=1nsi=n

如果没有特殊条件只能朴素分治 NTT 做到 O(nlog2n)。接下来介绍科技。


设有 cist=i,则有:i=0nci=i=1nici=n

考虑对于每个 i,先 O(ci) 二项式展开求出 gi=(1ix)ci,然后把 gi 卷到 ans 上,即 ansans×gi。我们算一下这样的复杂度,发现是 i=1nlogn(j=1icj)=logni=1nici=nlogn 的,于是复杂度为 O(nlogn)


code
#include<bits/stdc++.h>
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
inline int rd()
{
    int x=0,zf=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') (ch=='-')and(zf=-1),ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*zf;
}
inline void wr(int x)
{
    if(x==0) return putchar('0'),putchar(' '),void();
    int num[35],len=0;
    while(x) num[++len]=x%10,x/=10;
    for(int i=len;i>=1;i--) putchar(num[i]+'0');
    putchar(' ');
}
const int mod=998244353,N=4e6+5;
int n,m,a[N],b[N],c[N],cc[N],jc[N],inv[N],w[N],mmax;
vector<int>g[N];
inline int bger(int x){return x|=x>>1,x|=x>>2,x|=x>>4,x|=x>>8,x|=x>>16,x+1;}
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline int C(int n,int m){return 1ll*jc[n]*inv[m]%mod*inv[n-m]%mod;}
inline void init(int mmax)
{
	for(int i=1,j,k;i<mmax;i<<=1)
		for(w[j=i]=1,k=ksm(3,(mod-1)/(i<<1)),j++;j<(i<<1);j++)
			w[j]=1ll*w[j-1]*k%mod;
}
inline void DNT(int *a,int mmax)
{
	for(int i,j,k=mmax>>1,L,*W,*x,*y,z;k;k>>=1)
		for(L=k<<1,i=0;i<mmax;i+=L)
			for(j=0,W=w+k,x=a+i,y=x+k;j<k;j++,W++,x++,y++)
				*y=1ll*(*x+mod-(z=*y))* *W%mod,*x=md(*x+z);
}
inline void IDNT(int *a,int mmax)
{
	for(int i,j,k=1,L,*W,*x,*y,z;k<mmax;k<<=1)
		for(L=k<<1,i=0;i<mmax;i+=L)
			for(j=0,W=w+k,x=a+i,y=x+k;j<k;j++,W++,x++,y++)
				z=1ll* *W* *y%mod,*y=md(*x+mod-z),*x=md(*x+z);
	reverse(a+1,a+mmax);
	for(int inv=ksm(mmax,mod-2),i=0;i<mmax;i++) a[i]=1ll*a[i]*inv%mod;
}
inline void NTT(int *a,int *b,int n,int m)
{
	int mmax=bger(n+m);init(mmax);
	for(int i=m+1;i<mmax;i++) b[i]=0;DNT(a,mmax);DNT(b,mmax);
	for(int i=0;i<mmax;i++) a[i]=1ll*a[i]*b[i]%mod;
	IDNT(a,mmax);for(int i=n+m+1;i<mmax;i++) a[i]=0;
}
void dfs(int x,int fa){for(int i:g[x]) if(i^fa) c[x]++,dfs(i,x);}
int main()
{
	n=rd();for(int i=1,u,v;i<n;i++) u=rd(),v=rd(),g[u].push_back(v),g[v].push_back(u);dfs(1,0);
	jc[0]=1;for(int i=1;i<=n;i++) jc[i]=1ll*jc[i-1]*i%mod;
	inv[n]=ksm(jc[n],mod-2);for(int i=n-1;~i;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
	a[0]=1;for(int i=1;i<=n;i++) cc[c[i]]++;int len=0;
	for(int i=1;i<=n;i++)
	{
		int mul=1;if(!cc[i]) continue;
		for(int j=0;j<=cc[i];j++) b[j]=1ll*mul*C(cc[i],j)%mod,mul=1ll*mul*(mod-i)%mod;
		NTT(a,b,len,cc[i]);len+=cc[i];
	}int ans=0;
	for(int i=0;i<=n;i++) ans=md(ans+1ll*jc[n-i]*a[i]%mod);wr(ans);
    return 0;
}

uoj748. 机器人表演#

link 。提示:dp,贪心修正。

solution

由于 n,t300 我们想到 dp。先想一些贪心的算法,为 dp 补充细节。注意下面 S1 开始编号。

如何判断一个串合法:存在去掉一个为 S 子序列的方式,使得剩下的串将 0,1 分别视作左右括号后为合法括号串。

一个很显然的错误想法是钦定去掉最靠前的匹配子序列。比如 0010,原串为 00

我们考虑在 dp 过程中修正,若加入 1 使得括号序列不合法,记 prei 表示 rmax 满足 ri,k=rif(k)=1,f(k)={1(Si=0)1(Si=1)

则我们只需把 [pret,t] 踢出匹配子序列加入括号序列即可,其中 t 为当前匹配到的括号序列的点。

其实我也不会很严谨地证明这一过程,但是可以发现每次补偿完,前缀和都会到达 0,因此我们钦定的子序列是下标对应子序列字典序最小的子序列,不重不漏。(抄的抄的)

dp 式,下面不妨设没有满足条件的 pre1Sn+1=2

fi,j,k 表示到第 i 位,匹配了 S 的前 j 位,括号序列中有 k0 未匹配 1 的方案数,初始 f0,0,0=1,答案为 fn+2t,n,0

fi,j,kfi+1,j+1,kfi,j,kfi+1,j,k+1(Sj+10)fi,j,kfi+1,j,k1(Sj+11,k>0)fi,j,0fi+1,prej1,0(Sj+11)

第一行表示往答案串里加入与 Sj+1 相同的字符与 S 匹配并且加入匹配序列进行转移。

第二行表示往答案串里加入 0 且加入到括号序列让未匹配的 0 的个数 +1 进行转移。

第三行表示往答案串里加入 1 且加入到括号序列让未匹配的 0 的个数 1 进行转移。

第四行表示往答案串里加入 1 且加入到括号序列且[prej,j] 踢出匹配子序列加入括号序列进行转移。

由上可知这样时对的。

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=305,mod=998244353;
int n,m,a[N],pre[N],f[N*3][N][N];
string C;
inline void ad(int &x,int y){x+=y;(x>=mod)&&(x-=mod);}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m>>C;a[n+1]=2;pre[0]=-1;
	for(int i=1;i<=n;i++)
	{
		a[i]=C[i-1]-'0';int s=0;pre[i]=-1;
		for(int j=i;j>=1;j--)
		{
			s+=(a[j]?-1:1);
			if(s==1){pre[i]=j-1;break;}
		}
	}f[0][0][0]=1;
	for(int i=0;i<n+2*m;i++) for(int j=0;j<=n;j++) for(int k=0;k<=m;k++) if(f[i][j][k])
	{
		int t=f[i][j][k];if(j<n) ad(f[i+1][j+1][k],t);
		if(a[j+1]) ad(f[i+1][j][k+1],t);
		if(a[j+1]^1)
		{
			if(k) ad(f[i+1][j][k-1],t);
			else if(pre[j]!=-1) ad(f[i+1][pre[j]][0],t);
		}
	}cout<<f[n+2*m][n][0];
	return 0;
}

CF1854E. Game Bundles#

link 。提示:随机化,乱搞。

solution

第一次独立切的 div1 E 好耶!令 B=60

遇到这种题先想 SPJ 怎么写。发现 m=[xB]i=1k(1+xai)。于是暴力 O(B2) 判即可。

f(k,a)=[xB]i=1k(1+xai)。接下来开始乱搞。

注意到若 ai 取到比较小但又没有很小的时候 f(k,a) 会比较大。

我们把前 20 个数在 [1,10] 中随机,发现这时 f(20,a)104 是较为容易满足的。

随机 T=106 次,若 f(20,a)104,这时我们考虑往 a 后补 1(其实 0 貌似也行),让其的 f 最接近 m 但小于等于 m

之后我们考虑 ×(1+xt)B 次项的贡献,发现是加上 Bt 次项,于是我们每次找到离 m[xB]f 最近但小于等于它的位置 w,加入 Bw 即可。发现进行 15 次是个比较优秀的上界,随机出来的大数都能过。

m 比较小,我们先对 m5×103 打表,而后随机测试发现 m2×105 时才能稳定得出结果。

5000<m<2×105,则考虑随机拿出拿出 [1,500] 中的解数组 a,把上界从 104500 继续用上述算法即可。具体细节看代码。

事实上这样能在 1s 跑过 T=104 的多测,很优秀。

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
#define int LL
namespace C
{
	int b[65],c[65];
	inline void add(int x)
	{
		for(int i=0;i<=60;i++) c[i]=b[i];
		for(int i=x;i<=60;i++) b[i]+=c[i-x];
	}
	inline int chk(int n,int *a)
	{
		memset(b,0,sizeof(b));memset(c,0,sizeof(c));b[0]=1;
		for(int i=1;i<=n;i++) add(a[i]);return b[60];
	}
}
const int N=1e6+5;
int n,a[65],v[N],L[5005],c[5005][65];
mt19937 rnd(time(0));
#define wr cout<<m<<"\n";for(int k=1;k<=m;k++) cout<<a[k]<<" "
inline void init()
{
	const int nn=1e5;int cnt=0;
	for(int j=1;j<=6;j++)
	{
		n=j*10;
		for(int T=1;T<=nn;T++)
		{
			for(int i=1;i<=n;i++) a[i]=rnd()%60+1;
			int t=C::chk(n,a);if(t>5000) continue;
			if(!v[t])
			{
				v[t]=1;L[t]=n;cnt++;
				for(int i=1;i<=n;i++) c[t][i]=a[i];if(cnt==5000) return;
			}
		}
	}
}
inline void sol(int ed)
{
	if(ed<=5000)
	{
		for(int i=1;i<=L[ed];i++) a[i]=c[ed][i]; 
		cout<<L[ed]<<"\n";for(int i=1;i<=L[ed];i++) cout<<a[i]<<" ";return;
	}
	int UP=(ed<=(2e5)?500:10000);
	for(int T=1,m,l;T<=1000000;T++)
	{
		if(!UP){l=rnd()%500+1;for(int i=1;i<=L[l];i++) a[i]=c[l][i];m=L[l];}
		else{for(int i=1;i<=20;i++) a[i]=rnd()%10+1;m=20;}
		if(C::chk(m,a)>=UP)
		{
			while(C::b[60]+C::b[59]<=ed) C::add(a[++m]=1);
			for(int j=1;j<=15;j++)
			{
				if(C::b[60]==ed){wr;return;}int w=0;
				for(int i=1;i<60;i++) if(C::b[i]+C::b[60]<=ed&&C::b[i]>C::b[w]) w=i;C::add(a[++m]=60-w);
			}
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int ed;cin>>ed;(ed<=(2e5))&&(init(),1);sol(ed);
	return 0;
}

CF1436F. Sum Over Subsets#

link 。提示:莫反,合并信息。

solution

前置芝士:狄利克雷和,建议到我的博客这里学习。

这篇题解是对 O(nloglogn) 的做法的详细说明,参考了 Salieri 大佬的思路。是目前最优复杂度,且跑洛谷 rk1下面默认所有集合为可重集

s(A)=xAx,gcd(A)=gcdxAx,记 A 中的元素分别为 A1,A2,A|A|V 为所有数的最大值(值域),Sd={xxS,dx},即表示 Sd 的倍数构成的集合,Td 表示 S 中所有等于 d 的元素构成的可重集,有 TxTy(xy)=

A,BS|B|=|A|1gcd(A)=1s(A)s(B)=ASs(A)2(|A|1)[gcd(A)=1]

[gcd(A)=1] 莫反:ASs(A)2(|A|1)[gcd(A)=1]=ASs(A)2(|A|1)dgcd(A)μ(d)=ASs(A)2(|A|1)dA1,dA2,dA|A|μ(d)=d=1Vμ(d)ASds(A)2(|A|1)

我们只需快速计算 f(d)=ASds(A)2(|A|1) 即可。我们注意到有 Sx=xdTd,这让我们想到了狄利克雷后缀和。

现在问题是,合并两个集合,快速维护 f 值。

注意到 s(A)2(|A|1) 这种低次式子很能递推。具体来说:

temp(A)=(a1,a2,a3,a4,a5,a6)=(AAA1,AAAs(A),AAAs(A)2,AAA|A|,AAA|A|s(A),AAA|A|s(A)2),类似定义 b

CC=AABB,类似定义 c,我们需要快速合并出 c

c1=AAABB1=A1AA,A2BB1=a1b1c2=A1AA,A2BBs(A1)+s(A2)=AAAs(A)BBB1+AAA|1|BBBs(B)=a2b1+a1b2c3=A1AA,A2BB(s(A1)+s(A2))2=AAA|A|2BBB1+AAA|1|BBB|B|2+2A1AA,A2BBs(A1)s(B2)=a3b1+a1b3+2b2a2c4=AAA|A|BBB1+AAA|1|BBB|B|=a4b1+a1b4c5=A1AA,A2BB(|A1|+|B1|)(s(A1)+s(B1))=a5b1+b5a1+a4b2+b4a2c6=A1AA,A2BB(|A1|+|B1|)(s(A1)2+s(B1)2+2s(A1)s(B1))=a6b1+a1b6+a4b3+a3b4+2(a5b2+a2b5)

合并信息代码:

struct node{int a,b,c,d,e,f;}a[N];
inline node operator+(node X,node Y)
{
	return {1ll*X.a*Y.a%mod,(1ll*X.a*Y.b+1ll*X.b*Y.a)%mod,(1ll*X.a*Y.c+1ll*X.c*Y.a+2ll*X.b*Y.b)%mod,
	(1ll*X.d*Y.a+1ll*X.a*Y.d)%mod,(1ll*X.e*Y.a+1ll*X.a*Y.e+1ll*X.b*Y.d+1ll*X.d*Y.b)%mod,
	(1ll*X.a*Y.f+1ll*X.f*Y.a+1ll*X.d*Y.c+1ll*X.c*Y.d+2ll*X.e*Y.b+2ll*X.b*Y.e)%mod};
}
inline void operator+=(node &X,node Y){X=X+Y;}

初始时我们在 wi 放上 temp(Ti) 的标记,而后对 w 做狄利克雷后缀和,其中加法变成信息的合并即可。答案中的 f(d) 即为 wi,6wi,3。抛去初始化复杂度为 O(nloglogn)


前置芝士1:光速幂

前置芝士2:f0(y)=i=0y(yi)=2yf1(y)=i=0y(yi)i=i=1yyi(y1i1)i=yi=0y1(y1i)=y2y1f2(y)=i=0y(yi)i2=yi=0y1(y1i)(i+1)=y(i=0y1(y1i)i+i=0y1(y1i))=y(f1(y1)+2y1)f3(y)=i=0y(yi)i3=y(i=0y1(y1i)i2+2i=0y1(y1i)i+i=0y1(y1i))=y(f2(y1)+2f1(y1)+2y2)

接下来考虑如何亚 O(nlogn) 初始化。设初始有 yx 构成的集合 Tx,考虑计算此时的标记 temp

此时光速幂预处理 2 的幂次即可。注意 f2 要特判 y=1f3 要特判 y=1,2

有:temp=(f0(y),xf1(y),x2f2(y),f1(y),xf2(y),x2f3(y))

注意没出现的数也要初始化,此时 temp=(1,0,0,0,0,0)。由于光速幂这样时线性的。

于是总复杂度为 O(nloglogn)

code
//洛谷 CF1436F
//https://www.luogu.com.cn/problem/CF1436F
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e5+5,M=4e4,mod=998244353;
int n,pr[N],mu[N],v[N],V[N],p2[N],P2[N],cnt,ans;
struct node{int a,b,c,d,e,f;}a[N];
inline node operator+(node X,node Y)
{
	return {1ll*X.a*Y.a%mod,(1ll*X.a*Y.b+1ll*X.b*Y.a)%mod,(1ll*X.a*Y.c+1ll*X.c*Y.a+2ll*X.b*Y.b)%mod,
	(1ll*X.d*Y.a+1ll*X.a*Y.d)%mod,(1ll*X.e*Y.a+1ll*X.a*Y.e+1ll*X.b*Y.d+1ll*X.d*Y.b)%mod,
	(1ll*X.a*Y.f+1ll*X.f*Y.a+1ll*X.d*Y.c+1ll*X.c*Y.d+2ll*X.e*Y.b+2ll*X.b*Y.e)%mod};
}
inline void operator+=(node &X,node Y){X=X+Y;}
inline void ad(int &x,int y){x+=y;(x>=mod)&&(x-=mod);}
#define md(x) (((x)>=mod)?(x)-mod:(x))
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline void init(int M)
{
	for(int i=2;i<=M;i++)
	{
		if(!v[i]) pr[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt&&i*pr[j]<=M;j++)
		{
			v[i*pr[j]]=1;
			if(i%pr[j]==0){mu[i*pr[j]]=0;break;}mu[i*pr[j]]=-mu[i];
		}
	}mu[1]=1;
}
inline void FGT(node *a,int n){for(int i=1;i<=cnt;i++) for(int j=n/pr[i];j>=1;j--) a[j]+=a[pr[i]*j];}
#define pow2(x) (1ll*P2[(x)/M]*p2[(x)%M]%mod)
#define f1(y) (pow2(y-1)*(y)%mod)
#define f2(y) ((y==1)?1ll:(pow2(y-2)*(y-1)+pow2(y-1))%mod*(y)%mod)
#define f3(y) ((y==1)?1ll:((y==2)?10ll:(f2(y-1)+pow2(y-1)+2ll*f1(y-1))*(y)%mod))
#define Node(x,y) {pow2(y),1ll*x*f1(y)%mod,f2(y)*x%mod*x%mod,f1(y),f2(y)*x%mod,f3(y)*x%mod*x%mod}
int main()
{
	for(int i=p2[0]=1;i<=M;i++) p2[i]=md(p2[i-1]<<1);
	for(int i=P2[0]=1;i<=M;i++) P2[i]=1ll*P2[i-1]*p2[M]%mod;
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;int mx=0; 
	for(int i=1,x,y;i<=n;i++) cin>>x>>y,a[x]=Node(x,y),V[x]=1,mx=max(mx,x);init(mx);
	for(int i=1;i<=mx;i++) if(!V[i]) a[i]=Node(i,0);
	FGT(a,mx);for(int i=1;i<=mx;i++) ad(ans,md(mu[i]*md(a[i].f-a[i].c+mod)+mod));cout<<ans;
	return 0;
}

P2508. 圆上的整点#

link 。提示:结论题。

solution

引理 1:令 f(d)={0 (2d)(1)d12 (2d) . 则方程 x2+y2=n 的整数解数为 4dnf(d)

——摘抄自《初等数论》,证明我不会。


引理 2:令 od(n) 表示 n4k+1 型因子个数(包括 1),ev(n) 表示 n4k+3 型因子个数。则对于所有只包含 4k+3 型素因子的正整数 nod(n2)ev(n2)=1

归纳证明。首先结论对于所有 p2α 成立,其中 p=4k+3 是素数。

若结论对 a=(4p+3)2,b=(4q+3)2,(a,b)=1 成立,设 a,bx,y4k+3 因子,

od(ab)ev(ab)=(x+1)(y+1)+xyx(y+1)y(x+1)=1。于是结论对 ab 成立。


考虑原题,我们即要求 4(od(n2)ev(n2))。设 n=2mi=1kpiαii=1Kqiβi,其中 p1,pk4t+1 型素数,q1,,qK4t+3 型素数。

4(od(n2)ev(n2))=4i=1k(2αi+1)(od((i=1Kqiβi)2)ev((i=1Kqiβi)2))=4i=1k(2αi+1),于是分解质因数即可,复杂度 O(n)

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e5+5;
int n,ans=4;
int main()
{
	scanf("%d",&n);n>>=__builtin_ctz(n);
	for(int i=3;i*i<=n;i++) if(n%i==0)
	{
		int cnt=0;while(n%i==0) cnt++,n/=i;
		if((i&3)==1) ans*=(2*cnt+1);
	}
	if((n^1)&&(n&3)==1) ans*=3;printf("%d",ans);
	return 0;
}

CF785E. Anton and Permutation#

link 。提示:分块或树套树,可以先做做P2617

solution

显然有 O(nlog2n) 的树状数组套线段树做法,可以看提示里的那题。但是难写难调常数大,这里不细讲。

考虑交换 x,y 位置的数的影响,令 len(x,y) 的区间长度,X(x,y) 区间中 <ax 的数的个数,Y(x,y) 区间中 <ay 的数的个数。

首先考虑数对 (x,y) 的影响,这里可以 O(1) 做。

于是问题转换为单点改,求区间 <x 的数的个数。

考虑 x(x,y) 的影响,发现是 len2X,同理 y 的影响为 2Ylen。所以影响为 2(YX)

考虑两层分块,对序列和值域都分块。首先对散块 O(n) 求,对于第 i 块记一个 bi,j 表示第 i 个块中 jn 的数的个数,于是修改查询均为 O(n)。具体细节大家自己思考。

跑得飞快,目前谷 rk1

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
namespace IO
{
	const int _Pu=2e7+5,_d=32;
	char buf[_Pu],obuf[_Pu],*p1=buf+_Pu,*p2=buf+_Pu,*p3=obuf,*p4=obuf+_Pu-_d;
	inline void fin()
	{
		memmove(buf,p1,p2-p1);
		int rlen=fread(buf+(p2-p1),1,p1-buf,stdin);
		if(p1-rlen>buf) buf[p2-p1+rlen]=EOF;p1=buf;
	}
	inline void fout(){fwrite(obuf,p3-obuf,1,stdout),p3=obuf;}
	inline int rd()
	{
		if(p1+_d>p2) fin();int isne=0,x=0;
		for(;!isdigit(*p1);++p1) isne=(*p1=='-');x=(*p1++-'0');
	    for(;isdigit(*p1);++p1) x=x*10+(*p1-'0');
		if(isne) x=-x;return x;
	}
	inline void wr(LL x,char end='\n')
	{
		if(!x) return *p3++='0',*p3++=end,void();
		if(x<0) *p3++='-',x=-x;
		char sta[20],*top=sta;
		do{*top++=(x%10)+'0';x/=10;}while(x);
		do{*p3++=*--top;}while(top!=sta);(*p3++)=end;
	}
}using IO::rd;using IO::wr;
const int N=2e5+5,B=450;
int n,m,a[N],b[B+5][B+5],to[N],bl[N],L[B+5],R[B+5];LL ans;
inline int sol(int l,int r,int x)
{
	int ans=0,ll=bl[l],rr=bl[r],X=bl[x];
	if(ll==rr){for(int i=l;i<=r;i++) ans+=(a[i]<x);return ans;}
	for(int i=l;i<=R[ll];i++) ans+=(a[i]<x);for(int i=L[rr];i<=r;i++) ans+=(a[i]<x);
	for(int i=ll+1;i<rr;i++) ans+=b[i][X-1];
	for(int i=L[X];i<x;i++) ans+=(L[ll+1]<=to[i]&&to[i]<=R[rr-1]);return ans;
}
inline void upd(int x,int y,int o){for(int i=bl[y];i<=bl[n];i++) b[bl[x]][i]+=o;}
int main()
{
	n=rd(),m=rd();for(int i=1;i<=n;i++) a[i]=to[i]=i,bl[i]=(i-1)/B+1;
	for(int i=1;i<=bl[n];i++) L[i]=R[i-1]+1,R[i]=i*B;R[bl[n]]=n;
	for(int i=1;i<=bl[n];i++) for(int j=L[i];j<=R[i];j++) for(int k=bl[a[j]];k<=bl[n];k++) b[i][k]++;
	while(m--)
	{
		int x=rd(),y=rd();if(x==y){wr(ans);continue;}(x>y)&&(swap(x,y),1);
		swap(to[a[x]],to[a[y]]);(a[x]<a[y])?ans++:ans--;
		ans+=2*(sol(x+1,y-1,a[y])-sol(x+1,y-1,a[x]));wr(ans);swap(a[x],a[y]);
		upd(x,a[x],1);upd(y,a[y],1);upd(x,a[y],-1);upd(y,a[x],-1);
	}
	return IO::fout(),0;
}

LG P4714#

link 。提示:积性函数,算贡献。

solution

先吐槽一下:什么勾八题面,不能全靠样例解释啊[/笑哭]

考虑对于每个因数算贡献,设 n 的质因数分解为 n=i=1Kpiαi,d=i=1Kpiβi(1iK,0βiαi)

n 的因数 d 算贡献。发现是你有 k+1 个球,放在 βiαi 这些盒子中,除 βi 位置至少一个外其他无限制。

发现这等价于 i=1αiβi+1ai=k(i,ai0) 的方案数,为 (αiβi+kk)

βiαiβi,即求 β,i,βiαii=1K(βi+kk)

f(n)=β,i,0βiαii=1KF(βi),这显然是个积性函数(考虑乘素数幂的影响即可),于是即求 i=1K(j=1αi(j+kk)),其中 K,αi 都为 O(logn) 级别。

注意到 (j+kk)=(j+kj)=i=k+1j+kij!,于是组合数可以 O(j) 计算,还有个 Pollard-Rho 的复杂度,于是复杂度为 O(n1/4+log3n)

code
#include<bits/stdc++.h>
#define LL long long
#define bll __int128
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int mod=998244353,M=70;
mt19937 rnd(time(0));
LL n,k;int t,c[75],jc[75],inv[75],ans=1;LL PP[75];
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline int C(LL n,int m){int s=inv[m];for(LL i=n-m+1;i<=n;i++) s=i%mod*s%mod;return s;}
namespace PRHO
{
	#define mytz __builtin_ctzll
	#define Abs(x) ((x)>0?(x):-(x))
	inline LL gcd(LL a,LL b)
	{
		LL az=mytz(a),bz=mytz(b),z=min(az,bz),diff;b>>=bz;
		while(a) a>>=az,diff=a-b,az=mytz(diff),b=min(a,b),a=Abs(diff);return b<<z;
	}
	inline LL ksm(LL x,LL p,LL mod){LL s=1;for(;p;(p&1)&&(s=(bll)s*x%mod),x=(bll)x*x%mod,p>>=1);return s;}
	const LL pr[]={2,3,5,7,11,13,17,19,23,29,31,37};
	inline bool check(LL a,LL p)
	{
		LL d=a-1,t=0;while(~d&1) d>>=1,t++;LL now=ksm(p,d,a);
		if(now==1||now==0||now==a-1) return 1;
		for(int i=0;i<t;i++)
		{
			now=(bll)now*now%a;
			if(now==1) return 0;
			if(now==a-1&&i!=t-1) return 1;
		}
		return 0;
	}
	inline bool pd(LL x)
	{
		if(x==1) return 0;
		for(LL i:pr)
		{
			if(x==i) return 1;
			if(x%i==0||!check(x,i)) return 0;
		}return 1;
	}
	#define f(x,c,n) (((bll)(x)*(x)+(c))%(n))
	inline LL Find(LL x)
	{
		LL t1=1ll*rnd()*rnd()%(x-1)+1,c=1ll*rnd()*rnd()%(x-1)+1,t2=f(t1,c,x),d,mul=1;
		for(int i=1;;i<<=1,t1=t2,mul=1)
		{
			for(int j=1;j<=i;j++)
			{
				t2=f(t2,c,x);
				mul=(bll)mul*Abs(t1-t2)%x;
				if(j%127==0){d=gcd(mul,x);if(d>1) return d;}
			}d=gcd(mul,x);
			if(d>1) return d;
		}
	}
	void po(LL x)
	{
		if(x==1) return;
		if(pd(x)) return PP[++t]=x,void();LL num=Find(x);
		while(x%num==0) x/=num;po(x),po(num);
	}
	inline void bk(LL x)
	{
		t=c[1]=0;po(x);sort(PP+1,PP+1+t);t=unique(PP+1,PP+1+t)-PP-1;
		for(int i=1;i<=t;c[++i]=0) while(x%PP[i]==0) c[i]++,x/=PP[i];
	}
}using PRHO::bk;
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>k;
	jc[0]=1;for(int i=1;i<=M;i++) jc[i]=1ll*jc[i-1]*i%mod;
	inv[M]=ksm(jc[M],mod-2);for(int i=M-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;bk(n);
	for(int i=1,s=0;i<=t;i++,s=0){for(int j=0;j<=c[i];j++) s=md(s+C(j+k,j));ans=1ll*ans*s%mod;}
	return cout<<ans,0;
}

loj6849#

link 。提示:先做圆上整点这题,然后 f 打表出来 OEIS

solution

下面默认 x/y 表示 xy


结论:f(n)=i=0nj=1n[(i,j)=1][i2+j2=n2]。顺推不会,太难猜了。

证明:设 g(n)=i=0nj=1n[i2+j2=n2],则 h(n)=i=0nj=1ndi,dj[i2+j2=n2]=dnμ(d)i=0n/dj=1n/d[i2+j2=(n/d)2]=dnμ(d)g(n/d)

由于 h 是积性卷上积性,所以 h 也是积性,于是我们只需验证在 pαf=h 即可。

由圆上整点这题可得:设 n=2mi=1kpiαii=1Kqiβi,其中 p1,pk4t+1 型素数,q1,,qK4t+3 型素数。

g(n)=i=1k(2αi+1),于是分别对 n=1,p=2,p1,3(mod4) 验证 f=h 即可,这是容易的。


于是 S(n)=i=0nj=1n[(i,j)=1][i2+j2n2]

定义质数勾股数对 (a,b,c) 为满足 a2+b2=c2,gcd(a,b,c)=1 的三元组。我们即求 cn 的互素勾股数对个数。

任意质数勾股数对可以用两个正整数 x,y(y>x) 表示为 (y2x2,2xy,y2+x2)

由于三个数互素,于是 x,y 互素,且 gcd(y2x2,y2+x2)=gcd(2y2,x2+y2)=1,于是 x2+y2 为奇数,x,y 有且仅有一个偶数。

于是可以直接想到枚举 x,y 去统计答案。


m=n,类似结论那里莫反得到:

ans=i=0mj=1m[(i,j)=1][i2+j2n][2(i+j)]=d=1mμ(d)i=0m/dj=1m/d[i2+j2(n/(d2))][2d(i+j)]=1dm,2dμ(d)i=0m/dj=1m/d[j(n/(d2)i2)][2(i+j)]

t=(n/(d2)i2),则 ans=1dm,2dμ(d)i=0m/d(t/2+[2i])。于是枚举 d,i 计算即可,复杂度为调和级数 O(nlogn)=O(nlogn)

常数和空间写优秀一点。

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=4e7+5,M=1951957;
LL n,m,ans;int pr[M+5],mu[N];bitset<N>v;
inline int sq(LL x){return __builtin_sqrtl(x);}
inline void init(int M)
{
	for(int i=2;i<=M;i++)
	{
		if(!v[i]) pr[++pr[0]]=i,mu[i]=-1;
		for(int j=1;j<=pr[0]&&i*pr[j]<=M;j++)
		{
			v[i*pr[j]]=1;if(i%pr[j]==0) break;
			mu[i*pr[j]]=-mu[i];
		}
	}mu[1]=1;
}
int main()
{
	scanf("%lld",&n);init(m=sq(n));LL t;
	for(int i=1,j;i<=m;i+=2) if(mu[i]) for(j=1,t=n/i/i;i*j<=m;j++) ans+=mu[i]*((sq(t-1ll*j*j)+(j&1^1))>>1);
	return printf("%lld",ans+1),0;
}

CF1861E. Non-Intersecting Subpermutations#

link 。提示:想想怎么判断数组的价值,压进状态 dp

solution

为了后面写 dp 方便记原题中的 kK

这种计计题有个套路:先考虑判断数组的价值(有时候是判断合法性),然后压进状态 dp


大概判断思路是:能取排列就取排列。易证这样是最优的。

我们从小到大加入数组中的元素,令辅助动态数组为 a,大小为 |a|,初始为空。设答案的计数器为 cnt

考虑当前加入的数为 x

  • xa,之前 |a|=K1,此时 cntcnt+1
  • xa,之前 |a|<K1,往 a 中的末尾加入元素 x,更新数组大小。
  • xa,设它在 a 中的位置为 p。则我们把 1p 的数全部从 a 中删去,往 a 中的末尾加入元素 x,更新数组大小。

最终的计数器为数组的价值,注意到数组 a 始终没有两个相同元素


这时候就好 dp 了,记 fi,j,k 表示填了数组中的前 i 个数,|a|=j,数组价值 cost=k 的方案数。

注意到 0jK,0costnK,于是状态数是 O(n2) 的。

按照上面三种情况转移:

fi,j,k(K(j1))×fi1,j1,k(j<K)fi,0,kfi1,K1,k1fi,j,k=tjfi1,t,k(j>0)

第三种情况的 t 是枚举之前|a|,显然 |a| 不会变大,而 j>0 是因为 |a| 不会减少到 0,所以 0 位置不可被转移。


n,k 同阶,此时复杂度 O(n3),后缀和优化一下,复杂度 O(n2)空间问题可以把第一维压成 0/1,实现精细(这里的代码)可以直接把第一位压掉,直接开 n2 数组即可。

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=4005,mod=998244353;
int n,K,L,f[N][N],g[N][N],h[N],ans;
inline int md(int x){return x>=mod?x-mod:x;}
int main()
{
	scanf("%d%d",&n,&K);L=n/K;f[0][0]=g[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int k=0;k<=L;k++) h[k]=f[K-1][k];
		for(int j=K-1;j>=1;j--) for(int k=0;k<=L;k++) f[j][k]=1ll*f[j-1][k]*(K-j+1)%mod;
		for(int k=1;k<=L;k++) f[0][k]=h[k-1];f[0][0]=0;
		for(int j=1;j<K;j++) for(int k=0;k<=L;k++) f[j][k]=md(f[j][k]+g[j][k]);
		for(int j=K-1;j>=0;j--) for(int k=0;k<=L;k++) g[j][k]=md(g[j+1][k]+f[j][k]);
	}
	for(int k=0;k<=L;k++) ans=(ans+1ll*k*g[0][k])%mod;
	return printf("%d",ans),0;
}

CF342E. Xenia and Tree#

link 。提示:根号,仔细想想对啥分块。

solution

首先有一个依赖于 O(nlogn)O(1) lca 的根号做法,快速讲:就是对操作分块,若查询到的是同块内的操作,则直接暴力查即可。

否则对每个块内的修改操作,做一次 bfs,可以类似理解为多源最短路,然后查询直接跳直接的整块询问"最短路"即可。

所有基于 O(1) lca 不带 log,设 n,m 同阶,复杂的为 O(nn)。代码为此做法。


但是这题一看很 log 啊,我根本没想根号。但事实上根号好写好调,log 相对都难写。

下面是一个不需要三度化点分树的 O(nlogn),基本只基于树剖,有错误请指出

首先 dis(u,v)=du+dv2dlca(u,v),于是查询只需求 mincolv=red2dlca(u,v)+dv

枚举 lca,即求 mint=gpu(2dt+(minvsubtreeulca(u,v)=tcolv=reddv)),其中 gp 表示非严格祖先集合。

考虑后面那个 min,发现 lca(u,v)=t 是不必要的,因为若不等于,则一定存在等于的方案被计算过了,而且这里计算会更劣,大家自己思考一下。

于是即求 mint=gpu(2dt+ft),其中 f(t)=minvsubtreeu,colv=reddv

这里是经典瓶颈了,所谓瓶颈就在 2dt 上。大概一年前推到类似地方不会了。

考虑修改 xf(t) 的影响,发现是 tgpxf(t)min(f(t),dx)。看起来是正常的取 min。(其中初值 f(1)=1,其他初值为 +

但是查询是形如 min(ai+bi) 的,其中只修改 bi。这时要智慧了。

考虑重剖,发现修改形如:对 O(1) 条重链的前缀位置dxmin,对剩下的 O(logn) 条重链的所有位置dxmin。查询类似刻画。

由于都是取 min,考虑拆开:把修改前缀位置修改所有位置开做,发现这挺平衡的,下面对每个重链单独考虑。


考虑总共 O(mlogn) 次修改所有位置,查询前缀 min{2di+fi},直接预处理每个前缀的最小值即可,修改对每位都是相同的。


考虑总共 O(m) 次修改所有位置,查询 O(n) 次前缀 min{2di+fi}O(mlogn) 次全局这个最小值。发现 f 单调,答案单调,往颜色段均摊方面想,于是考虑 set 维护所有相同的 f 连续段(下面成为同颜色段)。

修改直接对颜色段做,类似珂朵莉树,每个颜色段记那东西的 min,修改后合并颜色段是好做的,由于每个段加入一次删除(被合并)一次,于是摊 O(1),乘 setlogO(logn),这时候查询前缀显然是好 O(logn) 的,全局最小直接每次修改都更新一下即可,修改复杂的不变,查全局变成 O(1)


这下所有复杂度都对了,是 O(nlogn) 的,常数肯定不大。但个人不想写,希望后人填坑(或指出假的地方)。实现上重链记 top 的同时记 end 就能锁定重链,然后每个重链开 set 做即可。

并不后的后记:当时不自信去 uoj 群问做法了,发现有人提出一样的了,大概是对的吧,还学了很多新东西:

考虑我们区间查的形如 min{ai+bi} 的东西,区间改 bi,为与 xmin,存在吉司机线段树 O(nlogn) 的做法(因为说没区间加是单 log 的),我不会吉老师所以没细讲。一个弱化点的版本为 loj 3033,一个好题解在这里

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1e5+5,B=320;
int n,m,f[B+5][N],bl[N],L[N],R[N];
basic_string<int>E[N];
struct node{int o,x;}Q[N];
queue<int>q;
namespace LCA
{
	int tot,d[N],id[N],mn[N][19];
	inline int MN(int x,int y){return d[x]<d[y]?x:y;}
	void dfs(int x,int fa)
	{
		d[x]=d[mn[id[x]=++tot][0]=fa]+1;
		for(int i:E[x]) if(i^fa) dfs(i,x);
	}
	inline void init(){dfs(1,0);for(int i=1;i<=__lg(n);i++) for(int j=1;j+(1<<(i-1))<=n;j++) mn[j][i]=MN(mn[j][i-1],mn[j+(1<<(i-1))][i-1]);}
	inline int lca(int u,int v)
	{
		if(u==v) return u;u=id[u],v=id[v];u>v&&(swap(u,v),1);
		int t=__lg(v-(u++));return MN(mn[u][t],mn[v-(1<<t)+1][t]);
	}
	inline int dis(int u,int v){return d[u]+d[v]-2*d[lca(u,v)];}
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m;
	for(int i=1,u,v;i<n;i++) cin>>u>>v,E[u]+=v,E[v]+=u;LCA::init();m++;
	Q[1]={1,1};bl[1]=1;for(int i=2;i<=m;i++) cin>>Q[i].o>>Q[i].x,bl[i]=(i-1)/B+1;
	for(int i=1;i<=bl[m];i++) L[i]=R[i-1]+1,R[i]=i*B;R[bl[m]]=m;
	for(int i=1;i<=bl[m];i++)
	{
		for(int j=L[i];j<=R[i];j++) if(Q[j].o==1) q.push(Q[j].x),f[i][Q[j].x]=1;
		while(!q.empty())
		{
			int t=q.front();q.pop();
			for(int j:E[t]) if(!f[i][j]) f[i][j]=f[i][t]+1,q.push(j);
		}
	}
	for(int i=1;i<=m;i++) if(Q[i].o==2)
	{
		int mn=1e9;
		for(int j=L[bl[i]];j<i;j++) if(Q[j].o==1) mn=min(mn,LCA::dis(Q[i].x,Q[j].x));
		for(int j=1;j<bl[i];j++) if(f[j][Q[i].x]) mn=min(mn,f[j][Q[i].x]-1);cout<<mn<<"\n";
	}
	return 0;
}

ABC180F. Unbranched#

link 。提示:差分,EGF

solution

首先发现恰好 L 不好做,于是差分:设 sol(L) 表示(抛去原有第三个条件而后)连通块大小的最大值 L 时的答案,于是答案为 sol(L)sol(L1)

最大值 L 就可以变为所有连通块大小都 L,就好做了。

考虑对于每个联通块第二个条件,发现每个连通块为链或环。这时候条件转化差不多了。


这时候不会真有人想 dp 吧?显然生成函数啊!由于有标号于是 EGF

设链的 EGFf(x)=i0aixii!,环的 EGFg(x)=i0bixii!

考虑链,发现:a0=0,a1=1,ai=i!2(i2),这是因为是无向图,如果按 i! 算,则每条链头尾都会被算一次,于是要除 2

考虑环,有:b0=0,b1=0,bi=ai1(i2),考虑扔掉编号最大的点,就得到了一个 i1 的链,链加一个点连接头尾得到了环,于是这俩构成双射,这俩个数就一样。


求出 EGF 后考虑计算答案。设最终链有 A 个,环有 B 个。注意到每条链的点数减边数为 1,环为 0。于是得到 A=nm0Bn

考虑把两者 EGF 结合,发现是 h(x)=f(x)nmexp(g(x))(不理解的可以想想 exp 的组合意义)。最后求得的就是 n![xn]hn

但是你发现你 WA 了,然后发现多算了,这是因为你 f(x) 快速幂的时候实际上把每条链做了个有标号 1,2,,nm,实际上要链与链要无标号(环你用 exp 已经做到了),于是最终要除个 (nm)!,这下做完了。


如果用 NTT 那套做可以做到 O(nlogn),但是不想写,写了个 O(n2logn) 的快速幂,其他部分为 O(n2)

关于多项式 exp 如何做,我写(贺)个简洁的:

F0=0,G(x)=exp(F(x))G(z)=F(z)expF(z)=F(z)G(z)G0=1,Gk=1ki=1kiFiGki

code
#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=1005,M=N-5,mod=1e9+7,I2=(mod+1)>>1;
int n,m,L,jc[N],inv[N],a[N],b[N],c[N],d[N],ans;
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
inline void mul(int *a,int *b,int n,int m)
{
	static int c[N];
	for(int i=0;i<=n;i++) for(int j=0;j<=m;j++) c[i+j]=(c[i+j]+1ll*a[i]*b[j])%mod;
	for(int i=0;i<=n+m;i++) a[i]=c[i],c[i]=0;
}
inline void Exp(int *a,int *b,int n)
{
	b[0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=i;j++) b[i]=(b[i]+1ll*a[j]*b[i-j]%mod*j)%mod;
		b[i]=1ll*ksm(i,mod-2)*b[i]%mod;
	}
}
inline void ksm(int *a,int *b,int n,int p)
{
	for(b[0]=1;p;p>>=1)
	{
		if(p&1){mul(b,a,n,n);for(int i=n;i<=2*n;i++) b[i]=0;}
		mul(a,a,n,n);for(int i=n;i<=2*n;i++) a[i]=0;
	}
}
inline int sol(int L)
{
	for(int i=1;i<=L;i++) a[i]=(i==1)?1:I2;ksm(a,c,n+1,n-m);
	for(int i=2;i<=L;i++) b[i]=(i==2)?I2:ksm(md(i+i),mod-2);Exp(b,d,n);
	mul(c,d,n,n);return 1ll*c[n]*jc[n]%mod*inv[n-m]%mod;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m>>L;
	jc[0]=1;for(int i=1;i<=M;i++) jc[i]=1ll*jc[i-1]*i%mod;
	inv[M]=ksm(jc[M],mod-2);for(int i=M-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
	ans=sol(L);for(int i=0;i<=M;i++) a[i]=b[i]=c[i]=d[i]=0;ans=md(ans+mod-sol(L-1));
	return cout<<ans,0;
}
posted @   HaHeHyt  阅读(500)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示
主题色彩
主题色彩