AUOJ 部分题目题解集合

模拟赛题解集合

auoj2 天才数学少女
Problem

qq 组询问,每次给定 a,pa,p ,求一个在 [0,2×1018][0,2\times 10^{18}] 的正整数 nn ,使得 ann(modp)a^n\equiv n(\bmod p) ,输出任意一个解或输出无解

q100,a,p109q\leq 100,a,p\leq 10^9

1s,256MB1s,256MB

Sol

显然有 xϕ(p)1(modp)x^{\phi(p)}\equiv 1(\bmod p)

g=gcd(p,ϕ(p))g=gcd(p,\phi(p)) ,如果求出了 ann(modg)a^n\equiv n(\bmod g) 的解 nn ,那么有 an+kϕ(p)an(modp)a^{n+k\phi(p)}\equiv a^n(\bmod p)

相当于求出 ann+kϕ(p)(modp)a^n\equiv n+k\phi(p)(\bmod p) 的一组解,因为 ann(modg)a^n\equiv n(\bmod g) 所以一定有解,做一个exgcd就可以求出答案

暴力递归下去做即可,只会递归 loglog 次,每次暴力算 ϕ\phi ,因为每次至少除以2所以复杂度为 O(Tp)O(T\sqrt p)

通过上面的过程可以看出一定有解

Code
#include<cstdio>
using namespace std;
#define ll long long
int t,a,b;
int solve1()
{
	a%=b;if(a==0)return b;
	int v1=1;
	for(int i=1;i<=100;i++){v1=1ll*v1*a%b;if(v1==i%b)return i;}
	return -1;
}
int phi(int x)
{
	int as=x;
	for(int i=2;1ll*i*i<=x;i++)if(x%i==0){as=1ll*as*(i-1)/i;while(x%i==0)x/=i;}
	if(x>1)as=1ll*as*(x-1)/x;
	return as;
}
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int exgcd(int a,int b,int &x,int &y){if(!b){x=1,y=0;return a;}int g=exgcd(b,a%b,y,x);y=y-a/b*x;return g;}
int getinv(int a,int b){int x,y;int g=exgcd(a,b,x,y);x%=(b/g);x=(x+(b/g))%(b/g);return x;}
int pw(int a,int b,int p){int as=1;while(b){if(b&1)as=1ll*as*a%p;a=1ll*a*a%p;b>>=1;}return as;}
ll solve2(int a,int b,int k)
{
	a%=b;if(a==0)return b;
	int p=phi(b);
	if(gcd(p,b)==1)
	{
		int v1=getinv(1ll*p*k%b,b);
		return 1ll*p*v1;
	}
	int st=gcd(p,b);
	long long v2=solve2(a,st,k);
	int s1=pw(a,v2%p+(v2>=p)*p,b),s2=1ll*k*v2%b,s3=(s1-s2+b)%b;
	s3/=st;
	int v3=1ll*s3*getinv(1ll*p*k%b,b)%(b/st);
	return v2+1ll*v3*p;
}
ll solve3(int a,int b)
{
	a%=b;if(!a)return b;
	if(gcd(a,b)==1)return solve2(a,b,1);
	int v1=pw(a,b,b),g=gcd(v1,b);
	if(g==b)return b;
	v1/=g;b/=g;int s1=pw(a,g,b);
	return solve2(s1,b,getinv(v1,b))*g+b*g;
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&a,&b);
		if(solve1()!=-1)printf("%d\n",solve1());
		else printf("%lld\n",solve3(a,b));
	}
}
auoj3 fake
Problem

给定 n,mn,ma1...ma_{1...m} ,求序列 b1,...,kb_{1,...,k} ,满足

  1. mkm|k
  2. 1b1<b2<...<bkn1\leq b_1<b_2<...<b_k\leq n
  3. i,m(ji)bjai\forall i,\sum_{m|(j-i)} b_j\leq a_i

求所有合法序列中 bi\sum b_i 的最大值

n109,m2×106n\leq 10^9,m\leq 2\times10^6

1s,1024MB1s,1024MB

Sol

考虑暴力,首先枚举 kk

那么显然有 m(ji)bjm(j1)bj(i1)km\sum_{m|(j-i)}b_j-\sum_{m|(j-1)} b_j\geq (i-1)*\frac{k}{m}

因此可以计算出 m(j1)bj\sum_{m|(j-1)} b_j 可能的最大值 v=mini(ai(i1)km)v=min_i(a_i-(i-1)*\frac{k}{m})

如果一种方案中 m(j1)bj<v\sum_{m|(j-1)} b_j<v ,如果 y,1<i<y,m(ji)bj(i1)km=m(j1)bj,m(jy)bj(y1)km>m(j1)bj\exists y,\forall 1<i<y,\sum_{m|(j-i)}b_j -(i-1)*\frac{k}{m}=\sum_{m|(j-1)}b_j,\sum_{m|(j-y)}b_j -(y-1)*\frac{k}{m}>\sum_{m|(j-1)}b_j ,此时一定存在 bxm+j>bxm+j11b_{xm+j}>b_{xm+j-1}-1 ,因为 m(j1)bj<v\sum_{m|(j-1)} b_j<v ,将 bxm+1,...,bxm+j1b_{xm+1},...,b_{xm+j-1} 全部加一一定合法

如果不存在 yy 满足条件,那么因为 m(j1)bj<v\sum_{m|(j-1)} b_j<v ,一定存在 bxm+m<bxm+m+11b_{xm+m}<b_{xm+m+1}-1 或者 ak<na_{k}<n,因此可以将 bxm+1,...,bxm+mb_{xm+1},...,b_{xm+m} 全部加一,使答案更优

因此最优解一定满足 m(j1)bj=v\sum_{m|(j-1)} b_j=v

考虑如果确定了 b1,bm+1,...b_1,b_{m+1},... ,接下来的操作可以看成一开始 0x<km,1<ym,bxm+y=bxm+y1+1\forall 0\leq x<\frac{k}{m},1<y\leq m,b_{xm+y}=b_{xm+y-1}+1 ,然后每次可以给一段 bxm+y,...,bxm+mb_{xm+y},...,b_{xm+m} 加上1

容易发现操作次数上限为 nb1+1kn-b_1+1-k ,因此需要 a1a_1 尽量小

那么可以构造一组方案,除了无解外有三种情况

  1. b1=nk+1b_1=n-k+1
  2. b1>1,m(j1)bj=vb_1>1,\sum_{m|(j-1)}b_j=v
  3. b1=1,m(j1)bj=vb_1=1,\sum_{m|(j-1)}b_j=v

大力讨论即可

解出一组 b1,bm+1,...b_1,b_{m+1},... 之后,接下来的操作相当于每次给一段 bxm+y,...,bxm+mb_{xm+y},...,b_{xm+m} 加上1 ,最多加若干次,并且需要满足i,m(ji)bjai\forall i,\sum_{m|(j-i)} b_j\leq a_i

显然贪心先放 yy 尽量小的最优,可以 O(m)O(m) 求出,枚举 kk 的总复杂度是 O(n)O(n)

对于第一种情况,显然 kk 越大越优

对于第二种情况,只有一个 kk 满足这种情况,可以暴力

考虑第三种情况,打表可以发现答案是一个上凸函数

注意到如果最后一个 m(ji)bj<ai\sum_{m|(j-i)} b_j< a_i 但操作次数不够了,那么这时所有 bxm+jb_{xm+j} 一定全部到了上界,即 bxm+j+(mj+1)=b(x+1)m+1(bk+1=n+1)b_{xm+j}+(m-j+1)=b_{(x+1)m+1}(b_{k+1}=n+1)

那么一定有 m(ji)bjn+v(mj+1)km\sum_{m|(j-i)} b_j\leq n+v-(m-j+1)\frac{k}{m} ,这是一个关于 kk 的上凸函数

对于每一个 m(ji)bj\sum_{m|(j-i)} b_j ,它能操作到的上界为 minx>j(axkm(xj))\min_{x>j}(a_x-\frac{k}{m}(x-j)) ,注意到以 kk 为横轴,这相当于若干条直线,那么最后的上界一定是一个凸壳,因此是上凸函数

两个上凸函数取min还是上凸函数,上凸函数相加还是上凸函数

因此这部分最后的答案是一个关于 kk 的上凸函数

因此这部分可以三分解决

复杂度 O(mlogm)O(m\log m)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 233333
int n,m;
long long v[N],d[N],las,las2;
long long solve(int s)
{
	if(s<0||s>m/n)return -1e18;
	d[n]=v[n];for(int i=n-1;i>=1;i--)d[i]=min(v[i],d[i+1]-s);
	int lb=1,rb=1+(s-1)*n;
	if(1ll*(lb+rb)*s/2>d[1])return -1e18;
	if(!s)return 0;
	int rb2=m-n+1,lb2=m-n+1-(s-1)*n;
	long long fuc=1ll*(lb2+rb2)*s/2,tp1=lb2-(fuc-d[1]);
	if(tp1<=0)tp1=1;
	if(fuc<d[1])return 1ll*(lb2+rb2)*(rb2-lb2+1)/2;
	long long v1=1ll*d[1]*n+1ll*s*n*(n-1)/2,v2=m-tp1-s*n+1;
	for(int i=2;i<=n;i++)d[i]=(d[i]-d[1]-s*(i-1));d[1]=0;
	for(int i=n-1;i>=2;i--)d[i]=min(d[i],d[i+1]);
	for(int i=2;i<=n;i++)
	{
		long long fuc=d[i]-d[i-1];
		if(fuc>v2)fuc=v2;
		v1+=1ll*(n-i+1)*fuc;v2-=fuc;
	}
	return v1;
}
bool check1(int s)
{
	d[n]=v[n];for(int i=n-1;i>=1;i--)d[i]=min(v[i],d[i+1]-s);
	int lb=1,rb=1+(s-1)*n;
	if(1ll*(lb+rb)*s/2>d[1])return 1;
	return 0;
}
bool check2(int s)
{
	d[n]=v[n];for(int i=n-1;i>=1;i--)d[i]=min(v[i],d[i+1]-s);
	int lb=1,rb=1+(s-1)*n;
	if(1ll*(lb+rb)*s/2>d[1])return 0;
	if(!s)return 0;
	int rb2=m-n+1,lb2=m-n+1-(s-1)*n;
	long long fuc=1ll*(lb2+rb2)*s/2,tp1=lb2-(fuc-d[1]);
	if(fuc<d[1])return 1;
	return 0;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%lld",&v[i]);
	int lb=1,rb=m/n,st1=m/n+1;
	while(lb<=rb)
	{
		int mid=(lb+rb)>>1;
		if(check1(mid))st1=mid,rb=mid-1;
		else lb=mid+1;
	}
	lb=1,rb=st1-1;int st2=0;st1--;
	while(lb<=rb)
	{
		int mid=(lb+rb)>>1;
		if(check2(mid))st2=mid,lb=mid+1;
		else rb=mid-1;
	}
	long long as=max(0ll,max(solve(st2-1),max(solve(st2),solve(st2+1))));
	lb=st2,rb=st1;
	while(lb<rb)
	{
		int mid1=(lb+rb)>>1,mid2;
		if(mid1==rb)mid2=mid1,mid1=mid2-1;
		else mid2=mid1+1;
		long long v1=solve(mid1),v2=solve(mid2);
		if(v2>=v1)as=max(as,v2),lb=mid1+1;
		else rb=mid2-1;
	}
	as=max(as,solve(lb));
	as=max(as,solve(rb));
	printf("%lld\n",as);
}
auoj4 绰绰有余
Problem

PDF题目

Sol

每条链最多会贡献两个奇度数的点,总共有 2n2n 个奇度数点,因此 m<nm<n 无解

对于链长<3的可以直接放,对于一条链长>3的链 kk ,考虑把它和 aa 条1和 bb 个2拼成合法的,那么一定有

k+a+2b=3(1+a+b)k+a+2b=3(1+a+b)

2a+b=k32a+b=k-3

如果找到了一组 (a,b)(a,b) ,可以这样构造:

如果无解,说明剩下的没有长度为 22 的链,且 kk 为偶数

如果有两条偶数的链,设长度为 j,kj,k ,考虑

a+2j+2k=3(a+2)a+2j+2k=3(a+2)

a=j+k3a=j+k-3 即可,构造如下

如果只剩一条偶数的链,此时显然 n<mn<m ,随便构造即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<queue>
using namespace std;
#define N 105000
queue<int> st[4];
int n,m,v[N],as[N][3],su,ct=0,tp;
void doit1(int a)
{
	int s=v[a]-3;ct++;as[ct][1]=as[ct][0]=as[ct][2]=a;
	while(s>1&&st[0].size())++ct,as[ct][0]=as[ct][2]=a,as[ct][1]=st[0].front(),st[0].pop(),s-=2;
	while(s&&st[1].size())++ct,as[ct][0]=a,as[ct][1]=as[ct][2]=st[1].front(),st[1].pop(),s--;
}
void doit2(int a,int b)
{
	int s=v[a]-3,v2=0;ct++;as[ct][1]=as[ct][0]=as[ct][2]=a;
	while(s>1&&st[0].size())++ct,as[ct][0]=as[ct][2]=a,as[ct][1]=st[0].front(),st[0].pop(),s-=2;
	ct++;as[ct][0]=a;as[ct][1]=as[ct][2]=b;s=v[b]-2;
	while(s>1&&st[0].size())++ct,as[ct][v2]=as[ct][v2+1]=b,as[ct][(!v2)*2]=st[0].front(),st[0].pop(),s-=2,v2^=1;
}
void doit3(int a)
{
	int s=v[a]-3;ct++;as[ct][1]=as[ct][0]=as[ct][2]=a;
	while(s>1&&st[0].size())++ct,as[ct][0]=as[ct][2]=a,as[ct][1]=st[0].front(),st[0].pop(),s-=2;
	++ct;as[ct][0]=a;as[ct][1]=st[0].front(),st[0].pop();as[ct][2]=st[0].front(),st[0].pop();
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&v[i]);
		if(v[i]==1)st[0].push(i);
		else if(v[i]==2)st[1].push(i);
		else if(v[i]&1)st[2].push(i);
		else st[3].push(i);
		su+=v[i];
		if(su>n*3){printf("no\n");return 0;}
	}
	if(su!=n*3||m<n){printf("no\n");return 0;}
	printf("yes\n");
	while(!st[2].empty())doit1(st[2].front()),st[2].pop();
	while(!st[3].empty())if(st[1].size())doit1(st[3].front()),st[3].pop();
	else if(st[3].size()>1)tp=st[3].front(),st[3].pop(),doit2(tp,st[3].front()),st[3].pop();
	else doit3(st[3].front()),st[3].pop();
	while(ct<n)
	{
		if(st[1].size()>=3)
		{
			int v1=st[1].front();st[1].pop();
			int v2=st[1].front();st[1].pop();
			int v3=st[1].front();st[1].pop();
			as[ct+1][0]=as[ct+1][1]=v1;as[ct+2][0]=as[ct+2][1]=v2;as[ct+1][2]=as[ct+2][2]=v3;ct+=2;
		}
		else if(!st[1].size())
		{
			int v1=st[0].front();st[0].pop();
			int v2=st[0].front();st[0].pop();
			int v3=st[0].front();st[0].pop();
			as[ct+1][0]=v3;as[ct+1][1]=v1;as[ct+1][2]=v2;ct++;
		}
		else
		{
			int v1=st[0].front();st[0].pop();
			int v2=st[1].front();st[1].pop();
			as[ct+1][0]=as[ct+1][1]=v2;as[ct+1][2]=v1;ct++;
		}
	}
	for(int i=1;i<=3;i++,printf("\n"))
	for(int j=1;j<=n;j++)
	printf("%d ",as[j][i-1]);
}
auoj5 Magnolia
Problem

给一个序列,求有多少个本质不同的子串,满足子串的元素和在 [L,R][L,R] 之间

n5×105,v109,L,R1018n\leq 5\times 10^5,|v|\leq 10^9,|L|,|R|\leq 10^{18}

4s,1024MB4s,1024MB

Sol

首先建SA,那么本质不同的子串为若干个给定 ll , rr 在一段后缀内的串

记录前缀和,对于一个 ll ,相当于求 sul1+Lsursul1+Rsu_{l-1}+L\leq su_r\leq su_{l-1}+Rrr 的数量,离线后离散化依次加入即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;
#define N 505050
map<int,int> mp;
int n,v[N],s[N],ct,f[N],t[N],tr[N];
long long su[N],l,r,su2[N],as;
bool cmp(int a,int b){return f[a]>f[b];}
struct SA{
	int a[N],b[N*2],sa[N],vl[N],n,m,su[N],rk[N];
	void pre()
	{
		for(int i=1;i<=n;i++)su[a[i]=s[i]]++;
		for(int i=1;i<=m;i++)su[i]+=su[i-1];
		for(int i=n;i>=1;i--)sa[su[a[i]]--]=i;
		for(int l=1;l<=n;l<<=1)
		{
			int ct=0;
			for(int i=n-l+1;i<=n;i++)b[++ct]=i;
			for(int i=1;i<=n;i++)if(sa[i]>l)b[++ct]=sa[i]-l;
			for(int i=1;i<=m;i++)su[i]=0;
			for(int i=1;i<=n;i++)su[a[i]]++;
			for(int i=1;i<=m;i++)su[i]+=su[i-1];
			for(int i=n;i>=1;i--)sa[su[a[b[i]]]--]=b[i];
			for(int i=1;i<=n;i++)b[i]=a[i];
			ct=1;a[sa[1]]=1;
			for(int i=2;i<=n;i++)a[sa[i]]=(b[sa[i]]==b[sa[i-1]]&&b[sa[i]+l]==b[sa[i-1]+l])?ct:++ct;
			m=ct;if(m==n)break;
		}
		for(int i=1;i<=n;i++)rk[sa[i]]=i;
		int fu=0;s[n+1]=1e9+9;
		for(int i=1;i<=n;i++)
		{
			f[i]=i;
			if(fu)fu--;
			if(rk[i]==1)continue;
			while(s[i+fu]==s[sa[rk[i]-1]+fu])fu++;
			f[i]=i+fu;
		}
	}
}sa;
void add(int x,int k){for(int i=x;i<=n;i+=i&-i)tr[i]+=k;}
int que(int x){if(x>n)x=n;int as=0;for(int i=x;i;i-=i&-i)as+=tr[i];return as;}
int main()
{
	scanf("%d%lld%lld",&n,&l,&r);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]),s[i]=mp[v[i]]?mp[v[i]]:(mp[v[i]]=++ct),t[i]=i,su[i]=su[i-1]+v[i],su2[i]=su[i];
	sa.n=n;sa.m=ct;
	sa.pre();sort(t+1,t+n+1,cmp);sort(su2+1,su2+n+1);
	int las=n;
	for(int i=1;i<=n;i++)
	{
		while(f[t[i]]<=las)add(lower_bound(su2+1,su2+n+1,su[las])-su2,1),las--;
		long long v1=l+su[t[i]-1],v2=r+su[t[i]-1]+1;
		int l1=lower_bound(su2+1,su2+n+1,v1)-su2,r1=lower_bound(su2+1,su2+n+1,v2)-su2-1;
		if(l1>r1)continue;
		as+=que(r1)-que(l1-1);
	}
	printf("%lld\n",as);
}
auoj6 Myosotis
Problem

有一个序列,定义一次操作为选出两个数,分数为前面那个数的值减去后面那个数的值,然后删去这两个数

对于每一个 kk ,求出最多进行 kk 次操作得到的最大分数

n5×105n\leq 5\times 10^5

5s,256MB5s,256MB

Sol

如果一个数被作为第一个数和第二个数分别选了一次,可以看成没选过这个数

考虑一个费用流,原点向每个点连费用为这个点权值,流量1的边,每个点向它右边的点连费用0,流量inf的边,每个点向汇点连费用为这个点权值的相反数,流量1的边,进行 kk 次操作的答案即为流量为 kk 的最大费用流

考虑模拟增广,每次相当于选出一对数 (i,j)(i,j),使得 i<ji<j 或者 i>ji>j 并且 (i,j)(i,j) 间的边的流量全部大于0,且 aiaja_i-a_j 尽量大,然后给这个区间的边流量加一或减一

因为每个点只会选一次,所以可以把流量放到点上

使用线段树维护,对于一个区间,记录这个区间内点的最小流量,以及如果将这个区间内流量最小的点看成流量为0的点时,这个区间内部的答案,与区间左端点连通的部分的点的最大值和最小值,与区间有端点连通的部分的点的最大值和最小值,整个区间的点的最大值和最小值,以及这些值的位置

合并时如果两边最小值一样可以直接将左边的右侧和右边的左侧合并起来,否则min更大的那一边没有限制,如果右边min更大,可以将左边的右侧和整个右边合并起来,另外一种情况同理,合并时更新答案

最后如果整体的min>0,那么整个区间都可以互相转移,可以用max-min更新答案

这样可以直接打标记,复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
using namespace std;
#define N 505050
int n,v[N];
long long as;
struct sth{int mx,mn,f1,f2;};
sth doit(sth a,sth b){sth c=a;if(b.mx>c.mx)c.mx=b.mx,c.f1=b.f1;if(b.mn<c.mn)c.mn=b.mn,c.f2=b.f2;return c;}
sth doit2(sth a,sth b){if(a.mx-a.mn<b.mx-b.mn)return b;return a;}
struct segt{
	struct node{int l,r,mn,lz;sth lb,rb,su,as;}e[N*4];
	void pushup(int x)
	{
		if(e[x<<1].mn<e[x<<1|1].mn)
		{
			e[x].mn=e[x<<1].mn;
			e[x].lb=e[x<<1].lb;
			e[x].rb=doit(e[x<<1|1].su,e[x<<1].rb);
			e[x].su=doit(e[x<<1].su,e[x<<1|1].su);
			e[x].as=doit2(doit2(e[x<<1].as,e[x<<1|1].as),e[x].rb);
		}
		else if(e[x<<1].mn>e[x<<1|1].mn)
		{
			e[x].mn=e[x<<1|1].mn;
			e[x].rb=e[x<<1|1].rb;
			e[x].lb=doit(e[x<<1].su,e[x<<1|1].lb);
			e[x].su=doit(e[x<<1].su,e[x<<1|1].su);
			e[x].as=doit2(doit2(e[x<<1].as,e[x<<1|1].as),e[x].lb);
		}
		else
		{
			e[x].mn=e[x<<1].mn;
			e[x].lb=e[x<<1].lb;
			e[x].rb=e[x<<1|1].rb;
			e[x].su=doit(e[x<<1].su,e[x<<1|1].su);
			e[x].as=doit2(doit2(e[x<<1].as,e[x<<1|1].as),doit(e[x<<1].rb,e[x<<1|1].lb));
		}
		sth su=(sth){e[x<<1|1].su.mx,e[x<<1].su.mn,e[x<<1|1].su.f1,e[x<<1].su.f2};
		e[x].as=doit2(e[x].as,su);
	}
	void pushdown(int x){e[x<<1].lz+=e[x].lz;e[x<<1].mn+=e[x].lz;e[x<<1|1].mn+=e[x].lz;e[x<<1|1].lz+=e[x].lz;e[x].lz=0;}
	void build(int x,int l,int r)
	{
		e[x].l=l;e[x].r=r;
		if(l==r){e[x].lb=e[x].rb=e[x].as=(sth){-1000000000,1000000000,-1,-1};e[x].su=(sth){v[l],v[l],l,l};return;}
		int mid=(l+r)>>1;
		build(x<<1,l,mid);build(x<<1|1,mid+1,r);
		pushup(x);
	}
	void modify(int x,int s)
	{
		if(e[x].l==e[x].r){e[x].lb=e[x].rb=e[x].as=e[x].su=(sth){-1000000000,1000000000,-1,-1};return;}
		int mid=(e[x].l+e[x].r)>>1;pushdown(x);
		if(mid>=s)modify(x<<1,s);else modify(x<<1|1,s);pushup(x);
	}
	void modify2(int x,int l,int r,int k)
	{
		if(e[x].l==l&&e[x].r==r){e[x].mn+=k;e[x].lz+=k;return;}
		int mid=(e[x].l+e[x].r)>>1;pushdown(x);
		if(mid>=r)modify2(x<<1,l,r,k);
		else if(mid<l)modify2(x<<1|1,l,r,k);
		else modify2(x<<1,l,mid,k),modify2(x<<1|1,mid+1,r,k);
		pushup(x);
	}
	void dfs(int x){if(e[x].l==e[x].r)return;pushdown(x);dfs(x<<1);dfs(x<<1|1),pushup(x);}
}tr;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	tr.build(1,1,n);
	for(int i=1;i<=n/2;i++)
	{
		sth fu=tr.e[1].as;
		if(tr.e[1].mn>0)fu=doit(tr.e[1].su,fu);
		if(fu.mx>fu.mn)
		as+=fu.mx-fu.mn;
		tr.modify(1,fu.f1);tr.modify(1,fu.f2);
		if(fu.f1>fu.f2)tr.modify2(1,fu.f2+1,fu.f1,1);
		else if(fu.f1<fu.f2) tr.modify2(1,fu.f1+1,fu.f2,-1);
		printf("%lld ",as);
	}
}
auoj8 魔王的行径
Problem

2n2^n 个人进行比赛,比赛规则如下:

第一轮,在胜者组的一次比赛后,赢得比赛的人留在胜者组,输的人进入败者组
称一个组的一次比赛为,将组内所有选手按照编号排序,然后第1名和第2名比赛,比赛的编号为1,第3名和第4名比赛,比赛的编号为2...
之后的每一轮比赛情况如下
初始胜者组和败者组都有2^k位选手
败者组进行一次比赛,赢的人留在败者组,输的人被淘汰
胜者组进行一次比赛,赢的人留在胜者组
胜者组比赛中输掉x号比赛的人和败者组中赢得x号比赛的人比赛,赢的人留在(/进入)败者组,输的人被淘汰
最后胜者组和败者组各留下x位选手
如此比赛直到胜者组和败者组各留下一名选手,然后她们之间进行比赛

给定 mm 名选手,你可以决定每一场比赛的胜负,求最多有多少场比赛有给定选手参加

n17n\leq 17

1s.512MB1s.512MB

Sol

比赛可以看成一个线段树的结构,在第 ii 轮比赛后,线段树上一个长度为 2i2^i 的区间内的人只会留下胜者组/败者组各一人,且两个区间间互不影响

dpi,0/1,0/1dp_{i,0/1,0/1} 表示线段树上一个点,最后留下来的胜者组/败者组的人是不是给定选手,这时的最优答案

转移直接枚举这个点的两场比赛的胜负即可

复杂度 O(2n)O(2^n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 530000
int dp[N][2][2],n,k,v[N],a;
void doit(int x,int l,int r)
{
	for(int i=0;i<2;i++)for(int j=0;j<2;j++)dp[x][i][j]=-1e9;
	if(l+1==r){if(v[l]+v[r]==1)dp[x][0][1]=dp[x][1][0]=1;else if(v[l]+v[r]==0)dp[x][0][0]=0;else dp[x][1][1]=1;return;}
	int mid=(l+r)>>1;doit(x<<1,l,mid);doit(x<<1|1,mid+1,r);
	for(int v1=0;v1<2;v1++)
	for(int v2=0;v2<2;v2++)
	for(int v3=0;v3<2;v3++)
	for(int v4=0;v4<2;v4++)
	dp[x][v1|v3][v2|v4]=max(dp[x][v1|v3][v2|v4],dp[x<<1][v1][v2]+dp[x<<1|1][v3][v4]+(v1|v3)+2*(v2|v4));
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=k;i++)scanf("%d",&a),v[a]=1;
	doit(1,1,1<<n);
	printf("%d\n",max(max(dp[1][0][0],dp[1][0][1]+1),max(dp[1][1][0]+1,dp[1][1][1]+1)));
}
auoj10 没有人受伤的世界
Problem

给一个两边各 nn 个点的二分图,每条边有一定概率出现,求存在完美匹配的概率,模 109+710^9+7

n7n\leq 7

6s,256MB6s,256MB

Sol

在一个完美匹配中,左边的 ii 个点一定对应了右边的 ii 个点

考虑对于左边的 ii 个点,记录它们能与右边的哪些 ii 个点的集合匹配

这样的状态数看起来有 2352^{35} 个,实际上大概在 3000030000 左右

dpi,jdp_{i,j} 表示考虑了左边前 ii 个点,这些点能匹配右边的哪些点集,这样的概率

暴力转移即可

复杂度 O(n22n30000)O(n^2*2^n*30000)

Code
#include<cstdio>
#include<map>
using namespace std;
#define mod 1000000007
map<__int128,int> v[11];
__int128 st[8][40100];
int dp[8][40100],n,p[8][8],ct[8];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	scanf("%d",&p[i][j]),p[i][j]=1ll*p[i][j]*570000004%mod;
	st[0][1]=1;ct[0]=1;dp[0][1]=1;
	for(int i=1;i<=n;i++)
	for(int j=0;j<1<<n;j++)
	{
		int s1=1;
		for(int k=1;k<=n;k++)if(j&(1<<k-1))s1=1ll*s1*p[i][k]%mod;else s1=1ll*s1*(mod+1-p[i][k])%mod;
		for(int k=1;k<=ct[i-1];k++)
		{
			__int128 nt=0;
			for(int l=0;l<1<<n;l++)
			if(st[i-1][k]&(((__int128)1)<<l))
			for(int s=1;s<=n;s++)if(!(l&(1<<s-1))&&(j&(1<<s-1)))nt|=((__int128)1)<<(l|(1<<s-1));
			if(nt==0)continue;
			if(!v[i][nt])v[i][nt]=++ct[i],st[i][ct[i]]=nt;
			int tp=v[i][nt];
			dp[i][tp]=(dp[i][tp]+1ll*dp[i-1][k]*s1)%mod;
		}
	}
	printf("%d\n",dp[n][1]);
}
auoj11 废墟
Problem

给定 n,kn,k ,求有多少个 1,...,n1,...,n 的排列满足相邻两个元素的和不超过 kk ,模 998244353998244353

n106,n<k<2nn\leq 10^6,n<k<2n

1s,512MB1s,512MB

Sol

fi,jf_{i,j} 表示 ii 个数的排列,前 jj 个数没有限制,第 j+1j+1 个数不能和最大的数相邻,第 j+2j+2 个数不能和最大的两个数相邻,以此类推的方案数

如果 i=j,j+1i=j,j+1 显然方案数为 i!i!

否则,考虑 ii 所在的位置

如果 ii 在两侧,那么它旁边的数只有 jj 种方案,而删除这两个数后剩下的数与这两个数互不影响,因此方案数为 2jfi2,j2j*f_{i-2,j}

否则,它两侧的数有 j(j1)j(j-1) 种方案,这之后因为两侧的数无论与谁相邻都不会违反限制,方案数为 j(j1)fi2,jj(j-1)*f_{i-2,j}

因此可以直接递推,复杂度 O(n)O(n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define mod 998244353
int n,m,as=1;
int main()
{
	scanf("%d%d",&n,&m);
	int tp=m-n;
	for(int i=1;i<=tp;i++)as=1ll*as*i%mod;
	for(int i=tp+1;i<=n;i++)as=1ll*as*(tp+((i-tp)&1))%mod;
	printf("%d\n",as);
}
auoj12 魔法
Problem

有一棵树,点权构成一个排列,有多次修改,每次交换两个点的点权,你需要输出每次修改后有多少个 ii 满足点权小于等于 ii 的点构成一条链

n,q5×105n,q\leq 5\times 10^5

3s,512MB3s,512MB

Sol

将限制分成形成连通块和每个点度数不超过2

对于第二个限制,只需要求出每个点相邻的点中的第三小点权即可

考虑分成两部分,第一部分是儿子中的第三小点权,第二部分是儿子中的第二小点权和父亲点权的max

对于第一部分,每个点使用set维护儿子点权即可,修改时改父亲的权值

对于第二部分,对每个点再开一个set,将一个点的第二小的儿子点权放到父亲的set里面,修改时先重新计算这个点作为父亲时的min,然后求它父亲的第二小儿子点权,然后更新它父亲的父亲

使用set维护所有这些值的min即可

对于第一个限制,考虑点减边容斥,只需要求出每条边两边点权的min

注意到因为要求是链,每个点只有点权最小的两条出边是有用的,因此暴力改即可

然后用线段树找出一段区间内值为1的位置个数即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<set>
#include<algorithm>
#pragma GCC optimize(3)
using namespace std;
#define N 500500
int head[N],cnt,v1[N],p[N],tid[N],f[N],id2[N],n,a,b,v2[N],s[N][2],q,g2[N],is[N],g3[N],as1,tr1[N];
struct edge{int t,next,id;}ed[N*2];
void adde(int f,int t,int id){ed[++cnt]=(edge){t,head[f],id};head[f]=cnt;ed[++cnt]=(edge){f,head[t],id};head[t]=cnt;}
void dfs(int u,int fa){f[u]=fa;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)id2[ed[i].t]=ed[i].id,dfs(ed[i].t,u);}
set<int> st[N];
multiset<int> st2[N];
struct sth{int a,b;};
sth doit(sth a,sth b){if(a.a<b.a)return a;if(a.a>b.a)return b;return (sth){a.a,a.b+b.b};}
struct lsjtree{
	struct node{int l,r,lz;sth tp;}e[N*4];
	void pushup(int x){e[x].tp=doit(e[x<<1].tp,e[x<<1|1].tp);}
	void pushdown(int x){if(e[x].lz)e[x<<1].tp.a+=e[x].lz,e[x<<1].lz+=e[x].lz,e[x<<1|1].tp.a+=e[x].lz,e[x<<1|1].lz+=e[x].lz,e[x].lz=0;}
	void build(int x,int l,int r){e[x].l=l;e[x].r=r;if(l==r){e[x].tp.a=l;e[x].tp.b=1;return;}int mid=(l+r)>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r);pushup(x);}
	void modify(int x,int l,int r,int v){if(e[x].l==l&&e[x].r==r){e[x].lz+=v;e[x].tp.a+=v;return;}pushdown(x);int mid=(e[x].l+e[x].r)>>1;if(mid>=r)modify(x<<1,l,r,v);else if(mid<l)modify(x<<1|1,l,r,v);else modify(x<<1,l,mid,v),modify(x<<1|1,mid+1,r,v);pushup(x);}
	void query(int x,int l,int r){if(l>r)return;if(e[x].l==l&&e[x].r==r){as1+=(e[x].tp.a==1)*e[x].tp.b;return;}pushdown(x);int mid=(e[x].l+e[x].r)>>1;if(mid>=r)query(x<<1,l,r);else if(mid<l)query(x<<1|1,l,r);else query(x<<1,l,mid),query(x<<1|1,mid+1,r);}
}tr;
void add(int x,int k){if(!x)return;for(int i=x;i<=n;i+=i&-i)tr1[i]+=k;}
int que()
{
	int as=0;
	for(int k=18;k>=0;k--)if((as+(1<<k))<=n&&tr1[as+(1<<k)]==0)as+=1<<k;
	return as;
}
void doit2(int x)
{
	if(!x)return;
	if(v2[x]==max(p[s[x][0]],p[s[x][1]]))return;
	int v1=v2[x],v3=max(p[s[x][0]],p[s[x][1]]);
	v2[x]=max(p[s[x][0]],p[s[x][1]]);
	if(v1>v3)tr.modify(1,v3,v1-1,-1);
	else tr.modify(1,v1,v3-1,1);
}
void just_doit(int x)
{
	set<int>::iterator it=st[x].begin();
	int v3=1e9,v2=1e9+1,st3=1e9+2;
	v3=*it;
	if(v3<1e9)
	{
		it++,v2=*it;
		if(v2<1e9)
		it++,st3=*it;
		else st3=1e9+1;
	}
	if(v1[x]!=st3)add(v1[x],-1),add(v1[x]=st3,1);
	if(v3<=n)doit2(id2[tid[v3]]);
	if(v2<=n)doit2(id2[tid[v2]]);
	doit2(id2[x]);
	int v12=max(p[x],*st2[x].begin());
	if(v12!=g3[x])add(g3[x],-1),add(g3[x]=v12,1);
}
void make_your_dream_come_true(int x)
{
	if(!x)return;
	set<int>::iterator it=st[x].begin();
	int v3=1e9,v2=1e9+1,st3=1e9+2;
	v3=*it;
	if(v3<1e9)
	{
		it++,v2=*it;
		if(v2<1e9)
		it++,st3=*it;
		else st3=1e9+1;
	}
	if(v1[x]!=st3)add(v1[x],-1),add(v1[x]=st3,1);
	if(v3<=n)doit2(id2[tid[v3]]);
	if(v2<=n)doit2(id2[tid[v2]]);
	doit2(id2[x]);
	if(!f[x])return;
	if(g2[x]!=v2)st2[f[x]].erase(st2[f[x]].find(g2[x])),g2[x]=v2,st2[f[x]].insert(g2[x]);
	int v12=max(p[f[x]],*st2[f[x]].begin());
	if(v12!=g3[f[x]])add(g3[f[x]],-1),add(g3[f[x]]=v12,1);
}
int main()
{
	scanf("%d",&n);tr.build(1,1,n);
	for(int i=1;i<=n;i++)scanf("%d",&p[i]),tid[p[i]]=i,st[i].insert(1e9),st2[i].insert(1e9);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),s[i][0]=a,s[i][1]=b,adde(a,b,i);
	dfs(1,0);
	for(int i=2;i<=n;i++)st[f[i]].insert(p[i]);
	for(int i=1;i<n;i++)v2[i]=max(p[s[i][0]],p[s[i][1]]),tr.modify(1,v2[i],n,-1);
	for(int i=1;i<=n;i++)
	{
		set<int>::iterator it=st[i].begin();
		int v3=1e9,v2=1e9+1,st3=1e9+2;
		v3=*it;
		if(v3<1e9)
		{
			it++,v2=*it;
			if(v2<1e9)
			it++,st3=*it;
			else st3=1e9+1;
		}
		v1[i]=st3;add(st3,1);
		if(i==1)continue;
		g2[i]=v2;st2[f[i]].insert(g2[i]);
	}
	for(int i=1;i<=n;i++)add(g3[i]=max(p[i],*st2[i].begin()),1);
	scanf("%d",&q);
	while(q--)
	{
		scanf("%d%d",&a,&b);
		if(a==b){as1=0;tr.query(1,1,que());printf("%d\n",as1);continue;}
		if(f[a]!=f[b])
		{
			if(a>1)st[f[a]].erase(p[a]),st[f[a]].insert(p[b]);
			if(b>1)st[f[b]].erase(p[b]),st[f[b]].insert(p[a]);
		}
		p[a]^=p[b]^=p[a]^=p[b];tid[p[a]]^=tid[p[b]]^=tid[p[a]]^=tid[p[b]];
		just_doit(a);just_doit(b);
		make_your_dream_come_true(f[a]);make_your_dream_come_true(f[b]);
		as1=0;tr.query(1,1,que());printf("%d\n",as1);
	}
}
auoj13 风暴
Problem

给一个 n×mn\times m 的网格图,每条边有 pp 的概率被删掉,求最后 (1,1),(n,m)(1,1),(n,m) 连通的概率,误差不超过 10610^{-6} ,多组数据

n8,m1018,T50,p>0.1n\leq 8,m\leq 10^{18},T\leq 50,p>0.1

3s,256MB3s,256MB

Sol

考虑轮廓线dp,只需要记录当前的轮廓线的连通情况(集合划分)以及哪一个连通块与起点相连,这样的状态数不多

dpi,j,Sdp_{i,j,S} 表示考虑到了 (i,j)(i,j) ,当前轮廓线状态为 SS 的方案数,转移时枚举这个点向上和向左得边是否连通(边界特判),暴力做

askas_k 表示 m=km=k 时的答案,当 mm 很大时发现 asm+1asm\frac {as_{m+1}}{as_m} 接近于一个定值,可以先算100项然后快速幂算后面

复杂度 O(Tn2Bell(n)100)O(Tn^2Bell(n)*100)

Code
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
#define N 3500
int id1[20767676],id2[N][11],T,n,m,st2[N],ct,id3[N],trans[N][11][2][2],las;
double dp[250][9][N],p;
long long fuc;
int justdoit()
{
	int id[21]={0,1,0},ct3=1;
	for(int i=1;i<=n;i++)if(!id[st2[i]])id[st2[i]]=++ct3,st2[i]=ct3;
	else st2[i]=id[st2[i]];
	int fg3=0;
	for(int i=1;i<=n;i++)if(st2[i]==1)fg3=1;
	if(!fg3)for(int i=1;i<=n;i++)st2[i]--;
	int ha1=0;
	for(int j=1;j<=n;j++)ha1=ha1*9+st2[j];
	if(!id1[ha1])
	{
		for(int j=1;j<=n;j++)id2[ct+1][j]=st2[j];
		id1[ha1]=++ct;id3[ct]=ha1;
	}
	return id1[ha1];
}
void fuckthisdp()
{
	memset(dp,0,sizeof(dp));p=1-p;m=100;
	if(n!=las)
	{
		for(int i=1;i<=ct;i++)id1[id3[i]]=0;ct=0;
		for(int i=1;i<=n;i++)st2[i]=i;dp[1][1][justdoit()]=1;
		memset(trans,0,sizeof(trans));
		for(int g=1;g<=ct;g++)
		for(int j=1;j<=n;j++)
		for(int k=0;k<2;k++)
		for(int l=0;l<=(j>1);l++)
		{
			for(int f=1;f<=n;f++)st2[f]=id2[g][f];
			if(!k&&!l)st2[j]=n+2;
			else if(!k)st2[j]=st2[j-1];
			else if(k&&l)
			{
				int v1=st2[j-1],v2=st2[j];
				if(v2<v1)v2^=v1^=v2^=v1;
				for(int f=1;f<=n;f++)if(st2[f]==v2)st2[f]=v1;
			}
			int fg1=0;
			for(int f=1;f<=n;f++)if(st2[f]==1)fg1=1;
			if(fg1)trans[g][j][k][l]=justdoit();
		}
	}
	las=n;
	for(int i=1;i<=n;i++)st2[i]=i;
	dp[1][1][justdoit()]=1;
	for(int i=1;i<=m;i++)
	for(int j=1;j<=n;j++)
	for(int g=1;g<=ct;g++)
	{
		if(i==1&&j==1)continue;
		int v1=j==1?i-1:i,v2=j==1?n:j-1;
		for(int k=0;k<=(i>1);k++)
		for(int l=0;l<=(j>1);l++)
		dp[i][j][trans[g][j][k][l]]+=dp[v1][v2][g]*(k?p:1-p*(i>1))*(l?p:1-p*(j>1));
	}
	double las=0,tp1,v2;
	for(int j=1;j<=m;j++)
	{
		double as=0;
		for(int i=1;i<=ct;i++)if(id2[i][n]==1)as+=dp[j][n][i];
		v2=as/las;las=as;
		if(j==fuc)tp1=as;
	}
	if(fuc>100)tp1=las*pow(v2,fuc-100);
	printf("%.10lf\n",tp1);
}
int main()
{
	scanf("%d",&T);
	while(T--)scanf("%d%lld%lf",&n,&fuc,&p),fuckthisdp();
}
auoj14 未来
Problem

有一棵有根树,一开始每个点有一个权值,随机一个排列,一次考虑排列的每一个元素对应的点,将这个点子树内的所有点权值加上自己的权值(包括自己)

定义排列的权值为考虑结束后所有点权值的和,求出所有排列的权值和,模 998244353998244353

树随机生成

n105n\leq 10^5

1s,512MB1s,512MB

Sol

考虑 iijj 的贡献系数

显然只有当 iijj 祖先时才有贡献,且系数只和距离有关

设路径上的点数为 dd ,将点编号为 1,...,d1,...,d

设初始 v1=1,v2=...=vn=0v_1=1,v_2=...=v_n=0 ,考虑一个排列 pp

fuf_u 为考虑到 uu 时它的权值, gug_u 为它最后的权值,那么有

f1=1f_1=1

fi=j<i,pj<pifjf_i=\sum_{j<i,p_j<p_i}f_j

gi=2fi+j<i,pj>pifj=j<ifjg_i=2f_i+\sum_{j<i,p_j>p_i}f_j=\sum_{j<i}f_j

可以发现,这相当于对于 i<j,pi<pji<j,p_i<p_j 的一对点连一条有向边 (i,j)(i,j)fif_i 相当于从1走到 ii 的方案数, gdg_d 相当于从1出发的路径数

枚举路径的点集大小,有

ansd=n!(i=1dCd1i11i!)ans_d=n!(\sum_{i=1}^d C_{d-1}^{i-1}\frac{1}{i!})

因为随机下深度是根号级别的,所以暴力就行了

复杂度 O(nn)O(n\sqrt n)

Code
#include<cstdio>
using namespace std;
#define N 105000
#define mod 998244353
int n,l[N],r[N],tid[N],dep[N],head[N],cnt,as[N],fr[N],ifr[N],v[N],as1,v2,ct,a,b;
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
struct edge{int t,next;}ed[N*2];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;}
void dfs(int u,int fa){l[u]=++ct;dep[u]=dep[fa]+1;tid[ct]=u;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u);r[u]=ct;}
int main()
{
	scanf("%d",&n);
	fr[0]=ifr[0]=1;
	for(int i=1;i<=5000;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	for(int i=1;i<=1000;i++)
	for(int j=0;j<=i;j++)
	as[i]=(as[i]+1ll*fr[i]*ifr[i-j]%mod*ifr[j]%mod*ifr[j+1])%mod;
	as[0]=2;
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs(1,0);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&v2);
		for(int j=l[i];j<=r[i];j++)as1=(as1+1ll*v2*as[dep[tid[j]]-dep[i]])%mod;
	}
	for(int i=1;i<=n;i++)as1=1ll*as1*i%mod;
	printf("%d\n",as1);
}
auoj15 幸运
Problem

有一棵有根树,给定若干个操作,操作有如下类型:

  1. 在一个点下加入一个叶子节点,编号为上一个点编号+1
  2. 删除 uu 到父亲的连边
  3. 撤销一个2操作
  4. 给定一个点,询问从1开始,每次随机向一个儿子走,走到一个叶子就返回根,走到给定点经过的边数的期望,模 998244353998244353

n,q106n,q\leq 10^6

7s,512MB7s,512MB

Sol

考虑一次从1开始,要么走到给定点,要么走到一个叶子并返回

pip_i 表示从1开始走到 ii 的概率, disidis_i 表示 ii 的深度

设给定点为 kk ,那么答案为 disk+xleaf,xsubtree of kvxdisx1xleaf,xsubtree of kvxdis_k+\frac{\sum_{x\in leaf,x\notin subtree\ of\ k}v_xdis_x}{1-\sum_{x\in leaf,x\notin subtree\ of\ k}v_x}

显然 1pu\frac{1}{p_u} 等于它所有祖先的儿子数的乘积

先离线处理完1操作,然后将还没有加入的点到父亲的边看成已经被删掉了,之后的1操作可以看成3操作

对于一个2操作,相当于删除一个子树,并给父亲的子树的 pp 乘上一个值,父亲也可能成为新的叶子

对于3操作,相当于撤销上面的操作

那么需要维护数据结构,支持区间加一个删除标记,区间删除一个删除标记,区间乘,求区间内没有删除标记的位置的和

线段树即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
using namespace std;
#define N 2050000
#define mod 998244353
int n,m,ct,fa[N],vl[N],l[N],r[N],q[N][3],d[N],dp[N],inv[N],dep[N],head[N],cnt,tid[N],ct1;
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
struct edge{int t,next;}ed[N*2];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;}
void dfs(int u,int fa){l[u]=++ct;tid[ct]=u;dep[u]=(dep[fa]+vl[u])%mod;dp[u]=1;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u);r[u]=ct;}
struct sth{int a,b,c;};
sth doit(sth a,sth b){sth c;if(a.a==b.a)c.a=a.a,c.b=(a.b+b.b)%mod,c.c=(a.c+b.c)%mod;else if(a.a<b.a)c=a;else c=b;return c;}
struct wkrtree{
	struct node{int l,r,l1,l2;sth tp;}e[N*4];
	void pushup(int x){e[x].tp=doit(e[x<<1].tp,e[x<<1|1].tp);}
	void pushdown(int x){if(e[x].l1)e[x<<1].tp.a+=e[x].l1,e[x<<1].l1+=e[x].l1,e[x<<1|1].tp.a+=e[x].l1,e[x<<1|1].l1+=e[x].l1,e[x].l1=0;
	if(e[x].l2!=1)e[x<<1].tp.b=1ll*e[x<<1].tp.b*e[x].l2%mod,e[x<<1].tp.c=1ll*e[x<<1].tp.c*e[x].l2%mod,e[x<<1].l2=1ll*e[x<<1].l2*e[x].l2%mod,
	e[x<<1|1].tp.b=1ll*e[x<<1|1].tp.b*e[x].l2%mod,e[x<<1|1].tp.c=1ll*e[x<<1|1].tp.c*e[x].l2%mod,e[x<<1|1].l2=1ll*e[x<<1|1].l2*e[x].l2%mod,e[x].l2=1;}
	void build(int x,int l,int r){e[x].l2=1;e[x].l=l;e[x].r=r;if(l==r){e[x].tp.b=dp[tid[l]];e[x].tp.c=1ll*dp[tid[l]]*dep[tid[l]]%mod;return;}int mid=(l+r)>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r);pushup(x);}
	void modify1(int x,int l,int r,int v){if(e[x].l==l&&e[x].r==r){e[x].l1+=v;e[x].tp.a+=v;return;}pushdown(x);int mid=(e[x].l+e[x].r)>>1;if(mid>=r)modify1(x<<1,l,r,v);else if(mid<l)modify1(x<<1|1,l,r,v);else modify1(x<<1,l,mid,v),modify1(x<<1|1,mid+1,r,v);pushup(x);}
	void modify2(int x,int l,int r,int v2){if(e[x].l==l&&e[x].r==r){e[x].l2=1ll*e[x].l2*v2%mod;e[x].tp.b=1ll*e[x].tp.b*v2%mod;e[x].tp.c=1ll*e[x].tp.c*v2%mod;return;}pushdown(x);int mid=(e[x].l+e[x].r)>>1;if(mid>=r)modify2(x<<1,l,r,v2);else if(mid<l)modify2(x<<1|1,l,r,v2);else modify2(x<<1,l,mid,v2),modify2(x<<1|1,mid+1,r,v2);pushup(x);}
	sth query(int x,int l,int r){if(l>r)return (sth){1,0,0};if(e[x].l==l&&e[x].r==r)return e[x].tp;pushdown(x);int mid=(e[x].l+e[x].r)>>1;if(mid>=r)return query(x<<1,l,r);else if(mid<l)return query(x<<1|1,l,r);else return doit(query(x<<1,l,mid),query(x<<1|1,mid+1,r));}
}tr;
void add(int u)
{
	int f=fa[u];
	tr.modify1(1,l[f],l[f],1);
	tr.modify1(1,l[u],r[u],-1);
	if(d[f]>0)
	{
		int tp=1ll*inv[d[f]+1]*d[f]%mod;
		tr.modify2(1,l[f],r[f],tp);
	}
	d[f]++;
}
void del(int u)
{
	int f=fa[u];
	tr.modify1(1,l[f],l[f],-1);
	tr.modify1(1,l[u],r[u],1);
	if(d[f]>1)
	{
		int tp=1ll*inv[d[f]-1]*d[f]%mod;
		tr.modify2(1,l[f],r[f],tp);
	}
	d[f]--;
}
int query(int u)
{
	if(u==1)return 0;
	sth fuc=doit(tr.query(1,1,l[u]-1),tr.query(1,r[u]+1,ct1));
	int v1=fuc.b,v2=fuc.c;
	if(fuc.a>=1)v1=v2=0;
	return (1ll*v2*pw(mod+1-v1,mod-2)%mod+dep[u])%mod;
}
int main()
{
	scanf("%d%d",&n,&m);ct1=n;
	for(int i=1;i<=n+m;i++)inv[i]=pw(i,mod-2);
	for(int i=2;i<=n;i++)scanf("%d%d",&fa[i],&vl[i]);
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&q[i][0]);
		if(q[i][0]==1)scanf("%d%d",&q[i][1],&q[i][2]),fa[++ct1]=q[i][1],vl[ct1]=q[i][2];
		else scanf("%d",&q[i][1]);
	}
	for(int i=2;i<=ct1;i++)adde(i,fa[i]);
	dfs(1,0);
	tr.build(1,1,ct1);
	for(int i=2;i<=ct1;i++)tr.modify1(1,l[i],r[i],1);
	for(int i=2;i<=n;i++)add(i);
	int ct2=n;
	for(int i=1;i<=m;i++)
	if(q[i][0]==1)add(++ct2);
	else if(q[i][0]==2)del(q[i][1]);
	else if(q[i][0]==3)add(q[q[i][1]][1]);
	else printf("%d\n",query(q[i][1]));
}
auoj17 小B的棋盘
Problem

棋盘上有 nn 枚棋子,你可以再下 kk 枚棋子,求最多有多少个点可能成为最后所有棋子的对称中心,若有无穷个输出-1,棋子可以重合

n105,k20n\leq 10^5,k\leq 20

1s,512MB1s,512MB

Sol

nkn\leq k 时为-1

将所有棋子按照坐标排序,可以发现如果 kk 枚棋子位置确定了,那么一定是排序后首尾配对

因为加入的不超过20个,因此一定有一对最后配对的棋子,第一个在原序列中前21个,第二个在原序列中后21个,直接做复杂度 O(nk2)O(nk^2)

同时可以发现每个点只可能匹配对应位置的前后20个,因此暴力计算每个位置能配多少对可以做到 O(nklognk)O(nk\log {nk})

Code
#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
#define ll long long
#define N 105000
int n,k,x,y;
ll f[N];
map<ll,int> st,st2;
int main()
{
	scanf("%d%d",&n,&k);
	if(n<=k){printf("-1\n");return 0;}
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&x,&y);
		x+=1000000000;y+=1000000000;
		f[i]=4000000000ull*x+y;
	}
	sort(f+1,f+n+1);
	for(int i=1;i<=n;i++)
	{
		st2[f[i]*2]=1;
		if(i<=k+1)
		for(int j=n-i+1-k;j<=n-i+1+k;j++)
		if(j>i&&j<=n)st[f[i]+f[j]]++;
		if(i>k+1)
		for(int j=n-i+1-k;j<=n-i+1+k;j++)
		if(j>i&&j<=n&&st.count(f[i]+f[j]))st[f[i]+f[j]]++;
	}
	int as=0;
	for(map<ll,int>::iterator it=st.begin();it!=st.end();it++)
	{
		int v1=it->second;
		if(v1<(n-k-1)/2)continue;
		int v2=st2[it->first];
		if((n+k)&1)
		{
			if(v2&&v1>=(n-k-1)/2)as++;
			if(!v2)
			{
				ll f1=it->first/4000000000ull,f2=it->first%4000000000ull;
				if((f1&1)||(f2&1))continue;
				if(v1>=(n-k+1)/2)as++;
			}
		}
		else
			if(v1>=(n-k)/2)as++;
	}
	printf("%d\n",as);
}
auoj18 小B的夏令营

见集训队作业题解 CF708E

auoj19 小B的图
Problem

mm 条边,每条边的权值为 v+xv+x 或者 vxv-x

多组询问,每次给定一个 xx ,求此时的最小生成树边权和

n,m,q2×105n,m,q\leq 2\times 10^5

1s,512MB1s,512MB

Sol

假设当前求出了一个 xx 时的生成树,当 xx 减小时,通过生成树的求法可以看出,生成树中 v+xv+x 类型的边也一定会被选,不在生成树中的 vxv-x 类型的边一定不会被选

xx 增大时,生成树中 vxv-x 类型的边也一定会被选,不在生成树中的 v+xv+x 类型的边一定不会被选

因此两边还不确定是否选的边的集合不交,考虑cdq分治,记录之前的分治过程确定的边的边权和以及缩点后剩下的边,向两侧处理时把必选的边缩起来即可

对两类边分别排序,求最小生成树时归并,复杂度 O((n+m)logqα(n))O((n+m)\log q\alpha(n))

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 105000
#pragma GCC optimize(3)
#define ll long long
struct edge{int a,b,v,c;friend bool operator <(edge a,edge b){return a.c<b.c;}};
vector<edge> e[21][2][2];
int fa[N],ct[21][2],n,a,b,q,c,d,f,is[N*2][2],id[N];
ll as[N];
struct que{int v,id;friend bool operator <(que a,que b){return a.v<b.v;}}qu[N];
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
void solve(int l,int r,int s,int t,ll v1,int v2)
{
	if(l>r)return;
	int mid=(l+r)>>1;
	for(int i=0;i<2;i++)e[s+1][0][i].clear(),e[s+1][1][i].clear(),vector<edge>().swap(e[s+1][0][i]),vector<edge>().swap(e[s+1][1][i]);
	ll as1=v1+1ll*v2*qu[mid].v;
	int m1=e[s][t][0].size(),m2=e[s][t][1].size();
	for(int i=1;i<=ct[s][t];i++)fa[i]=i;
	for(int i=1;i<=m1;i++)is[i-1][0]=0;
	for(int i=1;i<=m2;i++)is[i-1][1]=0;
	int l1=0,l2=0;
	for(int i=1;i<=m1+m2;i++)
	{
		int t1,t2;
		if(l1==m1)t1=1,t2=l2,l2++;
		else if(l2==m2)t1=0,t2=l1,l1++;
		else if(e[s][t][0][l1].c+qu[mid].v<=e[s][t][1][l2].c-qu[mid].v)t1=0,t2=l1,l1++;
		else t1=1,t2=l2,l2++;
		edge fu=e[s][t][t1][t2];
		if(finds(fu.a)!=finds(fu.b))
		as1+=fu.c+fu.v*qu[mid].v,fa[finds(fu.a)]=finds(fu.b),is[t2][t1]=1;
	}
	as[qu[mid].id]=as1;
	ll v11=v1,v21=v2;
	for(int i=1;i<=ct[s][t];i++)fa[i]=i;
	for(int i=0;i<m1;i++)if(is[i][0])fa[finds(e[s][t][0][i].a)]=finds(e[s][t][0][i].b),v21++,v11+=e[s][t][0][i].c;
	ct[s+1][0]=0;
	for(int i=1;i<=ct[s][t];i++)if(finds(i)==i)id[i]=++ct[s+1][0];
	for(int i=0;i<m1;i++)
	{
		int v1=finds(e[s][t][0][i].a),v2=finds(e[s][t][0][i].b);
		if(v1==v2)continue;
		e[s+1][0][0].push_back((edge){id[v1],id[v2],e[s][t][0][i].v,e[s][t][0][i].c});
	}
	for(int i=0;i<m2;i++)
	{
		if(!is[i][1])continue;
		int v1=finds(e[s][t][1][i].a),v2=finds(e[s][t][1][i].b);
		if(v1==v2)continue;
		e[s+1][0][1].push_back((edge){id[v1],id[v2],e[s][t][1][i].v,e[s][t][1][i].c});
	}
	ll v12=v1,v22=v2;
	for(int i=1;i<=ct[s][t];i++)fa[i]=i;
	for(int i=0;i<m2;i++)if(is[i][1])fa[finds(e[s][t][1][i].a)]=finds(e[s][t][1][i].b),v22--,v12+=e[s][t][1][i].c;
	ct[s+1][1]=0;
	for(int i=1;i<=ct[s][t];i++)if(finds(i)==i)id[i]=++ct[s+1][1];
	for(int i=0;i<m1;i++)
	{
		if(!is[i][0])continue;
		int v1=finds(e[s][t][0][i].a),v2=finds(e[s][t][0][i].b);
		if(v1==v2)continue;
		e[s+1][1][0].push_back((edge){id[v1],id[v2],e[s][t][0][i].v,e[s][t][0][i].c});
	}
	for(int i=0;i<m2;i++)
	{
		int v1=finds(e[s][t][1][i].a),v2=finds(e[s][t][1][i].b);
		if(v1==v2)continue;
		e[s+1][1][1].push_back((edge){id[v1],id[v2],e[s][t][1][i].v,e[s][t][1][i].c});
	}
	solve(l,mid-1,s+1,0,v11,v21);
	solve(mid+1,r,s+1,1,v12,v22);
	for(int i=0;i<2;i++)e[s][t][i].clear(),vector<edge>().swap(e[s][t][i]);
}
int main()
{
	scanf("%d%d%d%d",&n,&a,&b,&q);ct[0][0]=n;
	for(int i=1;i<=a;i++)scanf("%d%d%d",&c,&d,&f),e[0][0][0].push_back((edge){c,d,1,f});
	for(int i=1;i<=b;i++)scanf("%d%d%d",&c,&d,&f),e[0][0][1].push_back((edge){c,d,-1,f});
	sort(e[0][0][0].begin(),e[0][0][0].end());
	sort(e[0][0][1].begin(),e[0][0][1].end());
	for(int i=1;i<=q;i++)scanf("%d",&qu[i].v),qu[i].id=i;
	sort(qu+1,qu+q+1);
	solve(1,q,0,0,0,0);
	for(int i=1;i<=q;i++)printf("%lld\n",as[i]);
}
auoj20 Permutation
auoj21 LCM Game
auoj22 Easy Data Structure
auoj23 树与路径

见我写的题解

auoj24 树据结构
Problem

PDF题面

1s(2s),1024MB1s(2s),1024MB

Sol

对于1操作,使用树剖维护链加,为了查询历史版本,使用主席树维护

对于2操作,将一次复制产生的树看成一个点,使用LCT维护

对于3操作,在2操作时额外记录当前复制的根到大树根的距离,算一个点到根的距离时只需要主席树上求求这个点到它所在的模板树的根的距离,然后剩下的就是根的距离

每次操作在先倍增求出LCT上的LCA,然后再求小树的LCA,然后就可以求出它们在大树的LCA,需要特判它们在一个模板树的情况

对于4操作,考虑记录每个模板树的子树内边权和

2操作时相当于链加,可以LCT上解决

答案分为两部分:这棵模板树上的边权,它的儿子的边权

第一部分可以在主席树上查,对于第二部分,考虑每个点维护一个Splay,将所有虚儿子按照它们与这棵模板树连接的位置的dfs序排序,查询相当于求区间和,直接维护即可

复杂度 O(nlogn)O(n\log n) 细节极多

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 200500
#define M 9260817
#define ll long long
//rd
ll rd(){ll as=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')as=as*10+c-'0',c=getchar();return as;}
//tree
int n,m,op,a,b,c,head[N],cnt,vl1[N],f[N][18],dep[N],sz[N],sn[N],tp[N],id[N],tid[N],rb[N],rt[N],ti[N],ct,nt,bel[N][2],vl2[N],ct1=1;
ll las,val[N],d,e,g,vl3[N];
struct edge{int t,next,v;}ed[N*2];
void adde(int f,int t,int v){ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;ed[++cnt]=(edge){f,head[t],v};head[t]=cnt;}
void dfs0(int u,int fa)
{
	f[u][0]=fa;dep[u]=dep[fa]+1;sz[u]=1;for(int i=1;i<=17;i++)f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs0(ed[i].t,u),sz[u]+=sz[ed[i].t],vl1[ed[i].t]=ed[i].v,sn[u]=sz[sn[u]]>=sz[ed[i].t]?sn[u]:ed[i].t;
}
int LCA(int x,int y){if(dep[x]<dep[y])x^=y^=x^=y;for(int i=17;i>=0;i--)if(dep[x]-dep[y]>=(1<<i))x=f[x][i];if(x==y)return x;for(int i=17;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];return f[x][0];}
int getkth(int x,int k){for(int i=17;i>=0;i--)if(k>>i&1)x=f[x][i];return x;}
//pretree
struct pretree{
	int rt,lz[M],ch[M][2],ct;
	ll su[M];
	int build(int l,int r){int st=++ct;if(l==r){su[st]=vl1[tid[l]];return st;}int mid=(l+r)>>1;ch[st][0]=build(l,mid);ch[st][1]=build(mid+1,r);su[st]=su[ch[st][0]]+su[ch[st][1]];return st;}
	void init(){rt=build(1,n);}
	int modify(int x,int l,int r,int l1,int r1,int v){int st=++ct;lz[st]=lz[x];su[st]=su[x];ch[st][0]=ch[x][0];ch[st][1]=ch[x][1];if(l==l1&&r==r1){lz[st]+=v;return st;}su[st]+=1ll*(r1-l1+1)*v;int mid=(l+r)>>1;if(mid>=r1)ch[st][0]=modify(ch[x][0],l,mid,l1,r1,v);else if(mid<l1)ch[st][1]=modify(ch[x][1],mid+1,r,l1,r1,v);else ch[st][0]=modify(ch[x][0],l,mid,l1,mid,v),ch[st][1]=modify(ch[x][1],mid+1,r,mid+1,r1,v);return st;}
	ll query(int x,int l,int r,int l1,int r1){if(!x||l1>r1)return 0;if(l==l1&&r==r1)return su[x]+1ll*(r-l+1)*lz[x];ll s1=1ll*(r1-l1+1)*lz[x];int mid=(l+r)>>1;if(mid>=r1)s1+=query(ch[x][0],l,mid,l1,r1);else if(mid<l1)s1+=query(ch[x][1],mid+1,r,l1,r1);else s1+=query(ch[x][0],l,mid,l1,mid)+query(ch[x][1],mid+1,r,mid+1,r1);return s1;}
	void modify2(int l,int r,int v){rt=modify(rt,1,n,l,r,v);}
}tr1;
//HLD
void dfs2(int u,int fa,int v)
{
	tp[u]=v;id[u]=++ct;tid[ct]=u;
	if(sn[u])dfs2(sn[u],u,v);
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&ed[i].t!=sn[u])dfs2(ed[i].t,u,ed[i].t);
	rb[u]=ct;
}
void modify1(int x,int y,int v){while(tp[x]!=tp[y])tr1.modify2(id[tp[x]],id[x],v),x=f[tp[x]][0];if(id[x]<id[y])x^=y^=x^=y;tr1.modify2(id[y],id[x],v);}
ll query1(int t,int x,int y){ll as=0;while(tp[x]!=tp[y])as+=tr1.query(rt[t],1,n,id[tp[x]],id[x]),x=f[tp[x]][0];if(id[x]<id[y])x^=y^=x^=y;as+=tr1.query(rt[t],1,n,id[y],id[x]);return as;}
ll query(int t,int x,int y)
{
	int l=LCA(x,y);ll as=0;
	if(x!=l)as+=query1(t,x,getkth(x,dep[x]-dep[l]-1));
	if(y!=l)as+=query1(t,y,getkth(y,dep[y]-dep[l]-1));
	return as;
}
//splay
int ch[M][2],fa[M],ct2;
ll vl[M],s1[M],su[M];
struct Splay
{
	int rt;
	void init(){rt=++ct;ch[ct][1]=ct+1;fa[ct+1]=ct;ct++;vl[ct]=1e18;}
	void pushup(int x){su[x]=su[ch[x][0]]+su[ch[x][1]]+s1[x];}
	void rotate(int x){int f=fa[x],g=fa[f],tp=ch[f][1]==x;ch[g][ch[g][1]==f]=x;fa[x]=g;ch[f][tp]=ch[x][!tp];fa[ch[x][!tp]]=f;ch[x][!tp]=f;fa[f]=x;pushup(f);pushup(x);}
	void splay(int x,int y=0){while(fa[x]!=y){int f=fa[x],g=fa[f];if(g!=y)rotate((ch[g][1]==f)^(ch[f][1]==x)?x:f);rotate(x);}if(!y)rt=x;}
	int fnd(int x,ll v){if(vl[x]==v)return x;return vl[x]<v?fnd(ch[x][1],v):fnd(ch[x][0],v);}
	int getpre(int x,ll v){if(!x)return 0;if(vl[x]>v)return getpre(ch[x][0],v);int st=getpre(ch[x][1],v);return st?st:x;}
	void insert(ll v1,ll v2)
	{
		int tp=getpre(rt,v1);
		splay(tp);
		int st=ch[tp][1];
		while(ch[st][0])st=ch[st][0];
		splay(st,tp);
		ch[st][0]=++ct;s1[ct]=su[ct]=v2;fa[ct]=st;vl[ct]=v1;
		pushup(st);pushup(tp);
	}
	void del(ll v1)
	{
		int st=fnd(rt,v1);
		splay(st,0);
		int v3=ch[st][0],v2=ch[st][1];
		while(ch[v3][1])v3=ch[v3][1];while(ch[v2][0])v2=ch[v2][0];
		splay(v3);splay(v2,v3);
		fa[st]=ch[v2][0]=0;pushup(v2);pushup(v3);
	}
	ll query(ll l,ll r)
	{
		int s1=getpre(rt,l-1),s2=getpre(rt,r);
		splay(s2);
		s2=ch[s2][1];
		while(ch[s2][0])s2=ch[s2][0];
		splay(s1);splay(s2,s1);
		return su[ch[s2][0]];
	}
}tr[N];
//LCT
struct LCT{
	int fa[N],ch[N][2];
	ll lz[N];
	bool nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
	void pushdown(int x){if(!lz[x])return;lz[ch[x][0]]+=lz[x];lz[ch[x][1]]+=lz[x];val[x]+=lz[x];lz[x]=0;}
	void rotate(int x){int f=fa[x],g=fa[f],tp=ch[f][1]==x;pushdown(f);pushdown(x);if(nroot(f))ch[g][ch[g][1]==f]=x;fa[x]=g;ch[f][tp]=ch[x][!tp];fa[ch[x][!tp]]=f;ch[x][!tp]=f;fa[f]=x;}
	void splay(int x){while(nroot(x)){int f=fa[x],g=fa[f];if(nroot(f))rotate((ch[g][1]==f)^(ch[f][1]==x)?x:f);rotate(x);}}
	void access(int x){int tp=0;while(x){splay(x),pushdown(x);int st=ch[x][1];while(st&&ch[st][0])st=ch[st][0];if(st)splay(st);splay(x);ch[x][1]=tp;if(st>1){int v1=bel[st][0],v2=id[bel[st][1]];ll f1=v2*1000000ll+st;tr[v1].insert(f1,val[st]);}if(tp>1){int v1=bel[tp][0],v2=id[bel[tp][1]];ll f1=v2*1000000ll+tp;tr[v1].del(f1);}while(ch[x][0])x=ch[x][0];splay(x);tp=x;x=fa[x];}}
}lct;
//LCA2
struct anotherLCA{
	int dep[N],fa[N][19];
	void adde(int f,int t){fa[f][0]=t;dep[f]=dep[t]+1;for(int i=1;i<=18;i++)fa[f][i]=fa[fa[f][i-1]][i-1];}
	int LCA(int x,int y){if(dep[x]<dep[y])x^=y^=x^=y;for(int i=18;i>=0;i--)if(dep[x]-dep[y]>=(1<<i))x=fa[x][i];if(x==y)return x;for(int i=18;i>=0;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];return fa[x][0];}
}lca2;
ll biglca(ll x,ll y)
{
	int bel1=(x-1)/n+1,bel2=x-1ll*(bel1-1)*n,bel11=(y-1)/n+1,bel21=y-1ll*(bel11-1)*n;
	if(bel1==bel11)return 1ll*n*(bel1-1)+LCA(bel2,bel21);
	int tp=lca2.LCA(bel1,bel11);
	lct.access(bel1);lct.splay(bel1);
	int as1=0,as2=0;
	int st=bel1;
	while(st)
	{
		if(lca2.dep[st]>lca2.dep[tp])as1=bel[st][1],st=lct.ch[st][0];
		else st=lct.ch[st][1];
	}
	if(!as1)as1=bel2;
	lct.access(bel11);lct.splay(bel11);
	st=bel11;
	while(st)
	{
		if(lca2.dep[st]>lca2.dep[tp])as2=bel[st][1],st=lct.ch[st][0];
		else st=lct.ch[st][1];
	}
	if(!as2)as2=bel21;
	return 1ll*n*(tp-1)+LCA(as1,as2);
}
//init
void init(){for(int i=1;i<=n+m;i++)tr[i].init();}
//op1
void op1(int x,int y,int v)
{
	nt++;
	int l=LCA(x,y);
	if(x!=l)modify1(x,getkth(x,dep[x]-dep[l]-1),v);
	if(y!=l)modify1(y,getkth(y,dep[y]-dep[l]-1),v);
	rt[nt]=tr1.rt;
}
//op2
void op2(int x,int y,int v)
{
	++ct1;
	ti[ct1]=nt;
	vl2[ct1]=v;
	val[ct1]=v+tr1.su[tr1.rt];
	bel[ct1][0]=x;bel[ct1][1]=y;
	lct.access(x);
	lct.splay(x);
	lct.lz[x]+=val[ct1];
	ll t1=1000000ll*id[bel[ct1][1]]+ct1;
	tr[x].insert(t1,val[ct1]);
	lct.fa[ct1]=x;
	lca2.adde(ct1,x);
	vl3[ct1]=vl3[x]+query(ti[x],1,y)+v;
}
//op3
ll que1(ll x)
{
	int bel1=(x-1)/n+1,bel2=x-1ll*(bel1-1)*n;
	return vl3[bel1]+query(ti[bel1],1,bel2);
}
ll op3(ll x,ll y)
{
	ll l=biglca(x,y);
	return que1(x)+que1(y)-2*que1(l);
}
//op4
ll op4(ll x)
{
	int bel1=(x-1)/n+1,bel2=x-1ll*(bel1-1)*n;
	lct.access(bel1);
	return tr[bel1].query(1000000ll*id[bel2],1000000ll*rb[bel2]+999999)+tr1.query(rt[ti[bel1]],1,n,id[bel2]+1,rb[bel2]);
}
//main
int main()
{
	scanf("%d%d%d",&n,&m,&op);
	for(int i=1;i<n;i++)a=rd(),b=rd(),c=rd(),adde(a,b,c);
	dfs0(1,0);dfs2(1,0,1);
	init();
	tr1.init();
	rt[0]=tr1.rt;
	while(m--)
	{
		a=rd();
		if(a==1)
		{
			d=rd();e=rd();g=rd();d^=op*las;e^=op*las;g^=op*las;
			op1(d,e,g);
		}
		else if(a==2)
		{
			d=rd();e=rd();d^=op*las;e^=op*las;g^=op*las;
			op2((d-1)/n+1,d-1ll*((d-1)/n)*n,e);
		}
		else if(a==3)
		{
			d=rd();e=rd();d^=op*las;e^=op*las;
			printf("%lld\n",las=op3(d,e));
		}
		else
		{
			d=rd();d^=op*las;
			printf("%lld\n",las=op4(d));
		}
	}
}
auoj25 树上的数

见我写的题解

auoj29 endemic
Probiem

有一个序列,有多种操作,每种操作可以将一个长度为 lil_i 的区间整体加 x(x=1x=1)x(x=1或x=-1) ,费用为 cc

求一种操作方式使得序列不降且费用最小,输出最小费用或无解

n,m200n,m\leq 200

1s,512MB1s,512MB

Sol

考虑差分,相当于有若干个数,每次操作可以将一个数减1,另外一个数加1,求最小的费用使得所有数非负(边界的值看成inf)

建费用流, 起点向每一个负数连这个数绝对值流量的边,每个正数向汇点连这个数大小的边,对于每一个操作,枚举执行的区间,根据 xx ,从一个端点向另外一个端点连费用为 cc ,没有流量限制的边,保证满流情况下的最小费用流即为答案

Code
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 207
#define M 80101
int n,m,v[N],dis[N],head[N],cnt=1,is[N],cur[N],su,a,b;
long long as1;
char op[4];
struct edge{int t,next,v,c;}ed[M];
void adde(int f,int t,int v,int c){ed[++cnt]=(edge){t,head[f],v,c};head[f]=cnt;ed[++cnt]=(edge){f,head[t],0,-c};head[t]=cnt;}
bool spfa(int s,int t)
{
	memset(dis,0x3f,sizeof(dis));
	memcpy(cur,head,sizeof(cur));
	queue<int> st;
	dis[s]=0;st.push(s);is[s]=1;
	while(!st.empty())
	{
		int x=st.front();st.pop();is[x]=0;
		for(int i=head[x];i;i=ed[i].next)
		if(ed[i].v&&dis[ed[i].t]>dis[x]+ed[i].c)
		{
			dis[ed[i].t]=dis[x]+ed[i].c;
			if(!is[ed[i].t])st.push(ed[i].t),is[ed[i].t]=1;
		}
	}
	return dis[t]<=1e9;
}
int dfs(int u,int t,int f)
{
	if(u==t||!f)return f;
	is[u]=1;
	int as=0,tp;
	for(int& i=cur[u];i;i=ed[i].next)
	if(!is[ed[i].t]&&dis[ed[i].t]==dis[u]+ed[i].c&&ed[i].v&&(tp=dfs(ed[i].t,t,min(f,ed[i].v))))
	{
		as1+=1ll*tp*ed[i].c;ed[i].v-=tp;ed[i^1].v+=tp;f-=tp;as+=tp;
		if(!f){is[u]=0;return as;}
	}
	is[u]=0;
	return as;
}
void doit(int a,int b,int v,int c){if(a<=0||a>=n||b<0||b>n)return;if(b<=0)b=n+2;if(b>=n)b=n+2;adde(a,b,v,c);}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<n;i++)if(v[i]>v[i+1])adde(n+1,i,v[i]-v[i+1],0),su+=v[i]-v[i+1];
	else adde(i,n+2,v[i+1]-v[i],0);
	for(int i=1;i<=m;i++)
	{
		scanf("%s%d%d",op,&a,&b);
		if(op[0]=='-')for(int j=0;j<n;j++)doit(j+a,j,1e9,b);
		else for(int j=0;j<n;j++)doit(j,j+a,1e9,b);
	}
	while(spfa(n+1,n+2))
	su-=dfs(n+1,n+2,1e9);
	printf("%lld\n",su?-1:as1);
}
auoj30 epidemic
Problem

给一个序列,有如下操作

  1. 将一个区间加上一个数
  2. 将一个区间除以一个数,向下取整
  3. 询问区间max
  4. 将一个区间还原回初始状况

n,q105n,q\leq 10^5

2s,512MB2s,512MB

Sol

考虑分块(其实也可以线段树)

相当于支持整体加,整体除以及快速下放标记

注意到进行若干次操作后的标记一定形如 x=x+ab+cx=\lfloor\frac{x+a}{b}\rfloor+c ,其中 a<ba<b

如果 b>109b>10^9 ,无论如何前面那个不会超过1,那么只需要关系 x+ax+abb 的大小关系,即 bab-a 的值

然后就可以直接维护标记了

复杂度 O(qn)O(q\sqrt n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 105050
#define K 350
int n,m,a,b,c,d,bel[N],v[N],s[N],mx1[K],mx2[K],is[K],g[K][3],st=350,l[N],r[N];//v_i=g_0+(v_i+g_1)/g_2
void pushdown(int x)
{
	if(is[x])for(int i=l[x];i<=r[x];i++)s[i]=v[i],is[x]=0;
	for(int i=l[x];i<=r[x];i++)s[i]=g[x][0]+(s[i]+g[x][1])/g[x][2];
	g[x][0]=g[x][1]=0;g[x][2]=1;
}
void pushup(int x)
{
	mx2[x]=-1e9;
	for(int i=l[x];i<=r[x];i++)if(mx2[x]<s[i])mx2[x]=s[i];
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]),s[i]=v[i];
	for(int i=1;i<=n;i++)bel[i]=(i-1)/st+1,r[bel[i]]=i;
	for(int i=n;i>=1;i--)l[bel[i]]=i;
	int ct=(n-1)/st+1;
	for(int i=1;i<=ct;i++)pushup(i),mx1[i]=mx2[i],g[i][2]=1;
	while(m--)
	{
		scanf("%d%d%d%d",&a,&b,&c,&d);b++,c++;
		if(a==0)
		{
			if(bel[b]==bel[c])
			{
				pushdown(bel[b]);
				for(int i=b;i<=c;i++)s[i]+=d;
				pushup(bel[b]);
			}
			else
			{
				pushdown(bel[b]);pushdown(bel[c]);
				for(int i=b;i<=r[bel[b]];i++)s[i]+=d;
				for(int i=l[bel[c]];i<=c;i++)s[i]+=d;
				for(int i=bel[b]+1;i<bel[c];i++)mx2[i]+=d,g[i][0]+=d;
				pushup(bel[b]);pushup(bel[c]);
			}
		}
		else if(a==1)
		{
			if(bel[b]==bel[c])
			{
				pushdown(bel[b]);
				for(int i=b;i<=c;i++)s[i]/=d;
				pushup(bel[b]);
			}
			else
			{
				pushdown(bel[b]);pushdown(bel[c]);
				for(int i=b;i<=r[bel[b]];i++)s[i]/=d;
				for(int i=l[bel[c]];i<=c;i++)s[i]/=d;
				pushup(bel[b]);pushup(bel[c]);
				for(int i=bel[b]+1;i<bel[c];i++)
				{
					mx2[i]/=d;
					long long d1=g[i][0]/d,d2=g[i][1]+1ll*(g[i][0]%d)*g[i][2],d3=1ll*g[i][2]*d;
					if(d3>1.5e9)
					d2=1e9+d2-d3,d3=1e9;
					if(d2<0)d2=0;
					g[i][0]=d1;g[i][1]=d2;g[i][2]=d3;
				}
			}
		}
		else if(a==2)
		{
			int as=-1e9;
			if(bel[b]==bel[c])
			{
				pushdown(bel[b]);
				for(int i=b;i<=c;i++)as=max(as,s[i]);
			}
			else
			{
				pushdown(bel[b]);pushdown(bel[c]);
				for(int i=b;i<=r[bel[b]];i++)as=max(as,s[i]);
				for(int i=l[bel[c]];i<=c;i++)as=max(as,s[i]);
				for(int i=bel[b]+1;i<bel[c];i++)as=max(as,mx2[i]);
			}
			printf("%d\n",as);
		}
		else if(a==3)
		{
			if(bel[b]==bel[c])
			{
				pushdown(bel[b]);
				for(int i=b;i<=c;i++)s[i]=v[i];
				pushup(bel[b]);
			}
			else
			{
				pushdown(bel[b]);pushdown(bel[c]);
				for(int i=b;i<=r[bel[b]];i++)s[i]=v[i];
				for(int i=l[bel[c]];i<=c;i++)s[i]=v[i];
				for(int i=bel[b]+1;i<bel[c];i++)is[i]=1,mx2[i]=mx1[i],g[i][0]=g[i][1]=0,g[i][2]=1;
				pushup(bel[b]);pushup(bel[c]);
			}
		}
	}
}
auoj31 pandemic
Problem

有一棵树,你需要给每条边定向,使得对于所有路径,路径上方向与经过方向相反的边的数量的最大值尽量小,输出使得最大值最小的方案数,模 109+710^9+7

n1000n\leq 1000

1s,512MB1s,512MB

Sol

设直径长度为 ll ,答案下界显然是 s=l+12s=\lfloor\frac{l+1}2\rfloor

将直径一边从上到下另外一边从下到上就可以达到

dpi,j,kdp_{i,j,k} 表示 ii 的子树内,到根的路径最多有 jj 条向下的边,最多有 kk 条向上的边且子树内合法的方案数

合并两个子树时有 dpx,max(a,c),max(b,d)+=dpy,a,cdpz,b,d[a+ds][b+cs]dp_{x,max(a,c),max(b,d)}+=dp_{y,a,c}*dp_{z,b,d}*[a+d\leq s]*[b+c\leq s]

枚举两个max分别来自哪一侧,那么对应的另外一个合法的一定是一段前缀,对两边分别做二维前缀和和横纵的一维前缀和即可做到 O(n2)O(n^2) 转移

总复杂度 O(n3)O(n^3) ,常数很小可以通过

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 1005
#define mod 1000000007
int n,head[N],cnt,dep[N],as,a,b,sz[N],vl,as2,id[N],ct,is[N];
struct edge{int t,next;}ed[N*2];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;}
void dfs(int u,int fa){dep[u]=dep[fa]+1;int mx=0;sz[u]=1;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u),mx=max(mx,sz[ed[i].t]),sz[u]+=sz[ed[i].t];mx=max(mx,n-sz[u]);if(mx<vl)as2=u,vl=mx;}
int getid(){for(int i=1;i<=ct;i++)if(!is[i]){is[i]=1;return i;}is[++ct]=1;return ct;}
struct brute3{
	int dp[304][501][501],mx[N],s1,s2,v1[501][501],v2[501][501],s3,v3[501][501];
	int k1[501][501],k2[501][501],k3[501][501],k4[501][501],k5[501][501],k6[501][501];
	void merge(int u)
	{
		s3=max(s1,s2);
		if(s3>as)s3=as;
		for(int i=0;i<=s1;i++)
		for(int j=0;j<=s1;j++)
		k1[i][j]=k2[i][j]=k3[i][j]=v1[i][j];
		for(int i=0;i<=s2;i++)
		for(int j=0;j<=s2;j++)
		k4[i][j]=k5[i][j]=k6[i][j]=v2[i][j];
		for(int i=0;i<=s1;i++)
		for(int j=0;j<=s1;j++)
		k1[i][j]=(4ll*mod+k1[i][j]+(i?k1[i-1][j]:0)+(j?k1[i][j-1]:0)-(i&&j?k1[i-1][j-1]:0))%mod;
		for(int i=0;i<=s1;i++)
		for(int j=1;j<=s1;j++)
		k2[i][j]=(k2[i][j]+k2[i][j-1])%mod;
		for(int i=1;i<=s1;i++)
		for(int j=0;j<=s1;j++)
		k3[i][j]=(k3[i][j]+k3[i-1][j])%mod;
		for(int i=0;i<=s2;i++)
		for(int j=0;j<=s2;j++)
		k4[i][j]=(4ll*mod+k4[i][j]+(i?k4[i-1][j]:0)+(j?k4[i][j-1]:0)-(i&&j?k4[i-1][j-1]:0))%mod;
		for(int i=0;i<=s2;i++)
		for(int j=1;j<=s2;j++)
		k5[i][j]=(k5[i][j]+k5[i][j-1])%mod;
		for(int i=1;i<=s2;i++)
		for(int j=0;j<=s2;j++)
		k6[i][j]=(k6[i][j]+k6[i-1][j])%mod;
		for(int i=0;i<=s3;i++)
		for(int j=0;j<=s3;j++)
		{
			int d1=min(i-1,as-j),d2=min(j-1,as-i);
			if(d1>s2)d1=s2;if(d2>s2)d2=s2;
			if(d1>=0&&d2>=0&&i<=s1&&j<=s1)v3[i][j]=(v3[i][j]+1ll*v1[i][j]*k4[d1][d2])%mod;
			d1=min(i,as-j),d2=min(j,as-i);
			if(d1>s1)d1=s1;if(d2>s1)d2=s1;
			if(i<=s2&&j<=s2)v3[i][j]=(v3[i][j]+1ll*v2[i][j]*k1[d1][d2])%mod;
			if(i+j<=as)
			{
				int d3=i-1,d4=j;
				if(d3>s2)d3=s2;if(d4>s1)d4=s1;
				if(d3>=0&&d4>=0&&i<=s1&&j<=s2)v3[i][j]=(v3[i][j]+1ll*k2[i][d4]*k6[d3][j])%mod;
				d3=i,d4=j-1;
				if(d3>s1)d3=s1;if(d4>s2)d4=s2;
				if(d3>=0&&d4>=0&&i<=s2&&j<=s1)v3[i][j]=(v3[i][j]+1ll*k5[i][d4]*k3[d3][j])%mod;
			}
		}
		mx[u]=s3;
		for(int i=0;i<=s3;i++)
		for(int j=0;j<=s3;j++)
		dp[id[u]][i][j]=v3[i][j],v3[i][j]=0;
	}
	void dfs(int u,int fa)
	{
		for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)
		{
			int fg=0;
			dfs(ed[i].t,u);
			s1=mx[u];
			if(id[u])
			for(int j=0;j<=s1;j++)
			for(int k=0;k<=s1;k++)
			v1[j][k]=dp[id[u]][j][k];
			else v1[0][0]=1,id[u]=id[ed[i].t],fg=1;
			s2=mx[ed[i].t]+1;
			for(int j=0;j<=s2;j++)
			for(int k=0;k<=s2;k++)
			v2[j][k]=0;
			for(int j=0;j<s2;j++)
			for(int k=0;k<s2;k++)
			v2[j+1][k]=(v2[j+1][k]+dp[id[ed[i].t]][j][k])%mod,
			v2[j][k+1]=(v2[j][k+1]+dp[id[ed[i].t]][j][k])%mod;
			merge(u);
			if(!fg)is[id[ed[i].t]]=0;
		}
		if(!id[u])id[u]=getid(),dp[id[u]][0][0]=1;
	}
}br2;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	vl=n+1;dfs(1,0);
	for(int i=1;i<=n;i++)if(dep[i]>dep[as])as=i;
	dfs(as,0);
	as=0;
	for(int i=1;i<=n;i++)if(dep[i]>as)as=dep[i];
	as=as/2;
	br2.dfs(as2,0);
	int as3=0;
	for(int i=0;i<=br2.mx[as2];i++)
	for(int j=0;j<=br2.mx[as2];j++)
	as3=(as3+br2.dp[id[as2]][i][j])%mod;
	printf("%d\n",as3);
}
auoj32 高精度
Problem

有一个字符串和一个数,初始数为0,依次考虑字符串的每一位:

如果这一位是数字,那么将这个数乘10并加上这个数字

否则,将这个数除以10,下取整

求每一步操作之后的数的和

S5×105|S|\leq 5\times 10^5

3s,1024MB3s,1024MB

Sol

首先可以无视下取整这个条件,只需要在相加的时候规定小数部分不能进位就行

在将最后一位变成某个数的时候,可能之前这个位置上有数,这时可以看成在这个位置上加一个数,设第 jj 次加的数为 bjb_j

ii 次操作后数的位数为 lil_i ,最低位为第0位,那么可以发现第 ii 次操作加的数在第 jj 次操作后在 ljlil_j-l_i

viv_i 为最后第 ii 位被加了多少,那么有 vi=jk,lk=lj=ibjv_i=\sum_{j\leq k,l_k=l_j=i}b_j

因为相邻两个 ll 差不超过1,分治FFT即可

复杂度 O(Slog2S)O(|S|\log^2|S|)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 530000
#define mod 998244353
int n,v[N],a[N],b[N],su[N],ntt[N],rev[N],as[N],g[2][N*2];
char s[N];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
void dft(int s,int *a,int t)
{
	for(int i=0;i<s;i++)ntt[rev[i]]=a[i];
	for(int i=2;i<=s;i<<=1)
	{
		int tp=pw(3,(mod-1)/i);
		for(int j=0;j<s;j+=i)
		for(int k=j,vl=0;k<j+(i>>1);k++,vl++)
		{
			int v1=ntt[k],v2=1ll*ntt[k+(i>>1)]*g[t][i+vl]%mod;
			ntt[k]=(v1+v2)%mod;
			ntt[k+(i>>1)]=(v1-v2+mod)%mod;
		}
	}
	int inv=pw(s,t==0?mod-2:0);
	for(int i=0;i<s;i++)a[i]=1ll*ntt[i]*inv%mod;
}
void cdq(int l,int r)
{
	if(l==r){as[0]=(as[0]+v[l])%mod;return;}
	int mid=(l+r)>>1;
	cdq(l,mid);cdq(mid+1,r);
	int s=1;while(s<=r-l+2)s<<=1;
	for(int i=0;i<s;i++)rev[i]=(rev[i>>1]>>1)|((s>>1)*(i&1));
	for(int i=0;i<s;i++)a[i]=b[i]=0;
	int v1=n,v2=n;
	for(int i=l;i<=mid;i++)v1=min(v1,su[i]);
	for(int i=mid+1;i<=r;i++)v2=min(v2,su[i]);
	for(int i=l;i<=mid;i++)a[mid-l+1-(su[i]-v1)]+=v[i];
	for(int i=mid+1;i<=r;i++)b[su[i]-v2]++;
	dft(s,a,1);dft(s,b,1);for(int i=0;i<s;i++)a[i]=1ll*a[i]*b[i]%mod;dft(s,a,0);
	int f1=mid-l+1+v1-v2,f2=-f1;
	if(f1<0)f1=0;
	for(int i=f1;i<s;i++)as[i+f2]=(as[i+f2]+a[i])%mod;
}
int main()
{
	scanf("%d%s",&n,s+1);
	for(int i=0;i<2;i++)
	for(int j=2;j<=1<<19;j<<=1)
	{
		int tp=pw(3,(mod-1)/j),v2=1;
		if(i==0)tp=pw(tp,mod-2);
		for(int l=0;l<j>>1;l++)g[i][j+l]=v2,v2=1ll*v2*tp%mod;
	}
	for(int i=1;i<=n;i++)
	if(s[i]=='-')su[i]=su[i-1]-1;
	else
	{
		su[i]=su[i-1]+1;
		v[i]=s[i]-'0'-a[su[i]];
		a[su[i]]=s[i]-'0';
	}
	cdq(1,n);
	for(int i=1;i<=n+1;i++)as[i]=(as[i]+as[i-1]/10)%mod,as[i-1]%=10;
	int fg=0;
	for(int i=n+1;i>=0;i--)if(as[i]||fg)printf("%d",as[i]),fg=1;
	if(!fg)printf("0");
}
auoj33 最短路
Problem

有一张图,一开始有 mm 条白边,对于每一对在原图中最短路为2的点,它们之间连有一条黑边

给出经过一条白边的时间 aa 和经过一条黑边的时间 bb ,求1到每个点的最短路

n105,m3×105n\leq 10^5,m\leq 3\times 10^5

1s,1024MB1s,1024MB

Sol

考虑只有白边的一条最短路,显然除了路径上相邻的两个点剩下的点对间不可能有连边

设最短路长度为 dd ,那么一定存在经过 dd 条白边的方案和经过 d2\lfloor\frac d 2\rfloor 条黑边, dmod2d\bmod 2 条白边的方案

对于一条经过了 aa 条白边和 bb 条黑边的路径,显然有 a+2bda+2b\geq d

如果 a>0a>0 ,显然这条路径不可能比上面的两条都优秀

因此只需要额外考虑只经过黑边的路径

考虑bfs,对于每个点,暴力枚举它的出边,再暴力枚举出边那个点的出边判断这两个点是否连边

如果某条出边的那个点被访问到了,那之后遍历到这个点时就不用再走这条出边了

对于每个点记录它还连向哪些没有被访问的点,因为三元环只有 O(mm)O(m\sqrt m) 个,因此最多会有 O(mm)O(m\sqrt m) 次枚举某条出边但不删除,因此总复杂度 O(mm)O(m\sqrt m)

Code
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
#define N 151010
struct edge{int t,next;}ed[N*4];
long long as[N];
int n,m,a,b,x,y,head[N],cnt,is[N],dis[N];
vector<int> nt[N];
vector<int> fu;
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;nt[f].push_back(t);nt[t].push_back(f);}
int main()
{
	scanf("%d%d%d%d",&n,&m,&x,&y);
	for(int i=1;i<=m;i++)scanf("%d%d",&a,&b),adde(a,b);
	queue<int> st;st.push(1);
	for(int i=2;i<=n;i++)dis[i]=-1;
	while(!st.empty())
	{
		int v=st.front();st.pop();
		for(int i=head[v];i;i=ed[i].next)if(dis[ed[i].t]==-1)dis[ed[i].t]=dis[v]+1,st.push(ed[i].t);
	}
	for(int i=2;i<=n;i++)as[i]=min(1ll*x*dis[i],1ll*y*(dis[i]/2)+x*(dis[i]&1));
	st.push(1);
	for(int i=2;i<=n;i++)dis[i]=-1;
	while(!st.empty())
	{
		int v=st.front();st.pop();
		for(int i=head[v];i;i=ed[i].next)is[ed[i].t]=1;
		for(int i=head[v];i;i=ed[i].next)
		{
			int sz=nt[ed[i].t].size();
			for(int j=0;j<sz;j++)
			{
				int vl=nt[ed[i].t][j];
				if(dis[vl]!=-1)continue;
				if(vl==v||is[vl])fu.push_back(vl);
				else if(dis[vl]==-1)dis[vl]=dis[v]+1,st.push(vl);
			}
			nt[ed[i].t].clear();
			for(int j=0;j<fu.size();j++)nt[ed[i].t].push_back(fu[j]);
			fu.clear();
		}
		for(int i=head[v];i;i=ed[i].next)is[ed[i].t]=0;
	}
	for(int i=2;i<=n;i++)as[i]=min(as[i],dis[i]>=0?1ll*dis[i]*y:1000000000000000000ll),printf("%lld\n",as[i]);
}
auoj34 网格图
Problem

n×mn\times m 的网格图上一开始有 kk 个格子是黑的,每次可以选择一个白格子,如果这个格子周围四个格子中有至少两个是黑的,那么将这个格子染黑,求最后最多有多少个黑格子

n,m,k5×105n,m,k\leq 5\times 10^5

3s,512MB3s,512MB

Sol

最后的黑色部分一定由若干个矩形组成

如果两个矩形间存在两个点距离不超过2,那么可以将中间的格子染黑,进而将两个矩形合并起来

一种方式是依次合并矩形,直到不能合并为止

将矩形按照上边界排序,两个矩形 (x1,y1,x2,y2),(x3,y3,x4,y4)(y2y4)(x_1,y_1,x_2,y_2),(x_3,y_3,x_4,y_4)(y_2\leq y_4) 可以合并的条件是 ([x_1,x_2]\cap[x_3,x_4]\neq\emptyset\and y_2+2\geq y_3)\or ([x_1-1,x_2+1]\cap[x_3,x_4]\neq\emptyset\and y_2+1\geq y_3)\or ([x_1-2,x_2+2]\cap[x_3,x_4]\neq\emptyset\and y_2\geq y_3) 相当于区间插入/删除,区间查最大的 y2y_2 对应的矩形

因为加入的矩形一定是最大的,所以可以维护一个栈或vector

线段树上每个点开两个vector,分别表示完全覆盖这个区间的矩形和与这个区间有交的矩形

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 505000
int n,m,k,is[N],fa[N],s[N][4],v[N],id[N*4],ct;
vector<int> v1[N*2],v2[N*2];
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
bool cmp(int a,int b){return s[a][3]<s[b][3];}
void build(int x,int l,int r){id[x]=++ct;if(l==r)return;int mid=(l+r)>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r);}
void modify(int x,int l,int r,int l1,int r1,int v){if(l1==l&&r1==r){v2[id[x]].push_back(v);return;}v1[id[x]].push_back(v);int mid=(l1+r1)>>1;if(mid>=r)modify(x<<1,l,r,l1,mid,v);else if(mid<l)modify(x<<1|1,l,r,mid+1,r1,v);else modify(x<<1,l,mid,l1,mid,v),modify(x<<1|1,mid+1,r,mid+1,r1,v);}
int query(int x,int l,int r,int l1,int r1)
{
	if(l<1)l=1;if(r>n)r=n;
	int as=0;
	while(v2[id[x]].size()&&is[v2[id[x]].back()])v2[id[x]].pop_back();
	if(v2[id[x]].size())as=v2[id[x]].back();
	if(l1==l&&r1==r)
	{
		int as2=0;
		while(v1[id[x]].size()&&is[v1[id[x]].back()])v1[id[x]].pop_back();
		if(v1[id[x]].size())as2=v1[id[x]].back();
		if(s[as][3]<s[as2][3])as=as2;
		return as;
	}
	int mid=(l1+r1)>>1,as2;
	if(mid>=r)
	{
		as2=query(x<<1,l,r,l1,mid);if(s[as][3]<s[as2][3])as=as2;
	}
	else if(mid<l)
	{
		as2=query(x<<1|1,l,r,mid+1,r1);if(s[as][3]<s[as2][3])as=as2;
	}
	else
	{
		as2=query(x<<1,l,mid,l1,mid);if(s[as][3]<s[as2][3])as=as2;
		as2=query(x<<1|1,mid+1,r,mid+1,r1);if(s[as][3]<s[as2][3])as=as2;
	}
	return as;
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=k;i++)scanf("%d%d",&s[i][0],&s[i][1]),s[i][3]=s[i][1],s[i][2]=s[i][0],fa[i]=v[i]=i;
	build(1,1,n);sort(v+1,v+k+1,cmp);
	for(int i=1;i<=k;i++)
	{
		while(1)
		{
			int tp=0;
			int v1=query(1,s[v[i]][0]-2,s[v[i]][2]+2,1,n);
			if(v1&&s[v1][3]>=s[v[i]][1])tp=v1;
			else
			{
				int v1=query(1,s[v[i]][0]-1,s[v[i]][2]+1,1,n);
				if(v1&&s[v1][3]>=s[v[i]][1]-1)tp=v1;
				else
				{
					int v1=query(1,s[v[i]][0],s[v[i]][2],1,n);
					if(v1&&s[v1][3]>=s[v[i]][1]-2)tp=v1;
				}
			}
			if(!tp)break;
			is[tp]=1;fa[tp]=v[i];
			s[v[i]][0]=min(s[v[i]][0],s[tp][0]);
			s[v[i]][1]=min(s[v[i]][1],s[tp][1]);
			s[v[i]][2]=max(s[v[i]][2],s[tp][2]);
			s[v[i]][3]=max(s[v[i]][3],s[tp][3]);
		}
		modify(1,s[v[i]][0],s[v[i]][2],1,n,v[i]);
	}
	long long as=0;
	for(int i=1;i<=k;i++)if(fa[i]==i)as+=1ll*(s[i][2]-s[i][0]+1)*(s[i][3]-s[i][1]+1);
	printf("%lld\n",as);
}
auoj35 小W数排列
Problem

nn 个互不相同的整数 A1,...,nA_{1,...,n} ,求有多少个排列满足 i=1n1ApiApi+1k\sum_{i=1}^{n-1} |A_{p_i}-A_{p_{i+1}}|\leq k ,模 109+710^9+7

n100,k1000n\leq 100,k\leq 1000

1s,512MB1s,512MB

Sol

考虑怎么算不等式左边

一种方式是直接拆绝对值,但拆完有负数,第二维的状态很大

另外一种拆法是,对于排序后相邻的每一对 (Ai,Ai+1)(A_i,A_{i+1}) ,计算排列中相邻两个元素一个小于等于 AiA_i 另外一个大于等于 Ai+1A_{i+1} 的对数,然后加上对数乘 Ai+1AiA_{i+1}-A_i 的贡献

如果小于等于 AiA_i 的构成了 kk 段,那么对数为 2k[Ap1<Ai][Apn<Ai]2k-[A_{p_1}<A_i]-[A_{p_n}<A_i]

dpi,j,k,0/1,0/1dp_{i,j,k,0/1,0/1} 表示考虑到了 AiA_i ,前面的数构成了 jj 段,前面的贡献加起来是 kk ,开头结尾是否小于等于 AiA_i

转移有五种情况:

  1. 在中间且合并两个段
  2. 在中间且与某个段相邻
  3. 在中间新增一个段
  4. 在开头或结尾,且与某个段相邻
  5. 在开头和结尾,且新增一个段

复杂度 O(n2k)O(n^2k)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 105
#define M 1050
#define mod 1000000007
int dp[N][N][M][2][2],n,k,v[N],as;
int main()
{
	scanf("%d%d",&n,&k);
	if(n==1){printf("1\n");return 0;}
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	sort(v+1,v+n+1);
	dp[1][1][0][0][0]=dp[1][1][0][0][1]=dp[1][1][0][1][0]=1;
	for(int i=2;i<=n;i++)
	for(int j=0;j<=i;j++)
	for(int l=0;l<=k;l++)
	for(int s=0;s<=1;s++)
	for(int t=0;t<=1;t++)
	if(dp[i-1][j][l][s][t])
	{
		int vl=l+(j*2-s-t)*(v[i]-v[i-1]);
		if(vl>k)continue;
		dp[i][j][vl][s][t]=(dp[i][j][vl][s][t]+1ll*dp[i-1][j][l][s][t]*(j*2-s-t))%mod;
		dp[i][j+1][vl][s][t]=(dp[i][j+1][vl][s][t]+1ll*dp[i-1][j][l][s][t]*(j+1-s-t))%mod;
		dp[i][j-1][vl][s][t]=(dp[i][j-1][vl][s][t]+1ll*dp[i-1][j][l][s][t]*(j-1))%mod;
		if(!s)dp[i][j+1][vl][1][t]=(dp[i][j+1][vl][1][t]+1ll*dp[i-1][j][l][s][t])%mod;
		if(!t)dp[i][j+1][vl][s][1]=(dp[i][j+1][vl][s][1]+1ll*dp[i-1][j][l][s][t])%mod;
		if(!s)dp[i][j][vl][1][t]=(dp[i][j][vl][1][t]+1ll*dp[i-1][j][l][s][t])%mod;
		if(!t)dp[i][j][vl][s][1]=(dp[i][j][vl][s][1]+1ll*dp[i-1][j][l][s][t])%mod;
	}
	for(int i=0;i<=k;i++)as=(as+dp[n][1][i][1][1])%mod;
	printf("%d\n",as);
}
auoj36 小W玩游戏
Problem

给一个 n×mn\times m 的网格,每次随机选择一个位置,将这一行和这一列的数全部加1,进行 dd 次操作,求最后奇数个数不超过 kk 的方案数,模 998244353998244353

n,m2×105n,m\leq 2\times 10^5

1s,512MB1s,512MB

Sol

行列显然可以分开

对于行的情况,相当于要求出有 ii 行被选了奇数次的方案数

fif_i 表示恰好有 ii 行为奇数的方案数, gig_i 表示对于所有方案,选出 ii 个奇数行的方案数总和,那么有

gi=j=inCjifjg_i=\sum_{j=i}^nC_j^if_j

二项式反演即可推回去

考虑 gg ,根据奇数的egf有

gi=Cni[xd](exex2)i(ex)nid!g_i=C_n^i[x^d](\frac{e^x-e^{-x}}2)^i(e^x)^{n-i}*d!

gi=Cnid!12i[xd]j=0iCijen2jg_i=C_n^id!\frac{1}{2^i}[x^d]\sum_{j=0}^iC_i^je^{n-2j}

gi=Cni12ij=0iCij(n2j)dg_i=C_n^i\frac{1}{2^i}\sum_{j=0}^iC_i^j(n-2j)^d

NTT即可

最后枚举有多少行选了奇数次,那么列选奇数次的个数合法的一定是一段前缀/后缀,二分即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
using namespace std;
#define N 530000
#define mod 998244353
int n,m,f[N],g[N],ntt[N],fr[N],ifr[N],rev[N],a[N],b[N],c[N],as;
long long p,q;
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
void dft(int l,int *a,int t)
{
	for(int i=0;i<l;i++)rev[i]=(rev[i>>1]>>1)|((l>>1)*(i&1)),ntt[rev[i]]=a[i];
	for(int s=2;s<=l;s<<=1)
	{
		int v1=pw(3,(mod-1)/s);
		if(t==-1)v1=pw(v1,mod-2);
		for(int i=0;i<l;i+=s)
		for(int j=i,st1=1;j<i+(s>>1);j++,st1=1ll*st1*v1%mod)
		{
			int v1=ntt[j],v2=1ll*st1*ntt[j+(s>>1)]%mod;
			ntt[j]=(v1+v2)%mod;ntt[j+(s>>1)]=(v1-v2+mod)%mod;
		}
	}
	int inv=1ll*pw(l,t==-1?mod-2:0);
	for(int i=0;i<l;i++)a[i]=1ll*inv*ntt[i]%mod;
}
void solve(int n,int *as)
{
	int l=1;while(l<=n*2+2)l<<=1;for(int i=0;i<=l;i++)a[i]=b[i]=0;
	for(int i=0;i<=n;i++)a[i]=1ll*pw(mod+n-2*i,p)*ifr[i]%mod,b[i]=ifr[i];
	dft(l,a,1);dft(l,b,1);for(int i=0;i<l;i++)a[i]=1ll*a[i]*b[i]%mod;dft(l,a,-1);
	l=1;while(l<=n+200000)l<<=1;for(int i=0;i<=l;i++)c[i]=0;
	for(int i=n+1;i<=l;i++)a[i]=0;
	for(int i=0;i<=n;i++)a[i]=1ll*a[i]*fr[n]%mod*ifr[n-i]%mod*pw((mod+1)/2,i)%mod;
	for(int i=0;i<=n;i++)a[i]=1ll*a[i]*fr[i]%mod;
	for(int i=0;i<=200000;i++)c[i]=1ll*ifr[200000-i]*((i&1)?mod-1:1)%mod;
	dft(l,a,1);dft(l,c,1);for(int i=0;i<l;i++)a[i]=1ll*a[i]*c[i]%mod;dft(l,a,-1);
	for(int i=0;i<=n;i++)as[n-i]=1ll*a[i+200000]*ifr[i]%mod;
}
int main()
{
	scanf("%d%d%lld%lld",&n,&m,&p,&q);p=(p-1)%(mod-1)+1;
	fr[0]=ifr[0]=1;for(int i=1;i<=200000;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	solve(n,f);solve(m,g);
	for(int j=1;j<=m;j++)g[j]=(g[j]+g[j-1])%mod;
	for(int i=0;i<=n;i++)
	{
		long long v1=q-1ll*i*m,v2=n-2*i;
		if(v2>0)
		{
			if(v1<0)continue;
			long long v3=v1/v2;
			if(v3>m)v3=m;
			as=(as+1ll*f[i]*g[v3])%mod;
		}
		else if(v2==0){if(v1>=0)as=(as+1ll*f[i]*g[m])%mod;}
		else
		{
			if(v1>=0){as=(as+1ll*f[i]*g[m])%mod;continue;}
			v1*=-1,v2*=-1;
			long long v3=(v1-1)/v2;
			if(v3>m)continue;
			as=(as+1ll*f[i]*(g[m]-g[v3]+mod))%mod;
		}
	}
	printf("%d\n",as);
}
auoj37 小W维护序列
Problem

给一个序列,支持五种操作

  1. 求一个区间去重后,选出三个不同的数得到的所有三数乘积只和模 109+710^9+7
  2. 修改一个元素
  3. 删除一个元素
  4. 在某个元素后加入一个元素
  5. 求区间数的种数

n,q105n,q\leq 10^5

2s,128MB2s,128MB

Sol

考虑先给每个数一个绝对位置

维护两个splay,第一个splay上进行所有操作,在插入时在第二个splay上对应元素后插入

这样最后第二个splay的顺序就是一个合法的位置关系

对于每个数记录这个数上次出现的位置pre,显然一次修改只会改 O(1)O(1) 个pre

那么一次询问相当于一个 i,preii,pre_i 的二维数点

记录一个区间内的数选0~3个的所有方案的乘积的和就可以快速合并两个区间的信息

然后cdq分治即可

复杂度 O(nlog2n)O(n\log^2 n)

Code
#include<cstdio>
#include<set>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
#define N 205000
#define mod 1000000007
set<int> st[N];
map<int,int> fuc;
int n,q,ct,s[N][3],v[N],id[N],id2[N],v2[N];
struct sth{int a,b,c,d;}tr[N],as[N][2],tr2[N];
sth doit(sth a,sth b){sth c;c.a=a.a+b.a;c.b=(a.b+b.b)%mod;c.c=(a.c+b.c+1ll*a.b*b.b)%mod;c.d=(a.d+b.d+1ll*a.c*b.b+1ll*a.b*b.c)%mod;return c;}
sth inv(sth a){sth b;b.a=-a.a;b.b=mod-a.b;b.c=(2*mod-a.c-1ll*a.b*b.b%mod)%mod;b.d=(3ll*mod-a.d-1ll*a.b*b.c%mod-1ll*a.c*b.b%mod)%mod;return b;}
struct que{int x,y,l,r,id;};
struct pts{int x,y,z,fu;sth st;};
vector<pts> s0[21][2];
vector<que> s1[21][2];
bool cmp1(pts a,pts b){return a.y<b.y;}
bool cmp2(que a,que b){return a.y<b.y;}
struct Splay{
	int ch[N][2],fa[N],sz[N],rt;
	void pushup(int x){sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+1;}
	void rotate(int x){int f=fa[x],g=fa[f],tp=ch[f][1]==x;ch[g][ch[g][1]==f]=x;fa[x]=g;ch[f][tp]=ch[x][!tp];fa[ch[x][!tp]]=f;ch[x][!tp]=f;fa[f]=x;pushup(f);pushup(x);}
	void splay(int x,int y=0){while(fa[x]!=y){int f=fa[x],g=fa[f];if(g!=y)rotate((ch[f][1]==x)^(ch[g][1]==f)?x:f);rotate(x);}if(!y)rt=x;}
	int kth(int x,int k){int tp=sz[ch[x][0]];if(k==tp+1)return x;if(k<=tp)return kth(ch[x][0],k);return kth(ch[x][1],k-tp-1);}
}tr1,tr3;
void check(){for(int i=1;i<=ct;i++){int st=tr3.kth(tr3.rt,i+1);tr3.splay(st);id[st]=i;}}
void just_doit()
{
	ct=n+2;tr1.rt=tr3.rt=1;
	for(int i=1;i<n+2;i++)tr1.sz[i]=tr3.sz[i]=n-i,tr1.ch[i][1]=tr3.ch[i][1]=i+1,tr1.fa[i+1]=tr3.fa[i+1]=i;
	tr1.sz[n+2]=tr3.sz[n+2]=1;
	for(int i=1;i<=q;i++)
	if(s[i][0]==4)
	{
		tr1.splay(tr1.kth(tr1.rt,s[i][1]+1));
		tr1.splay(tr1.kth(tr1.rt,s[i][1]+2),tr1.rt);
		int v1=tr1.ch[tr1.rt][1],v2=tr1.rt;
		tr1.ch[v1][0]=++ct;tr1.sz[ct]=1;tr1.fa[ct]=v1;tr1.splay(ct);
		tr3.splay(v2);
		tr3.splay(tr3.kth(tr3.rt,tr3.sz[tr3.ch[tr3.rt][0]]+2),tr3.rt);
		v1=tr3.ch[tr3.rt][1],v2=tr3.rt;
		tr3.ch[v1][0]=ct;tr3.sz[ct]=1;tr3.fa[ct]=v1;tr3.splay(ct);
		s[i][1]=ct;
	}
	else if(s[i][0]==3)
	{
		tr1.splay(tr1.kth(tr1.rt,s[i][1]));
		tr1.splay(tr1.kth(tr1.rt,s[i][1]+2),tr1.rt);
		int v1=tr1.ch[tr1.rt][1],v3=tr1.ch[v1][0];
		s[i][1]=v3;
		tr1.ch[v1][0]=0;tr1.fa[v3]=0;tr1.pushup(v1);tr1.splay(v1);
	}
	else if(s[i][0]==1||s[i][0]==5)
	{
		int v1=tr1.kth(tr1.rt,s[i][1]+1);
		s[i][1]=v1;tr1.splay(v1);
		v1=tr1.kth(tr1.rt,s[i][2]+1);
		s[i][2]=v1;tr1.splay(v1);
	}
	else
	{
		int v1=tr1.kth(tr1.rt,s[i][1]+1);
		s[i][1]=v1;tr1.splay(v1);
	}
	check();
	int ct3=0;
	for(int i=1;i<=n;i++)if(!fuc[v2[i]])fuc[v2[i]]=++ct3;
	for(int i=1;i<=q;i++)if(s[i][0]==2||s[i][0]==4)if(!fuc[s[i][2]])
	fuc[s[i][2]]=++ct3;
	ct-=2;
	for(int i=1;i<=ct3;i++)st[i].insert(0);
	for(int i=1;i<=q;i++)
	{
		s[i][1]=id[s[i][1]];
		if(s[i][0]!=2&&s[i][0]!=4&&s[i][0]!=3)s[i][2]=id[s[i][2]];
	}
	for(int i=1;i<=n;i++)s0[0][0].push_back((pts){0,*(--st[fuc[v2[i]]].lower_bound(id[i+1])),id[i+1],1,(sth){1,v2[i],0,0}}),v[id[i+1]]=v2[i],st[fuc[v2[i]]].insert(id[i+1]);
	for(int i=1;i<=q;i++)
	if(s[i][0]==1||s[i][0]==5)s1[0][0].push_back((que){i,s[i][1]-1,s[i][1],s[i][2],i});
	else if(s[i][0]==4)
	{
		set<int>::iterator it=st[fuc[s[i][2]]].lower_bound(s[i][1]);
		if(it!=st[fuc[s[i][2]]].end())
		{
			int v1=*it,v2=*(--it);
			s0[0][0].push_back((pts){i,v2,v1,0,(sth){1,v[v1],0,0}});
			s0[0][0].push_back((pts){i,s[i][1],v1,1,(sth){1,v[v1],0,0}});
		}
		s0[0][0].push_back((pts){i,*(--st[fuc[s[i][2]]].lower_bound(s[i][1])),s[i][1],1,(sth){1,s[i][2],0,0}});
		st[fuc[s[i][2]]].insert(s[i][1]);v[s[i][1]]=s[i][2];
	}
	else if(s[i][0]==3)
	{
		set<int>::iterator it=st[fuc[v[s[i][1]]]].lower_bound(s[i][1]+1);
		if(it!=st[fuc[v[s[i][1]]]].end())
		{
			int v1=*it;it--;it--;int v2=*it;
			s0[0][0].push_back((pts){i,s[i][1],v1,0,(sth){1,v[v1],0,0}});
			s0[0][0].push_back((pts){i,v2,v1,1,(sth){1,v[v1],0,0}});
		}
		s0[0][0].push_back((pts){i,*(--st[fuc[v[s[i][1]]]].lower_bound(s[i][1])),s[i][1],0,(sth){1,v[s[i][1]],0,0}});
		st[fuc[v[s[i][1]]]].erase(s[i][1]);
	}
	else
	{
		set<int>::iterator it=st[fuc[s[i][2]]].lower_bound(s[i][1]);
		if(it!=st[fuc[s[i][2]]].end())
		{
			int v1=*it,v2=*(--it);
			s0[0][0].push_back((pts){i,v2,v1,0,(sth){1,v[v1],0,0}});
			s0[0][0].push_back((pts){i,s[i][1],v1,1,(sth){1,v[v1],0,0}});
		}
		it=st[fuc[v[s[i][1]]]].lower_bound(s[i][1]+1);
		if(it!=st[fuc[v[s[i][1]]]].end())
		{
			int v1=*it;it--;it--;int v2=*it;
			s0[0][0].push_back((pts){i,s[i][1],v1,0,(sth){1,v[v1],0,0}});
			s0[0][0].push_back((pts){i,v2,v1,1,(sth){1,v[v1],0,0}});
		}
		s0[0][0].push_back((pts){i,*(--st[fuc[v[s[i][1]]]].lower_bound(s[i][1])),s[i][1],0,(sth){1,v[s[i][1]],0,0}});
		st[fuc[v[s[i][1]]]].erase(s[i][1]);
		s0[0][0].push_back((pts){i,*(--st[fuc[s[i][2]]].lower_bound(s[i][1])),s[i][1],1,(sth){1,s[i][2],0,0}});
		st[fuc[s[i][2]]].insert(s[i][1]);v[s[i][1]]=s[i][2];
	}
	
}
void add1(int x,sth k){for(int i=x+1;i<=ct+1;i+=i&-i)tr[i]=doit(tr[i],k);}
sth que1(int x){sth fu=(sth){0,0,0,0};for(int i=x+1;i;i-=i&-i)fu=doit(fu,tr[i]);return fu;}
void add2(int x,sth k){for(int i=x+1;i<=ct+1;i+=i&-i)tr2[i]=doit(tr2[i],k);}
sth que2(int x){sth fu=(sth){0,0,0,0};for(int i=x+1;i;i-=i&-i)fu=doit(fu,tr2[i]);return fu;}
void make_your_dream_come_true(int l,int r,int d,int s)
{
	if(l==r)
	{
		sort(s0[d][s].begin(),s0[d][s].end(),cmp1);
		sort(s1[d][s].begin(),s1[d][s].end(),cmp2);
		int l1=0;
		for(int i=0;i<s1[d][s].size();i++)
		{
			while(l1<s0[d][s].size()&&s0[d][s][l1].y<=s1[d][s][i].y)if(s0[d][s][l1].fu==1)add1(s0[d][s][l1].z,s0[d][s][l1].st),l1++;
			else add2(s0[d][s][l1].z,s0[d][s][l1].st),l1++;
			as[s1[d][s][i].id][0]=doit(as[s1[d][s][i].id][0],que1(s1[d][s][i].r));
			as[s1[d][s][i].id][1]=doit(as[s1[d][s][i].id][1],que2(s1[d][s][i].r));
			as[s1[d][s][i].id][1]=doit(as[s1[d][s][i].id][1],que1(s1[d][s][i].l-1));
			as[s1[d][s][i].id][0]=doit(as[s1[d][s][i].id][0],que2(s1[d][s][i].l-1));
		}
		for(int i=0;i<l1;i++)
		if(s0[d][s][i].fu==0)add1(s0[d][s][i].z,s0[d][s][i].st);
		else add2(s0[d][s][i].z,s0[d][s][i].st);
		return;
	}
	int mid=(l+r)>>1;
	for(int i=0;i<s0[d][s].size();i++)s0[d+1][s0[d][s][i].x>mid].push_back(s0[d][s][i]);
	for(int i=0;i<s1[d][s].size();i++)
	s1[d+1][s1[d][s][i].x>mid].push_back(s1[d][s][i]);
	make_your_dream_come_true(l,mid,d+1,0);make_your_dream_come_true(mid+1,r,d+1,1);
	sort(s0[d+1][0].begin(),s0[d+1][0].end(),cmp1);
	sort(s1[d+1][1].begin(),s1[d+1][1].end(),cmp2);
	int l1=0;
	for(int i=0;i<s1[d+1][1].size();i++)
	{
		while(l1<s0[d+1][0].size()&&s0[d+1][0][l1].y<=s1[d+1][1][i].y)if(s0[d+1][0][l1].fu==1)add1(s0[d+1][0][l1].z,s0[d+1][0][l1].st),l1++;
		else add2(s0[d+1][0][l1].z,s0[d+1][0][l1].st),l1++;
		as[s1[d+1][1][i].id][0]=doit(as[s1[d+1][1][i].id][0],que1(s1[d+1][1][i].r));
		as[s1[d+1][1][i].id][1]=doit(as[s1[d+1][1][i].id][1],que2(s1[d+1][1][i].r));
		as[s1[d+1][1][i].id][1]=doit(as[s1[d+1][1][i].id][1],que1(s1[d+1][1][i].l-1));
		as[s1[d+1][1][i].id][0]=doit(as[s1[d+1][1][i].id][0],que2(s1[d+1][1][i].l-1));
	}
	for(int i=0;i<l1;i++)
	if(s0[d+1][0][i].fu==0)add1(s0[d+1][0][i].z,s0[d+1][0][i].st);
	else add2(s0[d+1][0][i].z,s0[d+1][0][i].st);
	for(int i=0;i<2;i++)s0[d+1][i].clear(),vector<pts>().swap(s0[d+1][i]);
	for(int i=0;i<2;i++)s1[d+1][i].clear(),vector<que>().swap(s1[d+1][i]);
}
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)scanf("%d",&v2[i]);
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&s[i][0],&s[i][1]);
		if(s[i][0]!=3)scanf("%d",&s[i][2]);
	}
	just_doit();
	make_your_dream_come_true(0,q,0,0);
	for(int i=1;i<=q;i++)
	if(s[i][0cpp]==1||s[i][0]==5)
	{
		sth as1=doit(as[i][0],inv(as[i][1]));
		if(s[i][0]==1)printf("%d\n",as1.d);else printf("%d\n",as1.a);
	}
}
auoj38 小D的奶牛
Problem

无向图团计数

n50n\leq 50

10s,512MB10s,512MB

Sol

考虑折半,处理出左边每一个合法的团以及这个团在另外一侧能加入哪些点以及右边每一个团

暴力处理可以做到复杂度 O(2n2n)O(2^{\frac{n}2}*n)

然后相当于求 AB=BA\cap B=B 的数量,可以看成求 (UA)B=(U\setminus A)\cap B = \emptyset 的数量,fwt即可

复杂度 O(2n2n)O(2^{\frac{n}2}*n)

Code
#include<cstdio>
using namespace std;
#define N 51
#define M 33554433
int n,s[N][N],f1[N],f2[N],as,s2[M];
long long s1[M];
char v[N][N];
void dfs(int d,int v1,int v2)
{
	if(d==n/2+1){if(v1)s1[((1<<25)-1)^v2]++,as++;return;}
	if((f1[d]&v1)==v1)dfs(d+1,v1|(1<<d-1),v2&f2[d]);
	dfs(d+1,v1,v2);
}
void dfs2(int d,int v1)
{
	if(d==n+1){if(v1)s2[v1]++,as++;return;}
	if((f2[d]&v1)==v1)dfs2(d+1,v1|(1<<(d-n/2-1)));
	dfs2(d+1,v1);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%s",v[i]+1);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)s[i][j]=v[i][j]-'0';
	as+=n-50;n=50;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n/2;j++)f1[i]|=(s[i][j]<<j-1),f2[i]|=(s[i][j+(n/2)]<<j-1);
	dfs(1,0,(1<<25)-1);dfs2(n/2+1,0);
	for(int i=2;i<=1<<25;i<<=1)
	for(int j=0;j<1<<25;j+=i)
	for(int k=j;k<j+(i>>1);k++)
	s1[k]+=s1[k+(i>>1)],s2[k]+=s2[k+(i>>1)];
	for(int i=0;i<1<<25;i++)s1[i]*=s2[i];
	for(int i=2;i<=1<<25;i<<=1)
	for(int j=0;j<1<<25;j+=i)
	for(int k=j;k<j+(i>>1);k++)
	s1[k]-=s1[k+(i>>1)];
	printf("%lld\n",s1[0]+as+1);
}
auoj39 小D的交通
Problem

nn 个点,对于一个 xx ,如果 gcd(x+i1,x+j1)>1gcd(x+i-1,x+j-1)>1 ,那么 (i,j)(i,j) 间有边

求一个 xx 使得所有点连通或输出无解

n105n\leq 10^5

1s,256MB1s,256MB

Sol

考虑一种乱搞方式

首先选所有小于等于 n\sqrt n 的质数 pp ,让 pxp|x

这时还剩下一些数没有连通,剩下的数减1后显然都是质数,考虑一个质数 pp ,可以通过调整 xmodpx\bmod p ,将一个原本不与1连通的 q+1q+1q+1pq+1-pq+1+pq+1+p 连通

因为 q+1p,q+1+pq+1-p,q+1+p 显然是奇数,所以显然这两个数与1连通

为了保证能连上,从中间向两侧开始选数更优

然后把所有的限制做一个高精CRT即可

发现 nn 很大时一个答案适用于很多 nn ,可以打表打出 nn 很大的答案

Code
#include<cstdio>
using namespace std;
#define N 105020
int n,pr[N],ch[N],is[N],ct,ct2,tp=10555,vl=1e8,s[N][2],ct3,las,v3;
int pw(int a,int b,int p){int as=1;while(b){if(b&1)as=1ll*as*a%p;a=1ll*a*a%p;b>>=1;}return as;}
struct justdoit{
	int a[N],b[N];
	void init(){b[0]=1;}
	void muladd(int v)
	{
		int t2=0;
		for(int i=0;i<=tp;i++)
		{long long vl2=t2+a[i]+1ll*b[i]*v;t2=vl2/vl,a[i]=vl2%vl;}
	}
	void mul(int v)
	{
		int t2=0;
		for(int i=0;i<=tp;i++){long long vl2=t2+1ll*b[i]*v;t2=vl2/vl,b[i]=vl2%vl;}
	}
	void mula(int v)
	{
		int t2=0;
		for(int i=0;i<=tp;i++){long long vl2=t2+1ll*a[i]*v;t2=vl2/vl,a[i]=vl2%vl;}
	}
	int mod2(int x)
	{
		int t2=0;
		for(int i=tp;i>=0;i--)t2=(1ll*t2*vl+a[i])%x;
		return t2;
	}
	void add(int x,int y)
	{
		s[++ct3][0]=x;s[ct3][1]=y;
	}
	void output()
	{
		for(int i=1;i<=ct3;i++)
		{
			int v3=1;
			for(int j=1;j<=ct3;j++)if(j!=i)v3=1ll*v3*s[j][1]%s[i][1];
			int tp2=1ll*s[i][0]*pw(v3,s[i][1]-2,s[i][1])%s[i][1];
			mula(s[i][1]);muladd(tp2);mul(s[i][1]);
		}
		int fg=0;
		for(int i=tp;i>=0;i--)
		if(!fg){if(a[i])printf("%d",a[i]),fg=1;}
		else printf("%08d",a[i]);
		printf("\n");
	}
}fu;
void doit(int i){if(!is[i]){fu.add(pr[ct2+v3+1]-i%pr[ct2+v3+1]+1,pr[ct2+v3+1]);for(int j=i;j>0;j-=pr[ct2+v3+1])is[j]=1;for(int j=i;j<=n;j+=pr[ct2+v3+1])is[j]=1;ct2++;}}
int main()
{
	scanf("%d",&n);
	if(n<=16){printf("No solution\n");return 0;}
	for(int i=2;i<=n;i++)
	{
		if(!ch[i])pr[++ct]=i;
		for(int j=2;i*j<=n;j++)
		{
			ch[i*j]=1;
			if(i%j==0)break;
		}
	}
	while(pr[v3]*pr[v3]<=n)v3++;
	fu.init();for(int j=1;j<=v3;j++)fu.add(0,pr[j]);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=v3;j++)
	if((i-1)%pr[j]==0)is[i]=1;
	is[0]=is[n+1]=1;
	for(int i=0;i<=n/2;i++)doit((n+1)/2+i),doit((n+1)/2-i);
	fu.output();
}
auoj40 N门问题
Problem

nn 扇门,有一扇后面有奖品,A一开始随机选择一扇门,然后主持人随机打开一扇没有奖品且当前没有被选中的门,接下来A会选择当前是奖品的概率最大的门,依次进行到只剩两扇门时结束

现B作为主持人,可以任意选择一扇没有奖品且当前没有被选中的门打开,但A仍然认为B在随机选择,求B在最优策略下A得到奖品的概率,输出6位小数

nn 由若干同余方程给出

T50000T\leq 50000

1s,256MB1s,256MB

Sol

考虑A怎么算每扇门后面有奖的概率

这相当于 P()iP(i)\frac{P(这扇门后面有奖|主持人这样操作)}{\sum_iP(第 i 扇门后面有奖|主持人这样操作)}

只关心大小关系是不用关系分母,于是看分子

对于当前选中的门,主持人开了门之后这个概率会乘 1n1\frac{1}{n-1}

对于其它门,概率会乘 1n2\frac{1}{n-2}

于是可以搜出 n10n\leq 10 的答案

发现 n=11n=11 时答案为0,猜想 n>10n>10 答案为0

注意到因为 nn 递减,一次操作后当前选的门都会成为概率最小的门

在进行了 n2\lfloor\frac n 2\rfloor 次操作后,所有剩下的门概率不同且至少剩6扇门

可以发现在这种情况下B总可以让A选不到门,因此答案为0

excrt即可

复杂度 O(Tlogv)O(T\log v)

Code
#include<cstdio>
using namespace std;
#define ll long long
#define N 50020
ll f[N][2];
int n;
double as[11]={0,0,0.5,0.666667,0.625000,0.466667,0.416667,0.342857,0.291667,0.253968,0.225000};
ll exgcd(ll a,ll b,ll &x,ll &y){if(!b){x=1,y=0;return a;}ll g=exgcd(b,a%b,x,y);ll t=x;x=y;y=t-a/b*y;return g;}
ll mul(ll x,ll y,ll mod){ll tmp=(long double)x*y/mod;return x*y-tmp*mod;}
ll excrt()
{
	for(int i=2;i<=n;i++)
	{
		ll a=f[1][0],b=f[1][1],c=f[i][0],d=f[i][1],x,y;
		ll g=exgcd(a,c,x,y);
		if((d-b)%g)return -1;
		ll l=a/g*c;
		x=(x%l+l)%l;
		x=mul((d-b)/g,x,l);
		x=((mul(x,a,l)+b)%l+l)%l;
		f[1][0]=l,f[1][1]=x;
	}
	return f[1][1];
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lld%lld",&f[i][1],&f[i][0]);
	ll fuc=excrt();
	if(fuc<2)printf("error\n");
	else if(fuc>10)printf("0.000000\n");
	else printf("%.6lf\n",as[fuc]);
}
auoj41 钩子
Problem

nn 个钩子,1号钩子的左边和 nn 号钩子的右边分别有一个被占据的钩子

nn 个人依次进来,每个人都会在当前与最近的一个被占据的钩子的距离最远的钩子中随机选一个占据

求对于每一对 (i,j)(i,j), 第 ii 个人占据第 jj 个钩子的概率

n1000n\leq 1000

1s,512MB1s,512MB

Sol

将长度为2的段看成两个长度为1的段,那么选了一个段后这个段一定会分裂成两个更小的段

那么假设当前有 kk 个距离最大的段,一定是前 kk 个人选了这些段的中间

有一部分段有2的概率被选,另外一些段有1的概率被选,可以dp求出每个人选某一段的概率

长度是奇数的段直接分成两段即可,对于长度为偶数的段,钦定从中间前面断开,显然另外一种情况和这种情况完全对称,因此将每一个数与它在这一段中对称的数的概率取平均即可

复杂度 O(n2logn)O(n^2\log n)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define N 1050
int dp[N][N],n,p,inv[N],ct,ct2,f[N],dp2[N][2],g[N][N];
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%p;a=1ll*a*a%p;b>>=1;}return as;}
vector<pair<int,int> > st[N*20];
vector<int> st2;
void rotate(int l,int r)
{
	for(int x=1;x<=n;x++)
	for(int i=l,j=r;i<j;i++,j--)
	dp[x][i]=dp[x][j]=1ll*(dp[x][i]+dp[x][j])*(p+1)/2%p;
}
void doit(int a,int b)
{
	int n=a+b;
	for(int j=0;j<=a+b;j++)dp2[j][0]=dp2[j][1]=0;
	for(int j=0;j<=a+b;j++)for(int i=0;i<=b;i++)g[j][i]=0;
	g[0][b]=1;
	for(int i=0;i<n;i++)
	for(int j=0;j<=b;j++)
	{
		int c1=n-i-j,c2=j;
		int tp1=1ll*c1*inv[c1+c2*2]%p,tp2=2ll*c2*inv[c1+c2*2]%p;
		if(j)
		{
			g[i+1][j-1]=(g[i+1][j-1]+1ll*g[i][j]*tp2)%p;
			dp2[i][1]=(dp2[i][1]+1ll*g[i][j]*tp2)%p;
		}
		g[i+1][j]=(g[i+1][j]+1ll*g[i][j]*tp1)%p;
		dp2[i][0]=(dp2[i][0]+1ll*g[i][j]*tp1)%p;
	}
}
void solve()
{
	if(!st[ct2].size())return;
	st2.clear();
	int mx=0,v1=0,v2=0;
	for(int i=0;i<st[ct2].size();i++)mx=max(mx,(st[ct2][i].second-st[ct2][i].first+2)/2);
	for(int i=0;i<st[ct2].size();i++)
	if((st[ct2][i].second-st[ct2][i].first+2)/2==mx)
	{
		if(st[ct2][i].second==st[ct2][i].first)
		st2.push_back(st[ct2][i].first),v1++;
		else if(st[ct2][i].second==st[ct2][i].first+1)
		st2.push_back(st[ct2][i].first),st2.push_back(st[ct2][i].first+1),v1+=2;
		else
		{
			int l=st[ct2][i].first,r=st[ct2][i].second,mid=(l+r)>>1;
			st2.push_back(mid);st[ct2+1].push_back(make_pair(l,mid-1));st[ct2+1].push_back(make_pair(mid+1,r));
			if((l+r)&1)f[mid]=1,v2++;else v1++;
		}
	}
	else st[ct2+1].push_back(st[ct2][i]);
	doit(v1,v2);
	for(int i=ct+1;i<=ct+st2.size();i++)
	for(int j=0;j<st2.size();j++)
	dp[i][st2[j]]=1ll*inv[f[st2[j]]?v2:v1]*dp2[i-ct-1][f[st2[j]]]%p;
	for(int i=1;i<=n;i++)f[i]=0;
	int st1=ct2;ct+=st2.size();ct2++;
	solve();
	for(int i=0;i<st[st1].size();i++)
	if((st[st1][i].second-st[st1][i].first+2)/2==mx)
	rotate(st[st1][i].first,st[st1][i].second);
}
int main()
{
	scanf("%d%d",&n,&p);
	for(int i=1;i<=n;i++)inv[i]=pw(i,p-2);
	st[0].push_back(make_pair(1,n));solve();
	for(int i=1;i<=n;i++,printf("\n"))
	for(int j=1;j<=n;j++)printf("%d ",dp[i][j]);
}
auoj44 矩阵求和
Problem

有一个 n×mn\times m 的矩阵,初始第 ii 行第 jj 列的值为 (i1)m+j(i-1)*m+j ,有三种操作:

  1. 交换两行
  2. 交换两列
  3. 求一个子矩阵做 kk 次二维前缀和后矩阵元素的和,模 109+710^9+7

n,m,q105,k10n,m,q\leq 10^5,k\leq 10

2s,512MB2s,512MB

Sol

设当前第 ii 行原来为第 wiw_i 行,第 jj 列原来为第 hjh_j 列 ,那么这个位置的元素为 (wi1)m+hi(w_i-1)*m+h_i

考虑进行二维前缀和的式子

vx,y=x1x,y1yx2x1,y2y1...xkxk1,ykyk1vxk,ykv_{x,y}^{'}=\sum_{x_1\leq x,y_1\leq y}\sum_{x_2\leq x_1,y_2\leq y_1}...\sum_{x_k\leq x_{k-1},y_k\leq y_{k-1}} v_{x_k,y_k}

相当于选出 kk 个二维坐标依次不降的点

设询问矩阵为 (x1,y1),(x2,y2)(x_1,y_1),(x_2,y_2) ,那么答案为

x1xx2y1yy2vx,yCx1x+kkCy1y+kk\sum_{x_1\leq x\leq x_2}\sum_{y_1\leq y\leq y_2}v_{x,y}C_{x_1-x+k}^kC_{y_1-y+k}^k

相当于 x1xx2y1yy2((wx1)(m1)+hy)Cx1x+kkCy1y+kk\sum_{x_1\leq x\leq x_2}\sum_{y_1\leq y\leq y_2}((w_x-1)*(m-1)+h_y)C_{x_1-x+k}^kC_{y_1-y+k}^k

等于 (m1)x1xx2(wx1)Cx1x+kky1yy2Cy1y+kk+x1xx2Cx1x+kky1yy2hyCy1y+kk(m-1)\sum_{x_1\leq x\leq x_2}(w_x-1)C_{x_1-x+k}^k\sum_{y_1\leq y\leq y_2}C_{y_1-y+k}^k+\sum_{x_1\leq x\leq x_2}C_{x_1-x+k}^k\sum_{y_1\leq y\leq y_2}h_yC_{y_1-y+k}^k

注意到 CikC_i^k 是一个关于 iikk 次多项式,拆开后维护 xi,yi,wxxi,hyyix^i,y^i,w_xx^i,h_yy^i 的区间和即可

复杂度 O(qk2+qklogn)O(qk^2+qk\log n)

Code
#include<cstdio>
using namespace std;
#define N 105000
#define K 11
#define mod 1000000007
int n,m,q,p1[N],p2[N],C[K][K],a,b,c,d,e,su1[K],su2[K],inv[K],f[K][K];
char op[10];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
struct sth{
	int v[K][N],v2[N],v3[N];
	void add1(int k,int x,int v1){
	for(int i=x;i<=1e5;i+=i&-i)v[k][i]=(v[k][i]+v1)%mod;}
	int query(int k,int x){int as=0;for(int i=x;i;i-=i&-i)as=(as+v[k][i])%mod;return as;}
	void modify1(int x,int y,int z)
	{
		int st1=v3[x],st2=z;
		for(int i=0;i<=10;i++)add1(i,x,(st2-st1+mod)%mod),st2=1ll*st2*y%mod*(mod-1)%mod,st1=1ll*st1*v2[x]%mod*(mod-1)%mod;
		v2[x]=y;v3[x]=z;
	}
}t1,t2,t3,t4;
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)t1.modify1(i,i,i-1),p1[i]=i,t3.modify1(i,i,1);
	for(int i=1;i<=m;i++)t2.modify1(i,i,i),p2[i]=i,t4.modify1(i,i,1);
	for(int i=0;i<=10;i++)C[i][i]=C[i][0]=1;
	for(int i=2;i<=10;i++)for(int j=1;j<i;j++)C[i][j]=C[i-1][j]+C[i-1][j-1];
	for(int i=0;i<=10;i++)inv[i]=pw(i,mod-2);
	for(int i=0;i<=10;i++)
	{
		f[i][0]=1;
		int tp2=1;
		for(int j=0;j<i;j++)
		for(int k=j+1;k>0;k--)
		f[i][k]=(f[i][k]+f[i][k-1])%mod,f[i][k-1]=1ll*f[i][k-1]*(mod-j)%mod;
		for(int j=1;j<=i;j++)tp2=1ll*tp2*inv[j]%mod;
		for(int j=0;j<=i;j++)f[i][j]=1ll*f[i][j]*tp2%mod;
	}
	while(q--)
	{
		scanf("%s",op+1);
		if(op[1]=='R')
		{
			scanf("%d%d",&a,&b);
			t1.modify1(a,a,p1[b]-1);t1.modify1(b,b,p1[a]-1);
			p1[a]^=p1[b]^=p1[a]^=p1[b];
		}
		else if(op[1]=='C')
		{
			scanf("%d%d",&a,&b);
			t2.modify1(a,a,p2[b]);t2.modify1(b,b,p2[a]);
			p2[a]^=p2[b]^=p2[a]^=p2[b];
		}
		else
		{
			scanf("%d%d%d%d%d",&a,&b,&c,&d,&e);
			for(int i=0;i<=10;i++)su1[i]=(t3.query(i,c)-t3.query(i,a-1)+mod)%mod,su2[i]=(t4.query(i,d)-t4.query(i,b-1)+mod)%mod;
			int v1=0,v2=0,v3=0,v4=0;
			for(int i=0;i<=e;i++)for(int j=0,tp2=1;j<=i;j++,tp2=1ll*tp2*(c+e)%mod)v1=(v1+1ll*tp2*f[e][i]%mod*su1[i-j]%mod*C[i][j])%mod;
			for(int i=0;i<=e;i++)for(int j=0,tp2=1;j<=i;j++,tp2=1ll*tp2*(d+e)%mod)v2=(v2+1ll*tp2*f[e][i]%mod*su2[i-j]%mod*C[i][j])%mod;
			for(int i=0;i<=10;i++)su1[i]=(t1.query(i,c)-t1.query(i,a-1)+mod)%mod,su2[i]=(t2.query(i,d)-t2.query(i,b-1)+mod)%mod;
			for(int i=0;i<=e;i++)for(int j=0,tp2=1;j<=i;j++,tp2=1ll*tp2*(c+e)%mod)v3=(v3+1ll*tp2*f[e][i]%mod*su1[i-j]%mod*C[i][j])%mod;
			for(int i=0;i<=e;i++)for(int j=0,tp2=1;j<=i;j++,tp2=1ll*tp2*(d+e)%mod)v4=(v4+1ll*tp2*f[e][i]%mod*su2[i-j]%mod*C[i][j])%mod;
			int as1=(1ll*v3*v2%mod*m+1ll*v1*v4)%mod;
			printf("%d\n",as1);
		}
	}
}
auoj45 西行寺无余涅槃
Problem

有一个数初始为0,给出 kkviv_i ,有 nn 次操作,每次给 kksis_i ,表示这次操作有 viv_i 种方式将这个数异或上 si(si<2m)s_i(s_i<2^m) ,这个数在每次操作中必须正好被异或一次,求最后这个数为 i(0i<2m)i(0\leq i<2^m) 的方案数,模 998244353998244353

n2k107,m+k20n*2^k\leq 10^7,m+k\leq 20

2s,512MB2s,512MB

Sol

FWT之后一个位置对另外一个位置的值的贡献为正负1,因此一次操作得到的集合幂级数FWT之后每个位置只有2k2^k 种取值

fi,Sf_{i,S} 表示第 ii 个位置,FWT后 SS 集合中元素对这个位置贡献为-1,其余元素贡献为1的操作数量

对于一个操作,考虑在 v1v_1 位置设成1,其余位置设成0,进行FWT,考虑这时第 ii 个位置的值为 sis_i,对于一个 fi,Sf_{i,S} ,如果 1S1\in S ,那么 fi,Sf_{i,S}sis_i 的贡献为-1,否则为1,因此有 si=S(1){1}Sfi,Ss_i=\sum_{S}(-1)^{|\{1\}\cap S|}f_{i,S}

显然有 (a\oplus b)\and c=(a\and c)\oplus(b\and c) ,因此FWT中 aba\oplus b 对位置 ii 的贡献为 aa 对这个位置的贡献乘上 bb 对这个位置的贡献

对于一个集合 TT ,考虑在 iTvi\oplus_{i\in T}v_i 的位置设成1,其余位置设成0,进行FWT,那么有 si=S(1)TSfi,Ss_i=\sum_{S}(-1)^{|T\cap S|}f_{i,S}

si,Ts_{i,T} 表示对 TT 集合进行上面的操作后第 ii 个位置的值,注意到 aa 的FWT加上 bb 的FWT等于 a+ba+b 的FWT,可以将所有操作的FWT一起做,这部分复杂度为 O(n2k+m2m+k)O(n2^k+m2^{m+k})

注意到 si,T=S(1)TSfi,Ss_{i,T}=\sum_S(-1)^{|T\cap S|}f_{i,S} ,因此 sis_i 等于 fif_i FWT后的结果,因此IFWT即可求出 fif_i

复杂度 O(n2k+(m+k)2m+k)O(n2^k+(m+k)2^{m+k})

Code
#include<cstdio>
using namespace std;
#define mod 998244353
int f[1050000],n,m,k,s[15],v[15],g[1025],lbit[1025],v2[1025],h[1050000];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=k;i++)scanf("%d",&s[i]),v2[0]=(v2[0]+s[i])%mod;
	for(int j=1;j<1<<k;j++)
	for(int l=k;l>0;l--)if(j&(1<<l-1))lbit[j]=l;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=k;j++)scanf("%d",&v[j]);
		g[0]=0;
		for(int j=1;j<1<<k;j++)g[j]=g[j^(1<<lbit[j]-1)]^v[lbit[j]];
		for(int j=0;j<1<<k;j++)f[(g[j]<<k)|j]++;
	}
	for(int l=2;l<=1<<m+k;l<<=1)
	for(int j=0;j<1<<m+k;j+=l)
	for(int s=j;s<j+(l>>1);s++)
	{
		int v1=f[s],v2=f[s+(l>>1)];
		f[s]=(v1+v2)%mod;
		f[s+(l>>1)]=(v1-v2+mod)%mod;
	}
	int inv=1;
	for(int i=1;i<=k;i++)inv=1ll*inv*(mod+1)/2%mod;
	for(int j=1;j<1<<k;j++)v2[j]=(v2[j^(1<<lbit[j]-1)]-2ll*s[lbit[j]]+2ll*mod)%mod;
	for(int i=0;i<1<<m;i++)
	{
		int as1=1;
		for(int j=0;j<1<<k;j++)
		as1=1ll*pw(v2[j],1ll*f[(i<<k)|j]*inv%mod)*as1%mod;
		h[i]=as1;
	}
	for(int l=2;l<=1<<m;l<<=1)
	for(int j=0;j<1<<m;j+=l)
	for(int s=j;s<j+(l>>1);s++)
	{
		int v1=h[s],v2=h[s+(l>>1)];
		h[s]=1ll*(mod+1)/2*(v1+v2)%mod;
		h[s+(l>>1)]=1ll*(mod+1)/2*(v1-v2+mod)%mod;
	}
	for(int i=0;i<1<<m;i++)printf("%d ",(h[i]+mod)%mod);
}
auoj46 鱼贯而入
Problem

PDF题面

Sol

显然如果不出现两个数模 lenlen 相同答案一定是0,因此 lenlen 一定是某个 ajaia_j-a_i 的约数

如果 p,kpp,kp 都是合法的 lenlen ,显然 pp 一定不比 kpkp

因此只需要找到所有满足小于自身的约数都小于等于 nn 的数即可

可以发现,除了质数,剩下的合法的数一定不会超过 p2p^2

因此只需要求出每一对差的所有质因子和所有不超过 n2n^2 的约数,暴力判断即可

复杂度 O(n3+n2v14)O(n^3+n^2v^{\frac1 4})O(n4+n2v14)O(n^4+n^2v^{\frac1 4})

Code
#include<cstdio>
#include<map>
#include<set>
#include<cstdlib>
#include<ctime>
using namespace std;
#define N 205
#define ll long long
int m,n;
ll f[11]={2,3,5,7,11,13,17,19,23},tp[233],v[N],su[N],ct,vl[N];
set<ll> fu,as;
ll mul(ll x,ll y,ll mod){ll tmp=(long double)x*y/mod;return (x*y-tmp*mod+mod)%mod;}
ll pw(ll a,ll p,ll k){ll as=1;while(p){if(p&1)as=mul(as,a,k);a=mul(a,a,k);p>>=1;}return as;}
ll mrtest(ll a,ll p){ll ct=0,st2=p-1;while(~st2&1)st2>>=1,ct++;tp[0]=pw(a,st2,p);for(int i=1;i<=ct;i++)tp[i]=mul(tp[i-1],tp[i-1],p);if(tp[ct]!=1)return 0;for(int i=ct;i>0;i--)if(tp[i]==1&&(tp[i-1]>1&&tp[i-1]<p-1))return 0;else if(tp[i]!=1)return 1;return 1;}
ll mr(ll p){if(p==1)return 0;for(int i=0;i<9;i++)if(f[i]==p)return 1;for(int i=0;i<9;i++)if(!mrtest(f[i],p))return 0;return 1;}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll pr(ll x)
{
	for(int i=0;i<9;i++)if(x%f[i]==0)return f[i];
	ll st=rand()%(x-1)+1,v1=1,v2=1,vl=1,ct=2;
	while(1)
	{
		v2=(mul(v2,v2,x)+st)%x;vl=mul(vl,v1<v2?v2-v1:v1-v2,x);
		ct++;
		if(ct%63==0)
		{
			ll g=gcd(vl,x);
			if(g>1&&g<x)return g;
			if(g==x)return x;
		}
		if((ct&-ct)==ct)
		{
			ll g=gcd(vl,x);
			if(g>1&&g<x)return g;
			if(g==x)return x;
			v1=v2;vl=1;
		}
	}
}
void justdoit(ll x)
{
	if(x==1)return;
	if(mr(x)){fu.insert(x);return;}
	ll st=x;while(st==x||st==1)st=pr(x);
	justdoit(st);justdoit(x/st);
}
void dfs(int d,ll x,ll las)
{
	if(x/las>n)return;
	if(d==ct+1){
	if(x>=n)as.insert(x);return;}
	dfs(d+1,x,las);
	for(int i=1;i<=su[d];i++)x*=v[d],dfs(d+1,x,v[d]);
}
int check(ll d)
{
	int su=0;
	map<ll,int> st;
	for(int i=1;i<=n;i++)
	{
		ll tp=vl[i]%d;
		while(st[tp])tp=(tp+1)%d,su++;
		st[tp]=1;
	}
	return su;
}
int main()
{
	scanf("%d%d",&m,&n);
	for(int i=1;i<=n;i++)scanf("%lld",&vl[i]);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	if(vl[i]>vl[j])
	{
		ct=0;fu.clear();
		justdoit(vl[i]-vl[j]);
		ll st2=vl[i]-vl[j];
		for(set<ll>::reverse_iterator it=fu.rbegin();it!=fu.rend();it++)
		{
			v[++ct]=*it;
			su[ct]=0;
			while(st2%v[ct]==0)st2/=v[ct],su[ct]++;
		}
		dfs(1,1,1);
	}
	int as1=0;
	for(set<ll>::iterator it=as.begin();it!=as.end();it++)as1=max(as1,check(*it));
	printf("%d\n",as1);
}
auoj47 同桌与室友
Problem

nn 个人,有一些人住双人宿舍,一些人住单间,也就是说一些人有唯一的一个室友,有些人则没有

同时有些人会和他的同桌共用一张双人桌,另一些人则单独坐

求出有多少个排列 pp ,满足 ii 换到 pip_i 的宿舍以及桌子上后,原本的室友以及同桌关系依旧不变,模 109+710^9+7

n2×105n\leq 2\times 10^5

1s,512MB1s,512MB

Sol

将同桌关系看成连蓝边,室友关系看成连红边,那么原图有若干环和链组成

链有三种形式:两侧蓝色,两侧红色,两侧一蓝一红

显然只有长度相同的环和长度形式相同的链间可以整体互换,乘上每一种出现次数的阶乘即可

对于两侧长度相同的链,可以翻转,因此每条这样的链答案再乘上2

对于一个环,可以旋转,因此每个环答案需要乘上环长除以2

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define N 400500
#define mod 1000000007
int ct[N][4];//1 - 1 1 - 2 2 - 2 circles
int n,k1,k2,a,b,fa[N],sz[N],as=1,s[N][2],is[N],fr[N];
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
int main()
{
	scanf("%d%d%d",&n,&k1,&k2);
	fr[0]=1;
	for(int i=1;i<=n;i++)fa[i+n]=fa[i]=i,sz[i]=2,s[i][0]=i,s[i][1]=i+n,fr[i]=1ll*fr[i-1]*i%mod;
	for(int i=1;i<=k1;i++)
	{
		scanf("%d%d",&a,&b);
		int a1=a,b1=b;a=finds(a),b=finds(b);
		sz[a]+=sz[b],fa[b]=a;
		s[a][s[a][0]!=a1]=s[b][s[b][0]==b1];
	}
	for(int i=1;i<=k2;i++)
	{
		scanf("%d%d",&a,&b);a+=n;b+=n;
		int a1=a,b1=b;a=finds(a);b=finds(b);
		if(a==b){is[a]=1;ct[sz[a]/2][3]++;
		as=1ll*as*sz[a]/2%mod;continue;}
		sz[a]+=sz[b],fa[b]=a;
		s[a][s[a][0]!=a1]=s[b][s[b][0]==b1];
	}
	for(int i=1;i<=n;i++)if(finds(i)==i&&!is[i])
	{
		int tp=(s[i][0]<=n)+(s[i][1]<=n);
		if(~tp&1)as=as*2%mod;
		ct[sz[i]/2][tp]++;
	}
	for(int i=0;i<4;i++)for(int j=1;j<=n;j++)as=1ll*as*fr[ct[j][i]]%mod;
	printf("%d\n",as);
}
auoj48 传送
Problem

有一棵树,边有边权,每个点有一个区间 [li,ri][l_i,r_i] ,花费 xx 的代价可以使得所有点的区间变成 [lix,ri+x][l_i-x,r_i+x]

你需要在每个点的区间中选择一个 aia_i ,使得对于任意的 i,j,dis(i,j)aiaji,j,dis(i,j)\geq |a_i-a_j|

求最小的代价使得存在合法方案

多组数据

T3,n106T\leq 3,n\leq 10^6

3s,512MB3s,512MB

Sol

注意到 aiajaiak1+ak1ak2+...+aklaj|a_i-a_j|\leq |a_i-a_{k_1}|+|a_{k_1}-a_{k_2}|+...+|a_{k_l}-a_j| ,因此只需要每条树边满足条件,整棵树就满足条件

对于每个点,可以求出这个点的 aia_i 在哪个区间中时子树内存在合法方案

一个点的区间就是所有儿子的区间各自平移之后的交

注意到区间的左边界一定形如 vxv-x ,右边界一定形如 v+xv+x ,那么可以求出每个点的区间后找最小的 xx

复杂度 O(n)O(n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 1050000
int n,l[N],r[N],t,ty,a,b,c,head[N],cnt;
struct edge{int t,next,v;}ed[N*2];
void adde(int f,int t,int v){ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;ed[++cnt]=(edge){f,head[t],v};head[t]=cnt;}
void dfs(int u,int fa){for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u),l[u]=max(l[u],l[ed[i].t]-ed[i].v),r[u]=min(r[u],r[ed[i].t]+ed[i].v);}
int rd(){char s=getchar();while(s<'0'||s>'9')s=getchar();int as=0;while(s>='0'&&s<='9')as=as*10+s-'0',s=getchar();return as;}
int main()
{
	scanf("%d%d",&t,&ty);
	while(t--)
	{
		scanf("%d",&n);cnt=0;for(int i=1;i<=n;i++)head[i]=0;
		for(int i=1;i<=n;i++)l[i]=rd();
		for(int i=1;i<=n;i++)r[i]=rd();
		for(int i=1;i<n;i++)a=rd(),b=rd(),c=rd(),adde(a,b,c);
		dfs(1,0);
		int as=0;
		for(int i=1;i<=n;i++)as=max(as,(l[i]-r[i]+1)/2);
		if(!ty)as=as>0;
		printf("%d\n",as);
	}
}
auoj49 生成树
Problem

给一个有红绿蓝三种颜色的边的图,求绿边数不超过 gg ,蓝边数不超过 bb 的生成树数量,模 109+710^9+7

n40n\leq 40

2s,512MB2s,512MB

Sol

fi,jf_{i,j} 表示有 ii 条绿边, jj 条蓝边的方案数

将红边权值设为1,绿边权值设为 xx ,蓝边权值设为 yy ,那么所有生成树的边权乘积的和即为 fi,jxiyj\sum f_{i,j}x^iy^j

因为次数不超过 n1n-1 ,设 y=xny=x^n ,那么变成了一元多项式

求出 n2n^2 个点的点值后插值即可

复杂度 O(n5)O(n^5)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define M 42
#define K 1650
#define mod 1000000007 
int n,m,s,t,a,b,c,f[M][M],v[K],v2[K],as[K],su[M][M][3],inv[K];
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%mod;a=1ll*a*a%mod;b>>=1;}return as;}
int det()
{
	int as=1;
	for(int i=1;i<n;i++)
	{
		int st2=i;
		for(int j=i;j<n;j++)
		if(f[j][i])st2=j;
		if(st2>i)as=mod-as;
		for(int j=1;j<n;j++)swap(f[st2][j],f[i][j]);
		if(!f[i][i])return 0;
		for(int j=i+1;j<n;j++)
		{
			int inv=1ll*f[j][i]*pw(f[i][i],mod-2)%mod;
			for(int k=i;k<n;k++)f[j][k]=(f[j][k]-1ll*f[i][k]*inv%mod+mod)%mod;
		}
	}
	for(int i=1;i<n;i++)as=1ll*as*f[i][i]%mod;
	return as;
}
int solve(int a,int b)
{
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	f[i][j]=0;
	for(int i=1;i<=n;i++)
	for(int j=i+1;j<=n;j++)
	{
		int vl=(su[i][j][0]+1ll*su[i][j][1]*a+1ll*su[i][j][2]*b)%mod;
		f[i][i]=(f[i][i]+vl)%mod;
		f[j][j]=(f[j][j]+vl)%mod;
		f[i][j]=(f[i][j]-vl+mod)%mod;
		f[j][i]=(f[j][i]-vl+mod)%mod;
	}
	return det();
}
void doit()
{
	int v1=1644;
	v2[0]=1;
	for(int i=1;i<=v1;i++)inv[i]=pw(i,mod-2);
	for(int i=1;i<=v1;i++)
	for(int j=i;j>=0;j--)
	v2[j+1]=(v2[j+1]+v2[j])%mod,v2[j]=1ll*v2[j]*(mod-i)%mod;
	for(int i=1;i<=v1;i++)
	{
		int st=1;
		for(int j=1;j<=v1;j++)if(j!=i)st=1ll*st*(mod+i-j)%mod;
		st=pw(st,mod-2);
		for(int j=0;j<=v1;j++)v2[j]=1ll*v2[j]*inv[i]%mod*(mod-1)%mod,v2[j+1]=(v2[j+1]-v2[j]+mod)%mod;
		for(int j=0;j<=v1;j++)as[j]=(as[j]+1ll*st*v[i]%mod*v2[j])%mod;
		for(int j=v1;j>=0;j--)v2[j+1]=(v2[j+1]+v2[j])%mod,v2[j]=1ll*v2[j]*(mod-i)%mod;
	}
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		if(a>b)a^=b^=a^=b;
		su[a][b][c-1]++;
	}
	for(int i=1;i<=1644;i++)v[i]=solve(i,pw(i,40));
	doit();
	int as1=0;
	for(int i=0;i<=s;i++)
	for(int j=0;j<=t;j++)
	as1=(as1+as[i+j*40])%mod;
	printf("%d\n",as1);
}
auoj50 简单数学题
Problem

给定 x,px,p ,求最小的 aa 使得 fibax(modp)fib_a\equiv x(\bmod p) 或输出无解

多组数据

T100,p2×109T\leq 100,p\leq 2\times10^9 ,pp 为质数且 p1,9(mod10)p\equiv 1,9(\bmod 10)

2s,512MB2s,512MB

Sol

fibi=(1+52)i(152)i5fib_i=\frac{(\frac{1+\sqrt 5}2)^i-(\frac{1-\sqrt 5}2)^i}{\sqrt 5}

因为 p1,9(mod10)p\equiv 1,9(\bmod 10) ,有 (p5)=1(\frac p 5)=1

又因为 (p5)(5p)=(1)(p1)(51)4=1(\frac p 5)(\frac 5 p)=(-1)^{\frac{(p-1)(5-1)} 4}=1 ,所以 (5p)=1(\frac 5 p)=1 ,即 55 存在模 pp 意义下的二次剩余

(1+52)i(152)i5x(modp)(\frac{1+\sqrt 5}2)^i-(\frac{1-\sqrt 5}2)^i\equiv\sqrt 5 x(\bmod p)

((1+52)i)25x(1+52)i(1)i0(modp)((\frac{1+\sqrt 5}2)^i)^2-\sqrt 5 x(\frac{1+\sqrt 5}2)^i-(-1)^i\equiv 0(\bmod p)

枚举 ii 的奇偶性,解二次方程后相当于求 (1+52)it(modp)(\frac{1+\sqrt 5}2)^i\equiv t(\bmod p) 的最小奇数/偶数解,BSGS时处理即可

复杂度 O(Tp)O(T\sqrt p)

Code
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<queue>
using namespace std;
#define ll long long
ll x,p,T,tp1;
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%p;a=1ll*a*a%p;b>>=1;}return as;}
struct comp{int a,b;};
comp operator *(comp a,comp b){return (comp){(1ll*a.a*b.a%p+1ll*a.b*b.b%p*tp1%p)%p,(1ll*a.b*b.a%p+1ll*a.a*b.b%p)%p};}
comp pw(comp a,int b){comp as=(comp){1,0};while(b){if(b&1)as=as*a;a=a*a;b>>=1;}return as;}
int cipolla(int a=5)
{
	if(a==0)return 0;
	if(pw(a,(p-1)/2)==p-1)return -1;
	int tp2=1;
	while(pw(tp1=(1ll*tp2*tp2%p-a+p)%p,(p-1)/2)!=p-1)tp2=(((rand()<<15)|rand())%p+p)%p+1;
	tp1=(1ll*tp2*tp2%p-a+p)%p;
	return pw((comp){tp2,1},(p+1)/2).a;
}
struct fuc{
	#define K 1050000
	int hd[K],nt[K],vl[K],v2[K],ct;
	queue<int> st;
	void init()
	{
		while(!st.empty())hd[st.front()]=0,st.pop();
		for(int i=0;i<=ct;i++)v2[i]=nt[i]=vl[i]=0;
		ct=0;
	}
	void ins(int a,int b)
	{
		int tp1=a&1048575;st.push(tp1);
		nt[++ct]=hd[tp1];hd[tp1]=ct;v2[ct]=b;vl[ct]=a;
	}
	int que(int a)
	{
		int tp1=a&1048575,as=-1;
		for(int i=hd[tp1];i;i=nt[i])if(vl[i]==a)as=v2[i];
		return as;
	}
}fu;
pair<ll,ll> bsgs(int a,int b)
{
	ll k=sqrt(p*4+1);
	ll st1=1,as1=-1,as2=-1;
	if(b==1)as1=0;
	for(int i=1;i<=k*2;i++)
	{
		st1=1ll*st1*a%p;
		if(st1==b)
		if(as1==-1)as1=i;
		else if(as2==-1)as2=i;
	}
	if(as2!=-1)return make_pair(as1,as2);
	st1=b;
	fu.init();
	for(int i=0;i<k;i++)fu.ins(st1,i),st1=1ll*st1*a%p;
	st1=pw(a,k);
	ll v2=st1;
	for(int i=1;i<=k;i++)
	{
		ll tp1=fu.que(v2);
		if(tp1!=-1)
		{
			ll v1=i*k-tp1;
			if(as1==-1)as1=v1;
			else if(as2==-1&&v1!=as1)as2=v1;
		}
		v2=1ll*v2*st1%p;
	}
	if(!as1)return make_pair(-1,-1);
	else if(!as2)as2=as1+p-1;
	return make_pair(as1,as2);
}
ll solve()
{
	if(!x)return 0;
	ll v1=cipolla(5);
	ll tp1=1ll*(1+v1)*(p+1)/2%p,tp3=1ll*(p+1-v1)*(p+1)/2%p,fu1=1ll*x*v1%p;
	ll v11=(tp1-tp3+p)%p;
	if(v11!=v1)tp1=tp3;
	ll as=1e17;
	ll tp2=(1ll*fu1*fu1+4)%p,fuc2=cipolla(tp2);
	if(fuc2!=-1)
	{
		ll as1=1ll*(fu1+fuc2)*(p+1)/2%p,as2=1ll*(fu1-fuc2+p)*(p+1)/2%p;
		pair<ll,ll> st1=bsgs(tp1,as1),st2=bsgs(tp1,as2);
		if(st1.first%2==0)as=min(as,st1.first);
		if(st1.second%2==0)as=min(as,st1.second);
		if(st2.first%2==0)as=min(as,st2.first);
		if(st2.second%2==0)as=min(as,st2.second);
	}
	tp2=(1ll*fu1*fu1+p-4)%p,fuc2=cipolla(tp2);
	if(fuc2!=-1)
	{
		ll as1=1ll*(fu1+fuc2)*(p+1)/2%p,as2=1ll*(fu1-fuc2+p)*(p+1)/2%p;
		pair<ll,ll> st1=bsgs(tp1,as1),st2=bsgs(tp1,as2);
		if(st1.first%2==1)as=min(as,st1.first);
		if(st1.second%2==1)as=min(as,st1.second);
		if(st2.first%2==1)as=min(as,st2.first);
		if(st2.second%2==1)as=min(as,st2.second);
	}
	return as>1e16?-1:as;
}
int main()
{
	scanf("%lld",&T);while(T--)scanf("%lld%lld",&x,&p),printf("%lld\n",solve());
}
auoj56 seed
Problem

nn 种种子,第 ii 种种子价值为 viv_i ,你拿到它时有 pip_i 次把它放回去的机会

你会随机拿出一个种子,如果可以放回去,你可以选择放回去并再拿一个种子,求最优策略下最后种子权值的期望

n105,pi20n\leq 10^5,\sum p_i\leq 20

1s,512MB1s,512MB

Sol

dpSdp_S 表示放回去的种子的可重集(放回多次算多次)为 SS 时的答案

枚举拿出来的是 pi=0p_i=0 的还是 pi>0p_i>0 的,第一种可以一起算,记忆化搜索即可

复杂度 O((pi)2pi)O((\sum p_i)2^{\sum p_i})

Code
#include<cstdio>
using namespace std;
int n,a,b,v[21][2],is1[21],ct,su;
long long t1;
long double dp[1050000];
long double dfs(int k)
{
	if(dp[k]>=0)return dp[k];
	long double as=(long double)1.0*t1/n;
	int is[21];
	for(int i=1;i<=ct;i++)is[i]=0;
	for(int i=1;i<=su;i++)if(!(k&(1<<i-1)))
	{
		if(is[is1[i]])continue;
		int v2=is1[i];is[v2]=1;
		long double f1=v[v2][1],f2=dfs(k|(1<<i-1));
		if(f2>f1)f1=f2;
		as+=f1/n;
	}
	for(int i=1;i<=ct;i++)if(!is[i])as+=(long double)1.0*v[i][1]/n;
	return dp[k]=as;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&a,&b);
		if(!a)t1+=b;
		else
		{
			v[++ct][0]=a;v[ct][1]=b;
			for(int i=su+1;i<=su+a;i++)is1[i]=ct;
			su+=a;
		}
	}
	for(int i=0;i<(1<<su);i++)dp[i]=-1;
	printf("%.4Lf\n",dfs(0));
}
auoj57 string
Problem

给定字符集大小 pp ,求有多少个长度不超过 nn 的字符串能被划分成不超过两个回文串,模 998244353998244353

n105n\leq 10^5

Sol

将字符串首尾相接形成环,字符串合法当且仅当在环上存在至少一条对称轴

考虑计算对称轴的数量,对于长度 nn ,如果 nn 为奇数,那么所有字符串的对称轴共有 npn+12n*p^{\frac{n+1}2} 条,如果为偶数,有 n2(pn2+pn+22)\frac n 2*(p^{\frac n 2}+p^{\frac{n+2} 2})

接着考虑如何去重,可以发现,如果一个环存在两条对称轴,那么这个环上的字符串一定是循环的,且循环节一定是一个题目中合法的串

fif_i 表示循环节为 ii 的约数的本质不同合法串数量, gig_i 表示循环节为 ii 的本质不同合法串数量,可以容斥通过 ff 求出 gg

最后枚举串长,枚举循环节,减去多算的对称轴数量即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
using namespace std;
int n,p,f[104040],as;
#define mod 998244353
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
void doit(int i,int j)
{
	as=(as-1ll*(i/j-1)*f[j]%mod*j%mod+mod)%mod;
}
int main()
{
	scanf("%d%d",&n,&p);
	for(int i=1;i<=n;i++)
	if(i&1)f[i]=pw(p,(i+1)/2);
	else f[i]=1ll*(mod+1)/2*(pw(p,i/2)+pw(p,i/2+1))%mod;
	for(int i=1;i<=n;i++)
	for(int j=i*2;j<=n;j+=i)f[j]=(f[j]-f[i]+mod)%mod;
	for(int i=1;i<=n;i++)
	if(i&1)as=(as+1ll*i*pw(p,(i+1)/2))%mod;
	else as=(as+1ll*i/2*pw(p,i/2)+1ll*i/2*pw(p,i/2+1))%mod;
	for(int i=1;i<=n;i++)
	for(int j=i;j<=n;j+=i)
	doit(j,i);
	printf("%d\n",as);
}
auoj58 tree
Problem

给定 xx ,质数 pp ,对于一条路径,定义它的权值为 xiwimodp\sum x^iw_i \bmod p ,其中 wiw_i 为经过的第 ii 条边的边权

给一棵树,求有多少个三元组 (a,b,c)(a,b,c) 满足 (a,b),(b,c),(a,c)(a,b),(b,c),(a,c) 的权值全部为0或者全部非0

n105n\leq 10^5

3s,512MB3s,512MB

Sol

对于一个三元组,考虑每一对边的状态是否相同

对于一个合法的三元组,有3对边相同

对于一个不合法的三元组,有1对边相同

因此可以通过计算合法的边对数计算答案

只需要求出对于每个点,有多少个点到它的路径权值为0,它到多少个点的路径权值为0

考虑点分治,可以将一条路径权值全部除以 xix^i ,使得根节点处乘的权值为 x0x^0

然后给出一边向上/向下路径的值就可以知道另外一边向下/向上路径的权值是多少时整条路径权值为0,map统计即可

pxp|x 特判

复杂度 O(nlog2n)O(n\log^2 n)

Code
#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
#define N 105000
#pragma GCC optimize(3)
int n,k,p,a,b,c,head[N],cnt,v2[N],dep[N],sz[N],as,vl,as2,s1[N],s2[N],vis[N],v[N],pw2[N],ipw2[N],f2[N];
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%p;a=1ll*a*a%p;b>>=1;}return as;}
struct edge{int t,next;}ed[N*2];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;}
void dfs1(int u,int fa)
{
	sz[u]=1;int mx=0;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&!vis[ed[i].t])dfs1(ed[i].t,u),sz[u]+=sz[ed[i].t],mx=max(mx,sz[ed[i].t]);
	mx=max(mx,vl-sz[u]);
	if(mx<as)as=mx,as2=u;
}
map<int,int> fu3,fu4;
void dfs2(int u,int fa)
{
	dep[u]=dep[fa]+1;
	fu4[f2[u]]++;fu3[v2[u]]++;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&!vis[ed[i].t])
	{
		dep[ed[i].t]=dep[u]+1;
		f2[ed[i].t]=(f2[u]-1ll*v[ed[i].t]*ipw2[dep[ed[i].t]]%p+p)%p;
		v2[ed[i].t]=(v2[u]+1ll*v[ed[i].t]*pw2[dep[ed[i].t]])%p;
		dfs2(ed[i].t,u);
	}
}
void dfs3(int u,int fa)
{
	if(fu3.count(f2[u]))s1[u]+=fu3[f2[u]];if(fu4.count(v2[u]))s2[u]+=fu4[v2[u]];
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&!vis[ed[i].t])dfs3(ed[i].t,u);
}
void dfs4(int u,int fa)
{
	fu4[f2[u]]++;fu3[v2[u]]++;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&!vis[ed[i].t])dfs4(ed[i].t,u);
}
void dfs5(int u,int fa)
{
	if(fu3.count(f2[u]))s1[u]-=fu3[f2[u]];if(fu4.count(v2[u]))s2[u]-=fu4[v2[u]];
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&!vis[ed[i].t])dfs5(ed[i].t,u);
}
void work(int u)
{
	fu3.clear();fu4.clear();
	vis[u]=1;
	dep[0]=-1;v2[u]=0;f2[u]=(p-v[u]-(v[u]==0?p:0));dfs2(u,0);dfs3(u,0);
	for(int i=head[u];i;i=ed[i].next)if(!vis[ed[i].t])
	fu3.clear(),fu4.clear(),dfs4(ed[i].t,u),dfs5(ed[i].t,u);
}
void doit(int u){work(u);for(int i=head[u];i;i=ed[i].next)if(!vis[ed[i].t])dfs1(ed[i].t,u),vl=sz[ed[i].t],as=1e7,dfs1(ed[i].t,u),doit(as2);}
int main()
{
	scanf("%d%d%d",&n,&k,&p);
	if(k%p==0){printf("%lld\n",1ll*n*n*n);return 0;}k%=p;
	pw2[0]=ipw2[0]=1;int inv=pw(k,p-2);
	for(int i=1;i<=n;i++)pw2[i]=1ll*pw2[i-1]*k%p,ipw2[i]=1ll*ipw2[i-1]*inv%p;
	for(int i=1;i<=n;i++)scanf("%d",&v[i]),v[i]%=p;
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	vl=n;as=1e7;dfs1(1,0);doit(as2);
	long long fuc=1ll*n*n*n*2;
	for(int i=1;i<=n;i++)fuc=(fuc-1ll*s1[i]*(n-s2[i])-2ll*s1[i]*(n-s1[i])-2ll*s2[i]*(n-s2[i])-1ll*s2[i]*(n-s1[i]));
	printf("%lld\n",fuc/2);
}
auoj62 string
Problem

给两个字符串,求第一个字符串有多少个长度与第二个字符串相同的子串满足与第二个字符串不同的位置不超过 kk

字符集大小为8

n106n\leq 10^6

5s,512MB5s,512MB

Sol

考虑计算每个位置开始的子串与第二个串匹配的数量

枚举每种字符,然后是一个差卷积,NTT即可

复杂度 O(nlogn)O(n\log n*|\small\sum|)

Code
#include<cstdio>
#include<cstring>
using namespace std;
#define N 2100500
#define mod 998244353
int sr[2][N],n,m,k,a[N],b[N],c[N],rev[N],ntt[N],as[N];
char s[N],t[N];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
void dft(int s,int *a,int t)
{
	for(int i=0;i<s;i++)ntt[rev[i]]=a[i];
	for(int l=2;l<=s;l<<=1)
	for(int i=0;i<s;i+=l)
	for(int j=i,ct1=l>>1;ct1<l;j++,ct1++)
	{
		int s1=ntt[j],s2=1ll*ntt[j+(l>>1)]*sr[t][ct1]%mod;
		ntt[j]=(s1+s2)%mod;ntt[j+(l>>1)]=(s1-s2+mod)%mod;
	}
	int inv=t==0?pw(s,mod-2):1;
	for(int i=0;i<s;i++)a[i]=1ll*inv*ntt[i]%mod;
}
void doit(char st)
{
	int l=1;while(l<=n+m)l<<=1;
	for(int i=0;i<l;i++)a[i]=b[i]=0;
	for(int i=1;i<=n;i++)if(s[i]==st)a[i]=1;
	for(int i=1;i<=m;i++)if(t[i]==st)b[m-i]=1;
	dft(l,a,1);dft(l,b,1);for(int i=0;i<l;i++)a[i]=1ll*a[i]*b[i]%mod;
	dft(l,a,0);
	for(int j=0;j<=n-m;j++)as[j]+=a[j+m];
}
int main()
{
	scanf("%d%s%s",&k,s+1,t+1);n=strlen(s+1);m=strlen(t+1);
	if(n<m){printf("0\n");return 0;}
	for(int t=0;t<=1;t++)
	for(int i=1;i<=21;i++)
	{
		sr[t][1<<i-1]=1;
		int tp1=pw(3,(mod-1)>>i),tp2=tp1;
		if(!t)tp1=pw(tp1,mod-2),tp2=tp1;
		for(int j=1;j<(1<<i-1);j++)sr[t][(1<<i-1)+j]=tp2,tp2=1ll*tp2*tp1%mod;
	}
	int l=1;while(l<=n+m)l<<=1;
	for(int i=0;i<l;i++)rev[i]=(rev[i>>1]>>1)|((i&1)*(l>>1));
	doit('s');doit('y');doit('f');
	doit('a');doit('k');
	doit('n');doit('o');doit('i');
	int as1=0;for(int j=0;j<=n-m;j++)if(as[j]>=m-k)as1++;
	printf("%d\n",as1);
}
auoj63 tree
Problem

对于有根树 T1T_1 ,用以下方式构造 T2T_2

选择一个点 xx ,将 (root,x)(root,x) 上的点建一棵二叉树,使得二叉树的中序遍历等于 (root,x)(root,x) 依次经过的点的顺序,然后对于链上的点的每一个不在链上的儿子,使用相同的方式建树,然后将建出的树的父亲设为自己

给一个 T2T_2 ,求所有可能的 T1T_1 中所有点深度和的最大值

n5000n\leq 5000

2s,512MB2s,512MB

Sol

操作可以看成将 T2T_2 划分为若干二叉树,并将每一个二叉树变成链

dpi,jdp_{i,j} 表示 ii 为根的子树中,当前根所在的二叉树大小为 jj ,以 ii为根时当前子树内的最大深度和

di=max(dpi,j)d_i=max(dp_{i,j}) ,转移枚举连了0~2个儿子,再枚举儿子间在链上的顺序,有

dpu,1=1+vsonufv+szvdp_{u,1}=1+\sum_{v\in son_u} f_v+sz_v

dpu,i=maxvsonu(tsonu,tv(ft+szt)+dpv,i1+szv+1)dp_{u,i}=max_{v\in son_u}(\sum_{t\in son_u,t\neq v}(f_t+sz_t)+dp_{v,i-1}+sz_v+1) (u在上面,一个儿子在下面)

dpu,i=maxvsonu(tsonu,tvft+dpv,i1+i(szuszv))dp_{u,i}=max_{v\in son_u}(\sum_{t\in son_u,t\neq v}f_t+dp_{v,i-1}+i*(sz_u-sz_v)) (u在下面,一个儿子在上面)

dpu,i=maxx,ysonu,xymaxj+k=i1(tsonu,tx,yft+dpx,j+dpy,k+(j+1)(szuszx))dp_{u,i}=max_{x,y\in son_u,x\neq y}\max_{j+k=i-1}(\sum_{t\in son_u,t\neq x,y}f_t+dp_{x,j}+dp_{y,k}+(j+1)*(sz_u-sz_x))

复杂度 O(n2)O(n^2)

Code
#include<cstdio>
#include<vector>
using namespace std;
#define N 5050
int dp[N][N],head[N],cnt,n,a,b,sz[N];
struct edge{int t,next;}ed[N*2];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;}
void dfs(int u,int fa)
{
	vector<int> sn;
	int su=0;sz[u]=1;
	for(int i=0;i<=n;i++)dp[u][i]=-1e9;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u),su+=dp[ed[i].t][0],sn.push_back(ed[i].t),sz[u]+=sz[ed[i].t];
	//choose zero
	dp[u][0]=dp[u][1]=su+sz[u];
	//choose one
	for(int i=0;i<sn.size();i++)
	for(int j=1;j<=sz[sn[i]];j++)
	dp[u][j+1]=max(dp[u][j+1],max(
	su-dp[sn[i]][0]+(j+1)*(sz[u]-sz[sn[i]])+dp[sn[i]][j],//other - u
	su-dp[sn[i]][0]+sz[u]+dp[sn[i]][j]//u - other
	));
	//choose two
	for(int i=0;i<sn.size();i++)
	for(int j=i+1;j<sn.size();j++)
	for(int k=1;k<=sz[sn[i]];k++)
	for(int l=1;l<=sz[sn[j]];l++)
	dp[u][k+l+1]=max(dp[u][k+l+1],max(
	su-dp[sn[i]][0]-dp[sn[j]][0]+(k+1)*(sz[u]-sz[sn[i]])+dp[sn[i]][k]+dp[sn[j]][l],//i - u - j
	su-dp[sn[i]][0]-dp[sn[j]][0]+(l+1)*(sz[u]-sz[sn[j]])+dp[sn[i]][k]+dp[sn[j]][l]//j - u - i
	));
	for(int i=1;i<=sz[u];i++)dp[u][0]=max(dp[u][0],dp[u][i]);
}
int main()
{
	scanf("%d",&n);
	for(int i=2;i<=n;i++)scanf("%d",&a),adde(a,i);
	dfs(1,0);
	printf("%d\n",dp[1][0]);
}
auoj64 sort
Problem

给一个序列,支持:

  1. 区间与一个数and
  2. 区间与一个数or
  3. 区间与一个数xor
  4. 区间排序

求出最后的序列

n,q105,vi<232n,q\leq 10^5,v_i<2^{32}

4s,512MB4s,512MB

Sol

一次4操作后,操作区间形成了有序段

对于一个有序段,可以用trie维护所有数

使用splay维护当前所有的有序段,以及每个有序段在4操作后的操作标记

分裂一个段时直接trie上分裂,同时复制标记即可

对于前三个操作,分裂后splay上区间打标记即可

对于4操作,找出所有需要合并的段,先将splay上的标记推到trie上,然后合并trie

合并trie下放标记时,可能有三种情况:

  1. 这一位数不变,直接无视这一位上下传标记即可
  2. 这一位取反,交换两个儿子即可
  3. 这一位无论原来是多少都会变成0/1,那么先合并两个儿子,然后接到一侧,再继续合并即可

复杂度 O(nlogn+nlogv)O(n\log n+n\log v)

Code
#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
#define N 505918
#define M 12021425
#define ui unsigned int
ui s1=(1ll<<32)-1;
struct sth{ui a,b,c;};
sth operator +(sth a,sth b)
{
	ui v1=b.c,v2=s1^b.a^b.b^b.c;
	b.b|=(a.b&v2)|(a.a&v1);
	b.a|=(a.a&v2)|(a.b&v1);
	b.c&=(s1^(a.a|a.b));
	b.c^=(a.c&(s1^b.a^b.b));
	return b;
}
//trie
sth lz[M];
int ch[M][2],fa[M],sz[M],ct;
int merge(int x,int y,int d);
void pushdown(int x,int d)
{
	lz[ch[x][0]]=lz[ch[x][0]]+lz[x];lz[ch[x][1]]=lz[ch[x][1]]+lz[x];
	if((lz[x].c>>d)&1)ch[x][0]^=ch[x][1]^=ch[x][0]^=ch[x][1];
	if((lz[x].b>>d)&1)ch[x][1]=merge(ch[x][0],ch[x][1],d-1),ch[x][0]=0;
	if((lz[x].a>>d)&1)ch[x][0]=merge(ch[x][0],ch[x][1],d-1),ch[x][1]=0;
	lz[x]=(sth){0,0,0};
}
void pushup(int x){sz[x]=sz[ch[x][0]]+sz[ch[x][1]];}
int merge(int x,int y,int d)
{
	if(!x||!y)return x+y;
	if(d>=0)pushdown(x,d),pushdown(y,d);
	sz[x]+=sz[y];
	ch[x][0]=merge(ch[x][0],ch[y][0],d-1);
	ch[x][1]=merge(ch[x][1],ch[y][1],d-1);
	return x;
}
pair<int,int> split(int x,int d,int k)
{
	if(k<=0)return make_pair(0,x);
	if(k>=sz[x])return make_pair(x,0);
	if(d<0){++ct;sz[ct]=sz[x]-k;sz[x]=k;return make_pair(x,ct);}
	pushdown(x,d);
	int tp=++ct;
	pair<int,int> s2=split(ch[x][1],d-1,k-sz[ch[x][0]]);
	pair<int,int> s1=split(ch[x][0],d-1,k);
	ch[x][0]=s1.first,ch[x][1]=s2.first;
	ch[tp][0]=s1.second,ch[tp][1]=s2.second;
	pushup(x);pushup(tp);return make_pair(x,tp);
}
ui getkth(int x,int d,int k,ui tp)
{
	if(d<0)return tp;
	pushdown(x,d);
	if(sz[ch[x][0]]>=k)return getkth(ch[x][0],d-1,k,tp);
	else return getkth(ch[x][1],d-1,k-sz[ch[x][0]],tp|(1u<<d));
}
int n,q,a,b,c,bel[N],st[N],lb[N],rb[N];
set<int> st1;
ui v[N],d;
ui justdoit(ui x,sth y){return (x&(s1^y.a)|y.b)^y.c;}
//splay
struct Splay{
	int ch[N][2],fa[N],rt,ct;
	sth lz1[N],vl[N];
	void pushdown(int x){vl[ch[x][0]]=vl[ch[x][0]]+lz1[x];vl[ch[x][1]]=vl[ch[x][1]]+lz1[x];lz1[ch[x][0]]=lz1[ch[x][0]]+lz1[x];lz1[ch[x][1]]=lz1[ch[x][1]]+lz1[x];lz1[x]=(sth){0,0,0};}
	void rotate(int x){int f=fa[x],g=fa[f],tp=ch[f][1]==x;pushdown(f);pushdown(x);ch[g][ch[g][1]==f]=x;fa[x]=g;ch[f][tp]=ch[x][!tp];fa[ch[x][!tp]]=f;ch[x][!tp]=f;fa[f]=x;}
	void splay(int x,int y=0){while(fa[x]!=y){int f=fa[x],g=fa[f];if(fa[f]!=y)rotate((ch[g][1]==f)^(ch[f][1]==x)?x:f);rotate(x);}if(!y)rt=x;}
	void doit(int x)
	{
		int tp=*(--st1.upper_bound(x));if(tp==x)return;
		int s1=bel[tp];splay(s1);pushdown(s1);ct++;
		pair<int,int> v1=split(st[s1],31,x-lb[s1]);
		ch[ct][1]=ch[s1][1];fa[ch[s1][1]]=ct;ch[s1][1]=0;ch[ct][0]=s1;fa[s1]=ct;
		rb[ct]=rb[s1];rb[s1]=x-1;lb[ct]=x;st[ct]=v1.second;st[s1]=v1.first;vl[ct]=vl[s1];
		bel[x]=ct;st1.insert(x);
	}
	void modify(int l,int r,sth tp)
	{
		doit(l);doit(r+1);
		int v1=*(--st1.lower_bound(l)),v2=*(st1.lower_bound(r+1));
		v1=bel[v1];v2=bel[v2];
		splay(v1);splay(v2,v1);
		pushdown(v1);pushdown(v2);
		int t1=ch[v2][0];
		vl[t1]=vl[t1]+tp;lz1[t1]=lz1[t1]+tp;
	}
	void doit2(int x,int y)
	{
		if(!x)return;
		pushdown(x);
		lz[st[x]]=lz[st[x]]+vl[x];vl[x]=(sth){0,0,0};
		if(x!=y)st[y]=merge(st[x],st[y],31);
		doit2(ch[x][0],y);doit2(ch[x][1],y);
		ch[x][0]=ch[x][1]=0;if(x!=y)fa[x]=0;
		st1.erase(lb[x]);
	}
	void modify2(int l,int r)
	{
		doit(l);doit(r+1);
		int v1=*(--st1.lower_bound(l)),v2=*(st1.lower_bound(r+1));
		v1=bel[v1];v2=bel[v2];
		splay(v1);
		splay(v2,v1);
		pushdown(v1);pushdown(v2);
		doit2(ch[v2][0],ch[v2][0]);
		st1.insert(l);bel[l]=ch[v2][0];
		lb[ch[v2][0]]=l;rb[ch[v2][0]]=r;
	}
	void getans(int x)
	{
		if(!x)return;
		pushdown(x);getans(ch[x][0]);
		if(lb[x]&&rb[x]<=n)for(int i=lb[x];i<=rb[x];i++)printf("%u ",justdoit(getkth(st[x],31,i-lb[x]+1,0),vl[x]));
		getans(ch[x][1]);
	}
}tr;
void init()
{
	for(int i=1;i<=n;i++)
	{
		int s1=++ct;st[i+1]=s1;sz[s1]=1;
		lb[i+1]=rb[i+1]=i;
		for(int j=31;j>=0;j--)
		{
			int tp=(v[i]>>j)&1;
			ch[s1][tp]=++ct;s1=ct;sz[ct]=1;
		}
	}
	lb[n+2]=rb[n+2]=n+1;tr.ct=n+2;
	for(int i=0;i<=n+1;i++)bel[i]=i+1,st1.insert(i);
	for(int i=1;i<=n+2;i++)tr.fa[i]=i-1,tr.ch[i][1]=(i==n+2?0:i+1);
	tr.rt=1;
}
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)scanf("%u",&v[i]);
	init();
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		if(a==2)scanf("%u",&d),tr.modify(b,c,(sth){s1^d,0,0});
		else if(a==1)scanf("%u",&d),tr.modify(b,c,(sth){0,d,0});
		else if(a==3)scanf("%u",&d),tr.modify(b,c,(sth){0,0,d});
		else tr.modify2(b,c);
	}
	tr.getans(tr.rt);
}
auoj65 小B的班级
Problem

给一棵带边权的树,在点上随机生成 mm 个红点和 mm 个蓝点

你需要将它们两两配对,使得每一对的距离和最大

求出对于所有 n2mn^{2m} 种点的位置,最大距离和的和,模 109+710^9+7

n,m2500n,m\leq 2500

1s,256MB1s,256MB

Sol

如果一条边一侧有 aa 个红点, bb 个蓝点,那么这条边最多被算 min(i,mj)+min(j,mi)=min(i+j,2mij)min(i,m-j)+min(j,m-i)=min(i+j,2m-i-j)

显然可以找到一个点,使得这个点的每个子树内,红点与蓝点的数量和不超过 mm

以这个点为根,不同子树内配对,一定可以配完

因此可以做到让每条边算 min(i+j,2mij)min(i+j,2m-i-j)

如果一条边一侧的子树大小为 xx ,那么贡献为 wi(i=0mj=0mmin(i+j,2mij)xi(nx)miCmixj(nx)mjCmj)w_i*(\sum_{i=0}^m\sum_{j=0}^mmin(i+j,2m-i-j)x^i(n-x)^{m-i}C_m^ix^j(n-x)^{m-j}C_m^j)

枚举 iijj 的贡献会分成两段,维护 xj(nx)mjCmjx^j(n-x)^{m-j}C_m^jxj(nx)mjCmjjx^j(n-x)^{m-j}C_m^j*j 的前缀和即可

复杂度 O(nm)O(nm)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 2505
#define mod 1000000007
int n,m,a,b,d,sz[N],head[N],cnt,c[N][N],as,as2[N],pw[N][N],f[N],suf[N],suf2[N];
struct edge{int t,next,v;}ed[N*2];
void adde(int f,int t,int v){ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;ed[++cnt]=(edge){f,head[t],v};head[t]=cnt;}
int solve(int s)
{
	if(n-s<s)s=n-s;
	if(as2[s])return as2[s];
	int as3=0;
	for(int i=0;i<=m;i++)f[i]=1ll*pw[s][i]*pw[n-s][m-i]%mod*c[m][i]%mod;
	suf[0]=f[0];for(int i=1;i<=m;i++)suf[i]=(suf[i-1]+f[i])%mod;
	for(int i=1;i<=m;i++)suf2[i]=(suf2[i-1]+1ll*f[i]*i)%mod;
	for(int j=0;j<=m;j++)as3=(as3+1ll*f[j]*(1ll*j*suf[m-j]%mod+suf2[m-j])%mod+2ll*f[j]*m%mod*(suf[m]-suf[m-j])-1ll*f[j]*(1ll*j*(suf[m]-suf[m-j])%mod+suf2[m]-suf2[m-j]))%mod;
	return as2[s]=(as3+mod)%mod;
}
void dfs(int u,int fa)
{
	sz[u]=1;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u),sz[u]+=sz[ed[i].t],as=(as+1ll*ed[i].v*solve(sz[ed[i].t]))%mod;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<=m;i++)c[i][i]=c[i][0]=1;
	for(int i=2;i<=m;i++)
	for(int j=1;j<i;j++)
	c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	for(int i=0;i<=n;i++)
	{
		pw[i][0]=1;
		for(int j=1;j<=m;j++)pw[i][j]=1ll*pw[i][j-1]*i%mod;
	}
	for(int i=1;i<n;i++)scanf("%d%d%d",&a,&b,&d),adde(a,b,d);
	dfs(1,0);
	printf("%d\n",as);
}
auoj66 小B的环
Problem

给一个环上的字符串,对于每一个 kk ,求出删去连续 kk 个字符后,能否是剩下的串没有两个相邻位置相同且首尾不同

多组数据

n5×106\sum n\leq 5\times10^6

2s,512MB2s,512MB

Sol

将环倍长,变为链上的问题

如果没有两个相邻字符相同,如果剩下 kk 个字符不合法,那么一定有 i,ai=ai+k1\forall i,a_i=a_{i+k-1}

那么相当于 a1,...,n=ak,...,n+k1a_{1,...,n}=a_{k,...,n+k-1} ,可以kmp/hash判断

否则,因为不能出现相邻两个相同,所以可以对于每一段分开做上面的东西

复杂度 O(n)O(n)

Code
#include<cstdio>
#include<cstring>
using namespace std;
#define N 5000500
int n,fail[N*2],is[N],as[N],is2[N];
char s[N*2],t[N];
void solve0()
{
	for(int i=n+1;i<=n*2;i++)s[i]=s[i-n];
	for(int i=2;i<=n*2;i++)
	{
		int st=fail[i-1];
		while(st&&s[st+1]!=s[i])st=fail[st];
		if(s[st+1]==s[i])fail[i]=st+1;
		else fail[i]=0;
	}
	int st4=n*2-fail[n*2];
	if(st4>n)st4=n;
	for(int i=0;i<n;i++){int st1=i+1;if(st1%st4==0)printf("0");else printf("1");}
	printf("\n");
}
void doit(int l,int r)
{
	int le=0;
	for(int i=l;;i=(i==n?1:i+1)){t[++le]=s[i];if(i==r)break;}
	for(int i=1;i<=le;i++)is2[i]=0;
	for(int i=2;i<=le;i++)
	{
		int st=fail[i-1];
		while(st&&t[st+1]!=t[i])st=fail[st];
		if(t[st+1]==t[i])fail[i]=st+1;
		else fail[i]=0;
	}
	for(int i=le;i;i=fail[i])is2[le-i+1]=1;
	for(int i=1;i<=le;i++)as[i]|=!is2[i];
}
int main()
{
	while(~scanf("%s",s+1))
	{
		n=strlen(s+1);
		int fg=0;
		for(int i=1;i<=n;i++)is[i]=s[i]==s[i==n?1:i+1],fg|=is[i];
		if(!fg)solve0();
		else
		{
			for(int i=0;i<=n;i++)as[i]=0;
			int las=0;
			for(int i=1;i<=n;i++)if(is[i])las=i==n?1:i+1;
			for(int i=1;i<=n;i++)if(is[i])doit(las,i),las=i+1;
			for(int i=0;i<n;i++)printf("%d",as[n-i]);
			printf("\n");
		}
	}
}
auoj68 骨灰
Problem

给一个网格图,有 nn 个格子为黑色,求有多少个矩形满足

  1. 长宽中大的一个是小的一个的2倍
  2. 所有边界上的格子均为黑色
  3. 短边长属于一个给定的集合 SS

n105n\leq 10^5

2s,1024MB2s,1024MB

Sol

考虑L型状的数量

对于一个黑色格子数大于 n\sqrt n 的行,每一个黑色格子最多对应一个一条边在这行上面的L型,这部分不超过 O(n1.5)O(n^{1.5})

对于剩下的行,每个黑格子最多向右延伸 n\sqrt n 个,因此这部分也不超过 O(n1.5)O(n^{1.5})

将每条对角线上的点拿出来考虑,预处理每个点向四个方向延伸的长度,枚举每个点,再向后枚举这个点矩形的对应顶点,根据上面的分析复杂度为 O(n1.5)O(n^{1.5}),需要卡常

Code
#include<cstdio>
#include<vector>
#include<algorithm>
#pragma GCC optimize("-Ofast")
using namespace std;
#define N 1060000
int n,as,xl[N],xr[N],yl[N],yr[N],ct,st[N>>1][4],st2[N>>1][4];
char st1[N];
struct pt{int x,y,id;}s[N],s2[N];
bool cmp1(pt a,pt b){return a.x==b.x?a.y<b.y:a.x<b.x;}
bool cmp2(pt a,pt b){return a.y==b.y?a.x<b.x:a.y<b.y;}
vector<pt> fu[N*3];
int main()
{
	scanf("%d%s",&n,st1+1);
	for(int i=1;i<=n;i++)scanf("%d%d",&s[i].x,&s[i].y),s[i].id=i;
	sort(s+1,s+n+1,cmp2);
	for(int i=1;i<=n;i++)s2[i]=s[i];
	for(int i=1;i<=n;i++)if(s[i].y==s[i-1].y&&s[i].x==s[i-1].x+1)xl[s[i].id]=xl[s[i-1].id]+1;
	for(int i=n;i>=1;i--)if(s[i].y==s[i+1].y&&s[i].x==s[i+1].x-1)xr[s[i].id]=xr[s[i+1].id]+1;
	sort(s+1,s+n+1,cmp1);
	for(int i=1;i<=n;i++)if(s[i].x==s[i-1].x&&s[i].y==s[i-1].y+1)yl[s[i].id]=yl[s[i-1].id]+1;
	for(int i=n;i>=1;i--)if(s[i].x==s[i+1].x&&s[i].y==s[i+1].y-1)yr[s[i].id]=yr[s[i+1].id]+1;
	for(int i=1;i<=n;i++)fu[2*s[i].x+s[i].y].push_back(s[i]);
	for(int i=1;i<=n*3;i++)if(fu[i].size()&&fu[i-1].size())
	{
		int las=0,s2=fu[i-1].size(),s1=fu[i].size();
		for(int j=0;j<s1;j++)st2[j][0]=fu[i][j].x+xr[fu[i][j].id],st2[j][1]=fu[i][j].y-yl[fu[i][j].id],st2[j][2]=fu[i][j].x,st2[j][3]=fu[i][j].y;
		for(int j=0;j<s2;j++)st[j][0]=fu[i-1][j].x,st[j][1]=fu[i-1][j].y,st[j][2]=fu[i-1][j].x-xl[fu[i-1][j].id],st[j][3]=fu[i-1][j].y+yr[fu[i-1][j].id];
		for(int j=0;j<fu[i].size();j++)
		{
			int v1=st2[j][0],v2=st2[j][1],v3=st2[j][2],v4=st2[j][3];
			while(las<s2&&st[las][0]<v3)las++;
			int tp=las;
			while(tp<s2)
			{
				if(v1<st[tp][0]||v2>st[tp][1])break;
				if(st[tp][2]<=v3&&st[tp][3]>=v4)
				if(st1[st[tp][0]-v3+1]=='1')as++;
				tp++;
			}
		}
	}
	for(int i=1;i<=n*3;i++)fu[i].clear();
	for(int i=1;i<=n;i++)s[i]=s2[i],xl[i]^=yl[i]^=xl[i]^=yl[i],xr[i]^=yr[i]^=xr[i]^=yr[i],s[i].x^=s[i].y^=s[i].x^=s[i].y;
	for(int i=1;i<=n;i++)fu[2*s[i].x+s[i].y].push_back(s[i]);
	for(int i=1;i<=n*3;i++)if(fu[i].size()&&fu[i-1].size())
	{
		int las=0,s2=fu[i-1].size(),s1=fu[i].size();
		for(int j=0;j<s1;j++)st2[j][0]=fu[i][j].x+xr[fu[i][j].id],st2[j][1]=fu[i][j].y-yl[fu[i][j].id],st2[j][2]=fu[i][j].x,st2[j][3]=fu[i][j].y;
		for(int j=0;j<s2;j++)st[j][0]=fu[i-1][j].x,st[j][1]=fu[i-1][j].y,st[j][2]=fu[i-1][j].x-xl[fu[i-1][j].id],st[j][3]=fu[i-1][j].y+yr[fu[i-1][j].id];
		for(int j=0;j<fu[i].size();j++)
		{
			int v1=st2[j][0],v2=st2[j][1],v3=st2[j][2],v4=st2[j][3];
			while(las<s2&&st[las][0]<v3)las++;
			int tp=las;
			while(tp<s2)
			{
				if(v1<st[tp][0]||v2>st[tp][1])break;
				if(st[tp][2]<=v3&&st[tp][3]>=v4)
				if(st1[st[tp][0]-v3+1]=='1')as++;
				tp++;
			}
		}
	}
	printf("%d\n",as);
}
auoj69 智子
Problem

给定 kk ,对于一堆 nn 个石子,两人轮流操作

  1. 先手第一次取1个石子
  2. 设对方上一次取了 tt 个石子,这一次你可以取 [1,k+t][1,k+t] 个石子

取最后一个石子的人获胜

求出在 [l,r][l,r] 中有多少个 nn 先手必胜

l,r,k1014l,r,k\leq 10^{14}

2s,1024MB2s,1024MB

Sol

sgi,jsg_{i,j} 表示还剩 ii 个棋子,上一个人取了 jj 个时的sg值,观察可以发现 sgn,1sg_{n,1} 存在循环节,且循环形如

0 1 2 ... k+1
0 1
0 1 2 3
0 1 ... 7
0 1 ... 15
...
0 1 ... 2^x-1
0 1 2 ... k+1
0 1
0 1 2 3
0 1 ... 7
0 1 ... 15
...
0 1 ... 2^y-1

其中 xx 为最大的满足 2xk+12^{x}\leq k+1 的数, yy 为最大的满足 2y2x+1(k+1)2^y\leq 2^{x+1}-(k+1) 的数

一个循环节内先手必胜的位置为 sgn1,1=0sg_{n-1,1}=0 的位置,因此只有 logk\log k 个位置,暴力即可

复杂度 O(logk)O(\log k)

Code
#include<cstdio>
#include<vector>
using namespace std;
#define ll long long
vector<ll> pts;
ll k,x,y,fu;
ll solve(ll x)
{
	if(x<1)return x;
	ll as1=(x/fu)*pts.size();x%=fu;
	for(int i=0;i<pts.size();i++)if(pts[i]<=x)as1++;
	return as1;
}
int main()
{
	scanf("%lld%lld%lld",&k,&x,&y);
	fu=k+2;
	ll tp=2;while(tp<=k+1)pts.push_back(fu),fu+=tp,tp<<=1;
	pts.push_back(fu),fu+=k+2;
	ll tp2=2,tp3=tp-(k+1);
	while(tp2<=tp3)pts.push_back(fu),fu+=tp2,tp2<<=1;
	pts.push_back(fu);
	printf("%lld\n",solve(y-1)-solve(x-2));
}
auoj70 墓地
Problem

给定 nn 个物品,每种物品的数量 kk 相同,每种物品有重量 wiw_i,以及第一次选的价值 aia_i 和以后选一次的价值 bib_i

qq 个版本,其中第 ii 个版本为在第 fif_i 个版本上修改一个物品的属性得到的

给定 mm ,对于每个版本求出总重量不超过 mm 时的最大收益

n,m,q3500n,m,q\leq 3500

1s,1024MB1s,1024MB

Sol

考虑将版本的关系看成树,那么一次修改的范围是这个版本的子树,除去每一个再次修改了这个物品的子树,相当于一个dfs序区间除去若干个dfs序区间

从下往上做,对于一次修改的物品,计算了它涉及的dfs序区间后,它子树内这种物品的修改就可以直接无视了,因此总的区间数为 O(n+q)O(n+q)

然后线段树分治,使用单调队列/二进制分组优化多重背包转移即可

复杂度 O((n+q)mlogn)O((n+q)m\log n)O((n+q)mlognlogk)O((n+q)m\log n\log k) 实测第二个跑得更快

Code
#include<cstdio>
#include<vector>
using namespace std;
#define N 3505
#pragma GCC optimize("-Ofast")
struct sth{int l,r;};
vector<sth> fuc[N];
int n,m,k,l,s[N][3],q[N][4],head[N],cnt,id[N],ct,tid[N];
struct edge{int t,next;}ed[N*2];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;}
struct sth2{int l,r,a,b,c;};
vector<sth2> fu[17][2];
long long s1[17][2][N],v2[N],as[N];
void dfs(int u,int fa)
{
	id[u]=++ct;tid[ct]=u;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa)dfs(ed[i].t,u);
	if(fa)
	{
		int las=ct;
		while(fuc[q[u][0]].size()&&fuc[q[u][0]].back().r>=id[u])
		{
			sth tp=fuc[q[u][0]].back();fuc[q[u][0]].pop_back();
			fu[0][0].push_back((sth2){tp.r+1,las,s[q[u][0]][0],q[u][1],q[u][2]});
			las=tp.l-1;
		}
		fuc[q[u][0]].push_back((sth){id[u],ct});
		fu[0][0].push_back((sth2){id[u],las,s[q[u][0]][0],q[u][1],q[u][2]});
	}
	else
	{
		for(int i=1;i<=n;i++)
		{
			int las=ct;
			while(fuc[i].size())
			{
				sth tp=fuc[i].back();fuc[i].pop_back();
				fu[0][0].push_back((sth2){tp.r+1,las,s[i][0],s[i][1],s[i][2]});
				las=tp.l-1;
			}
			fu[0][0].push_back((sth2){1,las,s[i][0],s[i][1],s[i][2]});
		}
	}
}
void ins(int x,int y,int a,int b,int c)
{
	int fu2=l-1;
	for(int i=0;i<=k;i++)v2[i]=-1e18;
	for(int i=a;i<=k;i++)v2[i]=s1[x][y][i-a]+b;
	if(1ll*a*(l-1)>=k)for(int i=a;i<=k;i++)v2[i]=max(v2[i],v2[i-a]+c);
	else
	{
	for(int tp=1;tp<=k;tp<<=1)
	if(fu2>=tp)
	{
		fu2-=tp;
		long long s11=1ll*a*tp,s2=1ll*c*tp;
		for(int i=k;i>=s11;i--)v2[i]=max(v2[i],v2[i-s11]+s2);
	}
	long long s11=1ll*a*fu2,s2=1ll*c*fu2;
	for(int i=k;i>=s11;i--)v2[i]=max(v2[i],v2[i-s11]+s2);
	}
	for(int i=0;i<=k;i++)s1[x][y][i]=max(s1[x][y][i],v2[i]);
}
void cdq(int l,int r,int d,int s)
{
	fu[d+1][0].clear();fu[d+1][1].clear();
	for(int i=0;i<fu[d][s].size();i++)
	if((fu[d][s][i].l<=l&&fu[d][s][i].r>=r))ins(d,s,fu[d][s][i].a,fu[d][s][i].b,fu[d][s][i].c);
	if(l==r){for(int i=0;i<=k;i++)as[tid[l]]=max(as[tid[l]],s1[d][s][i]);return;}
	int mid=(l+r)>>1;
	for(int i=0;i<fu[d][s].size();i++)
	if(!(fu[d][s][i].l<=l&&fu[d][s][i].r>=r))
	{
		if(fu[d][s][i].l<=mid)fu[d+1][0].push_back(fu[d][s][i]);
		if(fu[d][s][i].r>mid)fu[d+1][1].push_back(fu[d][s][i]);
	}
	for(int i=0;i<=k;i++)s1[d+1][0][i]=s1[d+1][1][i]=s1[d][s][i];
	cdq(l,mid,d+1,0);cdq(mid+1,r,d+1,1);
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&k,&l);
	for(int i=1;i<=n;i++)scanf("%d%d%d",&s[i][0],&s[i][1],&s[i][2]);
	for(int i=1;i<=m;i++)scanf("%d%d%d%d",&q[i+1][3],&q[i+1][0],&q[i+1][1],&q[i+1][2]),adde(i+1,q[i+1][3]+1);
	dfs(1,0);for(int i=1;i<=k;i++)s1[0][0][i]=-1e18;
	cdq(1,m+1,0,0);
	for(int i=1;i<=m;i++)printf("%lld\n",as[i+1]);
}
auoj77 Three
Problem

给一个序列,求出对于每个子区间,区间内最大的三个数的乘积的和,模 109+710^9+7

n106n\leq 10^6

3s,1024MB3s,1024MB

Sol

考虑在第三大的数处计算这个区间的贡献

从大到小加入所有数,加入一个数时,找出它左右分别最靠近的三个比它数,那么它作为第三大的区间有三种情况(它和左边两个数,它和右边两个数,它和左右个一个数),分别计算贡献即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
#define N 1000005
#define mod 1000000007
multiset<int> tp;
int n,v[N],st[N],as,pr[N],nt[N];
bool cmp(int a,int b){return v[a]>v[b];}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]),st[i]=i;
	sort(st+1,st+n+1,cmp);
	tp.insert(0);tp.insert(n+1);
	nt[n+1]=n+1;
	for(int i=1;i<=n;i++)
	{
		multiset<int>::iterator it=tp.lower_bound(st[i]);
		int v4=*it;nt[st[i]]=v4,pr[v4]=st[i];int v5=nt[v4],v6=nt[v5];
		it--;int v3=*it;nt[v3]=st[i],pr[st[i]]=v3;int v2=pr[v3],v1=pr[v2];
		tp.insert(st[i]);
		int v11=0;
		v11=(v11+1ll*v[v2]*v[v3]%mod*(v4-st[i])%mod*(v2-v1))%mod;
		v11=(v11+1ll*v[v3]*v[v4]%mod*(v5-v4)%mod*(v3-v2))%mod;
		v11=(v11+1ll*v[v4]*v[v5]%mod*(st[i]-v3)%mod*(v6-v5))%mod;
		as=(as+1ll*v11*v[st[i]])%mod;
	}
	printf("%d\n",as);
}
auoj78 Seat
Problem

nn 排座位,第 ii 排有 aia_i 个座位

会有 ai\sum a_i 个人进来,每个人会选择当前连续空着的段中最长的一段,并坐在这一段的中间

qq 个询问,每次询问第 kk 个进来的人选择的段的长度

n106,ai109n\leq 10^6,a_i\leq 10^9

1s,2048MB1s,2048MB

Sol

可以发现,对于两个数 p,p1p,p-1 ,它们分裂之后只会产生两种数,且这两种数相差不超过1

因此总的长度种数不超过 O(nlogv)O(n\log v)

因此可以求出每一个长度出现了多少次,然后暴力

注意到从大到小选择段,分裂产生的数也是递减的,因此维护两个队列,第一个维护原来的段,第二个维护所有分裂出来的段,每次在两边选一个长度大的出来即可

如果两边的长度相同,需要一起拿出来,不然如果分出两个不同的数时会出事

复杂度 O(nlogv)O(n\log v)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#pragma GCC optimize("-Ofast")
#define N 1050500
struct sth{int a;long long b;}v1[N],v2[N*10];
int n,x,a,b,c,m,q,l1,l2,v[N],st2[N],ct=-1,l3=1,ct2=-1,as[N];
long long qu[N],nw;
bool cmp2(int a,int b){return qu[a]<qu[b];}
void ins2(int a,long long b)
{
	if(a==0)return;
	if(ct2>=0&&v2[ct2].a==a)v2[ct2].b+=b;
	else v2[++ct2]=(sth){a,b};
}
void doit(int a,long long b)
{
	nw+=b;
	while(l3<=q&&qu[st2[l3]]<=nw)as[st2[l3]]=a,l3++;
}
int main()
{
	scanf("%d%d%d%d%d%d%d",&n,&x,&a,&b,&c,&m,&q);
	v[1]=x;for(int i=2;i<=n;i++)v[i]=(1ll*v[i-1]*v[i-1]%m*a+1ll*b*v[i-1]+c)%m+1;
	sort(v+1,v+n+1);
	for(int i=n;i>=1;i--)
	{
		if(ct>=0&&v1[ct].a==v[i])v1[ct].b++;
		else v1[++ct]=(sth){v[i],1};
	}
	for(int i=1;i<=q;i++)scanf("%lld",&qu[i]),st2[i]=i;
	sort(st2+1,st2+q+1,cmp2);
	while(l3<=q)
	{
		sth as;
		if(l1>ct)as=v2[l2++];
		else if(l2>ct2)as=v1[l1++];
		else if(v1[l1].a==v2[l2].a){as.a=v1[l1].a;as.b=v1[l1].b+v2[l2].b,l1++,l2++;}
		else if(v1[l1].a>v2[l2].a)as=v1[l1++];
		else as=v2[l2++];
		doit(as.a,as.b);
		ins2(as.a/2,as.b);
		ins2(as.a-as.a/2-1,as.b);
	}
	for(int i=1;i<=q;i++)printf("%d\n",as[i]);
}
auoj83 菱形
Problem

PDF题面

1s,1024MB1s,1024MB

Sol

开头和结尾不好处理,可以枚举从开头菱形的哪个点出发,到结尾菱形的哪个点结束

考虑中间的部分,显然只能向下和向右走

如果这一步和上一步的方向相同,这一步需要3的时间,不同则需要2的时间

那么一定是不同的尽量多

那么一定可以看成将两种操作排成序列,使得较少的一种不相邻

直接组合数即可

复杂度 O(T+n)O(T+n)

Code
#include<cstdio>
using namespace std;
#define N 105000
#define mod 998244353
int T,a,b,c,d,fr[N],ifr[N],fg1,fg2;
struct sth{int a,b;};
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%mod;a=1ll*a*a%mod;b>>=1;}return as;}
sth operator +(sth a,sth b){if(a.a<b.a)return a;if(a.a>b.a)return b;a.b=(a.b+b.b)%mod;return a;}
sth solve(int s,int t)
{
	int v1=b,v2=c,c1=0,tp21=1;
	if(s)v1--;else v2--;
	if(t)v1--;else v2--;
	if((s^a^1)&1)
	c1++,tp21*=fg1+1;
	if((t^d^1)&1)
	c1++,tp21*=fg2+1;
	if(v1<0||v2<0)return (sth){1000000000,0};
	if(v1<=v2&&v1+1<=c)
	{
		int tp=v1+1,tp2=c;
		int d1=tp2-tp+v1,d2=v1;
		int as1=1ll*tp21*fr[d1]*ifr[d2]%mod*ifr[d1-d2]%mod;
		as1=1ll*as1*pw(2,tp2-tp)%mod;
		c1+=2*(b+c-1)+(tp2-tp)+1;
		return (sth){c1,as1};
	}
	else
	{
		int tp=v2+1,tp2=b;
		int d1=tp2-tp+v2,d2=v2;
		int as1=1ll*tp21*fr[d1]*ifr[d2]%mod*ifr[d1-d2]%mod;
		as1=1ll*as1*pw(2,tp2-tp)%mod;
		c1+=2*(b+c-1)+(tp2-tp)+1;
		return (sth){c1,as1};
	}
}
int main()
{
	fr[0]=ifr[0]=1;for(int i=1;i<=102000;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%d%d",&a,&b,&c,&d);
		if(b==0&&c==0)
		{
			int as=(a-d+4)%4;
			if(as==3)as=1;printf("%d %d\n",as,as==2?2:1);continue;
		}
		if(b==0&&c==1)
		{
			int as=1,ct=1;
			if(a==0||a==2)as++;
			if(a==3)ct*=2,as+=2;
			if(d==0||d==2)as++;
			if(d==1)ct*=2,as+=2;
			printf("%d %d\n",as,ct);
			continue;
		}
		if(b==1&&c==0)
		{
			int as=1,ct=1;
			if(a==1||a==3)as++;
			if(a==2)ct*=2,as+=2;
			if(d==1||d==3)as++;
			if(d==0)ct*=2,as+=2;
			printf("%d %d\n",as,ct);
			continue;
		}
		int c1=0;fg1=fg2=0;
		if(a==3)a=0,c1++,fg1=1;
		if(a==2)a=1,c1++,fg1=1;
		if(d==0)d=3,c1++,fg2=1;
		if(d==1)d=2,c1++,fg2=1;
		sth as=solve(0,0)+solve(0,1)+solve(1,0)+solve(1,1);
		printf("%d %d\n",as.a+c1,as.b);
	}
}
auoj84 正方形
Problem

给一个 n×mn\times m 的网格图,支持如下操作

  1. 翻转一个格子
  2. 给一个矩形,求矩形内最大的全0矩形的边长

mn,nm4×106,q2000m\leq n,nm\leq 4\times 10^6,q\leq 2000

2s,1024MB2s,1024MB

Sol

nn 这一维建线段树,考虑维护的信息

因为需要合并,记录以上边界的每一个位置为左上角的最大正方形,以及以下边界的每一个位置为左下角的最大正方形

合并时需要支持查一段位置区间的最大正方形边长的min,容易发现可以单调队列优化

再记录区间内以每一列作为左边界的最大正方形边长就可以算答案

复杂度 O(mqlogn)O(mq\log n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 4050000
#define M 2050
int n,m,q,a,b,c,d,e,l[N*2],r[N*2],as[N*2],l1[M],r1[M],as1[M],st[N],ct;
struct que{
	int s[M],lb,rb,v[M];
	void init(){lb=rb=0;s[0]=1e9;}
	int getmn(){return s[lb];}
	void doitl(int x){while(lb<rb&&v[lb]<x)lb++;}
	void doitr(int x,int y){while(lb<=rb&&s[rb]>x)rb--;s[++rb]=x;v[rb]=y;}
}v1,v2;
struct fuc{int len,lb;};
fuc pushup(fuc a,fuc b,fuc c)
{
	for(int i=1;i<=m;i++)l1[i]=l[a.lb+i]+l[b.lb+i]*(l[a.lb+i]>=a.len),r1[i]=r[b.lb+i]+r[a.lb+i]*(r[b.lb+i]>=b.len),as1[i]=max(as[a.lb+i],as[b.lb+i]);
	v1.init();v2.init();
	int rb=1;v1.doitr(r[a.lb+1],1);v2.doitr(l[b.lb+1],1);
	for(int i=1;i<=m;i++)
	{
		v1.doitl(i);v2.doitl(i);
		while(rb<m&&min(r[a.lb+rb+1],v1.getmn())+min(l[b.lb+rb+1],v2.getmn())>=rb-i+2)
		rb++,v1.doitr(r[a.lb+rb],rb),v2.doitr(l[b.lb+rb],rb);
		as1[i]=max(as1[i],rb-i+1);
	}
	c.len=a.len+b.len;
	for(int i=1;i<=m;i++)l[c.lb+i]=l1[i],r[c.lb+i]=r1[i],as[c.lb+i]=as1[i];
	return c;
}
struct segt{
	struct node{int l,r;fuc tp;}e[N*4];
	void doit(int x,int y){for(int i=1;i<=m;i++)as[e[x].tp.lb+i]=l[e[x].tp.lb+i]=r[e[x].tp.lb+i]=st[(y-1)*m+i];}
	void build(int x,int l,int r)
	{
		e[x].l=l;e[x].r=r;e[x].tp.lb=ct;ct+=m;
		if(l==r){e[x].tp.len=1;doit(x,l);return;}
		int mid=(l+r)>>1;
		build(x<<1,l,mid);build(x<<1|1,mid+1,r);
		e[x].tp=pushup(e[x<<1].tp,e[x<<1|1].tp,e[x].tp);
	}
	void modify(int x,int v)
	{
		if(e[x].l==e[x].r){doit(x,v);return;}
		int mid=(e[x].l+e[x].r)>>1;
		if(mid>=v)modify(x<<1,v);else modify(x<<1|1,v);
		e[x].tp=pushup(e[x<<1].tp,e[x<<1|1].tp,e[x].tp);
	}
	fuc query(int x,int l,int r,fuc st)
	{
		if(e[x].l==l&&e[x].r==r){st=pushup(st,e[x].tp,st);return st;}
		int mid=(e[x].l+e[x].r)>>1;
		if(mid>=r)return query(x<<1,l,r,st);
		else if(mid<l)return query(x<<1|1,l,r,st);
		else
		{
			st=query(x<<1,l,mid,st);
			return query(x<<1|1,mid+1,r,st);
		}
	}
}tr;
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n*m;i++)scanf("%d",&st[i]);
	tr.build(1,1,n);
	while(q--)
	{
		scanf("%d",&a);
		if(a==0)
		{
			scanf("%d%d",&b,&c);
			st[b*m-m+c]^=1;
			tr.modify(1,b);
		}
		else
		{
			scanf("%d%d%d%d",&b,&c,&d,&e);
			fuc tp;tp.lb=ct;tp.len=0;for(int i=1;i<=m;i++)l[tp.lb+i]=r[tp.lb+i]=as[tp.lb+i]=0;
			tp=tr.query(1,b,d,tp);
			int as1=0;
			for(int i=c;i<=e;i++)as1=max(as1,min(as[i+tp.lb],e-i+1));
			printf("%d\n",as1);
		}
	}
}
auoj85 最小生成树
Problem

给一个 nn 个点的完全图,每条边有 pip_i 的概率边权为 ii , 1pi1-\sum p_i 的概率不存在,边权上界为 kk

对于每一个 vv ,求这张图的最小生成树为 vv 的概率

n40,k4n\leq 40,k\leq 4

1s,1024MB1s,1024MB

Sol

dpi,j,kdp_{i,j,k} 表示只考虑边权不超过 kk 的边, ii 个点的图最小生成树为 jj 的概率

考虑加入边权为 k+1k+1 的边,设 fi,jf_{i,j} 表示将边权大于等于 k+1k+1 的边都看成 k+1k+1 时, ii 个点的图最小生成树为 jj 的概率

p=i>kpi+(1pi)p=\sum_{i>k}p_i+(1-\sum p_i) ,枚举1号点的连通块大小,有 fi,j=l=1i1s=0jk1Ci1l1pl(il)dpl,s,kfil,jsk1f_{i,j}=\sum_{l=1}^{i-1}\sum_{s=0}^{j-k-1} C_{i-1}^{l-1}p^{l(i-l)}dp_{l,s,k}f_{i-l,j-s-k-1}

然后再容斥掉只考虑 k+1k+1 不连通的情况,令 q=i>k+1pi+(1pi)q=\sum_{i>k+1}p_i+(1-\sum p_i) ,有 dpi,j,k+1=fi,jl=1i1s=0jk1Ci1l1ql(il)dpl,s,k+1fil,jsk1dp_{i,j,k+1}=f_{i,j}-\sum_{l=1}^{i-1}\sum_{s=0}^{j-k-1} C_{i-1}^{l-1}q^{l(i-l)}dp_{l,s,k+1}f_{i-l,j-s-k-1}

复杂度 O(n4k3)O(n^4k^3)

Code
#include<cstdio>
using namespace std;
#define mod 1000000007
int n,k,v[5],dp[5][41][161],c[41][41],f[5][41][161];
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%mod;a=1ll*a*a%mod;b>>=1;}return as;}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=0;i<=k;i++)scanf("%d",&v[i]);
	for(int i=0;i<=n;i++)c[i][0]=c[i][i]=1;
	for(int i=2;i<=n;i++)for(int j=1;j<i;j++)c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	dp[0][1][0]=1;
	for(int i=1;i<=k;i++)
	{
		int s1=v[i],s2=v[0];
		for(int t=i;t<=k;t++)s2+=v[t];
		int tp3=1ll*s2*570000004%mod,tp2=1ll*s1*570000004%mod;
		for(int j=1;j<=n;j++)
		{
			for(int l=0;l<=(j-1)*(i-1);l++)dp[i][j][l]=dp[i-1][j][l];
			for(int l=1;l<=j;l++)
			for(int s=0;s<=(l-1)*i;s++)
			for(int t=0;t<=(j-l-1)*(i-1);t++)
			dp[i][j][s+t+i]=(dp[i][j][s+t+i]+1ll*c[j-1][j-l-1]*dp[i][l][s]%mod*dp[i-1][j-l][t]%mod*pw(tp3,l*(j-l)))%mod;
		}
		for(int j=1;j<=n;j++)
		for(int l=0;l<=(j-1)*i;l++)
		f[i][j][l]=dp[i][j][l];
		for(int j=1;j<=n;j++)
		for(int l=1;l<=j;l++)
		for(int s=0;s<=(l-1)*i;s++)
		for(int t=0;t<=(j-l-1)*i;t++)
		dp[i][j][s+t+i]=(dp[i][j][s+t+i]-1ll*c[j-1][l-1]*dp[i][l][s]%mod*f[i][j-l][t]%mod*pw(mod+tp3-tp2,l*(j-l))%mod+mod)%mod;
	}
	for(int i=n-1;i<=(n-1)*k;i++)printf("%d ",dp[k][n][i]);
}
auoj86 lowbit
Problem

定义 lowbitp(x)lowbit_p(x)pp 进制下 xx 最低的有值的一位的位权

对于 xx ,一次操作有 ab\frac a b 的概率将其变为 x+lowbitp(x)x+lowbit_p(x) , 1ab1-\frac a b 的概率将其变为 xlowbitp(x)x-lowbit_p(x)

定义 f(x)f(x) 表示 xx 期望操作多少次变为0

i=abf(i)\sum_{i=a}^b f(i) ,模 998244353998244353

a,b1018,p105a,b\leq 10^{18},p\leq 10^5

1s,512MB1s,512MB

Sol

首先考虑一位的情况

相当于有一个数,每次操作有一个概率+1,剩下的概率-1,到0或p终止,求到p的概率以及终止的期望时间

dpidp_{i}ii 到p的概率,显然有 dp0=0,dpp=1,dpi=dpi+1ab+dpi1(1ab)dp_0=0,dp_p=1,dp_i=dp_{i+1}*\frac a b+dp_{i-1}*(1-\frac a b)

dp1=xdp_1=x ,那么依次考虑 dp1,...,dpp2dp_{1},...,dp_{p-2} 的方程,可以用 xx 表示出 dp2,...,dpp1dp_2,...,dp_{p-1} ,最后用 dpp1dp_{p-1} 的方程可以解出 xx

对于期望使用类似的方式即可

考虑将 [a,b][a,b] 分成若干段,其中每一段的数前若干位全部相同,某一位上为一段前缀或后缀,这一位之后的全部任意

可以预处理算出,只考虑后面那些全部任意的位时,期望有多少个数最后进位以及这些数的期望时间之和,然后再处理这一位的概率

考虑每一位,因为这一位只有一种取值,可以用之前的信息 O(1)O(1) 算出向下一位进位的期望数量和这一位上对答案的贡献

考虑处理了超过 log\log 位 后,后面的都是0,因此后面每一次的转移固定,可以直接算出后面的期望步数

复杂度 O(plogpn+logp2n)O(p\log_p n+\log_p^2 n)

Code
#include<cstdio>
using namespace std;
#define N 100500
#define mod 998244353
#define ll long long
int f[N],g[N],a,b,k,p,s[N][2],t[N],st,fu,fu2,vl[N];
ll l,r;
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%mod;a=1ll*a*a%mod;b>>=1;}return as;}
void pre()
{
	t[1]=1;
	for(int i=2;i<k;i++)
	{
		//f_i=p*f_{i+1}+(1-p)f_{i-1}
		//f_{i+1}=1/p(f_i-(1-p)f_{i-1})
		t[i]=1ll*(t[i-1]+mod+1ll*(p-1)*t[i-2])%mod*pw(p,mod-2)%mod;
	}
	//f_{k-1}=p+(1-p)f_{k-2}
	int fu1=(t[k-1]+1ll*(p-1)*t[k-2])%mod;
	fu1=1ll*p*pw(fu1,mod-2)%mod;
	for(int i=1;i<k;i++)f[i]=1ll*t[i]*fu1%mod;f[k]=1;
	s[1][0]=1;
	for(int i=2;i<k;i++)
	{
		//g_i=p*g_{i+1}+(1-p)g_{i-1}+1
		//g_{i+1}=1/p(f_i-(1-p)f_{i-1}-1)
		s[i][0]=1ll*(s[i-1][0]+mod+1ll*(p-1)*s[i-2][0])%mod*pw(p,mod-2)%mod;
		s[i][1]=1ll*(s[i-1][1]+mod+1ll*(p-1)*s[i-2][1]+mod-1)%mod*pw(p,mod-2)%mod;
	}
	//g_{k-1}=(1-p)g_{k-2}+1
	int fu3=(s[k-1][0]+1ll*(p-1)*s[k-2][0])%mod,fu2=(1+1ll*(mod+1-p)*s[k-2][1]+1ll*(mod-1)*s[k-1][1])%mod;
	fu3=1ll*fu2*pw(fu3,mod-2)%mod;
	for(int i=1;i<k;i++)g[i]=(1ll*s[i][0]*fu3+s[i][1])%mod;
}
int solve2(ll r,ll tp,ll l,ll lg)
{
	int v0=0,v1=0,as=0;
	for(int i=0;i<=tp;i++)
	v1=(v1+1ll*fu2*f[i]+1ll*fu*f[i+1])%mod,v0=(v0+1ll*fu2*(mod+1-f[i])%mod+1ll*fu*(mod+1-f[i+1])%mod)%mod,as=(as+1ll*fu2*g[i]+1ll*fu*g[i+1])%mod;
	for(int i=l;i<=lg;i++)
	{
		int t=r%k;r/=k;
		int r1=(1ll*v1*f[t+1]+1ll*v0*f[t])%mod,r0=(1ll*v1*(mod+1-f[t+1])+1ll*v0*(mod+1-f[t]))%mod;
		as=(as+1ll*v1*g[t+1]+1ll*v0*g[t])%mod;
		v0=r0,v1=r1;
	}
	st=(st+v1)%mod;return as;
}
int solve(ll r)
{
	ll lg=1,tp=r;
	while(tp)tp/=k,lg++;
	fu=0;fu2=1;st=0;int as=0,ct=0,fg=0;
	while(r)
	{
		as=(as+solve2(r/k,r%k-1,ct,lg))%mod,ct++,r/=k;
		int v1=st,v2=f[1],v3=g[1];
		as=(as+1ll*v1*v3%mod*pw(mod+1-v2,mod-2))%mod;
		st=0;
		vl[0]=0;for(int i=1;i<=k;i++)vl[i]=fu;
		for(int i=0;i<k;i++)vl[i]=(vl[i]+fu2)%mod;
		ll su=0;
		for(int i=0;i<=k;i++)su=(su+1ll*vl[i]*g[i])%mod;
		as=(as+r%mod*su)%mod;
		fu=0;fu2=0;
		for(int i=0;i<=k;i++)fu=(fu+1ll*f[i]*vl[i])%mod;
		for(int i=0;i<=k;i++)fu2=(fu2+1ll*(mod+1-f[i])*vl[i])%mod;
	}
	return as;
}
int main()
{
	scanf("%d%d%d%lld%lld",&k,&a,&b,&l,&r);
	p=1ll*a*pw(b,mod-2)%mod;
	pre();
	int v1=solve(l),v2=solve(r+1);
	printf("%d\n",(v2-v1+mod)%mod);
}
auoj87 sequence
Problem

给定正整数 n,p,q,kn,p,q,k ,一个正整数序列 a1,...,ma_{1,...,m} 是好的当且仅当

  1. aiai+1pqa_i\geq a_{i+1}*\frac p q
  2. ai=n\sum a_i=n

求在所有满足条件的序列中, aiki\sum a_i*k^i 的最大值,输出最大值模 109+710^9+7

多组数据且数据随机

n109,k106,T10n\leq 10^9,k\leq 10^6,T\leq 10

2s,512MB2s,512MB

Sol

考虑一个构造过程:每次贪心地让最后一个数最大,然后让倒数第二个最大,...

设这样构造的序列为 a1,...,ama_1,...,a_m ,显然他是最长的,考虑另外一个序列 b1,...,bmb_1,...,b_m

因为 aa 每次贪心让最后一位最大,所以 i,ai>bi,j>i,aj=bj\exists i,a_i>b_i,\forall j>i,a_j=b_j

s1i=jiai,s2i=jibis1_i=\sum_{j\geq i}a_i,s2_i=\sum_{j\geq i}b_i ,如果 i,s2i>s1i\exists i,s2_i>s1_i ,找到最大的一个 ii ,设为 kk

那么此时有 s1k+1s2k+1,i=1k1ai>i=1k1bi,ak<bks1_{k+1}\geq s2_{k+1},\sum_{i=1}^{k-1}a_i>\sum_{i=1}^{k-1}b_i,a_k<b_k

但根据 aia_i 的构造过程可以得到,如果 ak=ak+1,j>k,aj=aja_k^{'}=a_k+1,\forall {j>k},a_j^{'}=a_j ,则不存在一个合法的 aa^{'}

因为 akbk,i=1k1aii=1k1bia_k^{'}\leq b_k,\sum_{i=1}^{k-1}a_i^{'}\geq \sum_{i=1}^{k-1}b_i ,所以不存在合法的 bb

因此有 i,s1is2i\forall i,s1_i\geq s2_i ,因为 iki^k 不降,所以贪心一定最优

对于 pq1\frac p q\leq 1 的情况,显然最优解为 a1=...=an=1a_1=...=a_n=1 ,然后答案为 i=1nik\sum_{i=1}^ni^k ,拉格朗日插值即可

对于 pq>1.1\frac p q>1.1 的情况,项数不多,可以直接二分每一项加多少

对于剩下的情况,可以发现如果调整了一项的值,那么它前面几项很大概率不用再调整,于是可以二分下一个调整的位置

复杂度 O()O(能过)

Code
#include<cstdio>
using namespace std;
#define N 1005000
#define mod 1000000007
int t,n,k,p,q;
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%mod;a=1ll*a*a%mod;b>>=1;}return as;}
struct leq1{
	int f[N],su[N],fr[N],ifr[N],ch[N],pr[N],ct,s1[N],s2[N];
	void solve()
	{
		ct=0;for(int i=0;i<=k+3;i++)ch[i]=0;
		fr[0]=ifr[0]=1;
		for(int i=1;i<=k+3;i++)fr[i]=1ll*fr[i-1]*i%mod;
		ifr[k+3]=pw(fr[k+3],mod-2);
		for(int i=k+2;i>=1;i--)ifr[i]=1ll*ifr[i+1]*(i+1)%mod;
		f[1]=1;
		for(int i=2;i<=k+3;i++)
		{
			if(!ch[i]){pr[++ct]=i;f[i]=pw(i,k);}
			for(int j=1;j<=ct&&1ll*i*pr[j]<=k+3;j++)
			{
				f[i*pr[j]]=1ll*f[i]*f[pr[j]]%mod;
				ch[i*pr[j]]=1;
				if(i%pr[j]==0)continue;
			}
		}
		for(int i=2;i<=k+3;i++)f[i]=(f[i]+f[i-1])%mod;
		int as=0;
		s1[0]=s2[k+4]=1;
		for(int i=1;i<=k+4;i++)s1[i]=1ll*s1[i-1]*(n-i+mod)%mod;
		for(int i=k+3;i>=0;i--)s2[i]=1ll*s2[i+1]*(n-i+mod)%mod;
		for(int i=1;i<=k+3;i++)as=(as+1ll*f[i]*s1[i-1]%mod*s2[i+1]%mod*ifr[i-1]%mod*ifr[(k+3)-i]%mod*(((k+3)^i)&1?mod-1:1))%mod;
		printf("%d\n",as);
	}
}s1;
struct geq11{
	int as[N];
	bool check(int l)
	{
		long long las=1;
		long long as=1;
		for(int i=2;i<=l;i++)
		{
			las=(1ll*las*p+q-1)/q;
			as+=las;
			if(as>n)return 0;
		}
		return as<=n;
	}
	int getsu(int a,int l)
	{
		long long las=a;
		long long as=a;
		for(int i=2;i<=l;i++)
		{
			las=(1ll*las*p+q-1)/q;
			as+=las;
			if(as>n)return 1e9+10;
		}
		return as;
	}
	void solve()
	{
		int lb=1,rb=100000,as2=1;
		while(lb<=rb)
		{
			int mid=(lb+rb)>>1;
			if(check(mid))as2=mid,lb=mid+1;
			else rb=mid-1;
		}
		int las=0,su=0;
		for(int i=as2;i>=1;i--)
		{
			int tp=(1ll*las*p+q-1)/q;
			if(tp==0)tp=1;
			int lb=tp,rb=n-las;
			while(lb<=rb)
			{
				int mid=(lb+rb)>>1;
				if(su+getsu(mid,i)<=n)as[i]=mid,lb=mid+1;
				else rb=mid-1;
			}
			las=as[i];su+=as[i];
		}
		int as1=0;
		for(int i=1;i<=as2;i++)as1=(as1+1ll*as[i]*pw(i,k))%mod;
		printf("%d\n",as1);
	}
}s3;
struct fuckthispart{
	int as[N],su2[N];
	bool check(int l)
	{
		long long las=1;
		long long as=1;
		for(int i=2;i<=l;i++)
		{
			las=(1ll*las*p+q-1)/q;
			as+=las;
			if(as>n)return 0;
		}
		return as<=n;
	}
	int getsu(int a,int l)
	{
		long long las=a;
		long long as=a;
		for(int i=2;i<=l;i++)
		{
			las=(1ll*las*p+q-1)/q;
			as+=las;
			if(as>n)return 1e9+10;
		}
		return as;
	}
	void solve()
	{
		int lb=1,rb=100000,as2=1;
		while(lb<=rb)
		{
			int mid=(lb+rb)>>1;
			if(check(mid))as2=mid,lb=mid+1;
			else rb=mid-1;
		}
		int fu=as2;
		su2[as2+1]=0;as[as2+1]=0;
		for(int i=as2;i>=1;i--)
		{
			as[i]=(1ll*as[i+1]*p+q-1)/q;
			if(!as[i])as[i]=1;
			su2[i]=su2[i+1]+as[i];
		}
		while(su2[1]<=n)
		{
			int lb=1,rb=fu,as3=0;
			while(lb<=rb)
			{
				int mid=(lb+rb)>>1;
				if(su2[mid+1]+getsu(as[mid]+1,mid)<=n)as3=mid,lb=mid+1;
				else rb=mid-1;
			}
			if(!as3)break;
			as[as3]++;
			for(int i=as3-1;i>=1;i--)as[i]=(1ll*as[i+1]*p+q-1)/q;
			for(int i=as3;i>=1;i--)su2[i]=su2[i+1]+as[i];
		}
		int as1=0;
		for(int i=1;i<=as2;i++)as1=(as1+1ll*as[i]*pw(i,k))%mod;
		printf("%d\n",as1);
	}
}s4;
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d%d%d",&n,&k,&p,&q);
		if(p<=q)s1.solve();
		else if(1.0*p/q>=1.1)s3.solve();
		else s4.solve();
	}
}
auoj88 classroom
Problem

教学楼由 tt 层组成,每一层的结构完全相同,一层的结构为一棵 nn 个点的树,经过一条边时间为1

有一些点上有通向相邻层的点的边,每一层中有这样的边的点的集合是相同的,如果你在一个有可以通向相邻层的边的点,你可以用1的时间移动到相邻层相同的点

多组询问,每次给定 kk 个点,你需要在 kk 个点中选择一个点,使得所有点到这个点的距离和最小,求出最小距离和

n2×105,k106n\leq 2\times 10^5,\sum k\leq 10^6

5s,1024MB5s,1024MB

Sol

考虑两个点之间的距离

如果两个点在同一层,那么距离就是树上的距离

否则,距离应该是将两个点放在同一层上,从一个点在树上走到一个可以向相邻层移动的点再走到另外一个点的最小距离再加上两个点层数的差

fif_i 表示 ii 离一个可以向相邻层移动的点的最小距离,那么从 uuvv 这样的最小距离为 disu,vdis_{u,v} 加上 (u,v)(u,v) 路径上最小的 fif_i 乘2

因此选一个点的答案可以看成三部分

  1. 求出所有点与它的层数的差的和
  2. 求出所有点放在树上时与它的距离和
  3. 求出所有与它不在同一层的点放在树上时到它的路径上 fif_i 最小值的和再乘2

第一个直接排序扫一遍即可

对于第二个,考虑将所有点建原树上的虚树,然后虚树上dp即可

fif_i 可以dfs两次求出,考虑对于每个点算出所有点它的路径上 fif_i 最小值的和,再减去相同层的这个值

考虑将边从大到小加入,建Kruskal重构树,那么 (u,v)(u,v) 路径上最小 fif_i 就是两个点在树上的LCA处的权值

那么先建出重构树,询问时建出询问点的虚树,再dfs两次即可

复杂度 O((n+m)logn)O((n+m)\log n)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 405918
#define ll long long
int n,q,k,a,b,vl[N],s[N][2],head[N],cnt,fa[N],f1[N],f[N][19],dep[N],id[N],ct,ct2,fid[N],tid[N],st[N],rb,sz[N],dep2[N],f2[N][19],id2[N],ct3,v1[N];
ll dp1[N],dp2[N],as[N*3];
vector<pair<int,int> > tp;
struct edge{int t,next;}ed[N*2];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;}
void dfs01(int u,int fa){id2[u]=++ct3;dep2[u]=dep2[fa]+1;f2[u][0]=fa;for(int i=1;i<=18;i++)f2[u][i]=f2[f2[u][i-1]][i-1];for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs01(ed[i].t,u),vl[u]=min(vl[u],vl[ed[i].t]+1);}
void dfs02(int u,int fa){for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)vl[ed[i].t]=min(vl[ed[i].t],vl[u]+1),dfs02(ed[i].t,u);}
struct edg{int f,t,v;friend bool operator <(edg a,edg b){return a.v>b.v;}}e[N];
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
void dfs1(int u,int fa){id[u]=++ct;dep[u]=dep[fa]+1;f[u][0]=fa;for(int i=1;i<=18;i++)f[u][i]=f[f[u][i-1]][i-1];for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs1(ed[i].t,u);}
int LCA(int x,int y,int dep[],int f[][19]){if(dep[x]<dep[y])x^=y^=x^=y;int tp=dep[x]-dep[y];for(int i=18;i>=0;i--)if((tp>>i)&1)x=f[x][i];if(x==y)return x;for(int i=18;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];return f[x][0];}
bool cmp(int a,int b){return id[a]<id[b];}
bool cmp2(int a,int b){return id2[a]<id2[b];}
vector<pair<int,int> > solve(vector<int> p,int dep[],int f[][19],int tp)
{
	vector<pair<int,int> > as;
	sort(p.begin(),p.end(),cmp);
	if(tp)sort(p.begin(),p.end(),cmp2);
	ct2=0;rb=0;
	for(int i=0;i<p.size();i++)fid[p[i]]=i+1,tid[i+1]=p[i],ct2++;
	for(int i=0;i<p.size();i++)
	{
		while(rb>1&&dep[st[rb-1]]>=dep[LCA(st[rb],p[i],dep,f)])as.push_back(make_pair(fid[st[rb]],fid[st[rb-1]])),rb--;
		int l=LCA(st[rb],p[i],dep,f);
		if(l!=st[rb])
		{
			int tp=fid[l];if(!tp)fid[l]=++ct2,tid[ct2]=l;
			as.push_back(make_pair(fid[st[rb]],fid[l])),st[rb]=l;
		}
		st[++rb]=p[i];
	}
	while(rb>1)as.push_back(make_pair(fid[st[rb]],fid[st[rb-1]])),rb--;
	return as;
}
void dfs21(int u,int fa){sz[u]=v1[tid[u]];dp2[u]=0;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs21(ed[i].t,u),sz[u]+=sz[ed[i].t],dp2[u]+=dp2[ed[i].t]+1ll*sz[ed[i].t]*(dep2[tid[ed[i].t]]-dep2[tid[u]]);}
void dfs22(int u,int fa){for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dp1[ed[i].t]=dp1[u]-1ll*sz[ed[i].t]*(vl[tid[u]]-vl[tid[ed[i].t]]),dp2[ed[i].t]=dp2[u]-1ll*(2*sz[ed[i].t]-k)*(dep2[tid[ed[i].t]]-dep2[tid[u]]),dfs22(ed[i].t,u);}
void doit(vector<int> p,int dep[],int f[][19],int tp)
{
	vector<pair<int,int> > e1=solve(p,dep,f,tp);
	for(int i=1;i<=ct2;i++)head[i]=0;cnt=0;
	for(int i=0;i<ct2-1;i++)adde(e1[i].first,e1[i].second);
	int rt=1;for(int i=1;i<=ct2;i++)rt=dep[tid[i]]<dep[tid[rt]]?i:rt;
	dfs21(rt,0);dp1[rt]=1ll*vl[tid[rt]]*sz[rt];
	dfs22(rt,0);
}
ll getans(vector<pair<int,int> > pt)
{
	sort(pt.begin(),pt.end());int q=pt.size();
	vector<int> p;
	for(int i=0;i<pt.size();i++)v1[pt[i].second]++;
	for(int i=1;i<=ct2;i++)fid[tid[i]]=0,tid[i]=0;
	for(int i=0;i<pt.size();i++){int v=pt[i].second;if(!fid[v])p.push_back(v),fid[v]=1;}
	doit(p,dep,f,0);for(int i=0;i<q;i++)as[i]=2*dp1[fid[pt[i].second]];
	for(int i=1;i<=ct2;i++)fid[tid[i]]=0,tid[i]=0;doit(p,dep2,f2,1);
	for(int i=0;i<q;i++)as[i]+=dp2[fid[pt[i].second]];
	for(int i=0;i<pt.size();i++)v1[pt[i].second]--;
	ll res=0;
	for(int i=0;i<q;i++)res+=pt[i].first-pt[0].first;
	for(int i=0;i<q;res+=1ll*(2*(i+1)-q)*(pt[i+1].first-pt[i].first),i++)as[i]+=res;
	int lb=0;
	while(lb<q)
	{
		int rb=lb;while(rb<q-1&&pt[rb+1].first==pt[lb].first)rb++;
		vector<int> p;
		for(int i=1;i<=ct2;i++)fid[tid[i]]=0,tid[i]=0;
		for(int i=lb;i<=rb;i++){int v=pt[i].second;if(!fid[v])p.push_back(v),fid[v]=1;v1[pt[i].second]++;}
		doit(p,dep,f,0);for(int i=lb;i<=rb;i++)as[i]-=2*dp1[fid[pt[i].second]],v1[pt[i].second]--;
		lb=rb+1;
	}
	ll mn=1e18;for(int i=0;i<q;i++)mn=min(mn,as[i]);
	return mn;
}
int main()
{
	scanf("%*d%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&vl[i]),vl[i]=vl[i]?0:1e9;
	for(int i=1;i<n;i++)scanf("%d%d",&s[i][0],&s[i][1]),adde(s[i][0],s[i][1]);
	dfs01(1,0);dfs02(1,0);
	for(int i=1;i<n;i++)e[i].f=s[i][0],e[i].t=s[i][1],e[i].v=min(vl[s[i][0]],vl[s[i][1]]);
	sort(e+1,e+n);ct=n;
	for(int i=1;i<=n*2;i++)fa[i]=i;
	for(int i=1;i<n;i++)
	{
		int v1=finds(e[i].f),v2=finds(e[i].t);
		fa[v1]=fa[v2]=f1[v1]=f1[v2]=++ct;vl[ct]=e[i].v;
	}
	for(int i=1;i<=ct;i++)head[i]=0;cnt=0;
	for(int i=1;i<ct;i++)adde(f1[i],i);
	dfs1(ct,0);
	scanf("%d",&q);
	while(q--)
	{
		tp.clear();scanf("%d",&k);
		for(int i=1;i<=k;i++)scanf("%d%d",&a,&b),tp.push_back(make_pair(a,b));
		printf("%lld\n",getans(tp));
	}
}
auoj89 鱼死网破
Problem

y>0y>0 的部分有 nn 个点和 kk 条平行于 xx 轴的线段,qq 次询问,每次询问给一个在 y<0y<0 部分的点,求 nn 个点中有多少个点满足这个点向它连成的线段不经过任意一条给定的线段

强制在线

n,m105,k50n,m\leq 10^5,k\leq 50

3s,512MB3s,512MB

Sol

考虑一个点会对哪些询问点产生贡献

可以发现,最后能贡献的点一定在若干个极角区间中

对于一个区间,因为询问点都在下面,考虑这样的差分:

因为一条线必定经过 O(k)O(k) 个端点中的一个,可以对于每个端点记录它有哪些修改,然后极角排序,询问时枚举每个端点二分算贡献即可

复杂度 O(nklogn)O(nk\log n)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define N 105000
#define M 105
struct pt{int x,y,id;}s[N],tp,t[M],t2[M];
vector<pt> r[M];
int v[M][3],n,m,k,op,x,y,ct,las;
pt operator -(pt a,pt b){return (pt){a.x-b.x,a.y-b.y,0};}
ll cross(pt a,pt b){return 1ll*a.x*b.y-1ll*a.y*b.x;}
bool cmp(pt a,pt b){return cross(a-tp,b-tp)>0;}
int main()
{
	scanf("%d%d%d%d",&n,&k,&m,&op);
	for(int i=1;i<=n;i++)scanf("%d%d",&s[i].x,&s[i].y);
	for(int i=1;i<=k;i++)
	{
		scanf("%d%d%d",&v[i][0],&v[i][1],&v[i][2]);
		if(v[i][0]>v[i][1])v[i][0]^=v[i][1]^=v[i][0]^=v[i][1];
		t[++ct]=(pt){v[i][0],v[i][2],i};
		t[++ct]=(pt){v[i][1],v[i][2],i+k};
	}
	for(int i=1;i<=n;i++)
	{
		int ct2=0;
		for(int j=1;j<=ct;j++)if(t[j].y<s[i].y)t2[++ct2]=t[j];
		tp=s[i];sort(t2+1,t2+ct2+1,cmp);
		int su=0;
		for(int j=1;j<=ct2;j++)
		if(t2[j].id<=k)
		{
			su++;
			if(su==1)r[t2[j].id].push_back(s[i]);
		}
		else
		{
			su--;
			if(!su)r[t2[j].id].push_back(s[i]);
		}
	}
	for(int i=1;i<=ct;i++)tp=t[i],sort(r[t[i].id].begin(),r[t[i].id].end(),cmp),t2[t[i].id]=t[i];
	while(m--)
	{
		scanf("%d%d",&x,&y);x^=las*op;y^=las*op;
		int as1=0;
		for(int i=1;i<=k;i++)
		{
			pt st=t2[i]-(pt){x,y};
			int lb=0,rb=r[i].size()-1,as=-1;
			while(lb<=rb)
			{
				int mid=(lb+rb)>>1;
				if(cross(r[i][mid]-t2[i],st)>=0)as=mid,lb=mid+1;
				else rb=mid-1;
			}
			as1+=as+1;
		}
		for(int i=1;i<=k;i++)
		{
			pt st=t2[i+k]-(pt){x,y};
			int lb=0,rb=r[i+k].size()-1,as=-1;
			while(lb<=rb)
			{
				int mid=(lb+rb)>>1;
				if(cross(r[i+k][mid]-t2[i+k],st)>0)as=mid,lb=mid+1;
				else rb=mid-1;
			}
			as1-=as+1;
		}
		printf("%d\n",las=n-as1);
	}
}
auoj90 漏网之鱼
Problem

给一个序列,多组询问,每次给一个区间,求这个区间的所有子区间的mex值之和

n,q106n,q\leq 10^6

4s,512MB4s,512MB

Sol

考虑固定 ll ,设 fif_i 表示 ii 第一次出现的位置(没有出现则为inf

对于 ii ,如果 rmaxj=1ifjr\geq max_{j=1}^if_j ,那么 [l,r][l,r] 的mex大于等于 ii

ff 建单调栈,单调栈中的每个元素会对一段后缀的 rr 的答案产生贡献

从大到小考虑 ll ,每次相当于将一个 fif_i 改为 ll

如果这个元素不在单调栈上则没有影响,否则删掉这个元素,在后面依次找可以加进去的元素,即一段区间中大于某个数的第一个位置,总共只会加 O(n)O(n)

那么相当于区间加,求一段区间在每一个时刻的区间和的和

若当前时刻为 tt ,区间加一个数 vv 可以看成加 v(xt+1)v(x-t+1) ,其中 xx 为询问时刻,维护一次函数即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
#pragma GCC optimize("-Ofast")
using namespace std;
#define N 1050000
#define ll long long
int n,q,v[N],vl[N],a,b;
ll as[N];
set<int> fu;
struct que{int l,r,id;};
vector<que> qu[N];
struct segt1{
	struct node{int l,r,mx;}e[N*4];
	void build(int x,int l,int r){e[x].l=l;e[x].r=r;e[x].mx=n+1;if(l==r)return;int mid=(l+r)>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r);}
	void modify(int x,int l,int v)
	{
		if(e[x].l==e[x].r){e[x].mx=v;return;}
		int mid=(e[x].l+e[x].r)>>1;
		if(mid>=l)modify(x<<1,l,v);else modify(x<<1|1,l,v);
		e[x].mx=max(e[x<<1].mx,e[x<<1|1].mx);
	}
	int query(int x,int l,int r,int v)
	{
		if(e[x].mx<v)return -1;
		if(e[x].l==e[x].r)return e[x].l;
		int mid=(e[x].l+e[x].r)>>1;
		if(mid>=r)return query(x<<1,l,r,v);
		else if(mid<l)return query(x<<1|1,l,r,v);
		else
		{
			int as=query(x<<1,l,mid,v);
			if(as==-1)as=query(x<<1|1,mid+1,r,v);
			return as;
		}
	}
}tr;
struct segt2{
	struct node{int l,r;ll s1,s2,l1,l2;}e[N*4];
	void pushdown(int x){if(e[x].l1){e[x<<1].l1+=e[x].l1;e[x<<1].s1+=(e[x<<1].r-e[x<<1].l+1)*e[x].l1;e[x<<1|1].l1+=e[x].l1;e[x<<1|1].s1+=(e[x<<1|1].r-e[x<<1|1].l+1)*e[x].l1;e[x].l1=0;}
	if(e[x].l2){e[x<<1].l2+=e[x].l2;e[x<<1].s2+=(e[x<<1].r-e[x<<1].l+1)*e[x].l2;e[x<<1|1].l2+=e[x].l2;e[x<<1|1].s2+=(e[x<<1|1].r-e[x<<1|1].l+1)*e[x].l2;e[x].l2=0;}}
	void pushup(int x){e[x].s1=e[x<<1].s1+e[x<<1|1].s1;e[x].s2=e[x<<1].s2+e[x<<1|1].s2;}
	void build(int x,int l,int r){e[x].l=l;e[x].r=r;if(l==r)return;int mid=(l+r)>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r);}
	void modify(int x,int l,int r,ll s1,ll s2)
	{
		if(e[x].l==l&&e[x].r==r){e[x].s1+=1ll*s1*(e[x].r-e[x].l+1);e[x].s2+=1ll*s2*(e[x].r-e[x].l+1);e[x].l1+=s1;e[x].l2+=s2;return;}
		pushdown(x);
		int mid=(e[x].l+e[x].r)>>1;
		if(mid>=r)modify(x<<1,l,r,s1,s2);
		else if(mid<l)modify(x<<1|1,l,r,s1,s2);
		else modify(x<<1,l,mid,s1,s2),modify(x<<1|1,mid+1,r,s1,s2);
		pushup(x);
	}
	pair<ll,ll> query(int x,int l,int r)
	{
		if(e[x].l==l&&e[x].r==r)return make_pair(e[x].s1,e[x].s2);
		pushdown(x);
		int mid=(e[x].l+e[x].r)>>1;
		if(mid>=r)return query(x<<1,l,r);
		else if(mid<l)return query(x<<1|1,l,r);
		else
		{
			pair<ll,ll> v1=query(x<<1,l,mid),v2=query(x<<1|1,mid+1,r);
			return make_pair(v1.first+v2.first,v1.second+v2.second);
		}
	}
}tr2;
int main()
{
	scanf("%*d%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]),v[i]=v[i]>n?n:v[i];
	scanf("%d",&q);
	for(int i=1;i<=q;i++)scanf("%d%d",&a,&b),qu[a].push_back((que){a,b,i});
	fu.insert(0);tr.build(1,0,n);tr2.build(1,1,n+1);
	fu.insert(n+1);
	for(int i=0;i<=n;i++)vl[i]=n+1;
	for(int i=n;i>=1;i--)
	{
		if(!fu.count(v[i]))vl[v[i]]=i,tr.modify(1,v[i],i);
		else
		{
			int las=-1,rb=n,lb=v[i];
			set<int>::iterator it=fu.find(v[i]);
			if(it!=fu.begin())las=*(--it);
			it=fu.find(v[i]);it++;rb=(*it)-1;
			tr2.modify(1,vl[v[i]],n+1,-1ll*i*(rb-lb+1),(rb-lb+1));
			tr.modify(1,v[i],i);vl[v[i]]=i;fu.erase(v[i]);
			if(las!=-1)tr2.modify(1,vl[las],n+1,-1ll*i*(v[i]-las),(v[i]-las));
			while(1)
			{
				int tp=tr.query(1,lb,rb,vl[las]+1);
				if(tp==-1)break;
				if(las!=-1)tr2.modify(1,vl[las],n+1,1ll*i*(tp-las),-(tp-las));
				las=tp;fu.insert(tp);lb=tp+1;
			}
			tr2.modify(1,vl[las],n+1,1ll*i*(rb-las+1),-(rb-las+1));
		}
		for(int j=0;j<qu[i].size();j++)
		{
			pair<ll,ll> tp=tr2.query(1,qu[i][j].l,qu[i][j].r);
			as[qu[i][j].id]=1ll*tp.first+1ll*tp.second*(i-1);
		}
	}
	for(int i=1;i<=q;i++)printf("%lld\n",as[i]);
}
auoj91 浑水摸鱼
Problem

给一个序列,对于一个区间 [l,r][l,r] ,定义它的权值序列为

从左往右考虑每个元素,如果这个数之前没有出现过,它的权值为前面的数种数+1,否则它的权值为这个数上次出现的位置的权值

求本质不同的权值序列数量

n5×104n\leq 5\times 10^4

4s,512MB4s,512MB

Sol

如果 ll 确定,那么后面的权值序列都可以确定

考虑将所有后缀排序,减去两两的lcp就是答案

记录 preipre_i 表示这个位置的数上一次出现的位置

两个区间 [l1,r1],[l2,r2][l_1,r_1],[l_2,r_2] 是相同的,当且仅当 i[l1,r1]preili,iprei=(i+l2l1)prei+l2l1\forall i\in[l_1,r_1]并且pre_i\geq l_i,满足i-pre_i=(i+l_2-l_1)-pre_{i+l_2-l_1}

只需要判断 ipreii-pre_i 是否相等,可以使用主席树维护hash值,排序时二分lcp再判断下一位即可

复杂度 O(nlog3n)O(n\log^3 n)

Code
#include<cstdio>
#include<set>
#include<algorithm>
#pragma GCC optimize("-Ofast")
using namespace std;
#define N 50050
#define ll long long
#define mod 10000000000001ll
int n,v[N],nt[N],vl[N];
ll pw[N],v1[N];
set<int> tp[N];
struct pretree{
	ll s1[N*32];
	int s2[N*32],ch[N*32][2],rt[N],ct,ct2;
	int build(int l,int r)
	{
		int st=++ct;
		if(l==r)return st;
		int mid=(l+r)>>1;
		ch[st][0]=build(l,mid);ch[st][1]=build(mid+1,r);
		return st;
	}
	void init(){rt[0]=build(1,n+1);}
	int modify(int x,int l,int r,int v,ll v1,int v2)
	{
		int st=++ct;s1[st]=(s1[x]+v1)%mod,s2[st]=s2[x]+v2;ch[st][0]=ch[x][0];ch[st][1]=ch[x][1];
		if(l==r)return st;
		int mid=(l+r)>>1;
		if(mid>=v)ch[st][0]=modify(ch[x][0],l,mid,v,v1,v2);
		else ch[st][1]=modify(ch[x][1],mid+1,r,v,v1,v2);
		return st;
	}
	void modify2(int v,ll v1,int v2){rt[ct2+1]=modify(rt[ct2],1,n+1,v,v1,v2);ct2++;}
	ll que1(int x1,int l,int r,int r1)
	{
		if(!x1)return 0;
		if(r==r1)return (mod-s1[x1])%mod;
		int mid=(l+r)>>1;
		if(mid>=r1)return que1(ch[x1][0],l,mid,r1);
		else return (mod-s1[ch[x1][0]]+que1(ch[x1][1],mid+1,r,r1))%mod;
	}
	int que2(int x1,int x2,int l,int r,int l1,int r1)
	{
		if(!x2)return 0;
		if(l==l1&&r==r1)return s2[x2]-s2[x1];
		int mid=(l+r)>>1;
		if(mid>=r1)return que2(ch[x1][0],ch[x2][0],l,mid,l1,r1);
		else if(mid<l1)return que2(ch[x1][1],ch[x2][1],mid+1,r,l1,r1);
		else return que2(ch[x1][0],ch[x2][0],l,mid,l1,mid)+que2(ch[x1][1],ch[x2][1],mid+1,r,mid+1,r1);
	}
}tr;
ll mul(ll a,ll b){ll tp=(long double)a*b/mod;return (a*b-tp*mod+mod)%mod;}
int lcp(int a,int b)
{
	int lb=1,rb=n-max(a,b)+1,as=1;
	if(a>b)a^=b^=a^=b;
	ll v11=tr.que1(tr.rt[a-1],1,n+1,a-1),v21=tr.que1(tr.rt[b-1],1,n+1,b-1);
	while(lb<=rb)
	{
		int mid=(lb+rb)>>1;
		if(mul(pw[b-a],mod+tr.que1(tr.rt[a-1],1,n+1,a+mid-1)+mod-v11+v1[a+mid-1]-v1[a-1])==(tr.que1(tr.rt[b-1],1,n+1,b+mid-1)+mod-v21+v1[b+mid-1]-v1[b-1]+mod)%mod)as=mid,lb=mid+1;
		else rb=mid-1;
	}
	return as;
}
bool cmp(int a,int b)
{
	int tp1=lcp(a,b);
	if(a+tp1-1==n)return 1;
	if(b+tp1-1==n)return 0;
	int v1=v[a+tp1];
	int v2=*tp[v1].lower_bound(a);
	int s1=tr.que2(tr.rt[a-1],tr.rt[v2-1],1,n+1,v2,n+1);
	int v3=v[b+tp1];
	int v4=*tp[v3].lower_bound(b);
	int s2=tr.que2(tr.rt[b-1],tr.rt[v4-1],1,n+1,v4,n+1);
	return s1<s2;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]),vl[i]=n+1,tp[v[i]].insert(i);
	for(int i=n;i>=1;i--)nt[i]=vl[v[i]],vl[v[i]]=i;
	tr.init();ll st=1;pw[0]=1;for(int i=1;i<=n;i++)st=50021*st%mod,pw[i]=st,tr.modify2(nt[i],st*(nt[i]-i+1)%mod,1),v1[nt[i]]=(v1[nt[i]]+st*(nt[i]-i+1)%mod)%mod;
	for(int i=2;i<=n;i++)v1[i]=(v1[i]+v1[i-1])%mod;
	for(int i=1;i<=n;i++)vl[i]=i;
	stable_sort(vl+1,vl+n+1,cmp);
	ll as=1ll*n*(n+1)/2;
	for(int i=1;i<n;i++)as-=lcp(vl[i],vl[i+1]);
	printf("%lld\n",as);
}
auoj92 序列
Problem

给一个序列 aamm 个数,你可以选出一些数插入到 aa 中,要求相邻两个 aa 之间最多插一个数,不能插在开头和结尾

对于每个 kk ,求出插入 kk 个数,得到的序列相邻两个位置的差的和的最大值

n105n\leq 10^5

1s,1024MB1s,1024MB

Sol

如果把 vv 插入 (ai,ai+1)(a_i,a_{i+1}) 中,贡献为 max(0,2(min(ai+1,ai)v),2(vmax(ai,ai+1)))max(0,2(min(a_{i+1},a_i)-v),2(v-max(a_i,a_{i+1})))

显然插入不会变差,可以变成插入至多 kk 个,然后可以将上面的贡献变为 max(2(min(ai+1,ai)v),2(vmax(ai,ai+1)))max(2(min(a_{i+1},a_i)-v),2(v-max(a_i,a_{i+1})))

考虑这样的费用流建图:

其中除了min,max两条链上的边以外,剩余的边流量为1,费用为0

对于min链上的边,从 xxx+1x+1 连流量inf,费用为2的边,从 x+1x+1xx 连流量inf,费用-2的边

max链上边的费用相反

求出每个流量的最大费用最大流就可以得到答案

考虑模拟增广,如果一次最短的增广路经过了大于2个 (ai,ai+1)(a_i,a_{i+1}) 的点,考虑最先经过的两个点,根据前面的假设,经过这两个点再走后面比直接走后面优秀,因此这两个点,它们连向两条链的四个点以及两条链的一部分组成的环权值大于0,但这意味着之前的流不是最优的,因此不可能出现这种情况

因此只需要考虑经过1~2个的情况

显然每次增广走最小的或最大的 viv_i 一定最优,可以分类讨论

对于经过1个的,一定是直接匹配,于是记录当前没有用的 (ai,ai+1)(a_i,a_{i+1}) 中min最大的和max最小的即可

对于经过2个的,如果一开始从 viv_i 流到了 minmin ,那么首先找匹配了 minmin 的一对 (x,y)(x,y) 让这一对匹配min,然后到max链,然后找一对没有匹配的 (x1,y1)(x_1,y_1) ,费用为 2(xvi)+2(yy1)2(x-v_i)+2(y-y_1)

如果一开始流到了 maxmax ,那么首先找匹配了 maxmax 的一对 (x,y)(x,y) 让这一对匹配max,然后到min链,然后找一对没有匹配的 (x1,y1)(x_1,y_1) ,费用为 2(vix)+2(y1y)2(v_i-x)+2(y_1-y)

只需要分别维护匹配了 max,minmax,min(x,y)(x,y)x+yx+y 的最小/最大值即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define N 100060
int n,m,v[N],v1[N],f1[N],f2[N],t1[N],t2[N],is[N];
long long su,las;
bool cmp1(int a,int b){return f1[a]>f1[b];}
bool cmp2(int a,int b){return f2[a]<f2[b];}
priority_queue<int> q1,q2;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<n;i++)f1[i]=min(v[i],v[i+1]),f2[i]=max(v[i],v[i+1]),su+=v[i]>v[i+1]?v[i]-v[i+1]:v[i+1]-v[i],t1[i]=t2[i]=i;
	for(int i=1;i<=m;i++)scanf("%d",&v1[i]);sort(v1+1,v1+m+1);
	sort(t1+1,t1+n,cmp1);
	sort(t2+1,t2+n,cmp2);
	int l1=1,r1=m,d1=1,d2=1;
	las=su;
	q1.push(-1e9);q2.push(-1e9);
	for(int i=1;i<=m;i++)
	{
		while(is[t1[d1]])d1++;
		int mx1=f1[t1[d1]];
		while(is[t2[d2]])d2++;
		int mn1=f2[t2[d2]];
		int vl1=mx1-v1[l1],vl2=v1[r1]-mn1;
		int vl3=q1.top()-v1[l1]-mn1,vl4=mx1+v1[r1]+q2.top();
		int mx=max(max(vl1,vl2),max(vl3,vl4));
		if(vl1==mx)
		{
			is[t1[d1]]=1;l1++;
			q2.push(-(f1[t1[d1]]+f2[t1[d1]]));
		}
		else if(vl2==mx)
		{
			is[t2[d2]]=1;r1--;
			q1.push(f1[t2[d2]]+f2[t2[d2]]);
		}
		else if(vl3==mx)
		{
			is[t2[d2]]=1;l1++;
			int v2=q1.top();q1.pop();q2.push(-v2);
			q1.push(f1[t2[d2]]+f2[t2[d2]]);
		}
		else
		{
			is[t1[d1]]=1;r1--;
			int v2=q2.top();q2.pop();q1.push(-v2);
			q2.push(-(f1[t1[d1]]+f2[t1[d1]]));
		}
		su+=2*mx;
		if(su<las)su=las;
		las=su;
		printf("%lld ",su);
	}
}
auoj93 小Z的树
Problem

有一个有根森林,每个点有权值,支持如下操作

  1. 将一个点的父亲设为0(即将它看做根),并修改它的权值
  2. 修改一个点的父亲
  3. 定义一个点的价值为它所在树的根的价值,求 [l,r][l,r] 内的点的价值和

n,q2×105n,q\leq 2\times10^5

5s,512MB5s,512MB

Sol

对操作分块,对于每一块的操作,只会修改 O(size)O(size) 个点的父亲

将这些点到父亲的边断开,那么剩下的每个连通块在这一段的修改中父亲都是一样的

对于所有与修改的点不连通的连通块,它们的值固定,可以先预处理前缀和

对于剩下的连通块,最多只有 O(size)O(size) 个,可以暴力维护它们的父亲

1,2操作暴力修改,对于3操作,先暴力求出这些连通块最后的根,然后对于每个连通块二分找到它内部有多少个点在询问区间中,直接算即可

复杂度 O(nnlogn)O(n\sqrt{n\log n})

Code
#include<cstdio>
#include<vector>
using namespace std;
#define N 205000
#define M 2000
int n,q,fa[N],vl[N],f1[N],f2[N],is[N],is2[N],id[N],tid[N],ct,qu[N][3],as[N];
vector<int> fu[M];
long long s1[N];
void doit(int l,int r)
{
	for(int i=1;i<=n;i++)f1[i]=f2[i]=is[i]=is2[i]=id[i]=0;ct=0;
	for(int i=1;i<=n;i++)
	{
		int st=i;
		while(1)
		{
			if(f1[st]){break;}
			if(!fa[st]){f1[st]=st;break;}
			st=fa[st];
		}
		int as=f1[st];
		st=i;
		while(!f1[st])f1[st]=as,st=fa[st];
	}
	for(int i=l;i<=r;i++)if(qu[i][0]!=2)is[f1[qu[i][1]]]=1,is2[qu[i][1]]=1;
	for(int i=1;i<=n;i++)if(!is[f1[i]])s1[i]=vl[f1[i]];else s1[i]=0;
	for(int i=2;i<=n;i++)s1[i]+=s1[i-1];
	for(int i=1;i<=n;i++)
	{
		int st=i;
		while(1)
		{
			if(f2[st]){break;}
			if(!fa[st]||is2[st]){f2[st]=st;break;}
			st=fa[st];
		}
		int as=f2[st];
		st=i;
		while(!f2[st])f2[st]=as,st=fa[st];
	}
	for(int i=1;i<=n;i++)if(is[f1[i]])
	{
		if(!id[f2[i]])id[f2[i]]=++ct,tid[ct]=f2[i];
		fu[id[f2[i]]].push_back(i);
	}
	for(int i=l;i<=r;i++)
	if(qu[i][0]==0){vl[qu[i][1]]=qu[i][2];fa[qu[i][1]]=0;}
	else if(qu[i][0]==1){fa[qu[i][1]]=qu[i][2];}
	else
	{
		long long as1=s1[qu[i][2]]-s1[qu[i][1]-1];
		for(int j=1;j<=ct;j++)as[j]=-2e9;
		for(int j=1;j<=ct;j++)
		{
			int st=tid[j];
			while(as[j]==-2e9)
			{
				if(!is[f1[st]]){as[j]=vl[f1[st]];break;}
				st=f2[st];
				if(!fa[st])as[j]=vl[st];
				else if(as[id[st]]!=-2e9)as[j]=as[id[st]];
				else st=fa[st];
			}
			as1+=1ll*as[j]*(lower_bound(fu[j].begin(),fu[j].end(),qu[i][2]+1)-lower_bound(fu[j].begin(),fu[j].end(),qu[i][1]));
		}
		printf("%lld\n",as1);
	}
	for(int j=1;j<=ct;j++)fu[j].clear();
}
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)scanf("%d",&vl[i]);
	for(int i=1;i<=q;i++)scanf("%d%d%d",&qu[i][0],&qu[i][1],&qu[i][2]);
	for(int i=1;i<=q;i+=500)doit(i,i+499>q?q:i+499);
}
auoj94 小Y的图
Problem

PDF题面

Sol

题目条件提示将图划分成若干个 5×55\times 5 的矩形

对于开头的矩形,暴搜可以发现存在一种方式,使得从 (1,1)(1,1) 开始,不经过 (3,3)(3,3) ,最后到达 (5,3)(5,3)

这样可以走到下面的 5×55\times 5 的中心,可以发现从中心开始,存在下一步向四个方向走的方式

于是偶数可以这样构造:

因为最后留出了 (3,3)(3,3) ,所以是合法的

对于奇数,考虑这样构造

中间斜线之后的那个格子,可以发现从 (4,2)(4,2) 开始, (3,1)(3,1) 结束存在一个合法的方案,于是可以这样构造

复杂度 O(n2)O(n^2)

Code
#include<cstdio>
using namespace std;
#define N 2333
int n,as[N][N],st[6][6],e1,e2,fg,ct=24,as2[6][6][6][6][6][6];
void dfs(int d,int x,int y)
{
	if(d==26){fg=1;return;}
	if(x==e1&&y==e2){st[x][y]=0;return;}
	if(x>3&&st[x-3][y]==0)st[x-3][y]=d,dfs(d+1,x-3,y);if(fg)return;
	if(x<5-2&&st[x+3][y]==0)st[x+3][y]=d,dfs(d+1,x+3,y);if(fg)return;
	if(y>3&&st[x][y-3]==0)st[x][y-3]=d,dfs(d+1,x,y-3);if(fg)return;
	if(y<5-2&&st[x][y+3]==0)st[x][y+3]=d,dfs(d+1,x,y+3);if(fg)return;
	{
		if(x>2&&y>2&&st[x-2][y-2]==0)st[x-2][y-2]=d,dfs(d+1,x-2,y-2);if(fg)return;
		if(x>2&&y<5-1&&st[x-2][y+2]==0)st[x-2][y+2]=d,dfs(d+1,x-2,y+2);if(fg)return;
		if(x<5-1&&y>2&&st[x+2][y-2]==0)st[x+2][y-2]=d,dfs(d+1,x+2,y-2);if(fg)return;
		if(x<5-1&&y<5-1&&st[x+2][y+2]==0)st[x+2][y+2]=d,dfs(d+1,x+2,y+2);if(fg)return;
	}
	st[x][y]=0;
}
void justdoit(int s1,int s2,int t1,int t2)
{
	if(as2[s1][s2][t1][t2][1][1]){for(int i=1;i<=5;i++)for(int j=1;j<=5;j++)st[i][j]=as2[s1][s2][t1][t2][i][j];return;}
	e1=t1,e2=t2;fg=0;
	for(int i=1;i<=5;i++)for(int j=1;j<=5;j++)st[i][j]=0;
	st[s1][s2]=1;dfs(2,s1,s2);
	for(int i=1;i<=5;i++)for(int j=1;j<=5;j++)as2[s1][s2][t1][t2][i][j]=st[i][j];
}
void doit(int s1,int s2,int t1,int t2,int x,int y)
{
	justdoit(s1,s2,t1,t2);
	for(int j=1;j<=5;j++)
	for(int k=1;k<=5;k++)
	as[x*5-5+j][y*5-5+k]=st[j][k]+ct;
	ct+=25;
}
int main()
{
	scanf("%d",&n);
	if(n==5){printf("1 14 7 4 15\n9 22 17 12 23\n19 5 25 20 6\n2 13 8 3 16\n10 21 18 11 24");return 0;}
	as[1][1]=1;as[1][2]=8;as[1][3]=5;as[1][4]=2;as[1][5]=9;
	as[2][1]=15;as[2][2]=20;as[2][3]=23;as[2][4]=12;as[2][5]=17;
	as[3][1]=6;as[3][2]=3;as[3][3]=23333;as[3][4]=7;as[3][5]=4;
	as[4][1]=22;as[4][2]=11;as[4][3]=16;as[4][4]=21;as[4][5]=10;
	as[5][1]=14;as[5][2]=19;as[5][3]=24;as[5][4]=13;as[5][5]=18;
	for(int i=2;i<n/5;i++)doit(3,3,5,3,i,1);
	doit(3,3,3,5,n/5,1);
	for(int i=2;i<n/5;i++)doit(3,3,3,5,n/5,i);
	doit(3,3,1,3,n/5,n/5);
	for(int i=n/5-1;i>1;i--)doit(3,3,1,3,i,n/5);
	doit(3,3,3,1,1,n/5);
	if(n%10==0)
	for(int i=n/5-1;i>1;i-=2)
	{
		for(int j=1;j<n/5-1;j++)doit(3,3,5,3,j,i);
		doit(3,3,3,1,n/5-1,i);
		for(int j=n/5-1;j>1;j--)doit(3,3,1,3,j,i-1);
		doit(3,3,3,1,1,i-1);
	}
	else
	if(n>15)
	{
		for(int i=n/5-1;i>4;i-=2)
		{
			for(int j=1;j<n/5-1;j++)doit(3,3,5,3,j,i);
			doit(3,3,3,1,n/5-1,i);
			for(int j=n/5-1;j>1;j--)doit(3,3,1,3,j,i-1);
			doit(3,3,3,1,1,i-1);
		}
		doit(3,3,5,3,1,4);
		for(int j=2;j<n/5-1;j++)doit(3,3,5,3,j,4);
		doit(3,3,3,1,n/5-1,4);
		for(int j=n/5-1;j>2;j-=2)
		{
			doit(3,3,3,1,j,3);
			doit(3,3,1,3,j,2);
			doit(3,3,3,5,j-1,2);
			doit(3,3,1,3,j-1,3);
		}
		doit(3,3,3,1,2,3);
		doit(3,3,1,5,2,2);
		doit(4,2,3,1,1,3);
		doit(3,3,3,1,1,2);
	}
	else
	{
		ct-=50;
		doit(3,3,3,1,2,3);
		doit(3,3,1,5,2,2);
		doit(4,2,3,1,1,3);
		doit(3,3,3,1,1,2);
	}
	as[3][3]=ct+1;
	for(int i=1;i<=n;i++,printf("\n"))
	for(int j=1;j<=n;j++)printf("%d ",as[i][j]);
}
auoj95 数据结构
Problem

你有 nn 个数据结构,每个数据结构有 ai,bi(aibi)a_i,b_i(a_i\geq b_i)

你可以把第 ii 个数据结构套在第 jj 个里面当且仅当 ai<bja_i<b_j

每个数据结构只能直接套一个数据结构,每个数据结构只能被直接套一次,但可以多重嵌套

你会一直执行嵌套操作,直到不能套为止

求可能出现的嵌套方案数,模 109+710^9+7

n300n\leq 300

1s,512MB1s,512MB

Sol

因为 aibia_i\geq b_i ,可以看成有 nn 个a和 nn 个b,一个b只能匹配小于他的a

因为最后要求不能再匹配,因此如果当前有一个a最后没有被匹配,那么大于它的b必须匹配

dpi,j,0/1dp_{i,j,0/1} 表示考虑了前 ii 个数,前面有 jj 个a和后面的b匹配,前面是否有a最后没有匹配

如果当前是b,则

dpi,j,0=dpi1,j,0+dpi1,j+1,0(j+1)dp_{i,j,0}=dp_{i-1,j,0}+dp_{i-1,j+1,0}*(j+1)

dpi,j,1=dpi1,j+1,1(j+1)dp_{i,j,1}=dp_{i-1,j+1,1}*(j+1)

如果当前是a,则

dpi,j,0=dpi1,j1,0dp_{i,j,0}=dp_{i-1,j-1,0}

dpi,j,1=dpi1,j1,1+dpi1,j,1+dpi1,j,0dp_{i,j,1}=dp_{i-1,j-1,1}+dp_{i-1,j,1}+dp_{i-1,j,0}

答案为 dp2n,0,0+dp2n,0,1dp_{2n,0,0}+dp_{2n,0,1}

复杂度 O(n2)O(n^2)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 605
#define mod 1000000007
int n,v[N],dp[N][N][2];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&v[i*2-1],&v[i*2]);
		v[i*2]<<=1;
		v[i*2-1]=v[i*2-1]<<1|1;
	}
	sort(v+1,v+n*2+1);
	dp[0][0][0]=1;
	for(int i=1;i<=n*2;i++)
	for(int j=0;j<=n;j++)
	for(int k=0;k<2;k++)
	if(~v[i]&1)
	{
		if(j)dp[i][j-1][k]=(dp[i][j-1][k]+1ll*dp[i-1][j][k]*j)%mod;
		if(!k)dp[i][j][k]=(dp[i][j][k]+dp[i-1][j][k])%mod;
	}
	else
	{
		dp[i][j+1][k]=(dp[i][j+1][k]+dp[i-1][j][k])%mod;
		dp[i][j][1]=(dp[i][j][1]+dp[i-1][j][k])%mod;
	}
	printf("%d\n",(dp[n*2][0][0]+dp[n*2][0][1])%mod);
}
auoj96 三国学者
Problem

给一棵有根树,每个点有 [1,L][1,L] 的权值 sis_i

mm 次修改,第 ii 次修改第 ((i1)modn)+1((i-1)\bmod n)+1 个点的权值为 viv_i ,每次修改之后,你需要求出有多少个长度为 LL 的序列 a1,...,ma_{1,...,m} 满足

  1. 1ain1\leq a_i\leq n
  2. aia_i 号点的权值为 Li+1L-i+1
  3. aia_iai+1a_{i+1} 的祖先

输出 iansi\sum i*ans_i109+710^9+7 的结果,其中 ansians_i 为第 ii 次修改后的答案

n,L106,m2×106,fai<in,L\leq 10^6,m\leq 2\times 10^6,fa_i<i

1s,512MB1s,512MB

Sol

fif_i 表示以 ii 结尾的合法序列数,那么有

fi=1(si=L)f_i=1(s_i=L)

fi=ji,sj=si+1fj(si<L)f_i=\sum_{j是i的祖先,s_j=s_i+1}f_j(s_i<L)

ans=si=1fians=\sum_{s_i=1}f_i

把每 nn 次修改看成一轮修改

每次修改对答案的改变量为修改后包含这个点的序列数量减去修改前包含这个点的序列数量

gig_i 表示以 ii 开头的合法序列数,有

gi=1(si=1)g_i=1(s_i=1)

gi=ji,sj=si1gj(si>1)g_i=\sum_{j在i子树内,s_j=s_i-1}g_j(s_i>1)

hi,j=ji,sj=kgjh_{i,j}=\sum_{j在i子树内,s_j=k}g_j

那么包含 ii 的序列数量为 fihi,si1f_i*h_{i,s_i-1}

注意到在一轮修改中,修改一个点时,它的祖先都被改过了,所以只需要对于每个点求出它的祖先都被改了,它被改了与没有被改时的 ff ,可以认为所有点都被改了,dfs时开个桶维护祖先的f即可

然后还需要查 hi,si1,hi,si1h_{i,s_i-1},h_{i,s_i^{'}-1} ,dfs时开另外一个桶记录即可

复杂度 O(n+m+L)O(n+m+L)

Code
#include<cstdio>
using namespace std;
#define N 2000600
#define mod 1000000007
int n,m,l,fa[N],head[N],cnt,v1[N],v2[N],f1[N],f2[N],g1[N],g2[N],as[N*2],vl[N],vl2[N],q2[N*2];
struct edge{int t,next;}ed[N];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;}
void dfs0(int u,int fa)
{
	int f11=v1[vl[u]-1],f21=v1[vl2[u]-1];
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs0(ed[i].t,u);
	int fu=(v1[vl[u]-1]-f11+mod+(vl[u]==1))%mod;
	f2[u]=(v1[vl2[u]-1]-f21+mod+(vl2[u]==1))%mod;
	f1[u]=fu;v1[vl[u]]=(v1[vl[u]]+fu)%mod;
}
void dfs1(int u,int fa)
{
	g1[u]=v2[vl[u]+1]+(vl[u]==l);g2[u]=v2[vl2[u]+1]+(vl2[u]==l);v2[vl2[u]]=(v2[vl2[u]]+g2[u])%mod;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs1(ed[i].t,u);
	v2[vl2[u]]=(v2[vl2[u]]+mod-g2[u])%mod;
}
int rd(){int as=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')as=as*10+c-'0',c=getchar();return as;}
int main()
{
	scanf("%d%d%d",&n,&m,&l);
	for(int i=2;i<=n;i++)fa[i]=rd(),adde(i,fa[i]);
	for(int i=1;i<=n;i++)vl[i]=rd();
	int tp=(m-1)/n+1;
	for(int i=1;i<=tp;i++)q2[i]=1;
	for(int i=1;i<=m;i++)q2[i]=rd();
	for(int i=1;i<=tp;i++)
	{
		for(int j=1;j<=l;j++)v1[j]=v2[j]=f1[j]=f2[j]=g1[j]=g2[j]=0;
		for(int j=1;j<=n;j++)vl2[j]=q2[i*n-n+j];
		dfs0(1,0);int las=v1[l];dfs1(1,0);
		for(int j=1;j<=n;j++)las=(las+1ll*f2[j]*g2[j]-1ll*f1[j]*g1[j]%mod+mod)%mod,as[i*n-n+j]=las,vl[j]=vl2[j];
	}
	int su=0;
	for(int i=1;i<=m;i++)su=(su+1ll*i*as[i])%mod;
	printf("%d\n",su);
}
auoj98 ZSY家今天的饭
Problem

有一棵带边权树,有 mm 个关键点,从中随机选出 kk 个点,找到一条路径经过所有 kk 个点且使得路径长度最短

求最短路径的长度的期望,模 998244353998244353

n105,m,k500n\leq 10^5,m,k\leq 500

2s,512MB2s,512MB

Sol

最短路径相当于最小的包含这 kk 个点的连通块的边数乘2减去连通块的直径

对于第一部分,枚举每条边,记录这条边两侧关键点的数量,可以 O(1)O(1) 算贡献

对于第二部分,考虑枚举直径(如果有多对直径 (u,v)(u,v) ,找 uu 尽量小时 vv 尽量小的)

对于一条直径 (u,v)(u,v) ,如果 xx 满足 dis(u,x),dis(v,x)dis(u,v)dis(u,x),dis(v,x)\leq dis(u,v) ,那么 xx 在连通块中时 (u,v)(u,v) 仍然是一条直径

可以发现合法的 xx 间的路径一定不会超过 (u,v)(u,v) ,因此可以找出所有的 xx ,那么 (u,v)(u,v) 的贡献次数为 Ccnt2m2C_{cnt-2}^{m-2}

为了处理路径长度相同的问题,如果 dis(u,x)=dis(u,v)dis(u,x)=dis(u,v) ,则需要 x>vx>v ,对于 vv 同理

复杂度 O(m2logn+m3)O(m^2\log n+m^3)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 105000
#define M 505
#define mod 998244353
int n,m,k,head[N],c[M][M],cnt,as,sz[N],v[N],is[N],f[N][18],de[N],a,b,d;
long long dep[N],dis[M][M];
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%mod;a=1ll*a*a%mod;b>>=1;}return as;}
struct edge{int t,next,v;}ed[N*2];
void adde(int f,int t,int v){ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;ed[++cnt]=(edge){f,head[t],v};head[t]=cnt;}
void dfs(int u,int fa)
{
	f[u][0]=fa;for(int i=1;i<=17;i++)f[u][i]=f[f[u][i-1]][i-1];
	de[u]=de[fa]+1;sz[u]=is[u];
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)
	{
		dep[ed[i].t]=dep[u]+ed[i].v;
		dfs(ed[i].t,u);sz[u]+=sz[ed[i].t];
		int tp=(1ll*c[m][k]-c[sz[ed[i].t]][k]-c[m-sz[ed[i].t]][k]+2ll*mod)%mod;
		as=(as+2ll*ed[i].v*tp)%mod;
	}
}
int LCA(int x,int y){if(de[x]<de[y])x^=y^=x^=y;for(int i=17;i>=0;i--)if(de[x]-(1<<i)>=de[y])x=f[x][i];if(x==y)return x;for(int i=17;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];return f[x][0];}
long long getdis(int x,int y){return dep[x]+dep[y]-2*dep[LCA(x,y)];}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;i++)scanf("%d",&v[i]),is[v[i]]=1;
	for(int i=1;i<n;i++)scanf("%d%d%d",&a,&b,&d),adde(a,b,d);
	for(int i=0;i<=m;i++)c[i][0]=c[i][i]=1;
	for(int i=2;i<=m;i++)
	for(int j=1;j<i;j++)c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	dfs(1,0);
	for(int i=1;i<=m;i++)
	for(int j=1;j<=m;j++)dis[i][j]=getdis(v[i],v[j]);
	if(k>=2)
	for(int i=1;i<=m;i++)
	for(int j=i+1;j<=m;j++)
	{
		int ct=2;
		for(int l=1;l<=m;l++)
		{
			long long tp=max(dis[i][l],dis[j][l]);
			if(tp<dis[i][j]||(tp==dis[i][j]&&(dis[i][l]<dis[i][j]||l<j)&&(dis[j][l]<dis[i][j]||l<i)&&(l!=j&&l!=i)))ct++;
		}
		if(ct>=k)as=(as-dis[i][j]%mod*c[ct-2][k-2]%mod+mod)%mod;
	}
	printf("%d\n",1ll*as*pw(c[m][k],mod-2)%mod);
}
auoj101 鼠
Problem

n+2n+2 个点,前 nn 个点每个点各有一条白色出边和一条黑色出边,出边一定向后,后两个点为终止节点

多组询问,每次给一只鼠,初始在位置 sis_i ,初始颜色为 cic_i 以及一个数 viv_i ,它每次会走和自己颜色相同的边,每经过 kk 条边会变换自己的颜色,求它最后停止的点编号减去 n+1n+1 的结果

n,q5×104n,q\leq 5\times 10^4

1s,512MB1s,512MB

Sol

考虑根号分治,对于 knk\leq \sqrt n 的部分,可以先预处理每个点向后走 kk 步白色边或黑色边时会到哪,然后可以 O(n)O(n) 求出对于一个 kk 的所有答案

对于 k>nk>\sqrt n ,显然切换颜色的次数不超过 n\sqrt n ,直接倍增即可

复杂度 O(nnlogn)O(n\sqrt n\log n) ,使用 O(1)O(1) 快速幂的分块思想可以做到 O(nn)O(n\sqrt n)

Code
#include<cstdio>
using namespace std;
#define N 50050
#define K 305
int n,q,a,b,c,f[N][K][2],nt[N][2],g[N][16][2];
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)scanf("%d%d",&nt[i][0],&nt[i][1]);
	nt[n+1][0]=nt[n+1][1]=n+1;
	nt[n+2][0]=nt[n+2][1]=n+2;
	for(int i=1;i<=n+2;i++)f[i][1][0]=nt[i][0],f[i][1][1]=nt[i][1];
	for(int j=2;j<=300;j++)
	for(int i=1;i<=n+2;i++)
	f[i][j][0]=nt[f[i][j-1][0]][0],f[i][j][1]=nt[f[i][j-1][1]][1];
	for(int j=1;j<=300;j++)
	for(int i=n;i>=1;i--)
	f[i][j][0]=f[f[i][j][0]][j][1],f[i][j][1]=f[f[i][j][1]][j][0];
	for(int i=1;i<=n+2;i++)g[i][0][0]=nt[i][0],g[i][0][1]=nt[i][1];
	for(int j=1;j<=15;j++)
	for(int i=1;i<=n+2;i++)
	g[i][j][0]=g[g[i][j-1][0]][j-1][0],g[i][j][1]=g[g[i][j-1][1]][j-1][1];
	while(q--)
	{
		scanf("%d%d%d",&a,&b,&c);
		if(b<=300)printf("%d\n",f[a][b][c]-n-1);
		else
		{
			while(a<=n)
			{
				int st=b,vl=0;
				while(st){if(st&1)a=g[a][vl][c];vl++;st>>=1;}
				c^=1;
			}
			printf("%d\n",a-n-1);
		}
	}
}
auoj102 Arcahv
Problem

2n2^n 个人,每个人有一个实力值,每个人的实力值不同

接下来会进行 nn 轮比赛,每一轮,当前的第1个人和第2个人会进行比赛,实力值更高的人留下来,然后第3个人和第4个人比赛,剩下的同理

qq 次询问,每次给定 x,kx,k ,表示在进行不超过 kk 次交换两个人的位置的操作的情况下,原来的第 xx 个人最多能赢几场比赛

强制在线

n19,q2×105n\leq 19,q\leq 2\times 10^5

1s,16MB1s,16MB

Sol

可以将比赛过程看成类似线段树的形式,一个人能赢 ii 次当且仅当它是它所在的长度为 2i2^i 的区间中的最大数

如果 k=0k=0 ,那么只需要看它到根的 loglog 个区间即可

否则,一个区间合法当且仅当它的第 k+1k+1 大数小于等于 xx 的实力值且 xx 的实力值大于等于区间长度

用归并排序就能 O(nlogn)O(n\log n) 求出每个区间排好序的结果,对于每一个区间长度 2i2^i ,只需要关心对于每一个 kk ,所有长度为 2i2^i 的区间中第 kk 大数的最小值,因为 2i=O(2n)\sum 2^i=O(2^n) ,所以可以直接存

时间复杂度 O(n2n+nq)O(n2^n+nq) ,空间复杂度 O(2n)O(2^n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 524300
#define M 200100
int n,k,q,t,a,b,las,fuc[N*2],tid[N],tr[N*2];
void solve(int l,int r)
{
	if(l==r)return;
	int mid=(l+r)>>1;
	solve(l,mid);solve(mid+1,r);
	int l1=l,r1=mid+1;
	for(int i=l;i<=r;i++)
	if(l1>mid)tr[n+i]=tr[r1++];
	else if(r1>r)tr[n+i]=tr[l1++];
	else if(tr[l1]<tr[r1])tr[n+i]=tr[l1++];
	else tr[n+i]=tr[r1++];
	for(int i=l;i<=r;i++)tr[i]=tr[n+i];
	for(int i=l;i<=r;i++)fuc[r-l+1+(r-i)]=min(fuc[r-l+1+(r-i)],tr[i]);
}
int query(int x,int y)
{
	int as=0;
	if(y)
	for(int i=1;i<=k;i++)
	{
		if((1<<i)>x)break;
		if((1<<i)<=y){as=i;continue;}
		if(fuc[y+(1<<i)]<=x)as=i;
		else break;
	}
	else
	{
		int st=tid[x]+n-1;
		for(int i=1;i<=k;i++)
		{
			st>>=1;
			if(tr[st]<=x)as=i;
		}
	}
	return as;
}
int main()
{
	scanf("%d%d%d",&n,&k,&t);
	for(int i=1;i<=n;i++)scanf("%d",&tr[i]),tid[tr[i]]=i;
	scanf("%d",&q);
	for(int i=0;i<=n;i++)fuc[i]=n+1;
	solve(1,n);
	for(int i=0;i<=n*2;i++)tr[i]=0;
	for(int i=1;i<=n;i++)
	{
		int st=tid[i]+n-1;
		while(st)tr[st]=i,st=st>>1;
	}
	for(int i=1;i<=q;i++)scanf("%d%d",&a,&b),a^=t*las,b^=t*las,printf("%d\n",las=query(a,b));
}
auoj104 Number
auoj105 Module
auoj106 Robot

见我写的题解

auoj 107 查拉图斯特拉如是说
Problem

i=0Cnif(i)\sum_{i=0}C_n^i f(i)998244353998244353 的结果,其中 ff 是一个 mm 次多项式

n109,m105n\leq 10^9,m\leq 10^5

2s,512MB2s,512MB

Sol

ans=i=0nCnij=0mfjijans=\sum_{i=0}^nC_n^i\sum_{j=0}^mf_ji^j

ans=i=0nCnij=0mfjk=0jSjkCikk!ans=\sum_{i=0}^nC_n^i\sum_{j=0}^mf_j\sum_{k=0}^jS_j^kC_i^kk!

ans=j=0mfjk=0jSjkk!(i=0nCniCik)ans=\sum_{j=0}^mf_j\sum_{k=0}^jS_j^kk!(\sum_{i=0}^nC_n^iC_i^k)

ans=j=0mfjk=0jSjkk!Cnk2nkans=\sum_{j=0}^mf_j\sum_{k=0}^jS_j^kk!C_n^k2^{n-k}

ans=k=0mk!Cnk2nkj=0mfjSjkans=\sum_{k=0}^mk!C_n^k2^{n-k}\sum_{j=0}^mf_jS_j^k

ans=k=0mk!Cnk2nkj=0mfj1k!i=1k(1)kiCkijians=\sum_{k=0}^mk!C_n^k2^{n-k}\sum_{j=0}^mf_j\frac 1 {k!}\sum_{i=1}^k(-1)^{k-i}C_k^ij^i

ans=k=0mCnk2nki=1k(1)kiCkij=0mfjjians=\sum_{k=0}^mC_n^k2^{n-k}\sum_{i=1}^k(-1)^{k-i}C_k^i\sum_{j=0}^mf_jj^i

前面可以NTT,只需要求出所有的 fjji\sum f_jj^i ,多点求值即可

复杂度 O(mlog2m)O(m\log^2 m)

Code
#include<cstdio>
#include<vector>
using namespace std;
#define N 263000
#define mod 998244353
int n,m,v[N],a[N],b[N],c[N],d[N],e[N],ntt[N],rev[N],v2[N],g[2][N*2],as[N],fr[N],ifr[N],las;
vector<int> st[N],fu;
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
void dft(int s,int *a,int t)
{
	if(las!=s)for(int i=0;i<s;i++)rev[i]=(rev[i>>1]>>1)|((i&1)*(s>>1));
	las=s;
	for(int i=0;i<s;i++)ntt[rev[i]]=a[i];
	for(int i=2;i<=s;i<<=1)
	{
		for(int j=0;j<s;j+=i)
		for(int k=j,vl=0;k<j+(i>>1);k++,vl++)
		{
			int v1=ntt[k],v2=1ll*ntt[k+(i>>1)]*g[t][i+vl]%mod;
			ntt[k]=v1+v2-(v1+v2>=mod)*mod;
			ntt[k+(i>>1)]=v1-v2+(v2>v1)*mod;
		}
	}
	int inv=pw(s,t==0?mod-2:0);
	for(int i=0;i<s;i++)a[i]=1ll*ntt[i]*inv%mod;
}
void polyinv(int n,int *s,int *t)
{
	if(n==1){t[0]=pw(s[0],mod-2);return;}
	polyinv((n+1)>>1,s,t);
	int l=1;while(l<=n*1.5+2)l<<=1;
	for(int i=0;i<l;i++)a[i]=b[i]=0;
	for(int i=0;i<n;i++)a[i]=t[i],b[i]=s[i];
	dft(l,a,1);dft(l,b,1);for(int i=0;i<l;i++)a[i]=(2ll*a[i]-1ll*a[i]*a[i]%mod*b[i]%mod+mod)%mod;dft(l,a,0);
	for(int i=0;i<n;i++)t[i]=a[i];
}
vector<int> polymod(vector<int> a,vector<int> b)
{
	int s1=a.size()-1,s2=b.size()-1;
	if(s1<s2)return a;
	int tp=s1-s2+1;
	for(int i=0;i<tp;i++)c[i]=d[i]=0;
	for(int i=0;i<=s2;i++)c[s2-i]=b[i];
	polyinv(tp,c,d);
	int l=1;while(l<=tp*2)l<<=1;
	for(int i=tp;i<l;i++)c[i]=d[i]=0;
	for(int i=0;i<tp;i++)c[i]=a[s1-i];
	dft(l,c,1);dft(l,d,1);for(int i=0;i<l;i++)c[i]=1ll*c[i]*d[i]%mod;dft(l,c,0);
	for(int i=0;i<tp;i++)e[tp-1-i]=c[i];
	l=1;while(l<=s1)l<<=1;
	for(int i=tp;i<l;i++)e[i]=0;
	for(int i=0;i<l;i++)c[i]=0;
	for(int i=0;i<=s2;i++)c[i]=b[i];
	dft(l,c,1);dft(l,e,1);for(int i=0;i<l;i++)c[i]=1ll*c[i]*e[i]%mod;dft(l,c,0);
	for(int i=0;i<=s1;i++)a[i]=(a[i]-c[i]+mod)%mod;
	while(a.size()>1&&a[a.size()-1]==0)a.pop_back();
	return a;
}
vector<int> polymul(vector<int> a,vector<int> b)
{
	int s1=a.size()-1,s2=b.size()-1;
	int l=1;while(l<=s1+s2)l<<=1;
	for(int i=0;i<l;i++)c[i]=d[i]=0;
	for(int i=0;i<=s1;i++)c[i]=a[i];
	for(int i=0;i<=s2;i++)d[i]=b[i];
	dft(l,c,1);dft(l,d,1);for(int i=0;i<l;i++)c[i]=1ll*c[i]*d[i]%mod;dft(l,c,0);
	vector<int> f;
	for(int i=0;i<=s1+s2;i++)f.push_back(c[i]);
	return f;
}
void pre(int x,int l,int r)
{
	if(l==r){st[x].push_back(mod-v2[l]);st[x].push_back(1);return;}
	int mid=(l+r)>>1;
	pre(x<<1,l,mid);pre(x<<1|1,mid+1,r);
	st[x]=polymul(st[x<<1],st[x<<1|1]);
}
void solve(int x,int l,int r,vector<int> fu)
{
	if(l==r){as[l]=fu[0];return;}
	int mid=(l+r)>>1;
	solve(x<<1,l,mid,polymod(fu,st[x<<1]));
	solve(x<<1|1,mid+1,r,polymod(fu,st[x<<1|1]));
}
void pre()
{
	fr[0]=ifr[0]=1;for(int i=1;i<=m;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	for(int i=0;i<2;i++)
	for(int j=2;j<=1<<18;j<<=1)
	{
		int tp=pw(3,(mod-1)/j),v2=1;
		if(i==0)tp=pw(tp,mod-2);
		for(int l=0;l<j>>1;l++)g[i][j+l]=v2,v2=1ll*v2*tp%mod;
	}
}
int main()
{
	scanf("%d%d",&n,&m);pre();
	for(int i=0;i<=m;i++)scanf("%d",&v[i]),fu.push_back(v[i]);
	for(int i=0;i<=m;i++)v2[i]=i;
	pre(1,0,m);solve(1,0,m,fu);
	int l=1;while(l<=m*2)l<<=1;
	for(int i=0;i<l;i++)c[i]=d[i]=0;
	for(int i=0;i<=m;i++)c[i]=1ll*as[i]*ifr[i]%mod,d[i]=1ll*ifr[i]*(i&1?mod-1:1)%mod;
	dft(l,c,1);dft(l,d,1);for(int i=0;i<l;i++)c[i]=1ll*c[i]*d[i]%mod;dft(l,c,0);
	int tp=1,as1=0;
	for(int i=0;i<=m;i++)
	{
		as1=(as1+1ll*tp*pw(2,n-i)%mod*c[i])%mod;
		tp=1ll*tp*(n-i)%mod;
	}
	printf("%d\n",as1);
}
auoj108 橡树上的逃亡
Problem

给一棵有根树,保证 fi<if_i<i ,并且对于 u<v<wu<v<w ,如果 wwuu 子树内,那么 vv 也在 uu 子树内

多组询问,每次询问在编号在一个区间的所有叶子中随机选 kk 次(可以重复),求包含选中的点的最小连通块的边数的期望,模 998244353998244353

强制在线

n,q2×105,k10n,q\leq 2\times 10^5,k\leq 10

2s,1024MB2s,1024MB

Sol

条件相当于每个点的子树内的点编号构成一段连续的区间

对于一个点 xx ,考虑 xx 到父亲的边贡献

设询问区间的叶子数为 aa, xx 子树内叶子数为 bb ,那么贡献为 1ak(akbk(ab)k)\frac 1 {a^k}(a^k-b^k-(a-b)^k) ,这是一个关于 bbkk 次多项式,可以通过维护 bi(0ik)\sum b^i(0\leq i\leq k) 维护

suisu_i 表示前 ii 个点有多少个叶子, [li,ri][l_i,r_i] 表示第 ii 个点的子树区间,询问区间为 [l,r][l,r],那么如果 [li,ri][l,r]=[l_i,r_i]\cap [l,r]=\emptyset 则贡献为0,否则设 [li,ri][l,r]=[s,t][l_i,r_i]\cap [l,r]=[s,t] ,子树内叶子数为 sutsus1su_t-su_{s-1}

找到询问区间中最左边的叶子 ll 和最右边的叶子 rr 。显然可以将询问区间换成 [l,r][l,r]

对于 (lca,l)(lca,l) 上的点,它们的区间为 [l,ri][l,r_i]

对于 (lca,r)(lca,r) 上的点,它们的区间为 [li,r][l_i,r]

对于被 [l,r][l,r] 完全包含的区间,它们的区间不变

考虑树链剖分,对于每个点,记录这个点子树内所有点的区间内叶子数和的 ii 次方和,这个点的每一个子树的这个值,以及这个点重儿子右侧的子树的这些值的和,这个点重儿子左侧的子树的这些值的和

跳重链时只需要知道这条重链某一侧的值的和,因此可以前缀和,跳轻链时需要知道子树中一段的和,因此将子树排序后做前缀和即可

对于链上的贡献,再树剖维护每条链上 suli1,surisu_{l_i-1},su_{r_i}0,1,...,k0,1,...,k 次方和即可

复杂度 O(nk+q(klogn+k2))O(nk+q(k\log n+k^2))

Code
#include<cstdio>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
#define N 200500
#define mod 998244353
set<int> tp1;
int n,op,q,a,b,c,las,l[N],r[N],head[N],cnt,is[N],d[N],su[N],sz[N],sn[N],tp[N],id[N],sid[N],f[N],ct,C[11][11],f1[N][19],dep[N];
struct edge{int t,next;}ed[N*2];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;d[f]++;d[t]++;}
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
struct sth{
	int v[11];
	friend sth operator +(sth a,sth b){for(int i=0;i<=10;i++)a.v[i]=(a.v[i]+b.v[i])%mod;return a;}
	friend sth operator -(sth a,sth b){for(int i=0;i<=10;i++)a.v[i]=(a.v[i]-b.v[i]+mod)%mod;return a;}
}s1[N],sl[N],sr[N],vl[N],vr[N],fu1[N],fu2[N],fu3[N],fu4[N],as1,as2,as3;
sth doit(int a){sth tp;int st=1;for(int i=0;i<=10;i++)tp.v[i]=st,st=1ll*st*a%mod;return tp;}
sth doit0(){sth tp=doit(0);tp.v[0]=0;return tp;}
vector<sth> su1[N];
void dfs(int u,int fa)
{
	l[u]=r[u]=u;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u),l[u]=min(l[u],l[ed[i].t]),r[u]=max(r[u],r[ed[i].t]);
}
void dfs1(int u,int fa)
{
	f1[u][0]=fa;for(int i=1;i<=17;i++)f1[u][i]=f1[f1[u][i-1]][i-1];dep[u]=dep[fa]+1;
	s1[u]=doit(su[r[u]]-su[l[u]-1]);
	sz[u]=1;
	int ct=0;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)sid[ed[i].t]=++ct,dfs1(ed[i].t,u),sz[u]+=sz[ed[i].t],sn[u]=sz[sn[u]]<sz[ed[i].t]?ed[i].t:sn[u],su1[u].push_back(s1[ed[i].t]),s1[u]=s1[u]+s1[ed[i].t];
	if(sn[u])
	{
		sl[u]=sr[u]=doit0();
		for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&sid[ed[i].t]<sid[sn[u]])sl[u]=sl[u]+s1[ed[i].t];
		for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&sid[ed[i].t]>sid[sn[u]])sr[u]=sr[u]+s1[ed[i].t];
	}
	vl[u]=doit(mod-su[l[u]-1]),vr[u]=doit(su[r[u]]);
	for(int i=1;i<su1[u].size();i++)su1[u][i]=su1[u][i]+su1[u][i-1];
}
void dfs2(int u,int fa,int v)
{
	tp[u]=v;id[u]=++ct;
	fu1[id[u]]=sl[u];
	fu2[id[u]]=sr[u];
	fu3[id[u]]=vl[u];
	fu4[id[u]]=vr[u];
	if(sn[u])dfs2(sn[u],u,v);
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&ed[i].t!=sn[u])dfs2(ed[i].t,u,ed[i].t);
}
int LCA(int x,int y){if(dep[x]<dep[y])x^=y^=x^=y;for(int i=17;i>=0;i--)if(dep[x]-dep[y]>=1<<i)x=f1[x][i];if(x==y)return x;for(int i=17;i>=0;i--)if(f1[x][i]!=f1[y][i])x=f1[x][i],y=f1[y][i];return f1[x][0];}
int getkth(int x,int k){for(int i=17;i>=0;i--)if(k&(1<<i))x=f1[x][i];return x;}
void queryl(int x,int y)
{
	as1=as1+s1[x];
	while(tp[x]!=tp[y])
	{
		int t=tp[x];
		as1=as1+fu2[id[x]-1]-fu2[id[t]-1];
		as2=as2+fu4[id[x]-1]-fu4[id[t]-1];
		if(f[t]==y)return;
		as2=as2+vr[f[t]];
		as1=as1+su1[f[t]][su1[f[t]].size()-1]-su1[f[t]][sid[t]-1];
		x=f[t];
	}
	as1=as1+fu2[id[x]-1]-fu2[id[y]];
	as2=as2+fu4[id[x]-1]-fu4[id[y]];
}
void queryr(int x,int y)
{
	as1=as1+s1[x];
	while(tp[x]!=tp[y])
	{
		int t=tp[x];
		as1=as1+fu1[id[x]-1]-fu1[id[t]-1];
		as3=as3+fu3[id[x]-1]-fu3[id[t]-1];
		if(f[t]==y)return;
		as3=as3+vl[f[t]];
		if(sid[t]>1)as1=as1+su1[f[t]][sid[t]-2];
		x=f[t];
	}
	as1=as1+fu1[id[x]-1]-fu1[id[y]];
	as3=as3+fu3[id[x]-1]-fu3[id[y]];
}
void query(int x,int y,int k)
{
	int l=LCA(x,y),d1=getkth(x,dep[x]-dep[l]-1),d2=getkth(y,dep[y]-dep[l]-1);
	as1=as2=as3=doit0();
	queryl(x,l);queryr(y,l);
	as1=as1+su1[l][sid[d2]-2]-su1[l][sid[d1]-1];
	for(int i=10;i>=0;i--)
	for(int j=0,st=1;j<=i;j++,st=1ll*st*(mod-su[x-1])%mod)
	as1.v[i]=(as1.v[i]+1ll*as2.v[i-j]*st%mod*C[i][j])%mod;
	for(int i=10;i>=0;i--)
	for(int j=0,st=1;j<=i;j++,st=1ll*st*su[y]%mod)
	as1.v[i]=(as1.v[i]+1ll*as3.v[i-j]*st%mod*C[i][j])%mod;
	int as=(1ll*pw(su[y]-su[x-1],c)*as1.v[0]%mod-as1.v[k]+mod)%mod;
	for(int i=k;i>=k;i--)
	for(int j=0,st=1;j<=i;j++,st=1ll*st*(su[y]-su[x-1])%mod)
	as=(as-1ll*as1.v[i-j]*st%mod*((i^j)&1?mod-1:1)%mod*C[i][j]%mod+mod)%mod;
	printf("%d\n",las=1ll*as*pw(pw(su[y]-su[x-1],mod-2),c)%mod);
}
int main()
{
	for(int i=0;i<=10;i++)C[i][0]=C[i][i]=1;
	for(int i=2;i<=10;i++)for(int j=1;j<i;j++)C[i][j]=C[i-1][j]+C[i-1][j-1];
	scanf("%d%d%d",&op,&n,&q);
	for(int i=2;i<=n;i++)scanf("%d",&f[i]);
	for(int i=n;i>=2;i--)adde(i,f[i]);
	for(int i=2;i<=n;i++)if(d[i]==1)su[i]=1,tp1.insert(i);
	for(int i=2;i<=n;i++)su[i]+=su[i-1];
	dfs(1,0);dfs1(1,0);dfs2(1,0,1);
	for(int i=1;i<=n;i++)fu1[i]=fu1[i]+fu1[i-1],fu2[i]=fu2[i]+fu2[i-1],fu3[i]=fu3[i]+fu3[i-1],fu4[i]=fu4[i]+fu4[i-1];
	while(q--)
	{
		scanf("%d%d%d",&a,&b,&c);a^=op*las,b^=op*las;
		int s1=su[b]-su[a-1];
		if(s1<=1){printf("%d\n",las=0);continue;}
		a=*tp1.lower_bound(a);b=*(--tp1.upper_bound(b));
		query(a,b,c);
	}
}
auoj110 矩阵
Problem

给一个 nnmm 列的矩阵,对于第 ii 列,如果这一列有奇数个1,这一列的价值为 ai3bi(ai=1biv)a_i3^{b_i}(|a_i|=1,b_i\leq v) ,否则这一列的价值为0,所有的 ai3bia_i3^{b_i} 两两不同

你可以删去任意多行,求矩阵价值的最大值

n2×105,v35n\leq 2\times 10^5,v\leq 35

2s,512MB2s,512MB

Sol

留下多行相当于行之间异或,因此可以求出所有行组成的线性基,留下 2v2v 个元素

注意到 j<i3j(3j)<3i\sum_{j<i}3^j-(-3^j)<3^i ,所以从大到小考虑 3i3^i ,每次尽量让当前 3i3^i 的系数最大,这样得到的一定最优

将所有位按照 bib_i 排序,因为线性基里面第 ii 个元素不会有前 i1i-1 位,因此这样小的不会影响到大的

假设当前处理到了 bi=tb_i=t ,记录集合 StS_t 表示在考虑 bi>tb_i>t 的位后,若只考虑 bi>tb_i>t 的贡献时最优,后面 2(t+1)2(t+1) 位可能的状况

转移时枚举每个元素,考虑当前 bi=tb_i=t 的两个位在线性基上的元素,枚举它们出不出现,可以得到 3t3^t 的贡献次数和后面 2t2t 位的状况,将贡献次数最大的那些状态当做新的 St1S_{t-1} 即可

注意到对于一个数,它在一个 bib_i 时如果两个元素都存在,那么最后 3t3^t 的贡献系数一定为1,因此每一个 bib_i 时一个元素最多有两种情况

对于前 2v3\frac {2v} 3bib_i ,状态不超过 22v32^{\frac{2v} 3}

对于后面的,因为这时后面只剩 2v3\frac {2v} 3 位,因此状态也不超过 22v32^{\frac{2v} 3}

因此总状态数只有 O(22v3)O(2^{\frac {2v} 3}) ,判重时直接排序即可

复杂度 O(v22v3)O(v*2^{\frac{2v}3})

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 75
#define ll __int128
ll vl[N],st,as,pw[N];
int n,m,a,b,id[N];
char fu[200500][N];
vector<ll> fuc,f11,f12,f13;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%s",fu[i]+1);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&a,&b);
		int tp=b*2-2+(a==1?1:0);
		id[i]=tp;
	}
	for(int i=1;i<=n;i++)
	{
		st=0;
		for(int j=1;j<=m;j++)if(fu[i][j]=='1')st|=((ll)1)<<id[j];
		for(int j=71;j>=0;j--)
		if(st&(((ll)1)<<j))
		if(vl[j])st^=vl[j];
		else {vl[j]=st;break;}
	}
	pw[1]=3;for(int i=2;i<=35;i++)pw[i]=pw[i-1]*3;
	fuc.push_back(0);
	for(int i=35;i>=1;i--)
	{
		ll f1=vl[i*2-1],f2=vl[i*2-2],sz=fuc.size();
		for(int j=0;j<sz;j++)
		{
			ll t1=fuc[j],t2=t1^f1,t3=t1^f2,t4=t1^f2^f1;
			int fu1=t1>>(i*2-2),fu2=t2>>(i*2-2),fu3=t3>>(i*2-2),fu4=t4>>(i*2-2);
			ll tp=(((ll)1)<<(i*2-2))-1;t1&=tp;t2&=tp;t3&=tp;t4&=tp;
			if(fu1==2)f11.push_back(t1);
			else if(fu1==1)f13.push_back(t1);
			else f12.push_back(t1);
			if(fu2==2)f11.push_back(t2);
			else if(fu2==1)f13.push_back(t2);
			else f12.push_back(t2);
			if(fu3==2)f11.push_back(t3);
			else if(fu3==1)f13.push_back(t3);
			else f12.push_back(t3);
			if(fu4==2)f11.push_back(t4);
			else if(fu4==1)f13.push_back(t4);
			else f12.push_back(t4);
		}
		if(f11.size())fuc=f11,as+=pw[i];
		else if(f12.size())fuc=f12;
		else fuc=f13,as-=pw[i];
		f11.clear();f12.clear();f13.clear();
		sort(fuc.begin(),fuc.end());
		vector<ll>::iterator t1=unique(fuc.begin(),fuc.end());
		fuc.erase(t1,fuc.end());
	}
	printf("%lld\n",(long long)as);
}
auoj112 向量
Problem

有一棵 nn 个点带正边权的树,你需要给每个点一个 mm 维向量 viv_i (mm 可以任以决定,但不能超过16),使得满足

i,j,dis(i,j)=maxk=1mvi,kvj,k\forall i,j,dis(i,j)=max_{k=1}^m|v_{i,k}-v_{j,k}|

n1000n\leq 1000

1s,512MB1s,512MB

Sol

m=O(logn)m=O(\log n) ,考虑分治

考虑边分治,对于当前选出的一条边,将一侧的点权设为这个点到这条边的距离,另外一侧点权为这个点到这条边的距离的相反数,这样跨过两部分的点这一层上就会取到dis

边分树只有 O(logn)O(\log n) 层,考虑将一层的所有块放在同一维向量中

显然,将同一块的点权全部加上一个数,这一块内还是满足要求

同时,每一块内部的一条边 (i,j)(i,j) 一定满足 dis(i,j)vivjdis(i,j)\geq |v_i-v_j|

所有块形成一棵树的关系,可以通过dfs决定给每个块加上的权值,使得对于每条边 (i,j)(i,j) 满足 dis(i,j)vivjdis(i,j)\geq |v_i-v_j|

因为 vivjvivk1+vk1vk2+...+vklvj|v_i-v_j|\leq |v_i-v_{k_1}|+|v_{k_1}-v_{k_2}|+...+|v_{k_l}-v_j| ,所以满足上一个条件后所有点对的差都不超过距离

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 3333
int n,m,head[N],cnt,s[N*2][3],ct,ct2,a,b,c,sz[N],bel[N][17],nw,fu[N][17],vl[N][17],as[N][17],vis[N],v3,as1,as2,f1[N],dep[N],is[N],v2[N];
struct edge{int t,next,v,id;}ed[N*2];
void adde(int f,int t,int v,int id){ed[++cnt]=(edge){t,head[f],v,id};head[f]=cnt;ed[++cnt]=(edge){f,head[t],v,id};head[t]=cnt;}
void dfs0(int u,int fa)
{
	int las=u,las2=u;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)
	{
		if(las==-1)s[++ct2][0]=++ct,s[ct2][1]=las2,las=las2=ct;
		s[++ct2][0]=las,s[ct2][1]=ed[i].t,s[ct2][2]=ed[i].v;
		las=-1;dfs0(ed[i].t,u);
	}
}
void dfs1(int u,int fa,int vl)
{
	sz[u]=1;is[u]=1;bel[u][nw]=vl;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&!vis[ed[i].id])dfs1(ed[i].t,u,vl),sz[u]+=sz[ed[i].t];
}
void dfs2(int u,int fa)
{
	sz[u]=1;f1[u]=fa;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&!vis[ed[i].id])v2[ed[i].t]=ed[i].id,dfs2(ed[i].t,u),sz[u]+=sz[ed[i].t];
	int tp=max(sz[u],v3-sz[u]);
	if(as1>tp)as1=tp,as2=u;
}
void dfs3(int u,int fa)
{
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&!vis[ed[i].id])dep[ed[i].t]=dep[u]+ed[i].v,dfs3(ed[i].t,u);
}
void dfs4(int u,int fa)
{
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&!vis[ed[i].id])dep[ed[i].t]=dep[u]-ed[i].v,dfs4(ed[i].t,u);
}
void dfs5(int u,int fa)
{
	as[u][nw]=fu[u][nw]+vl[bel[u][nw]][nw];
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa)
	{
		if(bel[u][nw]!=bel[ed[i].t][nw])
		{
			int nt=as[u][nw]-fu[ed[i].t][nw];
			vl[bel[ed[i].t][nw]][nw]=nt;
		}
		dfs5(ed[i].t,u);
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)scanf("%d%d%d",&a,&b,&c),adde(a,b,c,0);
	ct=n;dfs0(1,0);
	for(int i=1;i<=n;i++)head[i]=0;
	cnt=0;
	for(int i=1;i<=ct2;i++)adde(s[i][0],s[i][1],s[i][2],i);
	for(int i=1;i<=16;i++)
	{
		nw=i;
		for(int j=1;j<=ct;j++)is[j]=dep[j]=0;
		for(int j=1;j<=ct;j++)if(!is[j])
		{
			dfs1(j,0,j);
			if(sz[j]==1){bel[j][i]=j+ct2;continue;}
			v3=sz[j];as1=1e9;
			dfs2(j,0);
			vis[v2[as2]]=1;
			int v1=as2,v3=f1[as2];
			dep[v1]=0;dfs3(v1,v3);
			dep[v3]=-ed[v2[as2]*2-1].v;dfs4(v3,v1);
		}
		for(int j=1;j<=ct;j++)fu[j][i]=dep[j];
		dfs5(1,0);
	}
	printf("16\n");
	for(int i=1;i<=n;i++,printf("\n"))
	for(int j=1;j<=16;j++)printf("%d ",as[i][j]);
}
auoj117 随机变量
Problem

有一个数 zz ,一开始为0

进行 nn 次操作,每次从 00kk 中以 pi=aiaip_i=\frac{a_i}{\sum a_i} 的概率选出 ii ,并给 zz 加上选出的数

min(s,z)min(s,z) 的期望,模 998244353998244353

n107,ks5×107n\leq 10^7,ks\leq 5\times 10^7

4s,1024MB4s,1024MB

Sol

考虑求出最后 z=iz=i 的概率,相当于求 (pixi)n(modxs)(\sum p_ix^i)^n(\bmod x^s)

F(x)=pixiF(x)=\sum p_ix^i ,相当于求 G(x)=enlnF(x)(modxs)G(x)=e^{n\ln F(x)}(\bmod x^s)

两边求导有 G(x)=(nlnF(x))enlnF(x)(modxs)G^{'}(x)=(n\ln F(x))^{'}e^{n\ln F(x)}(\bmod x^s)

G(x)=(nlnF(x))G(x)(modxs)G^{'}(x)=(n\ln F(x))^{'}G(x)(\bmod x^s)

将ln展开有 G(x)=(nF(x)F(x)dx)G(x)(modxs)G^{'}(x)=(n\int\frac{F^{'}(x)}{F(x)}dx)^{'}G(x)(\bmod x^s)

G(x)=nF(x)F(x)G(x)(modxs)G^{'}(x)=n\frac{F^{'}(x)}{F(x)}G(x)(\bmod x^s)

G(x)F(x)=nF(x)G(x)(modxs)G^{'}(x)F(x)=nF^{'}(x)G(x)(\bmod x^s)

如果知道了 GG0,1,...,x10,1,...,x-1 项,可以 O(k)O(k) 求出下一项

复杂度 O(ks)O(ks)

Code
#include<cstdio>
using namespace std;
#define N 1050
#define M 10000050
#define mod 998244353
int n,m,k,p[N],p1[N],su,dp[M],st,fr[M],ifr[M];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=0;i<=m;i++)scanf("%d",&p[i]),su+=p[i];
	for(int i=0;i<=m;i++)p[i]=1ll*p[i]*pw(su,mod-2)%mod;
	for(int i=1;i<=m;i++)p1[i-1]=1ll*p[i]*i%mod;
	//g'f=gf'*n
	dp[0]=pw(p[0],n);
	st=pw(p[0],mod-2);
	fr[0]=1;for(int i=1;i<=k+1;i++)fr[i]=1ll*fr[i-1]*i%mod;
	ifr[k+1]=pw(fr[k+1],mod-2);
	for(int i=k;i>=0;i--)ifr[i]=1ll*ifr[i+1]*(i+1)%mod;
	for(int i=0;i<k;i++)
	{
		int s1=0,s2=0;
		for(int j=1;j<=m&&j<=i;j++)s1=(s1+1ll*p[j]*dp[i-j+1]%mod*(i-j+1))%mod;
		for(int j=0;j<m&&j<=i;j++)s2=(s2+1ll*p1[j]*dp[i-j])%mod;
		int tp=(1ll*n*s2-s1+mod)%mod;
		tp=1ll*tp*st%mod;
		dp[i+1]=1ll*tp*fr[i]%mod*ifr[i+1]%mod;
	}
	int as=0,su=0;
	for(int i=0;i<k;i++)as=(as+1ll*i*dp[i])%mod,su=(su+dp[i])%mod;
	as=(as+1ll*(mod+1-su)*k)%mod;
	printf("%d\n",as);
}
auoj118 两个整数
Problem

给定 nn ,以以下方式生成一个长度为 2n2n 的01串 ss

从小到大考虑每一位,如果这一位之前已经生成了 nn 个0,那么这一位一定是1,如果之前生成了 nn 个0那么这一位一定是0,否则以 50%50\% 概率生成1, 50%50\% 概率生成0

qq 次询问,每次给定 kk 个位置 a1,...,aka_1,...,a_k ,求 sa1=sa2=...=saks_{a_1}=s_{a_2}=...=s_{a_k} 的概率,对 998244353998244353 取模

n,q105,k2×105n,q\leq 10^5,\sum k\leq 2\times 10^5

2s,1024MB2s,1024MB

Sol

只考虑最后一位是1的情况,最后答案乘2即可

假设最后有连续 ii 个1,那么这种情况是前 2ni12n-i-1 位中有 n1n-1 个0和 nin-i 个1,然后一个0, ii 个1,这种情况出现的概率是 C2ni1n122ni\frac{C_{2n-i-1}^{n-1}}{2^{2n-i}} ,算出这种情况下合法的方案数除以 22ni2^{2n-i} 就是这种情况对答案的贡献

首先考虑 ak>2nia_k>2n-i 的情况,这时显然 sak=1s_{a_k}=1 ,因此如果存在一个 aj=2nia_j=2n-i ,那么因为这一位是0,所以合法方案数为0

否则,设有 ppaj<2nia_j<2n-i ,那么只需要这些数全部是1,那么方案数为 C2ni1pn1C_{2n-i-1-p}^{n-1}

如果 ak=2nia_k=2n-i ,那么这时需要前面的数全部是0,方案数为 C2ni1(k1)n1(k1)=CakknkC_{2n-i-1-(k-1)}^{n-1-(k-1)}=C_{a_k-k}^{n-k}ak=2na_k=2n 时不存在这种情况,需要特殊处理

如果 ak<2nia_k<2n-i ,那么前面的数可能都是0或者都是1,两种情况方案数分别为 C2ni1kn1,C2ni1knk1C_{2n-i-1-k}^{n-1},C_{2n-i-1-k}^{n-k-1}

因此最后相当于 O(k)O(k) 次求 i=lrCin122ni\sum_{i=l}^r\frac{C_{i}^{n-1}}{2^{2n-i}} 以及 O(1)O(1) 次求 i=lrCin1k22ni\sum_{i=l}^r\frac{C_{i}^{n-1-k}}{2^{2n-i}}

对于第一个,相当于 122ni=lrCin1\frac 1{2^{2n}}\sum_{i=l}^r C_i^{n-1} ,可以预处理前缀和

对于第二个,注意到可能的 kk 只有 O(k)O(\sqrt{\sum k}) 个,因此对于每一种求出前缀和即可

复杂度 O(nlogn+nk)O(n\log n+n\sqrt{\sum k})

Code
#include<cstdio>
using namespace std;
#define N 200300
#define mod 998244353
int n,q,k,v[N],fr[N],ifr[N],fu[501][N],g[N];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int C(int a,int b){if(a<b||b<0)return 0;return 1ll*fr[a]*ifr[a-b]%mod*ifr[b]%mod;}
int doit(int l,int k){int tp=n-1-k;if(tp<=500)return fu[tp][l];return g[l];}
int solve(int l,int r,int k){return (doit(r,k)-1ll*pw(2,r-l+1)*doit(l-1,k)%mod+mod)%mod;}
int main()
{
	fr[0]=ifr[0]=1;for(int i=1;i<=2e5;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	scanf("%d%d",&n,&q);
	for(int i=0;i<=500;i++)for(int j=1;j<=n*2;j++)fu[i][j]=(2ll*fu[i][j-1]+C(j,n-1-i))%mod;
	while(q--)
	{
		scanf("%d",&k);
		if(k>500)for(int j=1;j<=n*2;j++)g[j]=(2ll*g[j-1]+C(j,n-1-k))%mod;
		int as=0;
		for(int i=1;i<=k;i++)scanf("%d",&v[i]),as=(as+1ll*pw(2,2*n-v[i]+2)*solve(v[i-1]-i+1,v[i]-i-1,n-1))%mod;
		if(v[k]<n*2)as=(as+4ll*solve(v[k]-k,n*2-2-k,n-1)+4ll*solve(v[k]-k,n*2-2-k,n-1-k)+1ll*pw(2,n*2-v[k]+1)*C(v[k]-k,n-k))%mod;
		printf("%d\n",1ll*as*pw(499122177,2*n)%mod);
	}
}
auoj120 最大公约数
Problem

i=1nj=1nigcd(i,j)\sum_{i=1}^n\sum_{j=1}^n\frac i {gcd(i,j)}

n1010n\leq 10^{10}

2s,1024MB2s,1024MB

Sol

ans=g=1ni=1ngj=1ngi[gcd(i,j)=1]ans=\sum_{g=1}^n\sum_{i=1}^{\lfloor\frac ng \rfloor}\sum_{j=1}^{\lfloor\frac ng \rfloor}i[gcd(i,j)=1]

ans=g=1ni=1ngj=1ngikgcd(i,j)μ(k)ans=\sum_{g=1}^n\sum_{i=1}^{\lfloor\frac ng \rfloor}\sum_{j=1}^{\lfloor\frac ng \rfloor}i\sum_{k|gcd(i,j)}\mu(k)

ans=g=1nk=1ngkμ(k)i=1ngkj=1ngkians=\sum_{g=1}^n\sum_{k=1}^{\lfloor\frac ng\rfloor}k\mu(k)\sum_{i=1}^{\lfloor\frac n{gk} \rfloor}\sum_{j=1}^{\lfloor\frac n{gk} \rfloor}i

ans=g=1nk=1ngkμ(k)12(ngk3+ngk2)ans=\sum_{g=1}^n\sum_{k=1}^{\lfloor\frac ng\rfloor}k\mu(k)\frac12(\lfloor\frac n{gk} \rfloor^3+\lfloor\frac n{gk} \rfloor^2)

ans=T=1n12(nT3+nT2)kTkμ(k)ans=\sum_{T=1}^n\frac12(\lfloor\frac nT \rfloor^3+\lfloor\frac nT\rfloor^2)\sum_{k|T}k\mu(k)

f(T)=kTkμ(k),g(T)=Tμ(T),h(T)=Tf(T)=\sum_{k|T}k\mu(k),g(T)=T\mu(T),h(T)=T

那么有 f=g1f=g*1

fh=1(gh)f*h=1*(g*h)

(gh)(T)=iTiμ(i)Ti=TiTμ(i)=[T=1](g*h)(T)=\sum_{i|T}i\mu(i)\frac Ti=T\sum_{i|T}\mu(i)=[T=1]

fh=1ϵ=1f*h=1*\epsilon=1

对前面数论分块,发现只需要用到每一个 nT\lfloor\frac nT\rfloor 的前缀和,杜教筛即可

复杂度 O(n23)O(n^{\frac 23})

Code
#include<cstdio>
using namespace std;
#define N 3006000
#define mod 1000000007
#define ll long long
int pr[N],ch[N],f[N],as[N],ct;
ll n;
void pre(int m)
{
	f[1]=1;
	for(int i=2;i<=m;i++)
	{
		if(!ch[i])pr[++ct]=i,f[i]=1-i;
		for(int j=1;1ll*i*pr[j]<=m&&j<=ct;j++)
		{
			ch[i*pr[j]]=1,f[i*pr[j]]=f[i]*(1-pr[j]);
			if(i%pr[j]==0){f[i*pr[j]]=f[i];break;}
		}
	}
	for(int i=2;i<=m;i++)f[i]=(f[i]+f[i-1]+3ll*mod)%mod;
}
int solve(ll x)
{
	if(x<=3e6)return f[x];
	int id=n/x;
	if(as[id])return as[id];
	int as1=x%mod;
	for(ll l=2,r;l<=x;l=r+1)
	{
		r=x/(x/l);
		ll f1=((r%mod)*(r%mod+1)/2-(l%mod)*(l%mod-1)/2%mod+mod)%mod;
		as1=(as1-f1*solve(x/l)%mod+mod)%mod;
	}
	return as[id]=as1;
}
int main()
{
	scanf("%lld",&n);pre(3e6);
	int as1=0;
	for(ll l=1,r;l<=n;l=r+1)
	{
		r=n/(n/l);
		ll tp=n/l;
		int res=(tp%mod)*(tp%mod+1)/2%mod*(tp%mod)%mod;
		as1=(as1+1ll*res*(solve(r)-solve(l-1)+mod))%mod;
	}
	printf("%d\n",as1);
}
auoj121 凸包的价值
Problem

对于一个严格凸包(点数大于2,且没有三点共线),设它的顶点有 xx 个,它内部的点(包含边界上,不包含顶点)有 yy 个,外部的点有 zz 个,那么这个凸包的贡献为 xax(a+c)ycxxa^x(a+c)^yc^x

nn 个点,求所有凸包的贡献和,模 109+710^9+7

n2000n\leq 2000

2s,512MB2s,512MB

Sol

设凸包顶点集合为 AA ,内部点集合为 BB ,贡献相当于

AaA(a+c)BcnAB=SBAaA+ScnAS|A|a^{|A|}(a+c)^{|B|}c^{n-|A|-|B|}=\sum_{|S|\subset |B|}|A|a^{|A|+|S|}c^{n-|A|-|S|}

相当于枚举 AA ,再枚举凸包内部点的一个子集

注意到对于一个 T=AST=|A|\cup |S| , 因为 SS 中点都在 AA 形成的凸包上,因此 AA 一定是点集 TT 的凸包上的点

f(S)f(S) 为点集的凸包点数,那么答案等于 TaTcnTf(S)\sum_Ta^{|T|}c^{n-|T|}f(S)

注意到凸包点数相当于凸包边数,于是可以统计每条边的贡献

枚举一个点 ii ,将其余点按这个点极角排序,对于一条边 (i,j)(i,j) ,考虑凸包在这条边左侧的情况(另外一侧在另外一个点考虑)

那么,点集中的所有点必须在这条线的左侧,或者在这条线段上,假设有 kk 个点,那么这条边会对 T=x|T|=x 的情况贡献 Ckx2C_{k}^{x-2} 种方案,可以记录每个 kk 出现了多少次,然后对于每一种 kk 算贡献

注意到这样会统计所有点在一条线上的情况,于是需要再减掉这种情况

复杂度 O(n2logn)O(n^2\log n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 2333
#define ll long long
#define mod 1000000007
struct pt{int x,y;}p[N],tp2[N],tp3[N];
int n,a,b,ct1,ct2,su[N],as[N],c[N][N],f1[N];
ll cross(pt a,pt b){return 1ll*a.x*b.y-1ll*a.y*b.x;}
pt operator -(pt a,pt b){return (pt){a.x-b.x,a.y-b.y};}
bool cmp(pt a,pt b){return cross(a,b)<0;}
void solve(int x)
{
	ct1=ct2=0;
	for(int i=1;i<=n;i++)if(i!=x)
	{
		pt sb=p[i]-p[x];
		if(sb.y>0||(sb.y==0&&sb.x<0))tp2[++ct1]=sb;
		else tp3[++ct2]=sb;
	}
	sort(tp2+1,tp2+ct1+1,cmp);
	sort(tp3+1,tp3+ct2+1,cmp);
	int lb=0;
	for(int i=1;i<=ct1;i++)
	{
		int rb=i;
		while(rb<ct1&&cross(tp2[rb+1],tp2[i])==0)rb++;
		while(lb<ct2&&cross(tp2[i],tp3[lb+1])<0)lb++;
		for(int j=0;j<rb-i+1;j++)su[ct1-rb+lb+j]=(su[ct1-rb+lb+j]+1)%mod;
		for(int j=1;j<=rb-i+1;j++)f1[j+1]=(f1[j+1]+c[rb-i+1][j])%mod;
		i=rb;
	}
	lb=0;
	for(int i=1;i<=ct2;i++)
	{
		int rb=i;
		while(rb<ct2&&cross(tp3[rb+1],tp3[i])==0)rb++;
		while(lb<ct1&&cross(tp3[i],tp2[lb+1])<0)lb++;
		for(int j=0;j<rb-i+1;j++)su[ct2-rb+lb+j]=(su[ct2-rb+lb+j]+1)%mod;
		for(int j=1;j<=rb-i+1;j++)f1[j+1]=(f1[j+1]+c[rb-i+1][j])%mod;
		i=rb;
	}
}
int main()
{
	scanf("%d%d%*d%d",&n,&a,&b);
	for(int i=0;i<=n;i++)c[i][0]=c[i][i]=1;
	for(int i=2;i<=n;i++)
	for(int j=1;j<i;j++)
	c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
	for(int i=1;i<=n;i++)scanf("%d%d",&p[i].x,&p[i].y);
	for(int i=1;i<=n;i++)solve(i);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=i;j++)
	as[j+2]=(as[j+2]+1ll*su[i]*c[i][j])%mod;
	int as1=0;
	for(int i=3;i<=n;i++)
	{
		int tp=(as[i]+mod-f1[i])%mod;
		for(int j=1;j<=n;j++)tp=1ll*tp*(j<=i?a:b)%mod;
		as1=(as1+tp)%mod;
	}
	printf("%d\n",as1);
}
auoj122 交通网络
Problem

给一张完全图,其中所有 (i,i+1)(i,i+1) 的边为关键边

对于一棵生成树,如果它包含 kk 条关键边,它的权值为 k2kk2^k

求所有生成树的权值和,模 998244353998244353

n5×105n\leq 5\times10^5

2s,512MB2s,512MB

Sol

f(i)f(i) 表示有 ii 条关键边的方案数, g(i)=jiCjif(j)g(i)=\sum_{j\geq i}C_j^if(j)

考虑 gg 的组合意义,相当于钦定 ii 条关键边必须选的生成树数量

设关键边形成的连通块大小为 a1,...,ania_1,...,a_{n-i} ,那么方案为 nni2ain^{n-i-2}\prod a_i

只需要考虑 ai\prod a_i ,它的组合意义相当于在每一个连通块中选出一个点的方案数

那么 g(i)g(i) 相当于在链上交替选出 nin-i 个点和 ni1n-i-1 条边的方案数

考虑将边和点都看成元素,相当于在 2n12n-1 个数中选 2n2i12n-2i-1 个数,使得选择的第 ii 小的数与 ii 奇偶性相同

钦定在 2n2n 处有一个数,且这个数必选,考虑每一个数减去上一个数的差,显然必须为奇数,因为钦定了最后一个数为 2n2n ,此时相当于将 2n2n 分成 2n2i2n-2i 个奇数的方案数

这相当于将 2i2i 分成 2n2i2n-2i 个偶数的方案数,于是相当于将 ii 分成 2n2i2n-2i 个数的方案数,于是它等于 C2ni1iC_{2n-i-1}^{i}

根据二项式反演有 f(i)=ji(1)jiCjig(j)f(i)=\sum_{j\geq i}(-1)^{j-i}C_j^ig(j) ,NTT即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
using namespace std;
#define N 1050000
#define mod 998244353
int n,fr[N],ifr[N],f[N],as,a[N],b[N],rev[N],ntt[N];
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%mod;a=1ll*a*a%mod;b>>=1;}return as;}
void dft(int s,int *a,int t)
{
	for(int i=0;i<s;i++)rev[i]=(rev[i>>1]>>1)|((i&1)*(s>>1)),ntt[rev[i]]=a[i];
	for(int i=2;i<=s;i<<=1)
	{
		int tp=pw(3,mod-1+t*(mod-1)/i);
		for(int j=0;j<s;j+=i)
		for(int l=j,st=1;l<j+(i>>1);l++,st=1ll*st*tp%mod)
		{
			int v1=ntt[l],v2=1ll*ntt[l+(i>>1)]*st%mod;
			ntt[l]=(v1+v2)%mod;
			ntt[l+(i>>1)]=(v1-v2+mod)%mod;
		}
	}
	int inv=pw(s,t==-1?mod-2:0);
	for(int i=0;i<s;i++)a[i]=1ll*ntt[i]*inv%mod;
}
int main()
{
	scanf("%d",&n);
	fr[0]=ifr[0]=1;for(int i=1;i<=n*2;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	for(int i=1;i<n;i++)f[n-i]=1ll*fr[n+i-1]*ifr[n-i]%mod*ifr[2*i-1]%mod*pw(n,mod-3+i)%mod;
	int l=1;while(l<=n*2)l<<=1;
	for(int i=0;i<=n;i++)a[i]=1ll*f[i]*fr[i]%mod,b[n-i]=1ll*(i&1?mod-1:1)*ifr[i]%mod;
	dft(l,a,1);dft(l,b,1);for(int i=0;i<l;i++)a[i]=1ll*a[i]*b[i]%mod;dft(l,a,-1);
	for(int i=1;i<n;i++)as=(as+1ll*a[i+n]*i%mod*pw(2,i)%mod*ifr[i])%mod;
	printf("%d\n",as);
}
auoj123 倾尽天下
Problem

有一个长度为 nn 的数组 aa ,满足 a1=0,aij=aiaja_1=0,a_{ij}=a_i\oplus a_j

qq 次操作,每次操作为翻转一个 apa_p 处的值(0变1,1变0),求出每次操作后数组中有多少个1

n,q2×105n,q\leq 2\times 10^5

3s,512MB3s,512MB

Sol

00 看成 1111 看成 1-1 ,那么它就是一个完全积性函数

sui=j=1iajsu_i=\sum_{j=1}^ia_j ,考虑修改时对 sunsu_n 的改变,注意到只会改 pp 的倍数,所以可以先减去 pp 的倍数的贡献,算出新的贡献后加回去

注意到因为是完全积性函数,有 i=1npaip=apsunp\sum_{i=1}^{\lfloor\frac np\rfloor}a_{ip}=a_psu_{\lfloor\frac np\rfloor} ,因此 sun=sunapsunp+apsunpsu_n^{'}=su_n-a_psu_{\lfloor\frac np\rfloor}+a_p^{'}su_{\lfloor\frac np\rfloor}^{'}

注意到只会用到所有的 sunksu_{\lfloor\frac nk\rfloor} ,这只有 O(n)O(\sqrt n) 个取值,考虑维护这些位置处的前缀和

那么先从大到小减去每个位置对应的 apsunpa_psu_{\lfloor\frac np\rfloor} ,再从小到大加回去即可

复杂度 O(qn)O(q\sqrt n)

Code
#include<cstdio>
using namespace std;
#define N 200500
int n,vl[N],as[N],ct,q,a,tid[N],f[N];
int main()
{
	scanf("%d%d",&n,&q);
	vl[ct=1]=n;as[1]=n;
	for(int i=2;i<=n;i++)if(n/i!=n/(i-1))vl[++ct]=n/i,as[ct]=n/i;
	for(int i=1;i<=ct;i++)tid[vl[i]]=i;
	for(int i=1;i<=n;i++)f[i]=1;
	while(q--)
	{
		scanf("%d",&a);
		for(int i=1;i<=ct;i++)as[i]-=f[a]*as[tid[vl[i]/a]];
		f[a]*=-1;
		for(int i=ct;i>=1;i--)as[i]+=f[a]*as[tid[vl[i]/a]];
		printf("%d\n",(n-as[1])/2);
	}
}
auoj124 春风一顾

见AGC021F

auoj149 第一题
Problem

给一个字符串,多次询问,每次给出 k,pk,p ,求字符串所有本质不同的子序列中字典序第 kk 小的子序列的后 pp 个字符

q105,n3×105,k1018,p106q\leq 10^5,n\leq 3\times 10^5,k\leq 10^{18},\sum p\leq 10^6

2s,1024MB2s,1024MB

Sol

对于一个子序列,考虑将连续的字符缩在一起

因为字符集只有26,所以在前26段中,一定存在一段的字符大于下一段的字符

删去这一段字符,考虑剩下的段,如果保留了下一段,那么后面无论怎么选都比原来的子序列字典序更小,因为相邻两段字符不同,所以只需要 O(logk)O(\log k) 段就存在大于 kk 个小于这个子序列的子序列

因此相同的段数不超过 26+O(logk)26+O(\log k) ,建出子序列自动机,考虑倍增,求出每个位置后面接 2i2^i 个与这个位置相同的字符时有多少个新增的小于它的子序列以及接完到了哪个位置即可

复杂度 O(26n+nlogn+26qlogn+qlognlogk)O(26n+n\log n+26q\log n+q\log n\log k)

Code
#include<cstdio>
#include<cstring>
using namespace std;
#define N 300500
#define ll long long
int n,nt[N][26],f[N][19],s1[N][2],ct,q,l,s11;
long long dp[N],su[N][19],k;
char s[N];
int main()
{
	scanf("%s%d",s+1,&q);n=strlen(s+1);
	for(int i=0;i<26;i++)nt[n][i]=n+1;
	for(int i=n;i>=1;i--)
	{
		for(int j=0;j<26;j++)nt[i-1][j]=nt[i][j];
		nt[i-1][s[i]-'a']=i;
	}
	for(int i=n;i>=0;i--)
	{
		dp[i]=1;
		for(int j=0;j<26;j++)
		{
			dp[i]+=dp[nt[i][j]];
			if(dp[i]>1e18+100000)dp[i]=1e18+100000;
		}
	}
	for(int i=1;i<=n;i++)
	{
		su[i][0]=1;
		for(int j=0;j<s[i]-'a';j++)
		{
			su[i][0]+=dp[nt[i][j]];
			if(su[i][0]>1e18+1)su[i][0]=1e18+1;
		}
		f[i][0]=nt[i][s[i]-'a'];
	}
	f[n+1][0]=n+1;
	for(int i=1;i<=18;i++)
	for(int j=0;j<=n+1;j++)
	f[j][i]=f[f[j][i-1]][i-1],su[j][i]=su[j][i-1]+su[f[j][i-1]][i-1],su[j][i]=su[j][i]>1e18+1?1e18+1:su[j][i];
	while(q--)
	{
		scanf("%lld%d",&k,&l);
		int nw=0;ct=0;s11=0;
		if(k>dp[0]-1){printf("-1\n");continue;}
		while(k)
		{
			int st=0;
			for(int i=0;i<26;i++)
			if(dp[nt[nw][i]]>=k){st=i;break;}
			else k-=dp[nt[nw][i]];
			k--;nw=nt[nw][st];
			s1[++ct][0]=st;s1[ct][1]=1;
			for(int i=18;i>=0;i--)
			if(f[nw][i]<=n&&su[nw][i]<k&&su[nw][i]+dp[f[nw][i]]-1>=k)k-=su[nw][i],nw=f[nw][i],s1[ct][1]+=(1<<i);
			s11+=s1[ct][1];
		}
		s11-=l;if(s11<0)s11=0;
		for(int i=1;i<=ct;i++)
		{
			int tp=s1[i][1];if(tp>s11)tp=s11;
			s11-=tp;s1[i][1]-=tp;
			for(int j=0;j<s1[i][1];j++)printf("%c",s1[i][0]+'a');
		}
		printf("\n");
	}
}
auoj150 第二题
Problem

nn 张牌,每张牌有一个 [0,d1][0,d-1] 的颜色,保证 dnd|n ,第 ii 张牌的分数为 ii

进行 nd\frac nd 轮游戏,第 ii 轮拿出前 idid 张牌排成一个环,然后进行 mm 次操作,每次随机交换环上两张牌,然后第 j(0j<d)j(0\leq j<d) 个人拿走第 j,j+d,...,j+(i1)dj,j+d,...,j+(i-1)d 张牌,他这一轮的分数为所有颜色与 jj 相同的牌的分数和

求所有游戏后,每个人的分数期望

n3×106,d10n\leq 3\times 10^6,d\leq 10

2s,1024MB2s,1024MB

Sol

考虑每一轮,可以先处理出颜色为 ii 且位置模 ddjj 的牌的分数和,那么算贡献只需要求出操作后一张牌在模 dd 意义下顺时针方向移动了 kk 步的概率

这相当于 (x+x1+id2id)m(modxd1)(\frac{x+x^{-1}+id-2}{id})^m (\bmod x^d-1) ,单位根反演即可

注意到里面dft后一定虚部为0,所以可以只算实数部分

复杂度 O(nd+nlogm)O(nd+n\log m)

Code
#include<cstdio>
#include<cmath>
using namespace std;
#define N 3005000
#define K 11
#define ll long long
int n,d;
ll m,su[K][K];
char st[N];
long double pi=acos(-1),as[K],st1[K],st2[K],w[K*K];
int main()
{
	scanf("%d%d%lld%s",&n,&d,&m,st+1);
	for(int i=0;i<=d*d;i++)w[i]=cos(2*pi/d*i);
	for(int i=1;i<=n/d;i++)
	{
		for(int j=1;j<=d;j++)su[st[i*d-d+j]-'0'][j-1]+=i*d-d+j;
		for(int j=0;j<d;j++)st1[j]=pow(1-2.0/i/d*(1-w[j]),m),st2[j]=0;
		for(int j=0;j<d;j++)for(int k=0;k<d;k++)st2[j]=st2[j]+st1[k]*w[j*k];
		for(int j=1;j<=d;j++)for(int k=0;k<d;k++)as[j]+=su[j-1][k]*st2[(j-1-k+d)%d]/d;
	}
	for(int i=1;i<=d;i++)printf("%.10lf\n",(double)as[i]);
}
auoj151 第三题
Problem

给两棵有根树,它们的根之间连有不可删去的边,每条边有删去代价

你需要将两棵树的叶子一一配对,每一对连上不可删去的边,然后删去一些边使得图变成一棵树

求一种方案使得删去边的总代价和最小,输出最小总代价

n105n\leq 10^5

2s,1024MB2s,1024MB

Sol

设各有 mm 个叶子,对于一个合法的方案,如果将叶子全部删去,树会变成 m+1m+1 个连通块,且每个连通块至少有一个叶子

考虑 m+1m+1 个连通块,每个连通块至少有一个叶子,显然最多有一个连通块有两棵树的叶子,其它块可以每次贪心连一个剩余叶子数最少的块,这样一定可以构造出来,再连到最后一个块上即可

因此只需要构造满足上述条件的最小代价

考虑一个贪心,每次尝试加入最大的边,必须保证这时有叶子的连通块不少于 m+1m+1

对于一个 kk ,考虑所有边权大于等于 kk 的边,它们一定连出了若干连通块

对于一个没有叶子的连通块,上述方式显然会选所有边

对于有叶子的连通块,最后可能选了若干条边

如果某条边当前连接的是一个有叶子的连通块和一个没有叶子的连通块,那么考虑到这条边时一定会选

因为这里面的所有边都会被考虑一次,因此最后贪心选的边一定将树分成了若干个包含叶子的连通块

这时再选一条边包含叶子的连通块一定减一,所以没有选的边的数量一定是 m+1m+1 减去选了所有边权大于等于 kk 的边后,包含叶子的连通块数量

显然一种合法的方案至少要不选这么多条边,因此对于每一个 kk ,这种方案选的边权大于等于 kk 的边的数量一定是最多的

因此这样是最优的

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 200500
int n,fa[N],su[N],s1,d[N];
long long as;
struct edge{int f,t,v;friend bool operator <(edge a,edge b){return a.v>b.v;}}e[N];
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)scanf("%d%d%d",&e[i].f,&e[i].t,&e[i].v),d[e[i].f]++,d[e[i].t]++,as+=e[i].v;
	for(int i=2;i<=n;i++)if(d[i]==1)su[i]=su[i+n]=1,s1++;s1--;
	for(int i=n;i<n*2-1;i++)scanf("%d%d%d",&e[i].f,&e[i].t,&e[i].v),e[i].f+=n,e[i].t+=n,as+=e[i].v;
	sort(e+1,e+n*2-1);
	for(int i=1;i<=n*2;i++)fa[i]=i;fa[n+1]=1;
	for(int i=1;i<=n*2-2;i++)
	{
		int a=finds(e[i].f),b=finds(e[i].t);
		if(a==b)continue;
		if(!s1&&su[a]&&su[b])continue;
		s1-=su[a]&su[b];fa[b]=a;as-=e[i].v;su[a]|=su[b];
	}
	printf("%lld\n",as);
}
auoj402 B
Problem

求有多少个 nn 个点的有标号无根树,满足

  1. 每个点度数不超过 dd
  2. 树上的每一条路径都是合法的

op=0op=0 时,一条路径合法当且仅当这条路径经过的点的编号递增或者递减

op=1op=1 时,一条路径合法当且仅当路径上存在一个点,这个点向两侧的路径同时递增或同时递减

答案对 mm 取模

n200n\leq 200

1s,128MB1s,128MB

Sol

考虑 op=0op=0 的情况,如果有一个点度数大于2,那么这个点连出的三条边中一定有两条边连向的点编号都大于它或者都小于它,这时这三个点的路径显然不合法

因此这时路径只能是一条链,且显然编号只能递增(递减与递增的树等价),所以答案为1(注意 m=1m=1 的情况)

考虑 op=1op=1 ,观察可以发现,树合法当且仅当存在一个点,它的每一个子树内所有点到这个点的路径全部递增或者全部递减

满足这个条件的树显然合法,如果一棵树不满足条件,考虑一个点 uu ,存在一个点 vvvv 的儿子 ww ,使得 u>...>v>wu->...->v->wvv 为转折点

考虑 vv ,以它为根,它的子树中一定有一个子树不满足递增或递减的条件,如果这个子树为 uu 在的子树,设路径为 v>...>x>yv->...->x->y ,不妨设 u>...>vu->...->v 的路径递减,那么 v>...>xv->...->x 递增 x>yx->y 递减,此时 w>v>...>x>yw->v->...->x->y 先递增再递减再递增,不满足条件

否则,这个点一定在它的子树内,如果一直找下去会找到叶子,此时一定是上面的情况

因此,一定存在一个点满足上述条件

如果有两个点 u,v(u<v)u,v(u<v) 同时满足条件,如果 vv 存在两个子树到它都是递增,设两个子树为 x,yx,y ,一定有一个子树中不含 uu ,设为 xx ,那么 u>...>v>xu->...->v->x 不满足上述条件,矛盾

如果两个点 u,v(u<v1)u,v(u<v-1) 满足条件,根据上面有所有 [u+1,v1][u+1,v-1] 的点都在 (u,v)(u,v) 的路径上或在路径某个点的子树上,如果一个点在子树上,设它是 xx ,它父亲是 ff ,那么因为 u>...>f>xu->...->f->x 合法,有 f>xf>x ,因为 v>...>f>xv->...->f->x 合法,有 f<xf<x ,矛盾,因此所有点都在 (u,v)(u,v) 路径上,这些点显然只能顺序排列,因此这些点全部满足条件

因此,所有满足条件的点必定是一段编号连续的点,且这些点形成一条链

考虑点减边容斥,只需要算每个点合法的方案数,减去每条 (i,i+1)(i,i+1) 两侧均合法的方案数

考虑 ii 向前的部分,从小到大加入每个点,以大的点为根,每个点最多有 d1d-1 个小于自己的儿子

dpi,jdp_{i,j} 表示加入了前 ii 个点,当前还有 jj 个点没有父亲的方案数

枚举这个点有多少儿子,那么有

dpi,j=k=0j1dpi1,j+k1Cj+k1kdp_{i,j}=\sum_{k=0}^{j-1}dp_{i-1,j+k-1}C_{j+k-1}^k

对于一个点,先加入小于它的点,那么这些点的父亲都是它,大于它的类似,那么点 ii 的贡献为 jk[j+kd]dpi1,jdpni,k\sum_{j}\sum_{k}[j+k\leq d]dp_{i-1,j}dp_{n-i,k}

对于一条边,类似考虑,对于连接 (i,i+1)(i,i+1) 的边,贡献为 jd1kd1dpi1,jdpni1,k\sum_{j\leq d-1}\sum_{k\leq d-1}dp_{i-1,j}dp_{n-i-1,k}

复杂度 O(n3)O(n^3)

Code
#include<cstdio>
using namespace std;
#define N 233
int n,d,m,k,dp[N][N],c[N][N];
int main()
{
	scanf("%d%d%d%d",&n,&d,&m,&k);
	if(d==1&&n>2){printf("0\n");return 0;}
	if(k==0||n==2){printf("%d\n",1%m);return 0;}
	for(int i=0;i<=n;i++)c[i][0]=c[i][i]=1;
	for(int i=2;i<=n;i++)
	for(int j=1;j<i;j++)
	c[i][j]=(1ll*c[i-1][j]+c[i-1][j-1])%m;
	dp[0][0]=dp[1][1]=1;
	for(int i=2;i<=n;i++)
	for(int j=1;j<i;j++)
	for(int l=0;l<d&&j-l+1>0;l++)
	dp[i][j-l+1]=(dp[i][j-l+1]+1ll*dp[i-1][j]*c[j][l])%m;
	int as=0;
	for(int i=1;i<=n;i++)
	for(int j=0;j<=d;j++)
	for(int l=0;l<=d;l++)
	if(j+l<=d)
	as=(as+1ll*dp[i-1][j]*dp[n-i][l])%m;
	for(int i=1;i<n;i++)
	for(int j=0;j<d;j++)
	for(int l=0;l<d;l++)
	as=(as-1ll*dp[i-1][j]*dp[n-i-1][l]%m+m)%m;
	printf("%d\n",as);
}
auoj403 C
Problem

PDF题面

Sol

首先考虑暴力dp,所有跨过中心的线将环分成了若干份,因为对称只用考虑一半

枚举第一条线的端点左右两个点是否有连边,然后变为链上的情况

首先处理出 nn 个点的链,只有相邻和隔一个连边时的方案数,设 dpi,0/1dp_{i,0/1} 表示前 ii 个点分成了若干段,最后一个点连向中心,最后一个点两侧是否连边,转移时枚举下一段长度以及下一段连向中心的点两侧是否有连边,直接转移

复杂度 O(n2)O(n^2)

然后可以发现答案是一个16阶线性递推,于是可以做到 O(n)O(n)

Code
//n^2
/*
#include<cstdio>
using namespace std;
#define N 23333
#define mod 998244353
int n,f[N],dp[N][2][2];
int main()
{
	scanf("%d",&n);
	f[0]=f[2]=1;
	for(int i=2;i<=n;i+=2)f[i]=(f[i-2]+f[i-4])%mod;
	for(int i=1;i<=n;i++)
	if(i&1)
	dp[i+1][0][1]=dp[i+1][1][0]=1ll*(i+1)*i%mod*i%mod*f[i-1]%mod;
	else dp[i+1][0][0]=1ll*(i+1)*i%mod*i%mod*f[i]%mod,dp[i+1][1][1]=1ll*(i+1)*i%mod*i%mod*f[i-2]%mod;
	for(int i=2;i<=n;i++)
	for(int j=1;i+j+1<=n;j++)
	for(int t=0;t<2;t++)
	if(j&1)
	{
		dp[i+j+1][t][1]=(dp[i+j+1][t][1]+1ll*dp[i][t][0]*f[j-1]%mod*j%mod*j)%mod;
		dp[i+j+1][t][0]=(dp[i+j+1][t][0]+1ll*dp[i][t][1]*f[j-1]%mod*j%mod*j)%mod;
	}
	else
	{
		dp[i+j+1][t][1]=(dp[i+j+1][t][1]+1ll*dp[i][t][1]*f[j-2]%mod*j%mod*j)%mod;
		dp[i+j+1][t][0]=(dp[i+j+1][t][0]+1ll*dp[i][t][0]*f[j]%mod*j%mod*j)%mod;
	}
	printf("%d\n",(dp[n][0][0]+dp[n][1][1])%mod);
}
*/
//n
#include<cstdio>
using namespace std;
#define N 23333
#define mod 998244353
int n,as[2333333]={0,0,0,24,4,240,204,1316,2988,6720,26200,50248,174280,436904,1140888,3436404,8348748},tp[17]={0,0,4,8,998244352,16,998244343,4,998244341,998244305,26,998244309,15,998244337,998244349,998244349,998244352};
int main(){scanf("%d",&n);for(int i=17;i<=n;i++)for(int j=1;j<=16;j++)as[i]=(as[i]+1ll*as[i-j]*tp[j])%mod;printf("%d\n",as[n]);}
auoj404 放送事故
Problem

有一张无向图,进行如下操作:

//初始若(i,j)有边,则 f[i][j]=1,否则 f[i][j]=0
for(int o=1;o<=lim;++o)
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
f[i][j]=f[i][j]||(i!=j&&f[i][o]&&f[o][j])

TT 组询问,每次给出 n,mn,m ,求有多少个 nn 个点的完全图满足 lim=mlim=m 时执行上述代码最后的 fflim=nlim=n 时相同,答案模 109+710^9+7

n200,T11451n\leq 200,T\leq 11451

5s,512MB5s,512MB

Sol

只考虑编号不超过 mm 的点和边,考虑这时的一个连通块 SS

在操作后, SS 中的任意两个元素 i,ji,j ,一定有 fi,j=1f_{i,j}=1

对于一个编号大于 mm 的点 kk ,如果它连向一个连通块 SS ,那么操作后 iS,fi,k=fk,i=1\forall i\in S,f_{i,k}=f_{k,i}=1

如果有一个编号大于 mm 的点 kk 连向了两个连通块,那么如果操作完,这两个连通块间的 ff 为1,但只操作前 mm 个时两个连通块间 ff 为0,因此不存在这种情况

如果一个编号大于 mm 的点 kk 连向一个连通块 SS ,与 kk 相邻的另外一个大于 mm 的点连向另外一个连通块 TT ,显然也是不合法的

如果一个编号大于 mm 的点 kk 连向一个连通块 SS ,与 kk 相邻的另外一个大于 mm 的点没有连向编号小于等于 mm 的点,也不合法

如果有一个编号大于 mm 的点 kk 没有连向编号小于等于 mm 的点,那么与它相关的 ff 不会变,那么它所在的连通块只能是完全图

因此,只考虑编号大于 mm 的点和边,对于一个连通块,只有两种情况

  1. 连通块每个点都连向了同一个只考虑小于等于 mm 时的连通块
  2. 每个点都没有连向小于等于 mm 的点,且连通块为完全图

首先处理情况2,设 fjf_j 表示 jj 个点分成若干完全图的方案,转移显然

然后考虑处理情况1,设 dpi,jdp_{i,j} 表示小于等于 mm 的部分当前有 ii 个点,大于等于 mm 的部分有 jj 个点的方案数,显然答案为 dpm,nmdp_{m,n-m}

初值为 dp0,i=fidp_{0,i}=f_i

考虑转移,对于第一部分的一个连通块,第二部分有若干连通块连向它,且这些连通块不和第二部分的其它点有连边,枚举新加入的连通块的大小以及第二部分的点数,有

dpi,j=a=1i1b=1j1Ci1a1Cjbdpia,jbga(2a1)b2b(b1)2dp_{i,j}=\sum_{a=1}^{i-1}\sum_{b=1}^{j-1}C_{i-1}^{a-1}C_j^bdp_{i-a,j-b}g_a(2^a-1)^b2^{\frac{b(b-1)}2}

其中 gag_a 表示 aa 个点连通图数,可以容斥算

复杂度 O(n4+T)O(n^4+T)

Code
#include<cstdio>
using namespace std;
#define N 205
#define mod 1000000007
int f[N],c[N][N],fr[N],T,n,m,g[N],dp[N][N],tp[N][N],tp2[N];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int main()
{
	scanf("%d",&T);
	n=200;
	for(int i=0;i<=n;i++)c[i][0]=c[i][i]=1;
	for(int i=2;i<=n;i++)
	for(int j=1;j<i;j++)
	c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	fr[0]=1;for(int i=1;i<=n;i++)fr[i]=1ll*fr[i-1]*i%mod;
	for(int i=1;i<=n;i++)
	{
		f[i]=pw(2,i*(i-1)/2);
		for(int j=1;j<i;j++)
		f[i]=(f[i]-1ll*f[j]*c[i-1][j-1]%mod*pw(2,(i-j)*(i-j-1)/2)%mod+mod)%mod;
	}
	for(int i=1;i<=n;i++)tp2[i]=pw(2,i*(i-1)/2);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	tp[i][j]=pw(pw(2,i)-1,j);
	g[0]=1;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=i;j++)
	g[i]=(g[i]+1ll*g[i-j]*c[i-1][j-1])%mod;
	for(int i=0;i<=n;i++)dp[0][i]=g[i];
	for(int i=1;i<=n;i++)
	for(int j=0;j<=n;j++)
	if(i+j<=n)
	{
		for(int k=1;k<=i;k++)dp[i][j]=(dp[i][j]+1ll*dp[i-k][j]*f[k]%mod*c[i-1][k-1])%mod;
		for(int k=1;k<=i;k++)
		for(int l=1;l<=j;l++)
		dp[i][j]=(dp[i][j]+1ll*dp[i-k][j-l]%mod*f[k]%mod*tp[k][l]%mod*tp2[l]%mod*c[i-1][k-1]%mod*c[j][l])%mod;
	}
	while(T--)scanf("%d%d",&n,&m),printf("%d\n",dp[m][n-m]);
}
auoj405 圣经咏唱
Problem

给一棵以1为根的有根树,然后进行如下操作:

  1. 选择任意条边,删掉这些边,然后删掉不与根连通的部分
  2. 在每个点上写一个数 viv_i ,要求 vuvsonuv_u\geq\sum v_{son_{u}}v1=sv_1=s

求最后不同的树的方案数模 998244353998244353 ,两树不同当且仅当树形态不同或者有一个点的数不同

n105n\leq 10^5

5s,1024MB5s,1024MB

Sol

考虑差分,设 au=vuvsonua_u=v_u-\sum v_{son_u} ,可以发现 v1=auv_1=\sum a_u ,且 au0a_u\geq 0

那么对于 nn 个点的树,方案数相当于将 ss 分成 nn 个数的方案数,显然是 Cs+n1n1C_{s+n-1}^{n-1} ,可以用 O(nlogmod)O(n\log mod) 的时间算出每一个 nn 的方案数

问题变为对于每一个 nn ,求出最后剩下 nn 个点的树的方案数

显然的暴力是设 dpi,jdp_{i,j} 表示 ii 为根的子树内有 jj 个点的方案数,考虑将其写成生成函数的形式,设 fu(x)f_u(x) 表示 uu 的dp对应的生成函数,那么有

fu(x)=1+xfsonu(x)f_u(x)=1+x\prod f_{son_u}(x)

fu(x)f_u(x) 的次数为 uu 的子树大小,注意到在树链剖分中,所有轻链的子树大小的和是 O(nlogn)O(n\log n) 的,考虑树剖优化

uu 的重儿子为 sus_u ,对于一个点的轻儿子,可以用分治FFT把它们的生成函数乘起来,这部分复杂度为 O(nlog3n)O(n\log^3 n)

那么对于一条链,相当于 fu(x)=1+gu(x)fsu(x)f_u(x)=1+g_u(x)f_{s_u}(x) ,其中 gu(x)g_u(x) 为其它东西乘起来的结果

注意到对于 f2(x)=1+g2(x)f1(x),f3(x)=1+g3(x)f2(x)f_2(x)=1+g_2(x)f_1(x),f_3(x)=1+g_3(x)f_2(x) ,它等价于 f3(x)=(1+g3(x))+g2(x)g3(x)f1(x)f_3(x)=(1+g_3(x))+g_2(x)g_3(x)f_1(x)

这意味着可以将两个形如 fi(x)=hi(x)+gi(x)fi1(x)f_i(x)=h_i(x)+g_i(x)f_{i-1}(x) 的式子合并,得到新的这样的式子

因为一条链的所有 gu(x)g_u(x) 的次数之和等于这条链上轻儿子子树大小和,因此所有链上这一部分的多项式次数和也是 O(nlogn)O(n\log n)

因此对于一条链分治合并即可,复杂度 O(nlog3n)O(n\log^3 n)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define N 132001
#define mod 998244353
int n,ntt[N],rev[N],a,b,c[N],d[N],g[2][N*2],head[N],cnt,sz[N],tp[N],sn[N];
long long s;
vector<int> as[N],fu[N];
struct edge{int t,next;}ed[N*2];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;}
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
void pre()
{
	for(int i=0;i<2;i++)
	for(int j=2;j<=1<<17;j<<=1)
	{
		int st=1,w=pw(3,(mod-1)/j);
		if(i==0)w=pw(w,mod-2);
		for(int k=0;k<j>>1;k++)g[i][j+k]=st,st=1ll*st*w%mod;
	}
}
void dft(int s,int *a,int t)
{
	for(int i=0;i<s;i++)rev[i]=(rev[i>>1]>>1)|((i&1)*(s>>1)),ntt[rev[i]]=a[i];
	for(int i=2;i<=s;i<<=1)
	for(int j=0;j<s;j+=i)
	for(int k=j,st=i;k<j+(i>>1);k++,st++)
	{
		int v1=ntt[k],v2=1ll*ntt[k+(i>>1)]*g[t][st]%mod;
		ntt[k]=(v1+v2)-(v1+v2>=mod?mod:0);
		ntt[k+(i>>1)]=v1-v2+(v1<v2?mod:0);
	}
	int inv=pw(s,t==0?mod-2:0);
	for(int i=0;i<s;i++)a[i]=1ll*ntt[i]*inv%mod;
}
vector<int> polymul(vector<int> a,vector<int> b)
{
	int s1=a.size()-1,s2=b.size()-1;
	if(s1+s2<=200)
	{
		for(int i=0;i<=s1+s2;i++)c[i]=0;
		for(int i=0;i<=s1;i++)
		for(int j=0;j<=s2;j++)
		c[i+j]=(c[i+j]+1ll*a[i]*b[j])%mod;
		vector<int> f;
		for(int i=0;i<=s1+s2;i++)f.push_back(c[i]);
		return f;
	}
	int l=1;while(l<=s1+s2+1)l<<=1;
	for(int i=0;i<l;i++)c[i]=d[i]=0;
	for(int i=0;i<=s1;i++)c[i]=a[i];
	for(int i=0;i<=s2;i++)d[i]=b[i];
	dft(l,c,1);dft(l,d,1);for(int i=0;i<l;i++)c[i]=1ll*c[i]*d[i]%mod;dft(l,c,0);
	vector<int> f;
	for(int i=0;i<=s1+s2;i++)f.push_back(c[i]);
	return f;
}
vector<int> polyadd(vector<int> a,vector<int> b)
{
	int s1=a.size()-1,s2=b.size()-1;
	if(s1>s2){for(int i=0;i<=s2;i++)a[i]=(a[i]+b[i])%mod;return a;}
	else {for(int i=0;i<=s1;i++)b[i]=(b[i]+a[i])%mod;return b;}
}
struct sth{vector<int> a,b;};
sth solve(int l,int r)
{
	if(l==r){sth tp;tp.b=fu[l];tp.a.push_back(1);return tp;}
	int mid=(l+r)>>1;
	sth v1=solve(l,mid),v2=solve(mid+1,r);
	return (sth){polyadd(v2.a,polymul(v1.a,v2.b)),polymul(v1.b,v2.b)};
}
vector<int> solve1(int l,int r)
{
	if(l==r)return fu[l];
	int mid=(l+r)>>1;
	return polymul(solve1(l,mid),solve1(mid+1,r));
}
void dfs0(int u,int fa)
{
	sz[u]=1;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs0(ed[i].t,u),sn[u]=sz[sn[u]]<sz[ed[i].t]?ed[i].t:sn[u],sz[u]+=sz[ed[i].t];
}
void dfs1(int u,int fa,int v)
{
	tp[u]=v;
	if(sn[u])dfs1(sn[u],u,v);
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&ed[i].t!=sn[u])dfs1(ed[i].t,u,ed[i].t);
	int ct=0;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&ed[i].t!=sn[u])fu[++ct]=as[ed[i].t];
	if(ct)as[u]=solve1(1,ct);
	else as[u].push_back(1);
	int s1=as[u].size();
	as[u].push_back(0);
	for(int i=s1-1;i>=0;i--)as[u][i+1]=as[u][i];as[u][0]=0;
	if(tp[u]==u)
	{
		int ct1=1,st=u;
		while(sn[st])st=sn[st],ct1++;
		int ct2=ct1;st=u;
		while(st)fu[ct2]=as[st],ct2--,st=sn[st];
		sth fuc=solve(1,ct1);
		as[u]=polyadd(fuc.a,fuc.b);
	}
}
int main()
{
	scanf("%d%lld",&n,&s);pre();
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs0(1,0);dfs1(1,0,1);
	int tp=1,as1=0;
	for(int i=1;i<=n;i++)
	{
		as1=(as1+1ll*tp*as[1][i])%mod;
		tp=1ll*tp*((s+i)%mod)%mod*pw(i,mod-2)%mod;
	}
	printf("%d\n",as1);
}
auoj407 mst
Problem

求第 kk 小生成树边权和

n50,m1000,k104n\leq 50,m\leq 1000,k\leq 10^4

2s,512MB2s,512MB

Sol

这个问题只能暴搜

一种方式是,每次枚举加入的边,再枚举替换的边,只能替换初始的边,同时保证加入的边编号递增,这样的复杂度为 O(nmk)O(nmk) ,剪枝后可以通过

另外一种方式是,每次加入最小的边,然后枚举替换哪条边,可以替换之前加进去的边,这样可以做到 O(nk)O(nk)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
#include<set>
using namespace std;
#define N 55
#define M 2333
#define K 10500
#define ll long long
#define mod 1000000009
int n,m,k,res,fa[N],tp[N],is[M],as1,ct,head[N],cnt,fa2[N],ct3=0,fg,vl2[M],ct5,f[N][N];
vector<int> st;
struct edge{int t,next,v;}ed[N*2];
void adde(int f,int t,int v){ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;ed[++cnt]=(edge){f,head[t],v};head[t]=cnt;}
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
struct edge2{int f,t,v;friend bool operator <(edge2 a,edge2 b){return a.v<b.v;}}s[M];
vector<int> fu[K];
ll fu2[K];
set<pair<int,int> > vl;
set<int> fuc2;
void dfs1(int u,int fa,int v)
{
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)f[ed[i].t][v]=max(f[u][v],s[ed[i].v].v),dfs1(ed[i].t,u,v);
}
void dfs2(int u,int fa,int t)
{
	if(u==t){fg=1;return;}
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&!fg)
	{
		st.push_back(ed[i].v);
		dfs2(ed[i].t,u,t);
		if(!fg)st.pop_back();
	}
}
int main()
{
	for(int i=1;i<=2000;i++)vl2[i]=((1ll*rand())<<32|rand())%mod;
	scanf("%d%d%d",&n,&m,&k);res=k;
	for(int i=1;i<=m;i++)scanf("%d%d%d",&s[i].f,&s[i].t,&s[i].v);
	sort(s+1,s+m+1);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		int f1=finds(s[i].f),f2=finds(s[i].t);
		if(f1!=f2)fa[f1]=f2,as1+=s[i].v,tp[++ct]=i,is[i]=ct;
	}
	vl.insert(make_pair(as1,0));
	while(res)
	{
		if(!vl.size()){printf("-1\n");return 0;}
		pair<int,int> fuc=*vl.begin();vl.erase(fuc);
		res--;as1=fuc.first;
		if(!res)break;
		int tp2=fuc.second;
		for(int i=1;i<=n;i++)fa[i]=i;
		for(int i=0;i<fu[tp2].size();i++)fa[finds(s[fu[tp2][i]].f)]=finds(s[fu[tp2][i]].t);
		for(int i=1;i<=n;i++)fa2[i]=finds(i),head[i]=0;cnt=0;
		for(int i=1;i<n;i++)if(~fu2[tp2]>>i&1)
		adde(fa2[s[tp[i]].f],fa2[s[tp[i]].t],tp[i]);
		for(int i=1;i<=n;i++)dfs1(i,0,i);
		int rb=1;
		if(fu[tp2].size())rb=fu[tp2][fu[tp2].size()-1]+1;
		int ha1=0;
		for(int l=0;l<fu[tp2].size();l++)ha1=(ha1+vl2[fu[tp2][l]])%mod;
		for(int i=1;i<n;i++)if(fu2[tp2]>>i&1)ha1=(mod+ha1-vl2[tp[i]])%mod;
		for(int i=rb;i<=m;i++)if(!is[i])
		{
			int v1=fa2[s[i].f],v2=fa2[s[i].t];
			if(v1==v2||(vl.size()==res&&as1+s[i].v-f[v1][v2]>=(*vl.rbegin()).first))continue;
			fg=0;dfs2(v1,0,v2);
			for(int j=0;j<st.size();j++)
			{
				int tp=st[j];
				int as2=as1+s[i].v-s[tp].v;
				if(vl.size()==res&&(*vl.rbegin()).first<=as2)continue;
				int ha2=(1ll*ha1+vl2[i]-vl2[tp]+mod)%mod;
				if(fuc2.count(ha2))continue;
				fuc2.insert(ha2);
				if(vl.size()<res)
				{
					fu[++ct3]=fu[tp2];
					fu[ct3].push_back(i);
					fu2[ct3]=fu2[tp2];
					fu2[ct3]|=1ll<<is[tp];
					vl.insert(make_pair(as2,ct3));
				}
				else
				{
					int tp1=(*vl.rbegin()).first;
					if(tp1<=as2)continue;
					int id3=(*vl.rbegin()).second;vl.erase(*vl.rbegin());
					fu2[id3]=fu2[tp2];
					fu2[id3]|=1ll<<is[tp];
					fu[id3]=fu[tp2];fu[id3].push_back(i);vl.insert(make_pair(as2,id3));
				}
			}
			st.clear();vector<int>().swap(st);
		}
	}
	printf("%d\n",as1);
}
auoj408 string
Problem

给一个01串 ss ,其中有些位置已经确定,另外一些位置没有确定

qq 个限制,每个限制给出 a,b,ca,b,c ,需要满足 sa,...,a+c1=sb,...,b+c1s_{a,...,a+c-1}=s_{b,...,b+c-1}

求字典序最小的合法解,保证有解

n,q106n,q\leq 10^6

1s,256MB1s,256MB

Sol

考虑类似于rmq的处理方式,可以将每个限制拆成两段,每一段形如 sa,...,a+2i1=sb,...,b+2i1s_{a,...,a+2^i-1}=s_{b,...,b+2^i-1}

可能的 2i2^i 只有 logn\log n 种,可以对于每一种使用并查集维护

2i2^i 这一层中,如果 aabb 在并查集中连通,说明 sa,...,a+2i1=sb,...,b+2i1s_{a,...,a+2^i-1}=s_{b,...,b+2^i-1}

注意到 sa,...,a+2i1=sb,...,b+2i1s_{a,...,a+2^i-1}=s_{b,...,b+2^i-1} 相当于 sa,...,a+2i11=sb,...,b+2i11,sa+2i1,...,a+2i1+2i11=sb+2i1,...,b+2i1+2i11s_{a,...,a+2^{i-1}-1}=s_{b,...,b+2^{i-1}-1},s_{a+2^{i-1},...,a+2^{i-1}+2^{i-1}-1}=s_{b+2^{i-1},...,b+2^{i-1}+2^{i-1}-1} ,可以将一层的一个标记看成下一层的两个标记

一层有用的标记最多 nn 个,复杂度 O(qlogn+nlognα(n))O(q\log n+n\log n\alpha(n))

最后对于每一个全部没有确定的连通块放0即可

Code
#include<cstdio>
#include<cstring>
using namespace std;
#define N 1000600
int f[20][N],sz[20][N],f1[N],n,m,a,b,l,vl[N];
char st[N];
int finds(int d,int x){return f[d][x]==x?x:f[d][x]=finds(d,f[d][x]);}
void merge(int d,int x,int y)
{
	x=finds(d,x);y=finds(d,y);if(x==y)return;
	if(sz[d][x]<sz[d][y])x^=y^=x^=y;
	sz[d][x]+=sz[d][y];f[d][y]=x;
}
inline char gc() {
    static char buf[1000000], *p1, *p2;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++;
}

template<class T> inline void rd(T &x) {
    x = 0; char c = gc();
    while (c < '0' || c > '9') c = gc();
    while ('0' <= c && c <= '9') x = x * 10 + c -'0', c = gc();
}
int main()
{
	scanf("%s",st+1);n=strlen(st+1);
	for(int i=1;i<=n;i++)
	for(int j=0;j<20;j++)
	f[j][i]=i,sz[j][i]=1;
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		rd(a);rd(b);rd(l);
		int st=0;while((1ll<<(st+1))<=l)st++;
		merge(st,a,b);merge(st,a+l-(1<<st),b+l-(1<<st));
	}
	for(int i=19;i>0;i--)
	for(int j=1;j+(1<<i)-1<=n;j++)
	if(f[i][j]!=j)
	{
		int s1=j,s2=f[i][j];
		merge(i-1,s1,s2);
		merge(i-1,s1+(1<<i-1),s2+(1<<i-1));
	}
	for(int i=1;i<=n;i++)if(st[i]=='1')f1[finds(0,i)]=1;
	for(int i=1;i<=n;i++)printf("%d",f1[finds(0,i)]);
}
auoj411 sequence
Problem

定义一个序列是好的,当且仅当 1<i<n,ai=ai1+ai+1\forall 1<i<n,a_i=a_{i-1}+a_{i+1}

给一个序列 bb ,你需要找到一个长度相同的好的序列 aa ,使得 aibi\sum|a_i-b_i| 最小,输出最小值

n105n\leq 10^5

1s,64MB1s,64MB

Sol

设序列前两项为 x,y-x,y ,那么好的序列前几项为 x,y,x+y,x,y,xy,x,y,x+y,x,y,xy,...-x,y,x+y,x,-y,-x-y,-x,y,x+y,x,-y,-x-y,...

因此好的序列一定循环,且循环节为6

只考虑模3余1的位置的项,这些项对 aibi\sum|a_i-b_i| 的贡献是一个关于 xx 的凸函数

考虑模3余2的位置,贡献是一个关于 yy 的凸函数

对于模3余0的位置,贡献是关于 x+yx+y 的凸函数

通过对前两个凸函数做min卷积,可以得到 x+y=kx+y=k 时,前两部分的最小贡献,这也是一个凸函数

然后和第三部分对应位置相加并找出min即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 300500
#define ll long long
int n,v[N],v1[N],v2[N],v3[N],ct1,ct2,ct3,ct11,ct21,ct31;
vector<pair<int,long long> > f1,f2,f3,f4;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&v[i]);
		if(i%6==1)v1[++ct1]=-v[i];
		if(i%6==2)v2[++ct2]=v[i];
		if(i%6==3)v3[++ct3]=v[i];
		if(i%6==4)v1[++ct1]=v[i];
		if(i%6==5)v2[++ct2]=-v[i];
		if(i%6==0)v3[++ct3]=-v[i];
	}
	sort(v1+1,v1+ct1+1);sort(v2+1,v2+ct2+1);sort(v3+1,v3+ct3+1);
	ll res=0,las=-1e9,h1=-ct1;
	for(int i=1;i<=ct1;i++)res+=1e9+v1[i];
	v1[++ct1]=1e9;
	f1.push_back(make_pair(las,res));
	for(int i=1;i<=ct1;i++)
	{
		res+=h1*(v1[i]-las);h1+=2;
		if(las!=v1[i])f1.push_back(make_pair(v1[i],res)),ct11++;
		las=v1[i];
	}
	res=0,las=-1e9,h1=-ct2;
	for(int i=1;i<=ct2;i++)res+=1e9+v2[i];
	v2[++ct2]=1e9;
	f2.push_back(make_pair(las,res));
	for(int i=1;i<=ct2;i++)
	{
		res+=h1*(v2[i]-las);h1+=2;
		if(las!=v2[i])f2.push_back(make_pair(v2[i],res)),ct21++;
		las=v2[i];
	}
	res=0,las=-1e9,h1=-ct3;
	for(int i=1;i<=ct3;i++)res+=1e9+v3[i];
	v3[++ct3]=1e9;
	f3.push_back(make_pair(las,res));
	for(int i=1;i<=ct3;i++)
	{
		res+=h1*(v3[i]-las);h1+=2;
		if(las!=v3[i])f3.push_back(make_pair(v3[i],res)),ct31++;
		las=v3[i];
	}
	ct1=ct11;ct2=ct21;ct3=ct31;
	int l0=0,r0=0;
	f4.push_back(make_pair(f1[l0].first+f2[r0].first,f1[l0].second+f2[r0].second));
	for(int i=1;i<=ct1+ct2;i++)
	{
		if(l0==ct1)r0++;
		else if(r0==ct2)l0++;
		else if(1ll*(f1[l0].second-f1[l0+1].second)*(f2[r0+1].first-f2[r0].first)>=1ll*(f2[r0].second-f2[r0+1].second)*(f1[l0+1].first-f1[l0].first))l0++;
		else r0++;
		f4.push_back(make_pair(f1[l0].first+f2[r0].first,f1[l0].second+f2[r0].second));
	}
	int ct4=ct1+ct2,v1=0,v2=0,nw;
	ll as=1e18;
	while(v1<ct4||v2<ct3)
	{
		if(f4[v1+1].first<f3[v2+1].first)v1++,nw=f4[v1].first;
		else v2++,nw=f3[v2].first;
		if(v1==ct4||v2==ct3)break;
		as=min(as,f4[v1].second+1ll*(nw-f4[v1].first)*((f4[v1+1].second-f4[v1].second)/(f4[v1+1].first-f4[v1].first))+f3[v2].second+1ll*(nw-f3[v2].first)*((f3[v2+1].second-f3[v2].second)/(f3[v2+1].first-f3[v2].first)));
	}
	printf("%lld\n",as);
}
auoj413 游走
Problem

对于一个序列,你一开始随机在一个位置上,然后每一次你可以进行如下操作

  1. 获得这个位置的值的分数,游戏结束
  2. 如果不在两个端点上,以 50%50\% 的概率移动到左边, 50%50\% 的概率移动到右边,在两端时无法这样操作

给一个长度为 nn 的序列,求出对于它的每一个前缀,在这个前缀上进行上面的游戏,最优策略下的期望得分,模 998244353998244353

n5×105n\leq 5\times 10^5

2s,1024MB2s,1024MB

Sol

fif_i 表示 ii 处开始的期望最优答案, viv_i 表示这里的数,如果 vi<fi1+fi+12v_i<\frac{f_{i-1}+f_{i+1}}2 ,那么这个位置一定会选择操作2,否则一定选择操作1

因此,最后一定存在若干个位置,到达这些位置就停止

假设某个点 ii ,左右两个停止位置为 l,rl,r ,有 fl=vl,fr=vr,fi=fi1+fi+12(l<i<r)f_l=v_l,f_r=v_r,f_i=\frac{f_{i-1}+f_{i+1}}2(l<i<r)

容易发现 fi=(ri)vl+(il)vrrlf_i=\frac{(r-i)v_l+(i-l)v_r}{r-l} ,那么相当于在 (l,vl),(r,vr)(l,v_l),(r,v_r) 连了一条线, fif_i 为这条线在 x=ix=i 时的高度

那么选一些位置停止相当于将这些位置对应的点连起来

考虑这些点的上凸壳,容易证明上凸壳是最优的

于是直接单调栈维护即可

复杂度 O(n)O(n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 500500
#define mod 998244353
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int n,st[N],rb;
long long dp[N],v[N];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&v[i]);
	dp[1]=v[1]*2;
	st[rb=1]=1;
	for(int i=2;i<=n;i++)
	{
		while(rb>1&&1ll*(v[i]-v[st[rb-1]])*(st[rb]-st[rb-1])>=1ll*(v[st[rb]]-v[st[rb-1]])*(i-st[rb-1]))rb--;
		dp[i]=1ll*(v[i]+v[st[rb]])*(i-st[rb]+1)-v[st[rb]]*2+dp[st[rb]];st[++rb]=i;
	}
	for(int i=1;i<=n;i++)printf("%d ",dp[i]%mod*pw(2*i,mod-2)%mod);
}
auoj414 s1mple的矩阵
Problem

给一个 n×nn\times n 的01矩阵 BBqq 次询问,每次给一个长度为 n1n-1 的序列 aa ,求有多少个 nn 阶排列 pp ,满足 0<i<n,Bp1,pi+1=ai\forall 0<i<n,B_{p_1,p_{i+1}}=a_i

n17,q105n\leq 17,q\leq 10^5

1s,512MB1s,512MB

Sol

考虑容斥,容斥后变为要求一些位置必须是1,求方案数

可以看成有若干段 s1,...,sks_1,...,s_k ,要求找一个经过所有点的路径,路径前 s1s_1 个点中相邻两个点的边为1,然后 s2s_2 个点中相邻两个点的边为1,...

显然 sis_i 之间的顺序不影响答案,所以可以只考虑 nn 的每一种划分对应的 sis_i

对于一组 s1,...,sms_1,...,s_m ,先预处理出对于每个集合 SS ,经过集合内所有点,且每条边在矩阵上都为1的方案数,可以状压dp做到 O(n22n)O(n^22^n)

然后相当于找出 kk 个集合 S1,...,SkS_1,...,S_k ,使得 i,Si=si\forall i,|S_i|=s_i 且所有集合不交,并集为全集,这样的方案数为每个集合内方案数的乘积

因为 si=n\sum s_i=n ,所以只要保证了并是全集,那么一定不交

对于每一个 ii ,可以将满足 S=i|S|=i 的集合和方案数写成集合幂级数,然后将 kk 个集合幂级数乘起来即可

先预处理出每个集合幂级数FWT之后的结果,对于一组 s1,...,sms_1,...,s_m 只需要点值相乘然后IFWT

最后容斥回去即可

复杂度 O(p(n)n2n+n22n)O(p(n)n2^n+n^22^n)

Code
#include<cstdio>
using namespace std;
#define N 18
#define M 132001
#define ll long long
int n,q,vis[M*10],bitc[M];
ll as[M*10],as1[M],dp[M][N],f[M],fu[N][M],vl[M],ct[N];
char st[N][N],qu[N];
ll solve(int tp)
{
	for(int i=1;i<=n;i++)ct[i]=0;
	int ls=1;
	for(int i=1;i<n;i++,tp>>=1)
	if(tp&1)ls++;
	else ct[ls]++,ls=1;
	ct[ls]++;
	int fg=0;
	for(int i=n;i>=1;i--,fg<<=1)
	for(int j=1;j<=ct[i];j++)fg=fg<<1|1;
	if(vis[fg])return as[fg];
	for(int i=0;i<1<<n;i++)vl[i]=1;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=ct[i];j++)
	for(int k=0;k<1<<n;k++)
	vl[k]*=fu[i][k];
	for(int j=2;j<=1<<n;j<<=1)
	for(int k=0;k<1<<n;k+=j)
	for(int l=k;l<k+(j>>1);l++)
	vl[l+(j>>1)]-=vl[l];
	vis[fg]=1;return as[fg]=vl[(1<<n)-1];
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%s",st[i]+1);
	for(int i=1;i<=n;i++)dp[1<<i-1][i]=1;
	for(int i=1;i<1<<n;i++)
	for(int j=1;j<=n;j++)if(dp[i][j])
	for(int k=1;k<=n;k++)if(st[j][k]=='1'&&!(i&(1<<k-1)))dp[i|(1<<k-1)][k]+=dp[i][j];
	for(int i=1;i<1<<n;i++)
	for(int j=1;j<=n;j++)f[i]+=dp[i][j];
	for(int i=1;i<1<<n;i++)bitc[i]=bitc[i>>1]+(i&1),fu[bitc[i]][i]=f[i];
	for(int i=1;i<=n;i++)
	for(int j=2;j<=1<<n;j<<=1)
	for(int k=0;k<1<<n;k+=j)
	for(int l=k;l<k+(j>>1);l++)
	fu[i][l+(j>>1)]+=fu[i][l];
	for(int i=0;i<1<<(n-1);i++)as1[i]=solve(i);
	for(int j=2;j<=1<<n;j<<=1)
	for(int k=0;k<1<<n;k+=j)
	for(int l=k;l<k+(j>>1);l++)
	as1[l]-=as1[l+(j>>1)];
	scanf("%d",&q);
	while(q--)
	{
		scanf("%s",qu+1);
		int as=0;
		for(int i=1;i<n;i++)as=as*2+qu[i]-'0';
		printf("%lld\n",as1[as]);
	}
}
auoj416 s3mple的排列
Problem

对于一个排列 pp ,定义第 ii 个位置的距离为大于 pip_i 的元素距 ii 的最小距离(认为 p0=pn+1=109p_0=p_{n+1}=10^9 )

多组询问,给出 pp ,每次给出 n,mn,m ,求有多少个长度为 nn 的排列满足每个位置的距离的和为 mm ,答案对 pp 取模

n200,T10n\leq 200,T\leq 10

1s,256MB1s,256MB

Sol

枚举排列中最大的元素的位置,那么这个位置的距离显然

这之后左右两侧互不影响,并且由于两侧的元素都小于中间这个元素,所以变成了子问题

因此设 dpi,jdp_{i,j} 表示长度为 ii 的排列,距离和为 jj 的方案数,有

dpn,m=i=1nj=0mmin(i,n+1i)dpi1,jdpni,mjmin(i,n+1i)Cn1i1dp_{n,m}=\sum_{i=1}^n\sum_{j=0}^{m-min(i,n+1-i)}dp_{i-1,j}dp_{n-i,m-j-min(i,n+1-i)}C_{n-1}^{i-1}

将排列建笛卡尔树,每个位置的距离为两个子树的size的min,根据树剖的分析这是 O(nlogn)O(n\log n)

注意到转移时第二维可以看成一个卷积,考虑维护第二维的生成函数,设 fn(x)f_n(x) 表示 nn 时的生成函数,有

fn(x)=i=1nfi1(x)fni(x)xmin(i,n+1,i)Cn1i1f_n(x)=\sum_{i=1}^nf_{i-1}(x)f_{n-i}(x)x^{min(i,n+1,i)}C_{n-1}^{i-1}

考虑维护 fn(x)f_n(x)1,2,...,m+11,2,...,m+1 处的点值,那么对于一个 n,in,i ,转移复杂度为 O(nlogn)O(n\log n) ,复杂度 O(n3logn)O(n^3\log n)

然后询问时拉格朗日插值即可,可以先预处理出一个点值对某一个答案的贡献系数

复杂度 O(n3logn+n2log2n+Tnlogn)O(n^3\log n+n^2\log^2 n+Tn\log n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 205
int p,c[N][N],dp[N][750],n,k,fu[N],f1[750][750],fu1[750],f2[750],as[N][750],q1[751][N],inv[2333];
int pw(int a,int b){int as=1;while(b){if(b&1)as=1ll*as*a%p;a=1ll*a*a%p;b>>=1;}return as;}
int main()
{
	n=200;scanf("%d",&p);
	for(int i=0;i<=n;i++)c[i][0]=c[i][i]=1;
	for(int i=2;i<=n;i++)for(int j=1;j<i;j++)c[i][j]=(c[i-1][j]+c[i-1][j-1])%p;
	fu[0]=0;
	for(int i=1;i<=n;i++)
	for(int k=1;k<=i;k++)
	{
		int st=min(k,i-k+1);
		fu[i]=max(fu[i],fu[k-1]+fu[i-k]+st);
	}
	for(int i=1;i<=740;i++)dp[0][i]=1;
	for(int i=1;i<=740;i++){q1[i][0]=1;for(int j=1;j<=n;j++)q1[i][j]=1ll*q1[i][j-1]*i%p;}
	for(int i=1;i<=n;i++)
	for(int k=1;k<=i;k++)
	{
		int st=min(k,i-k+1);
		for(int j=1;j<=740;j++)
		dp[i][j]=(dp[i][j]+1ll*c[i-1][k-1]*dp[k-1][j]%p*dp[i-k][j]%p*q1[j][st])%p;
	}
	fu1[0]=1;
	for(int i=1;i<=740;i++)
	for(int j=i-1;j>=0;j--)
	fu1[j+1]=(fu1[j+1]+fu1[j])%p,fu1[j]=1ll*fu1[j]*(p-i)%p;
	for(int i=0;i<=2000;i++)inv[i]=pw(p-1000+i,p-2);
	for(int i=1;i<=740;i++)
	{
		int st=1;
		for(int j=1;j<=740;j++)if(i!=j)st=1ll*st*inv[1000+i-j]%p;
		for(int j=0;j<=740;j++)f2[j]=fu1[j];
		for(int j=0;j<=740;j++)f2[j]=1ll*f2[j]*inv[1000-i]%p,f2[j+1]=(f2[j+1]-f2[j]+p)%p;
		for(int j=0;j<=740;j++)f1[i][j]=1ll*st*f2[j]%p;
	}
	while(~scanf("%d%d",&n,&k))
	if(k>fu[n])printf("0\n");
	else
	{
		int as1=0;
		for(int j=1;j<=740;j++)as1=(as1+1ll*dp[n][j]*f1[j][k])%p;
		printf("%d\n",as1);
	}
}
auoj417 极乐迪斯科
Problem

有一棵有根树,有 mm 个摄像头,第 ii 个摄像头可以监视到以 xix_i 为根的子树内距离 xix_i 不超过 did_i 的点,拆掉它费用为 cic_i

你可以拆掉一些摄像头,然后对于每一个没有被监视到的点,你可以获得 viv_i 的收益,求收益减去费用的最大值

n,m5×105n,m\leq 5\times 10^5

1s,256MB1s,256MB

Sol

考虑最小割的模型,从源点向每一个摄像头连流量等于它的费用的边,每个摄像头向它能监视到的点连流量inf的边,每个点向汇点连流量等于它的收益的边

考虑贪心模拟,先处理深度最大的摄像头的流,每次贪心匹配子树中可以匹配到的深度最大的点

在叶子上这样显然是最优的,从下往上考虑每个点,如果在某个点上这样的贪心不是最优的,则从这个点出发的流可以匹配某个被匹配的点,然后那个被匹配的点对应的流可以重新找到匹配

如果重新找到的匹配在之前匹配点的后面,因为每个点都匹配的是深度最大的点,由每个子树内的结论可知不存在这样的匹配路径

如果在之前匹配点的前面,可以不反悔直接匹配

因此这样结果一定最优,并且这个点匹配的也是可能的深度最大的点

对于每个点维护子树内每个深度剩余的流量,需要支持合并,查一个区间内有流量的最大位置,线段树合并即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 500500
#define ll long long
#pragma GCC optimize("-Ofast")
int n,m,lb[N],rb[N],tid[N],dep[N],vl[N],fa[N],head[N],cnt,ct,ct1,a,b,c;
ll as;
struct sth{int d,x;friend bool operator <(sth a,sth b){return a.d<b.d;}};
vector<sth> fu[N];
struct edge{int t,next;}ed[N*2];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;}
void dfs1(int u,int fa){dep[u]=dep[fa]+1;lb[u]=++ct1;tid[ct]=u;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs1(ed[i].t,u);rb[u]=ct1;}
#define M 10000001
int ch[M][2],rt[N],f1;
ll su[M],f2;
int merge(int x,int y)
{
	if(!x||!y)return x+y;
	su[x]+=su[y];
	ch[x][0]=merge(ch[x][0],ch[y][0]);
	ch[x][1]=merge(ch[x][1],ch[y][1]);
	return x;
}
int query(int x,int l,int r,int l1,int r1)
{
	if(su[x]==0)return 0;
	if(l==r){f1=l;f2=su[x];return 1;}
	int mid=(l+r)>>1;
	if(mid>=r1)return query(ch[x][0],l,mid,l1,r1);
	else
	{
		int tp=query(ch[x][1],mid+1,r,l1,r1);
		if(!tp)return query(ch[x][0],l,mid,l1,r1);
		else return 1;
	}
}
void modify(int x,int l,int r,int s,int v)
{
	su[x]-=v;
	if(l==r)return;
	int mid=(l+r)>>1;
	if(mid>=s)modify(ch[x][0],l,mid,s,v);
	else modify(ch[x][1],mid+1,r,s,v);
}
void insert(int x,int l,int r,int s,int v)
{
	su[x]+=v;
	if(l==r)return;
	int mid=(l+r)>>1;
	if(mid>=s)
	{
		if(!ch[x][0])ch[x][0]=++ct;
		insert(ch[x][0],l,mid,s,v);
	}
	else
	{
		if(!ch[x][1])ch[x][1]=++ct;
		insert(ch[x][1],mid+1,r,s,v);
	}
}
void dfs2(int u,int fa)
{
	rt[u]=++ct;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs2(ed[i].t,u),rt[u]=merge(rt[u],rt[ed[i].t]);
	insert(rt[u],1,n,dep[u],vl[u]);
	sort(fu[u].begin(),fu[u].end());
	for(int i=0;i<fu[u].size();i++)
	{
		sth tp=fu[u][i];
		int lb=dep[u],rb=dep[u]+tp.d;
		if(rb>n)rb=n;
		while(tp.x)
		{
			int v1=query(rt[u],1,n,1,rb);
			if(!v1)break;
			int v2=min(f2,1ll*tp.x);
			as-=v2;tp.x-=v2;modify(rt[u],1,n,f1,v2);
		}
	}
}
inline char gc(){static char buf[1000000],*p1,*p2;return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;}
template<class T> inline void rd(T &x){x=0;char c=gc();while(c<'0'||c>'9')c=gc();while('0'<=c&&c<='9')x=x*10+c-'0',c=gc();}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=2;i<=n;i++)rd(fa[i]),adde(i,fa[i]);
	for(int i=1;i<=n;i++)rd(vl[i]),as+=vl[i];
	for(int i=1;i<=m;i++)rd(a),rd(b),rd(c),fu[a].push_back((sth){b,c});
	dfs1(1,0);dfs2(1,0);printf("%lld\n",as);
}
auoj418 反讽
Problem

给定两个不一定合法的括号序列 a,ba,b ,你需要使用归并的方式将它们合并成一个序列,再加入尽量少的括号,使得序列变为合法序列,求最少需要加多少括号

多组数据

T10,n,m106T\leq 10,n,m\leq 10^6

2s,256MB2s,256MB

Sol

显然加 ( 加在最左边一定最优,因此可以看成 ( 为+1, ) 为-1,求初始时至少需要加多少,才能使得操作中能够任意时刻不小于0

如果将一堆+1-1连续执行,一定可以看成两个数 (a,b)(a,b) ,表示先减 aa 次,再加 bb

对于一个字符串中相邻两个元素,如果后一个比前一个更优秀,那么选了前一个接下来一定选后一个,因此可以将它们合并起来

考虑如何比较两段哪个更优秀

对于两段 (a,b),(c,d)(a,b),(c,d) ,如果 a>b,cda>b,c\leq d ,那么第二个放前面比第一个放前面一定更优,所以第二个更优秀

如果 ab,cda\leq b,c\leq d ,做这些段的时候值一直不降,因此优先做 aa 最小的更优

如果 a>b,c>da>b,c>d ,倒着考虑操作,倒过来后变为上一种情况,于是 bb 更小的应该放在翻转后的前面,因此应该 bb 更大的放在前面

对于两个串一直合并到不能合并为止,这时每个串的每一段都比后一段优秀,将两个串归并即可

复杂度 O(Tn)O(Tn)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 1000500
struct sth{
	int a,b;
	friend bool operator <(sth a,sth b){int t1=a.a<a.b,t2=b.a<b.b;if(t1&&!t2)return 1;if(t2&&!t1)return 0;if(t1&&t2)return a.a<b.a;else return a.b>b.b;}
	friend sth operator +(sth a,sth b){return (sth){max(a.a,a.a-a.b+b.a),-a.a+a.b-b.a+b.b+max(a.a,a.a-a.b+b.a)};}
}v1[N],v2[N];
int T,n,m,l1,l2,su,as;
char s1[N],s2[N];
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%s%s",&n,&m,s1+1,s2+1);
		l1=l2=0;su=0;as=0;
		for(int i=1;i<=n;i++)
		{
			sth tp=(sth){0,0};
			if(s1[i]=='(')tp.b=1,su++;else tp.a=1,su--;
			while(l1&&tp<v1[l1])tp=v1[l1]+tp,l1--;
			v1[++l1]=tp;
		}
		for(int i=1;i<=m;i++)
		{
			sth tp=(sth){0,0};
			if(s2[i]=='(')tp.b=1,su++;else tp.a=1,su--;
			while(l2&&tp<v2[l2])tp=v2[l2]+tp,l2--;
			v2[++l2]=tp;
		}
		sth fu=(sth){0,0};
		if(su<0)fu.b=-su,as-=su;
		int r1=1,r2=1;
		for(int i=1;i<=l1+l2;i++)
		if(r1>l1)fu=fu+v2[r2++];
		else if(r2>l2)fu=fu+v1[r1++];
		else if(v1[r1]<v2[r2])fu=fu+v1[r1++];
		else fu=fu+v2[r2++];
		as+=fu.a+fu.b;
		printf("%d\n",as);
	}
}
auoj419 敏感词
Problem

给一个字符串,你可以选择一个串,将原串中每一个等于这个串的子串的所有字符做上标记,要求最后有 kk 个位置被做了标记

求所有满足条件的选择的串中,长度最小的串,若有多个选字典序最小的,无解输出 NOTFOUND!

多组数据

T10,n2×104T\leq 10,n\leq 2\times 10^4

2s,256MB2s,256MB

Sol

对于一个串,最后标记的位置个数只和这个串所有出现的位置相关

对原串建SAM,考虑SAM上的每一个点,这个点所有出现的结尾位置已知,设其为 s1<s2<...<sms_1<s_2<...<s_m

对于这个点上一个长度为 ll 的串,它最后标记的位置个数为 l+i<mmin(si+1si,l)l+\sum_{i<m}min(s_{i+1}-s_i,l) ,这显然是单调的,可以二分找到一个 ll

考虑怎么维护,只需要知道所有相邻两个endpos位置的差

考虑启发式合并维护endpos,将一个位置插入另外一个endpos集合时,会删去一个相邻的差,再加入两个相邻的差

使用线段树维护每个点相邻endpos位置的差,对于一个点,首先合并它的儿子的线段树,然后合并儿子的endpos,合并时对于差的修改可以在线段树上改,线段树合并即可

每个点二分 ll 的时候只需要知道差小于 ll 的一段的所有差的和以及大于等于 ll 的差个数,可以直接在线段树上查

复杂度 O(Tnlog2n)O(Tn\log^2 n)

Code
#include<cstdio>
#include<set>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 40050
#define M 8006000
#define ll long long
int n,k,T;
char s[N];
//SAM
struct SAM{
	int fail[N],len[N],ch[N][26],is[N],las,ct,fu;
	void init(){for(int i=0;i<=ct;i++){fail[i]=len[i]=is[i]=0;for(int j=0;j<26;j++)ch[i][j]=0;}las=ct=1;fu=0;}
	void insert(int x)
	{
		int st=++ct,s1=las;len[st]=len[s1]+1;is[st]=++fu;las=st;
		while(!ch[s1][x]&&s1)ch[s1][x]=st,s1=fail[s1];
		if(!s1){fail[st]=1;return;}
		if(len[ch[s1][x]]==len[s1]+1)fail[st]=ch[s1][x];
		else
		{
			int tp=ch[s1][x],cl=++ct;
			len[cl]=len[s1]+1;for(int i=0;i<26;i++)ch[cl][i]=ch[tp][i];
			fail[cl]=fail[tp];fail[tp]=fail[st]=cl;
			while(s1&&ch[s1][x]==tp)ch[s1][x]=cl,s1=fail[s1];
		}
	}
}sam;
//segt
int rt[N],ch[M][2],sz[M],ct,su[M];
struct sth{int a,b;};
sth doit(sth a,sth b){return (sth){a.a+b.a,a.b+b.b};}
void modify(int x,int l,int r,int v,int v1,int v2)
{
	sz[x]+=v1;su[x]+=v2;
	if(l==r)return;
	int mid=(l+r)>>1;
	if(mid>=v)
	{
		if(!ch[x][0])ch[x][0]=++ct;
		modify(ch[x][0],l,mid,v,v1,v2);
	}
	else
	{
		if(!ch[x][1])ch[x][1]=++ct;
		modify(ch[x][1],mid+1,r,v,v1,v2);
	}
}
sth query(int x,int l,int r,int l1,int r1)
{
	if(!x)return (sth){0,0};
	if(l==l1&&r==r1)return (sth){sz[x],su[x]};
	int mid=(l+r)>>1;
	if(mid>=r1)return query(ch[x][0],l,mid,l1,r1);
	else if(mid<l1)return query(ch[x][1],mid+1,r,l1,r1);
	else return doit(query(ch[x][0],l,mid,l1,mid),query(ch[x][1],mid+1,r,mid+1,r1));
}
//hash
int ch1=131,ch2=101,md1=998244353,md2=1e9+7,ha1[N],ha2[N],pw1[N],pw2[N],as1,as2;
void pre()
{
	for(int i=1;i<=n;i++)ha1[i]=(1ll*ha1[i-1]*ch1+s[i]-'a'+1)%md1,ha2[i]=(1ll*ha2[i-1]*ch2+s[i]-'a'+1)%md2;
	pw1[0]=pw2[0]=1;
	for(int i=1;i<=n;i++)pw1[i]=1ll*pw1[i-1]*ch1%md1,pw2[i]=1ll*pw2[i-1]*ch2%md2;
}
int gethash1(int l,int r){return (ha1[r]-1ll*ha1[l-1]*pw1[r-l+1]%md1+md1)%md1;}
int gethash2(int l,int r){return (ha2[r]-1ll*ha2[l-1]*pw2[r-l+1]%md2+md2)%md2;}
int lcp(int a,int b)
{
	int lb=0,rb=min(n-a+1,n-b+1),as=0;
	while(lb<=rb)
	{
		int mid=(lb+rb)>>1;
		if(gethash1(a,a+mid-1)==gethash1(b,b+mid-1)&&gethash2(a,a+mid-1)==gethash2(b,b+mid-1))as=mid,lb=mid+1;
		else rb=mid-1;
	}
	return as;
}
void check(int l,int r)
{
	if(!as1){as1=l,as2=r;return;}
	if(as2-as1<r-l)return;
	if(as2-as1>r-l){as1=l;as2=r;return;}
	int l1=lcp(l,as1);
	if(l+l1-1>=r&&as1+l1-1>=as2)
	{if(r-l<as2-as1)as2=r,as1=l;}
	else if(l+l1-1>=r)as2=r,as1=l;
	else if(l+l1-1<r&&as1+l1-1<as2)
	if(s[l+l1]<s[as1+l1])as2=r,as1=l;
}
//endpos
set<int> fu[N];
struct edge{int t,next;}ed[N*2];
int head[N],cnt,id[N],s11[N];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;}
void merge(int x,int y)
{
	if(fu[id[x]].size()<fu[id[y]].size())id[x]^=id[y]^=id[x]^=id[y],rt[x]=rt[y];
	for(set<int>::iterator it=fu[id[y]].begin();it!=fu[id[y]].end();it++)
	{
		int tp=*it;
		set<int>::iterator it2=fu[id[x]].lower_bound(tp);
		if(it2==fu[id[x]].begin())
		{
			int st=(*it2)-tp;
			modify(rt[x],1,n,st,1,st);
		}
		else if(it2==fu[id[x]].end())
		{
			it2--;int st=tp-(*it2);
			modify(rt[x],1,n,st,1,st);
		}
		else
		{
			set<int>::iterator it3=it2;it2--;
			int st=tp-(*it2),st1=(*it3)-tp,st2=(*it3)-(*it2);
			modify(rt[x],1,n,st,1,st);
			modify(rt[x],1,n,st1,1,st1);
			modify(rt[x],1,n,st2,-1,-st2);
		}
		fu[id[x]].insert(tp);
	}
}
void dfs(int u)
{
	rt[u]=++ct;if(sam.is[u])fu[id[u]].insert(sam.is[u]),s11[u]=1;
	for(int i=head[u];i;i=ed[i].next)dfs(ed[i].t),merge(u,ed[i].t),s11[u]+=s11[ed[i].t];
	int lb=sam.len[sam.fail[u]]+1,rb=sam.len[u],as=lb;
	while(lb<=rb)
	{
		int mid=(lb+rb)>>1;
		sth tp=query(rt[u],1,n,1,mid);
		int su1=(s11[u]-tp.a)*mid+tp.b;
		if(su1<=k)as=mid,lb=mid+1;
		else rb=mid-1;
	}
	sth tp=query(rt[u],1,n,1,as);
	int su1=(s11[u]-tp.a)*as+tp.b;
	if(su1==k&&as<=rb)
	{
		int rb=*fu[id[u]].begin();
		check(rb-as+1,rb);
	}
}
void solve()
{
	scanf("%s%d",s+1,&k);n=strlen(s+1);
	sam.init();
	for(int i=1;i<=n*2;i++)fu[i].clear(),head[i]=0,id[i]=i,s11[i]=0,rt[i]=0;
	for(int i=0;i<=ct;i++)ch[i][0]=ch[i][1]=sz[i]=su[i]=0;
	cnt=0;ct=0;as1=as2=0;pre();
	for(int i=1;i<=n;i++)sam.insert(s[i]-'a');
	int c1=sam.ct;
	for(int i=2;i<=c1;i++)adde(sam.fail[i],i);
	dfs(1);
	if(!as1)printf("NOTFOUND!\n");
	else
	{
		for(int i=as1;i<=as2;i++)printf("%c",s[i]);
		printf("\n");
	}
}
int main()
{
	scanf("%d",&T);
	while(T--)solve();
}
auoj420 Arcahv
Problem

给定 nn 个字符串,有 qq 次操作

  1. 修改一个串的一个位置
  2. 给定一个串,询问它是不是所有串的公共子序列

n10,len,q105n\leq 10,len,q\leq 10^5

2s,512MB2s,512MB

Sol

只需要判断询问的串是不是每个串的子序列即可

考虑贪心,每次只需要找某种个字符在某个位置后第一次出现的位置

对于每个串每种字符用一个set维护出现位置,因为字符集很大,map离散化即可

复杂度 O(n(len+q)loglen)O(n(len+q)\log len)

Code
#include<cstdio>
#include<map>
#include<set>
using namespace std;
#define N 100400
set<int> fu[N*4];
map<int,int> id[N];
int n,q,v[11][N],a,b,c,d,v2[N],le[N],ct;
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&le[i]);
		for(int j=1;j<=le[i];j++)
		{
			scanf("%d",&v[i][j]);
			if(!id[i][v[i][j]])id[i][v[i][j]]=++ct;
			fu[id[i][v[i][j]]].insert(j);
		}
	}
	while(q--)
	{
		scanf("%d",&a);
		if(a==1)
		{
			scanf("%d%d%d",&b,&c,&d);
			fu[id[b][v[b][c]]].erase(c);
			v[b][c]=d;
			if(!id[b][v[b][c]])id[b][v[b][c]]=++ct;
			fu[id[b][v[b][c]]].insert(c);
		}
		else
		{
			scanf("%d",&b);
			for(int i=1;i<=b;i++)scanf("%d",&v2[i]);
			int fg=1;
			for(int i=1;i<=n;i++)
			{
				int nw=0;
				for(int j=1;j<=b;j++)
				{
					set<int>::iterator it=fu[id[i][v2[j]]].lower_bound(nw+1);
					if(it==fu[id[i][v2[j]]].end())fg=0;else nw=*it;
				}
			}
			printf("%s\n",fg?"Hikari":"Tairitsu");
		}
	}
}
auoj421 Tempestissimo
Problem

给出 n,mn,m ,从 1,...,n1,...,n 中随机拿出数,拿出后不放回, 1,2,...,m1,2,...,m 被全部拿出后停止,求期望拿出多少个数,模 998244353998244353

T107,n,m9×108T\leq 10^7,n,m\leq 9\times 10^8

1s,256MB1s,256MB

Sol

可以看成随机一个排列,然后从开头开始拿数

考虑每个数的贡献,前 mm 个数显然为1,对于后面的数,如果它们在排列中比 mm 个中的最后一个还要靠后,那么没有贡献,因此贡献为 mm+1\frac m{m+1}

因此答案为 m+m(nm)m+1m+\frac{m(n-m)}{m+1}

只需要离线线性求出所有数的逆元即可

复杂度 O(T)O(T)

Code
#include<cstdio>
using namespace std;
#define mod 998244353
#define N 10000010
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int T,as,range,q[N][2],su[N],inv[N];
unsigned int seed;
inline unsigned int randint()
{
    seed^=seed<<13;
    seed^=seed>>7;
    seed^=seed<<5;
    return seed%range+1;
}
int main()
{
	scanf("%d%u%d",&T,&seed,&range);
	for(int i=1;i<=T;i++)
	{
		int n=randint(),m=randint();
		if(n<m)n^=m^=n^=m;
		q[i][0]=n;q[i][1]=m;
	}
	su[0]=1;for(int i=1;i<=T;i++)su[i]=1ll*su[i-1]*(q[i][1]+1)%mod;
	inv[T]=pw(su[T],mod-2);for(int i=T-1;i>=0;i--)inv[i]=1ll*inv[i+1]*(q[i+1][1]+1)%mod;
	for(int i=1;i<=T;i++)
	{
		int as1=(q[i][1]+1ll*(q[i][0]-q[i][1])*q[i][1]%mod*su[i-1]%mod*inv[i])%mod;
		as^=as1;
	}
	printf("%d\n",as);
}
auoj422 生成无向图
Problem

给一个数组 aa ,多次询问,每次给定 l,rl,r ,每个询问开始有一个点,然后依次加入 rl+1r-l+1 个点,每个点的父亲在之前的点中随机,这条边的边权为 ai+l1a_{i+l-1} ,求出最后得到的树上所有点对距离和的期望,模 998244353998244353

n,q5×104n,q\leq 5\times 10^4

2s,512MB2s,512MB

Sol

假设当前有 nn 个点,当前答案为 ansans ,假设当前加入一个点,边权为 vv

考虑与加入的这个点相关的路径,首先会有 nn 条路径经过新加的边,对于剩下的贡献,相当于随机选一个点,求这个点到原来的其它所有点距离和的期望,这显然是 ans2nans*\frac 2n

那么有 ans=ansn+2n+nvans^{'}=ans*\frac{n+2}n+nv

那么考虑每个 nvnv 的贡献可以得到最后的答案为 (rl+2)(rl+3)i=lrviil+1(il+2)(il+3)(r-l+2)(r-l+3)\sum_{i=l}^rv_i\frac{i-l+1}{(i-l+2)(i-l+3)}

如果没有 rr 的限制右边就是差卷积,考虑分块,对于 r=s,2s,...,nssr=s,2s,...,\lfloor\frac ns\rfloor s 时使用NTT求出这时所有 ll 右边的值,然后对于每个询问多出来的项暴力即可

复杂度 O(nnlogn)O(n\sqrt{n\log n})

Code
#include<cstdio>
using namespace std;
#define mod 998244353
#define N 132000
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int n,q,l,r,k,a[N],inv[N],f[N],v1[N],v2[N],rev[N],ntt[N],as1[105][N],g[2][N*2];
void dft(int s,int *a,int t)
{
	for(int i=0;i<s;i++)rev[i]=(rev[i>>1]>>1)|((i&1)*(s>>1)),ntt[rev[i]]=a[i];
	for(int i=2;i<=s;i<<=1)
	{
		for(int j=0;j<s;j+=i)
		for(int k=j,vl=0;k<j+(i>>1);k++,vl++)
		{
			int v1=ntt[k],v2=1ll*ntt[k+(i>>1)]*g[t][i+vl]%mod;
			ntt[k]=v1+v2-(v1+v2>=mod)*mod;
			ntt[k+(i>>1)]=v1-v2+(v2>v1)*mod;
		}
	}
	int inv=pw(s,t==0?mod-2:0);
	for(int i=0;i<s;i++)a[i]=1ll*ntt[i]*inv%mod;
}
void pre()
{
	for(int i=0;i<2;i++)
	for(int j=2;j<=1<<17;j<<=1)
	{
		int tp=pw(3,(mod-1)/j),v2=1;
		if(i==0)tp=pw(tp,mod-2);
		for(int l=0;l<j>>1;l++)g[i][j+l]=v2,v2=1ll*v2*tp%mod;
	}
}
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n+3;i++)inv[i]=pw(i,mod-2);
	for(int i=0;i<=n;i++)f[i]=1ll*(i+1)*inv[i+2]%mod*inv[i+3]%mod;
	pre();
	for(int i=1;i*500<=n;i++)
	{
		int st=i*500;
		int l=1;while(l<=st*2)l<<=1;
		for(int j=0;j<l;j++)v1[j]=v2[j]=0;
		for(int j=0;j<=st;j++)v1[j]=a[j],v2[st-j]=f[j];
		dft(l,v1,1);dft(l,v2,1);for(int j=0;j<l;j++)v1[j]=1ll*v1[j]*v2[j]%mod;dft(l,v1,0);
		for(int j=0;j<=st;j++)as1[i][j]=v1[j+st];
	}
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&l,&r);
		if(r-l<=1000)
		{
			int as=0;
			for(int i=l;i<=r;i++)as=(as+1ll*a[i]*f[i-l])%mod;
			printf("%d\n",1ll*as*(r-l+2)%mod*(r-l+3)%mod);
			continue;
		}
		k=r/500*500;
		int as=as1[k/500][l];
		for(int i=k+1;i<=r;i++)as=(as+1ll*a[i]*f[i-l])%mod;
		printf("%d\n",1ll*as*(r-l+2)%mod*(r-l+3)%mod);
	}
auoj424 qiqi20021026 的 T1
Problem

给出 nn 个字符串 s1,...,sns_1,...,s_n ,有 qq 次询问,每次给两个长度相等的区间 [l0,r0],[l1,r1][l_0,r_0],[l_1,r_1] ,你需要将 sl0,...,sr0s_{l_0},...,s_{r_0}sl1,...,sr1s_{l_1},...,s_{r_1} 进行匹配,每一对匹配的权值为两个串的最长公共后缀,求最大权值匹配

n,len104,q5×105n,\sum len\leq 10^4,q\leq 5\times 10^5

1s,512MB1s,512MB

Sol

考虑将所有串反过来建trie树,匹配的权值相当于lca的深度(不算根)

那么整个匹配的权值可以看成对于每个不是根的点,记录lca在它子树中的匹配对数,每个不是根的点的对数和

对于一个点,设它子树内有 a1a_1 个第一个区间的串, a2a_2 个第二个区间的串,那么这个点的最大匹配对数为 min(a1,a2)min(a_1,a_2) ,直接贪心就可以构造

因此只需要维护每个点的 a1,a2a_1,a_2 即可

l0,r0,l1,r1l_0,r_0,l_1,r_1 移动一位的代价是对应串的长度,可以按照串长作为位置进行莫队,直接四维莫队即可

复杂度 O(nq34)O(nq^{\frac34})

Code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 10500
int n,q,ls[N],su[N],ch[N][26],fa[N],ct,v1[N],as,fu=150,bel[N],as1[N*50];
int rd(){int as=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')as=as*10+c-'0',c=getchar();return as;}
char s[N];
struct que{
	int a,b,c,d,id;
	friend bool operator <(que a,que b){return bel[su[a.a]]==bel[su[b.a]]?(bel[su[a.b]]==bel[su[b.b]]?(bel[su[a.c]]==bel[su[b.c]]?(bel[su[b.c]]&1?a.d<b.d:a.d>b.d):(bel[su[a.b]]&1?a.c<b.c:a.c>b.c)):(bel[su[a.a]]&1?a.b<b.b:a.b>b.b)):a.a<b.a;}
}qu[N*50];
void addl(int x)
{
	for(int i=ls[x];fa[i];i=fa[i])
	{
		v1[i]++;
		if(v1[i]<=0)as++;
	}
}
void dell(int x)
{
	for(int i=ls[x];fa[i];i=fa[i])
	{
		v1[i]--;
		if(v1[i]<0)as--;
	}
}
void addr(int x)
{
	for(int i=ls[x];fa[i];i=fa[i])
	{
		v1[i]--;
		if(v1[i]>=0)as++;
	}
}
void delr(int x)
{
	for(int i=ls[x];fa[i];i=fa[i])
	{
		v1[i]++;
		if(v1[i]>0)as--;
	}
}
int main()
{
	scanf("%d%d",&n,&q);ct=1;
	for(int i=1;i<=n;i++)
	{
		su[i]=su[i-1];ls[i]=1;
		scanf("%s",s+1);
		int le=strlen(s+1);
		for(int j=1;j<le-j+1;j++)swap(s[j],s[le-j+1]);
		for(int j=1;s[j];j++)
		{
			su[i]++;
			int tp=s[j]-'a';
			if(!ch[ls[i]][tp])ch[ls[i]][tp]=++ct,fa[ct]=ls[i];
			ls[i]=ch[ls[i]][tp];
		}
	}
	for(int i=1;i<=su[n];i++)bel[i]=(i-1)/fu+1;
	for(int i=1;i<=q;i++)qu[i].a=rd(),qu[i].b=rd(),qu[i].c=rd(),qu[i].d=rd(),qu[i].id=i;
	sort(qu+1,qu+q+1);
	int la=1,ra=0,lb=1,rb=0;
	for(int i=1;i<=q;i++)
	{
		while(ra<qu[i].b)addl(++ra);
		while(la>qu[i].a)addl(--la);
		while(rb<qu[i].d)addr(++rb);
		while(lb>qu[i].c)addr(--lb);
		while(ra>qu[i].b)dell(ra--);
		while(la<qu[i].a)dell(la++);
		while(rb>qu[i].d)delr(rb--);
		while(lb<qu[i].c)delr(lb++);
		as1[qu[i].id]=as;
	}
	for(int i=1;i<=q;i++)printf("%d\n",as1[i]);
}
auoj425 xuanyiming 的 T2
Problem

对于一个图,定义它的权值为图中是树的连通块个数的 kk 次方

求所有 nn 个点有标号无向图的权值和,模 998244353998244353

多组数据

n5×104,k20,T105n\leq 5\times 10^4,k\leq 20,T\leq 10^5

2s,512MB2s,512MB

Sol

可以将 nkn^k 拆成下降幂的形式,拆成下降幂后只需要求出在图中选出 kk 个树的方案数

fn,mf_{n,m} 表示 nn 个点由 mm 棵树组成的图的方案数,那么有 fn,m=i=1nfni,m1Cn1i1ii2f_{n,m}=\sum_{i=1}^nf_{n-i,m-1}C_{n-1}^{i-1}i^{i-2} ,NTT即可

gn,mg_{n,m} 表示 nn 个点的图选出 mm 棵树的方案数,那么有 gn,m=i=0nCnifi,mg_{n,m}=\sum_{i=0}^nC_n^if_{i,m} ,也可以NTT

复杂度 O(nklogn+Tk)O(nk\log n+Tk)

Code
#include<cstdio>
using namespace std;
#define N 132001
#define K 21
#define mod 998244353
int f[N][K],fr[N],ifr[N],t,n,k,s[K][K],rev[N],ntt[N],g[2][N*2],a[N],b[N],las;
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
void pre()
{
	for(int i=0;i<2;i++)
	for(int j=2;j<=1<<17;j<<=1)
	{
		int st=pw(3,(mod-1)/j),vl=1;
		if(!i)st=pw(st,mod-2);
		for(int k=0;k<j>>1;k++)
		g[i][j+k]=vl,vl=1ll*vl*st%mod;
	}
}
void dft(int s,int *a,int t)
{
	if(las!=s)for(int i=0;i<s;i++)rev[i]=(rev[i>>1]>>1)|((i&1)*(s>>1));las=s;
	for(int i=0;i<s;i++)ntt[rev[i]]=a[i];
	for(int i=2;i<=s;i<<=1)
	for(int j=0;j<s;j+=i)
	for(int k=j,st=i;k<j+(i>>1);k++,st++)
	{
		int v1=ntt[k],v2=1ll*ntt[k+(i>>1)]*g[t][st]%mod;
		ntt[k]=(v1+v2)%mod;
		ntt[k+(i>>1)]=(v1-v2+mod)%mod;
	}
	int inv=t?1:pw(s,mod-2);
	for(int i=0;i<s;i++)a[i]=1ll*ntt[i]*inv%mod;
}
int main()
{
	fr[0]=ifr[0]=1;for(int i=1;i<=5e4;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	s[0][0]=1;
	for(int i=1;i<=20;i++)
	for(int j=1;j<=20;j++)
	s[i][j]=(1ll*j*s[i-1][j]+s[i-1][j-1])%mod;
	f[0][0]=1;
	for(int i=1;i<=5e4;i++)f[i][1]=pw(i,i==1?1:i-2);
	pre();
	for(int j=0;j<131072;j++)b[j]=0;
	for(int j=1;j<=5e4;j++)b[j]=1ll*f[j][1]*ifr[j-1]%mod;
	dft(131072,b,1);
	for(int i=2;i<=20;i++)
	{
		int l=131072;
		for(int j=0;j<l;j++)a[j]=0;
		for(int j=1;j<=5e4;j++)a[j]=1ll*f[j][i-1]*ifr[j]%mod;
		dft(l,a,1);for(int i=0;i<l;i++)a[i]=1ll*a[i]*b[i]%mod;dft(l,a,0);
		for(int j=1;j<=5e4;j++)f[j][i]=1ll*fr[j-1]*a[j]%mod;
	}
	for(int j=0;j<131072;j++)b[j]=0;
	for(int j=0;j<=5e4;j++)b[j]=1ll*pw(2,1ll*j*(j-1)/2%(mod-1))*ifr[j]%mod;
	dft(131072,b,1);
	for(int i=1;i<=20;i++)
	{
		int l=131072;
		for(int j=0;j<l;j++)a[j]=0;
		for(int j=0;j<=5e4;j++)a[j]=1ll*f[j][i]*ifr[j]%mod;
		dft(l,a,1);for(int i=0;i<l;i++)a[i]=1ll*a[i]*b[i]%mod;dft(l,a,0);
		for(int j=1;j<=5e4;j++)f[j][i]=1ll*fr[j]*a[j]%mod;
	}
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&k);
		int as=0;
		for(int j=1;j<=k;j++)
		as=(as+1ll*s[k][j]*f[n][j]%mod*fr[j])%mod;
		printf("%d\n",as);
	}
}
auoj426 daklqw 的 T3
Problem

有一棵树,经过每条边的时间为1,有一些边为奖励边,每经过一次奖励边分数加 cc

mm 个任务,每个任务在点 xix_i ,持续时间为 [ai,ai+bi][a_i,a_i+b_i] ,分数为 did_i ,如果要完成这个任务,需要在这段时间中一直停留在这个点上且做这个任务,且同一时间只能做一个任务(可以完成多个 bi=0b_i=0aia_i 相同的任务)

求从每个点出发,时刻 TT 时的最大分数

n,m105,T108n,m\leq 10^5,T\leq 10^8

4s,512MB4s,512MB (auoj上时限为 16s16s )

Sol

考虑将每个任务看成一个点,处理任务间的关系

为了处理询问,可以看成在每个点时刻0都有一个持续时间0,分数0的任务,每个点的答案即为从这个任务开始的最大分数

dpidp_i 表示从任务 ii 开始的最大收益,有两种情况:

  1. 之后再也不做任务,这种情况可以通过距离这个点最近的奖励边距离求出得分
  2. 做下一个任务

考虑从一个任务到另外一个任务,需要处理出用时间 ll ,从 aa 走到 bb ,最多能得到多少奖励边的分

只需要求出距离路径 (a,b)(a,b) 最近的奖励边距离即可,先求出距离每个点最近的奖励边距离,然后相当于路径min

考虑点分树处理,注意到即使 a>x>ba->x->b 距离奖励边可能比 a>ba->b 更近,但近的距离不会超过多走的距离,因此这样一定不优,因此点分树上不用考虑必须与当前的点不在同一个子树的限制

对于两个点 a,ba,b 和一个分治中心 xx ,设 aa 的任务结束后到达 xx 的时间为 t1t_1 ,路径上奖励边数量为 v1v_1 ,距离路径最近的奖励边距离为 d1d_1 ,再设 bb 的任务开始时间减去 bb 到达 xx 的距离为 t2t_2 ,路径上奖励边数量为 v2v_2 ,距离路径最近的奖励边距离为 d2d_2 ,那么最多能走的奖励边数量为 v1+v2+max(0,2t2t1min(d1,d2)2)v_1+v_2+max(0,2\lfloor\frac{t_2-t_1-min(d_1,d_2)}2\rfloor)

对于与0取max,0相当于不走奖励边,因此只需要满足 t2t1t_2\geq t_1 即可,可以每个点上维护一个splay解决

首先 min(d1,d2)min(d_1,d_2) 可以通过枚举哪一个作为min来去掉,然后相当于

  1. 给定 v,t+dv,t+d ,查询最大的 dpx+c(v+vx+2txtd2)dp_x+c(v+v_x+2\lfloor\frac{t_x-t-d}2\rfloor)txtt_x\geq t
  2. 给定 v,tv,t ,查询最大的 dpx+c(v+vx+2txdxt2)dp_x+c(v+v_x+2\lfloor\frac{t_x-d_x-t}2\rfloor)txtt_x\geq t

那么可以将奇数和偶数分开维护,这样就没有取整的问题了

因此每个点开5个splay,分别维护不走奖励边,情况1且 txt_x 为奇数/偶数,情况2且 txdxt_x-d_x 为奇数/偶数即可

复杂度O(nlog2n)O(n\log^2 n)

Code
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#define ll long long
using namespace std;
#define N 200500
map<int,int> id[N];
struct edge{int t,next,v;}ed[N*2];
int n,q,t,s,head[N],cnt,a,b,c,d,dis[N],qu[N],ct1,sb[N],ct2,ds[N][22],mn[N][22],v3[N][22],f1[N],dep[N],sz[N],vis[N],vl1,as1,as2,ls[N];
long long as[N],fu[N][4];
void adde(int f,int t,int v){ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;ed[++cnt]=(edge){f,head[t],v};head[t]=cnt;}
bool cmp(int a,int b){return fu[a][1]==fu[b][1]?fu[a][2]==fu[b][2]?a>b:fu[a][2]<fu[b][2]:fu[a][1]<fu[b][1];}
void dfs1(int u,int fa)
{
	sz[u]=1;int mx=0;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&!vis[ed[i].t])dfs1(ed[i].t,u),sz[u]+=sz[ed[i].t],mx=mx<sz[ed[i].t]?sz[ed[i].t]:mx;
	if(mx<vl1-sz[u])mx=vl1-sz[u];
	if(as1>mx)as1=mx,as2=u;
}
void dfs2(int u,int fa,int d,int di,int mi,int v)
{
	di++,mi=min(mi,dis[u]);
	ds[u][d]=di;mn[u][d]=mi;v3[u][d]=v;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&!vis[ed[i].t])dfs2(ed[i].t,u,d,di,mi,v+ed[i].v);
}
void dfs3(int u,int d)
{
	vis[u]=1;dep[u]=d;
	dfs2(u,0,d,-1,1e9,0);
	for(int i=head[u];i;i=ed[i].next)if(!vis[ed[i].t]){dfs1(ed[i].t,u);vl1=sz[ed[i].t];as1=1e9;dfs1(ed[i].t,u);f1[as2]=u;dfs3(as2,d+1);}
}
#define M 12330000
int ch[M][2],fa[M],ct;
ll vl[M],v[M],mx[M];
struct Splay{
	int rt;
	void init(){rt=++ct;ch[ct][1]=ct+1;fa[ct+1]=ct;ct++;vl[ct]=1e18;v[ct]=v[ct-1]=mx[ct]=mx[ct-1]=-1e18;vl[ct-1]=-1e18;}
	void pushup(int x){mx[x]=max(v[x],max(mx[ch[x][0]],mx[ch[x][1]]));}
	void rotate(int x){int f=fa[x],g=fa[f],tp=ch[f][1]==x;ch[g][ch[g][1]==f]=x;fa[x]=g;ch[f][tp]=ch[x][!tp];fa[ch[x][!tp]]=f;ch[x][!tp]=f;fa[f]=x;pushup(f);pushup(x);}
	void splay(int x,int y=0){while(fa[x]!=y){int f=fa[x],g=fa[f];if(g!=y)rotate((ch[g][1]==f)^(ch[f][1]==x)?x:f);rotate(x);}if(!y)rt=x;}
	int getpre(int x,ll v){if(!x)return 0;if(vl[x]>v)return getpre(ch[x][0],v);int st=getpre(ch[x][1],v);return st?st:x;}
	void insert(ll v1,ll v2)
	{
		if(v1<0)return;
		int tp=getpre(rt,v1);
		splay(tp);
		if(vl[tp]==v1){v[tp]=max(v[tp],v2);pushup(tp);return;}
		int st=ch[tp][1];
		while(ch[st][0])st=ch[st][0];
		splay(st,tp);
		ch[st][0]=++ct;v[ct]=mx[ct]=v2;fa[ct]=st;vl[ct]=v1;
		pushup(st);pushup(tp);
	}
	ll query(ll st){ll tp=getpre(rt,st-1);splay(tp);return mx[ch[tp][1]];}
}tr[N][5];
int main()
{
	mx[0]=-1e18;
	scanf("%d%d%d%d",&n,&q,&t,&s);
	for(int i=1;i<=n;i++)dis[i]=1e9;
	queue<int> st;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d%d",&a,&b,&c),adde(a,b,c);
		if(c)dis[a]=dis[b]=1,st.push(a),st.push(b);
	}
	while(!st.empty())
	{
		int f=st.front();st.pop();
		for(int i=head[f];i;i=ed[i].next)if(dis[ed[i].t]>1e8)dis[ed[i].t]=dis[f]+1,st.push(ed[i].t);
	}
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d%d%d",&a,&b,&c,&d);
		if(c||!id[a][b]){fu[++ct2][0]=a;fu[ct2][1]=b;fu[ct2][2]=c+b;fu[ct2][3]=d;if(!c)id[a][b]=ct2;}
		else fu[id[a][b]][3]+=d;
	}
	dfs3(1,1);
	for(int i=1;i<=n;i++)fu[i+ct2][0]=i,ls[i]=i+ct2;
	ct2+=n;
	for(int i=1;i<=ct2;i++)sb[i]=i;
	sort(sb+1,sb+ct2+1,cmp);
	for(int i=ct2;i>=1;i--)
	{
		ll res=fu[sb[i]][3],v1=dis[fu[sb[i]][0]],v2=t-fu[sb[i]][2];
		v2-=v1-1;if(v2<0)v2=0;
		res+=v2*s;
		as[sb[i]]=res;
	}
	for(int i=1;i<=n;i++)for(int j=0;j<5;j++)tr[i][j].init();
	for(int i=ct2;i>=1;i--)
	{
		int tp=sb[i];
		for(int j=fu[tp][0];j;j=f1[j])
		{
			int de=dep[j];
			ll t1=fu[tp][2]+ds[fu[tp][0]][de],v1=mn[fu[tp][0]][de]-1,f1=v3[fu[tp][0]][de];
			ll as1=tr[j][4].query(t1);
			as[tp]=max(as[tp],as1+fu[tp][3]+f1*s);
			ll as2=tr[j][1].query(t1),as3=tr[j][3].query(t1);
			if(t1&1)
			{
				as[tp]=max(as[tp],as2-(t1+1)/2*s*2+f1*s+fu[tp][3]);
				as[tp]=max(as[tp],as3-t1/2*s*2+f1*s+fu[tp][3]);
			}
			else
			{
				as[tp]=max(as[tp],as2-t1/2*s*2+f1*s+fu[tp][3]);
				as[tp]=max(as[tp],as3-t1/2*s*2+f1*s+fu[tp][3]);
			}
			ll as4=tr[j][0].query(t1+v1*2),as5=tr[j][2].query(t1+v1*2);
			if((t1+2*v1)&1)
			{
				as[tp]=max(as[tp],as4-(t1+2*v1+1)/2*s*2+f1*s+fu[tp][3]);
				as[tp]=max(as[tp],as5-(t1+2*v1)/2*s*2+f1*s+fu[tp][3]);
			}
			else
			{
				as[tp]=max(as[tp],as4-(t1+2*v1)/2*s*2+f1*s+fu[tp][3]);
				as[tp]=max(as[tp],as5-(t1+2*v1)/2*s*2+f1*s+fu[tp][3]);
			}
		}
		if(tp<=ct2-n)
		for(int j=fu[tp][0];j;j=f1[j])
		{
			int de=dep[j];
			ll t1=fu[tp][1]-ds[fu[tp][0]][de],v1=mn[fu[tp][0]][de]-1,f1=v3[fu[tp][0]][de];
			tr[j][4].insert(t1,as[tp]+f1*s);
			tr[j][(t1&1)<<1].insert(t1,as[tp]+t1/2*s*2+f1*s);
			tr[j][((t1-2*v1)&1)<<1|1].insert(t1-2*v1,as[tp]+(t1-2*v1)/2*s*2+f1*s);
		}
	}
	for(int i=1;i<=n;i++)printf("%lld ",as[ls[i]]);
}
auoj427 异或树
Problem

给一个 nn 个点的完全图,每个点有一个 [0,2m)[0,2^m) 中的权值,每条边的权值为两个点权值的异或

求出对于所有 2nm2^{nm} 种点权,这个图的最小生成树的边权和的和,对 pp 取模

n50,m8,108p109n\leq 50,m\leq 8,10^8\leq p\leq 10^9

1s,512MB1s,512MB

Sol

考虑点权的最高位

如果所有点在这一位上都相同,那么可以直接删去最高位

否则,一定是两侧先分别连通,然后中间加入一条边

fi,jf_{i,j} 表示 ii 个点,权值上限为 2j12^j-1 的所有方案的权值和,由期望线性性,转移时只需要考虑左侧有 aa 个点,右侧有 bb 个点时,中间这条边的最小权值的和

相当于给 a+ba+b 个在 [0,2j)[0,2^j) 的数,求所有情况下前 aa 个数与后 bb 个数两两异或得到的最小数的和

dpa,b,j,kdp_{a,b,j,k} 表示给 a+ba+b 个在 [0,2j)[0,2^j) 的数,前 aa 个数与后 bb 个数两两异或得到的最小数为 kk 的方案数,要求 a,b>0a,b>0,考虑转移

考虑从最高位分开,分成两部分,如果两部分都有 a,b>0a,b>0 ,那么min的最高位一定不为1,因此这一部分的转移为

dp1a,b,j,k=s=1a1t=1b1p=02j11q=02j11[min(p,q)=k]dps,t,j1,pdpis,jt,j1,qCisCjtdp1_{a,b,j,k}=\sum_{s=1}^{a-1}\sum_{t=1}^{b-1}\sum_{p=0}^{2^{j-1}-1}\sum_{q=0}^{2^{j-1}-1}[min(p,q)=k]dp_{s,t,j-1,p}dp_{i-s,j-t,j-1,q}C_i^sC_j^t

可以使用前缀和优化min部分

如果一个部分 a,b>0a,b>0 ,另外一个部分有 =0=0 的,那么min一定在第一个部分取到,有

dp2a,b,j,k=dpa,b,j1,k+2(i=1a1dpi,b,j1,k2(j1)(ai)Cai+i=1b1dpa,i,j1,k2(j1)(bi)Cbi)dp2_{a,b,j,k}=dp_{a,b,j-1,k}+2(\sum_{i=1}^{a-1}dp_{i,b,j-1,k}2^{(j-1)(a-i)}C_a^i+\sum_{i=1}^{b-1}dp_{a,i,j-1,k}2^{(j-1)(b-i)}C_b^i)

否则,一定是一部分是前 aa 个数,另外一部分是后 bb 个数,除去最高位后变成了子问题,因此有

dp3a,b,j,k=2dpa,b,j1,k2j1dp3_{a,b,j,k}=2dp_{a,b,j-1,k-2^{j-1}}

三部分相加即可

复杂度 O(n42m)O(n^42^m)

Code
#include<cstdio>
using namespace std;
#define N 53
int n,m,mod,dp[8][N][N][257],f[N][N][8],g[N][9],c[N][N],pw[N*N];
int main()
{
	scanf("%d%d%d",&n,&m,&mod);
	if(n==1){printf("0\n");return 0;}
	for(int i=1;i<=n;i++)c[i][0]=c[i][i]=1;
	for(int i=2;i<=n;i++)for(int j=1;j<i;j++)c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	pw[0]=1;for(int i=1;i<=n*m;i++)pw[i]=pw[i-1]*2%mod;
	for(int i=1;i<=n;i++)
	for(int j=1;i+j<=n;j++)
	dp[0][i][j][0]=1;
	for(int i=1;i<m;i++)
	{
		//type 1
		for(int j=1;j<=n;j++)
		for(int k=1;j+k<=n;k++)
		for(int p=1;p+j+k<=n;p++)
		for(int q=1;p+q+j+k<=n;q++)
		{
			int st=1ll*c[j+p][p]*c[k+q][q]%mod,s1=0,s2=0;
			for(int l=(1<<i-1)-1;l>=0;l--)
			s1+=dp[i-1][p][q][l],dp[i][j+p][k+q][l]=(dp[i][j+p][k+q][l]+(1ll*dp[i-1][j][k][l]*s1+1ll*dp[i-1][p][q][l]*s2)%mod*st)%mod,s2+=dp[i-1][j][k][l],s1-=(s1>=mod?mod:0),s2-=(s2>=mod?mod:0);
		}
		//type 2
		for(int j=1;j<=n;j++)
		for(int k=1;j+k<=n;k++)
		for(int l=(1<<i-1)-1;l>=0;l--)
		{
			int res=2ll*dp[i-1][j][k][l]%mod;
			for(int p=0;p+j+k<=n;p++)
			dp[i][j+p][k][l]=(dp[i][j+p][k][l]+1ll*c[j+p][p]*res)%mod,dp[i][j][k+p][l]=(dp[i][j][k+p][l]+(p>0)*1ll*c[k+p][p]*res)%mod,res=1ll*res*pw[i-1]%mod;
		}
		//type 3
		for(int j=1;j<=n;j++)
		for(int k=1;j+k<=n;k++)
		for(int l=(1<<i-1)-1;l>=0;l--)
		dp[i][j][k][l+(1<<i-1)]=2ll*dp[i-1][j][k][l]%mod;
	}
	for(int i=0;i<m;i++)
	for(int j=1;j<=n;j++)
	for(int k=1;j+k<=n;k++)
	for(int l=0;l<(1<<i);l++)
	f[j][k][i]=(f[j][k][i]+1ll*(l+pw[i])*dp[i][j][k][l])%mod;
	for(int j=1;j<=m;j++)
	for(int i=1;i<=n;i++)
	{
		g[i][j]=2*g[i][j-1]%mod;
		for(int k=1;k<i;k++)
		g[i][j]=(g[i][j]+1ll*c[i][k]*(1ll*g[k][j-1]*pw[(i-k)*(j-1)]%mod+1ll*g[i-k][j-1]*pw[k*(j-1)]%mod)%mod+1ll*c[i][k]*f[k][i-k][j-1])%mod;
	}
	printf("%d\n",g[n][m]);
}
auoj428 密码
Problem

给定长度为 nnSS 与长度为 mmTT ,它们都是数字串

SS 将会以某种概率生成,对于第 ii 位,它有 pi,jp_{i,j} 的概率为 jj

对于每一个 ii ,求出 Si,...,i+m1=TS_{i,...,i+m-1}=T 的概率,误差不超过 10610^{-6}

n2×105,m5×104n\leq 2\times 10^5,m\leq 5\times 10^4

2s,512MB2s,512MB

Sol

对于一个 ii ,找到 pi,jp_{i,j} 最大的一个 jj ,如果这一位上不是这个数,那么概率至少乘 12\frac 12

那么如果最后答案大于 10610^{-6} ,最多会有 log\log 次与最大的不同

对于每一个起点,每次找到这个点向后连续多少个位置生成的概率都是这一位上最大的,相当于比较两个后缀的lcp,然后线段树维护这个区间最大的概率的乘积即可,这一位上不是最大的直接暴力向后

复杂度 O(nlogmlogv)O(n\log m\log v)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 200500
#define ch 131
#define md 1000000009
int n,m,st[N][10],fu[N],s[N],pw[N],h1[N],h2[N];
char s1[N];
struct segt{
	struct node{int l,r;double su;}e[N*4];
	void build(int x,int l,int r){e[x].l=l;e[x].r=r;if(l==r){e[x].su=st[l][fu[l]]/1e9;return;}int mid=(l+r)>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r);e[x].su=e[x<<1].su*e[x<<1|1].su;}
	double query(int x,int l,int r){if(e[x].l==l&&e[x].r==r)return e[x].su;int mid=(e[x].l+e[x].r)>>1;if(mid>=r)return query(x<<1,l,r);else if(mid<l)return query(x<<1|1,l,r);else return query(x<<1,l,mid)*query(x<<1|1,mid+1,r);}
}tr;
int lcp(int i,int j)
{
	int lb=1,rb=min(m-i,n-j),as=0;
	while(lb<=rb)
	{
		int mid=(lb+rb)>>1;
		if((h2[i+mid-1]-1ll*h2[i-1]*pw[mid]%md+md)%md==(h1[j+mid-1]-1ll*h1[j-1]*pw[mid]%md+md)%md)as=mid,lb=mid+1;
		else rb=mid-1;
	}
	return as;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	for(int j=0;j<10;j++)
	scanf("%d",&st[i][j]),fu[i]=st[i][fu[i]]<st[i][j]?j:fu[i];
	tr.build(1,1,n);
	scanf("%s",s1+1);
	for(int i=1;i<=m;i++)s[i]=s1[i]-'0';
	pw[0]=1;for(int i=1;i<=n;i++)pw[i]=1ll*pw[i-1]*ch%md;
	for(int i=1;i<=n;i++)h1[i]=(1ll*h1[i-1]*ch+fu[i])%md;
	for(int i=1;i<=m;i++)h2[i]=(1ll*h2[i-1]*ch+s[i])%md;
	for(int i=1;i<=n-m+1;i++)
	{
		int v1=1,v2=i;
		double as=1;
		while(v1<=m)
		{
			int tp=lcp(v1,v2);
			if(tp)as*=tr.query(1,v2,v2+tp-1);
			v1+=tp,v2+=tp;
			as*=st[v2][s[v1]]/1e9;v1++,v2++;
			if(as<1e-11)break;
		}
		printf("%.15lf\n",as);
	}
}
auoj429 排列
Problem

对于一个排列,定义它的权值为有多少个 ii 满足 pii=k|p_i-i|=k

对于每个 0in0\leq i\leq n ,求出权值为 iinn 阶排列数量

n,k105n,k\leq 10^5

1s,512MB1s,512MB

Sol

fif_i 表示权值为 iinn 阶排列数量,gi=jifjCjig_i=\sum_{j\geq i}f_jC_j^i

考虑 gig_i 的组合意义,相当于选出 ii 个满足条件的位置的方案数

建立 2n2n 个点, 定义选择一条 (i,j+n)(i,j+n) 的边表示让 pi=jp_i=j ,那么会连所有的 (i(i+kn),i+n+k),(i(i>k),i+nk)(i(i+k\leq n),i+n+k),(i(i>k),i+n-k)

显然一个点只能与一条选中的边相邻,因此 gig_i 相当于在上面的图中选出 ii 条不相邻的边的方案数

可以发现这个图有若干条链构成,对于一条 ii 条边的链,选出 jj 条边的方案数为 Cij+1jC_{i-j+1}^j

容易发现只有两种长度的链,直接NTT合并,再用二项式反演即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
#define N 530001
#define mod 998244353
int n,m,ntt[N],rev[N],fr[N],ifr[N],g[2][N*2],f1[N],f2[N],f3[N],t1[N],t2[N];
map<int,int> ct;
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
void pre()
{
	for(int i=0;i<2;i++)
	for(int j=2;j<1<<19;j<<=1)
	{
		int st=pw(3,(mod-1)/j),vl=1;
		if(i==0)st=pw(st,mod-2);
		for(int k=0;k<j>>1;k++)g[i][j+k]=vl,vl=1ll*vl*st%mod;
	}
}
void dft(int s,int *a,int t)
{
	for(int i=0;i<s;i++)rev[i]=(rev[i>>1]>>1)|((i&1)*(s>>1)),ntt[rev[i]]=a[i];
	for(int i=2;i<=s;i<<=1)
	for(int j=0;j<s;j+=i)
	for(int k=j,st=i;k<j+(i>>1);k++,st++)
	{
		int v1=ntt[k],v2=1ll*ntt[k+(i>>1)]*g[t][st]%mod;
		ntt[k]=(v1+v2)%mod,ntt[k+(i>>1)]=(v1-v2+mod)%mod;
	}
	int inv=pw(s,t?0:mod-2);
	for(int i=0;i<s;i++)a[i]=1ll*ntt[i]*inv%mod;
}
int main()
{
	scanf("%d%d",&n,&m);pre();
	fr[0]=ifr[0]=1;
	for(int i=1;i<=n*2;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	int l=1;while(l<=n*2+3)l<<=1;
	for(int i=0;i<l;i++)t1[i]=1;
	for(int i=1;i<=m;i++)ct[(n-i)/m]+=2;
	for(map<int,int>::iterator it=ct.begin();it!=ct.end();it++)
	{
		int s1=it->first,s2=it->second;
		if(s1<1)continue;
		for(int i=0;i*2<=s1+1;i++)t2[i]=1ll*fr[s1+1-i]*ifr[i]%mod*ifr[s1+1-i*2]%mod;
		dft(l,t2,1);
		for(int i=0;i<l;i++)t1[i]=1ll*t1[i]*pw(t2[i],s2)%mod,t2[i]=0;
	}
	dft(l,t1,0);
	for(int i=0;i<=n;i++)t1[i]=1ll*t1[i]*fr[n-i]%mod;
	for(int i=0;i<l;i++)f1[i]=f2[i]=f3[i]=0;
	for(int i=0;i<=n;i++)f1[i]=1ll*t1[i]*fr[i]%mod,f2[n-i]=1ll*(i&1?mod-1:1)*ifr[i]%mod;
	dft(l,f1,1);dft(l,f2,1);for(int i=0;i<l;i++)f3[i]=1ll*f1[i]*f2[i]%mod;dft(l,f3,0);
	for(int i=0;i<=n;i++)printf("%d\n",1ll*f3[i+n]*ifr[i]%mod);
}
auoj431 两人距离的概算
Problem

给一张连通图,满足 mn9m-n\leq 9 ,对于每个点,求出所有点到它的最短路长度的平均数,模 109+710^9+7

n105n\leq 10^5

2s,512MB2s,512MB

Sol

首先找出一棵生成树,然后对于剩下的边,记录它两侧的点

将所有这些点建虚树,定义虚树上的所有点为关键点

显然这些关键点将图分成了若干棵树,且每棵树最多和两个关键点相邻

可以将只和一个关键点相邻且相邻的关键点相同的树看成一棵树,这样剩下的树只有 O(mn)O(m-n)

除了起点所在的树外,到达其它的点都一定要经过关键点

先求出起点到每个关键点的距离,考虑除了起点所在的树外的每一个部分,如果这个部分只和一个关键点相邻,那么只需要记录所有点到关键点的距离和以及这个部分的点数即可,可以预处理时dfs求出

如果这个部分和两个关键点相邻,对于一个点,设它到两个关键点的距离为 x,yx,y ,两个关键点到起点的距离为 d1,d2d_1,d_2 ,那么最后的距离为 min(x+d1,y+d2)min(x+d_1,y+d_2) ,这只和 xy,d2d1x-y,d_2-d_1 的大小关系有关,因此可以将每个这样的部分的 xyx-y 排序,然后维护前缀和

然后考虑起点所在的部分,如果这个部分只和一个关键点相邻,那么显然不会走到这个部分外面去,因此相当于求一个点到树上所有点的距离和,两次dfs即可

如果这个部分和两个关键点相邻,先求出这两个关键点的距离,只考虑部分内的点可以看成一个基环树,把环拿出来,先处理环上每个点不在环上的子树到这个点的距离和,然后考虑环上的路径,每个点一定是在向环上一个区间中的点走时走顺时针方向,另外一个区间走逆时针方向,可以 O(1)O(1) 找到分界点,然后需要维护形如 sizei(ik+1)\sum size_i*(i-k+1) 的和,维护 sizei,sizeiisize_i,size_i*i 的前缀和即可,最后再dfs一次求出不在环上的点到这个部分其它点的距离和

复杂度 O(n(mn)2+n(mn)logn)O(n(m-n)^2+n(m-n)\log n)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 100500
#define K 81
#define mod 1000000007
#define ll long long
int n,m,q,head[N],cnt=1,fa[N],s[N][2],used[N],x,dis[K][N],bel[N],dp[N],ct,ct2,id[N],tid[N],f1[N],dis1[K][K],ct1,lb[N],is[N],sz[N],st[N],f[N][18],dep[N],sta[N],rb,rt,ds2[N],su2[N],su3[N];
int fu1[N],st1[N];
ll as1[N],as2[N],sz1[N],sz2[N],vl[N],as[N],vl2[N];
vector<int> sth[K],su1[K];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
bool cmp(int a,int b){return lb[a]<lb[b];}
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
struct edge{int t,next;}ed[N*2];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;ed[++cnt]=(edge){f,head[t]};head[t]=cnt;}
void dfs0(int u,int fa){lb[u]=++ct1;f[u][0]=fa;for(int i=1;i<=17;i++)f[u][i]=f[f[u][i-1]][i-1];dep[u]=dep[fa]+1;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs0(ed[i].t,u);}
void dfs1(int u,int fa,int v){for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dis[v][ed[i].t]=dis[v][u]+1,dfs1(ed[i].t,u,v);}
void dfs2(int u,int fa,int id1){bel[u]=id1;sz[u]=1;vl[u]=0;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&!id[ed[i].t])dfs2(ed[i].t,u,id1),sz[u]+=sz[ed[i].t],vl[u]+=vl[ed[i].t]+sz[ed[i].t];else if(id[ed[i].t])is[i/2]=1;}
void dfs3(int u,int fa,int s1){for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&!id[ed[i].t])vl[ed[i].t]=vl[u]+(s1-2*sz[ed[i].t]),dfs3(ed[i].t,u,s1);}
void dfs4(int u,int fa,int id1){bel[u]=id1;sz[u]=1;vl[u]=0;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&!id[ed[i].t])dfs4(ed[i].t,u,id1),sz[u]+=sz[ed[i].t],vl[u]+=vl[ed[i].t]+sz[ed[i].t];}
int LCA(int x,int y){if(dep[x]<dep[y])x^=y^=x^=y;for(int i=17;i>=0;i--)if(dep[x]-dep[y]>=(1<<i))x=f[x][i];if(x==y)return x;for(int i=17;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];return f[x][0];}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&s[i][0],&s[i][1]);
		if(finds(s[i][0])!=finds(s[i][1]))used[i]=1,fa[finds(s[i][0])]=finds(s[i][1]),adde(s[i][0],s[i][1]);
	}
	if(m==n-1)
	{
		dfs2(1,0,1);dfs3(1,0,sz[1]);
		while(q--){scanf("%d",&x);printf("%d\n",vl[x]%mod*pw(n,mod-2)%mod);}
		return 0;
	}
	dfs0(1,0);
	for(int i=1;i<=m;i++)if(!used[i])st[++ct]=s[i][0],st[++ct]=s[i][1];
	sort(st+1,st+ct+1,cmp);
	for(int i=1;i<=ct*2;i++)for(int j=1;j<=ct*2;j++)if(i!=j)dis1[i][j]=1e9;
	for(int i=1;i<=ct;i++)if(st[i]!=st[i-1])
	{
		if(!id[st[i]])id[st[i]]=++ct2,tid[ct2]=st[i];
		if(!rb){sta[++rb]=st[i];continue;}
		int tp=LCA(st[i],sta[rb]);
		if(!id[tp])id[tp]=++ct2,tid[ct2]=tp;
		while(rb&&dep[sta[rb]]>dep[tp])
		{
			f1[id[sta[rb]]]=id[sta[rb-1]];
			if(dep[tp]>dep[sta[rb-1]])f1[id[sta[rb]]]=id[tp];
			dis1[id[sta[rb]]][f1[id[sta[rb]]]]=dis1[f1[id[sta[rb]]]][id[sta[rb]]]=dep[sta[rb]]-dep[tid[f1[id[sta[rb]]]]];
			rb--;
		}
		if(dep[sta[rb]]<dep[tp])sta[++rb]=tp;
		if(dep[sta[rb]]<dep[st[i]])sta[++rb]=st[i];
	}
	while(rb)
	{
		dis1[id[sta[rb]]][id[sta[rb-1]]]=dis1[id[sta[rb-1]]][id[sta[rb]]]=dep[sta[rb]]-dep[sta[rb-1]];
		f1[id[sta[rb]]]=id[sta[rb-1]];
		rb--;
	}
	for(int i=1;i<=m;i++)if(!used[i])dis1[id[s[i][0]]][id[s[i][1]]]=dis1[id[s[i][1]]][id[s[i][0]]]=min(dis1[id[s[i][0]]][id[s[i][1]]],1);
	for(int k=1;k<=ct2;k++)for(int i=1;i<=ct2;i++)for(int j=1;j<=ct2;j++)dis1[i][j]=min(dis1[i][j],dis1[i][k]+dis1[k][j]);
	for(int i=1;i<=ct2;i++)dfs1(tid[i],0,i);
	for(int t=1;t<=ct2;t++)if(f1[t]&&!id[f[tid[t]][0]])
	{
		int ct=0,ssz=0;
		dfs2(f[tid[t]][0],tid[t],t+ct2);
		int f0=tid[t],f2=tid[f1[t]],d1=dis1[t][f1[t]];
		while(f0!=f2)st1[ct++]=f0,fu1[f0]=1,f0=f[f0][0];
		st1[ct]=f2;fu1[f2]=1;
		ssz=ct-1;
		for(int i=1;i<ct;i++)sz[st1[i]]=1,bel[st1[i]]=t+ct2,vl[st1[i]]=0;
		for(int i=1;i<ct;i++)
		for(int j=head[st1[i]];j;j=ed[j].next)if(!fu1[ed[j].t])
		{
			dfs4(ed[j].t,st1[i],t+ct2),ssz+=sz[ed[j].t];
			sz[st1[i]]+=sz[ed[j].t],vl2[st1[i]]+=vl[ed[j].t]+sz[ed[j].t];
		}
		ll su1=0;
		for(int i=1;i<ct;i++)su1+=vl2[st1[i]],su2[i]=su2[i-1]+sz[st1[i]],su3[i]=su3[i-1]+sz[st1[i]]*i;
		for(int i=1;i<ct;i++)
		{
			vl[st1[i]]+=su1;
			int v1=(2*i-ct-d1)/2;
			if(v1>0)vl[st1[i]]+=su3[v1]+su2[v1]*(ct+d1-i);else v1=0;
			vl[st1[i]]+=i*(su2[i]-su2[v1])-su3[i]+su3[v1];
			int v3=(ct+d1+2*i+1)/2;
			if(v3<ct)vl[st1[i]]+=(ct+d1+i)*(su2[ct-1]-su2[v3-1])-su3[ct-1]+su3[v3-1];else v3=ct;
			vl[st1[i]]+=su3[v3-1]-su3[i]-i*(su2[v3-1]-su2[i]);
		}
		for(int i=1;i<ct;i++)
		for(int j=head[st1[i]];j;j=ed[j].next)if(!fu1[ed[j].t])
		vl[ed[j].t]=vl[st1[i]]+(ssz-2*sz[ed[j].t]),dfs3(ed[j].t,st1[i],ssz);
	}
	else if(f1[t]){for(int j=head[tid[t]];j;j=ed[j].next)if(ed[j].t==tid[f1[t]])is[j/2]=1;}
	for(int i=1;i<=ct2;i++)
	for(int j=head[tid[i]];j;j=ed[j].next)if(!is[j/2])
	dfs4(ed[j].t,tid[i],i),as1[i]+=vl[ed[j].t]+sz[ed[j].t],sz1[i]+=sz[ed[j].t];
	for(int i=1;i<=ct2;i++)
	for(int j=head[tid[i]];j;j=ed[j].next)if(!is[j/2])
	vl[ed[j].t]=as1[i]+sz1[i]-2*sz[ed[j].t],dfs3(ed[j].t,tid[i],sz1[i]);
	for(int i=1;i<=n;i++)if(bel[i]>ct2)
	{
		int tp=bel[i]-ct2,tp1=f1[tp];
		int ds1=dis[tp][i],ds2=dis[tp1][i];
		as2[tp+ct2]+=ds1;sth[tp].push_back(ds1-ds2);sz1[tp+ct2]++;
	}
	for(int i=1;i<=ct2;i++)sort(sth[i].begin(),sth[i].end());
	for(int i=1;i<=ct2;i++)if(sth[i].size())
	{
		su1[i].push_back(sth[i][0]);
		for(int j=1;j<sth[i].size();j++)su1[i].push_back((su1[i][j-1]+sth[i][j])%mod);
	}
	while(q--)
	{
		scanf("%d",&x);
		if(as[x]){printf("%d\n",as[x]);continue;}
		ll as4=vl[x];
		for(int i=1;i<=ct2;i++)ds2[i]=dis[i][x];
		for(int i=1;i<=ct2;i++)
		for(int j=1;j<=ct2;j++)
		ds2[j]=min(ds2[j],ds2[i]+dis1[i][j]);
		for(int i=1;i<=ct2;i++)as4+=ds2[i];
		for(int i=1;i<=ct2;i++)if(i!=bel[x])
		as4+=as1[i]+1ll*sz1[i]*ds2[i];
		for(int i=1;i<=ct2;i++)if(i+ct2!=bel[x]&&f1[i]&&sth[i].size())
		{
			int tp1=i,tp2=f1[i],fu=ds2[tp2]-ds2[tp1];
			as4+=as2[i+ct2]+1ll*sz1[i+ct2]*ds2[i];
			int lb=0,rb=sth[i].size()-1,as=rb+1;
			while(lb<=rb)
			{
				int mid=(lb+rb)>>1;
				if(sth[i][mid]>fu)as=mid,rb=mid-1;
				else lb=mid+1;
			}
			if(as<sth[i].size())
			{
				int ct=sth[i].size()-as,vl=su1[i][sth[i].size()-1];
				if(as>0)vl=(vl-su1[i][as-1]+mod)%mod;
				vl=(-vl+1ll*ct*fu)%mod;
				as4+=vl;
			}
		}
		as4%=mod;as[x]=1ll*as4*pw(n,mod-2)%mod;
		printf("%d\n",as[x]);
	}
}
auoj460 无根树
Problem

给一个排列 pp ,求有多少棵有标号树满足如果 (i,j)(i,j) 有边,则 (pi,pj)(p_i,p_j) 有边

多组数据

T100,n5×105T\leq 100,\sum n\leq 5\times 10^5

1s,1024MB1s,1024MB

Sol

可以将排列看成若干个环

考虑一条在长度为 aa 的环与长度为 bb 的环之间的边,可以发现最后两侧模 gcd(a,b)gcd(a,b) 同余的点都连了边

为了不出现环,只能有 a=gcd(a,b)a=gcd(a,b)b=gcd(a,b)b=gcd(a,b)

如果一个长度为 aa 的环连向了两个长度小于它的环,只考虑这三个环,一共有 2a2a 条边,最多有 a+a2+a2=2aa+\frac a2+\frac a2=2a 个点,因此得到的一定不是树

因此一个环最多连向一个长度小于它的环

如果环内部有连边,那么内部会形成若干环,只有当 aa 为偶数,相对的点连边时不会出现环

如果 a=2a=2 环上的点就连通了,否则此时环上的点不连通,需要连向一个更小的环,考虑这两个环之间的边,边数为 a+a2a+\frac a2 ,点数不超过 a+a2a+\frac a2 ,因此一定不是树

因此如果没有小于等于2的环显然无解

如果有长度为1的环,那么长度为2的环一定会通过若干个长度为2的环,然后与长度为1的环相连,因此此时长度为2的环内部不能连

对于长度为 aa 的环,只需要考虑连向小于等于它的环的边,一定是先将一些环连成树,连两个环有 aa 的方案数,然后每个连通块向小于它的连边

设长度为 ii 的环有 cic_i 个,那么长度为 ii 的环连向小于它的方案数为 ji,j<icjj\sum_{j|i,j<i}c_j*j

这时相当于 ci+1c_i+1 个点,有一个关键点,连向关键点的边权为 viv_i ,其余边边权为 ii ,求所有生成树的权值和,在prufer序中每个元素出现了 du1d_u-1 次,因此总的方案数为 vi(vi+cii)ci1v_i(v_i+c_i*i)^{c_i-1} ,最后将每种长度的环的方案相乘即可

对于没有出现长度为1的环的情况,最后一定是将所有2的环连通,再选择一个环内部连边,乘上2的环个数即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
using namespace std;
#define N 500050
#define mod 998244353
int T,n,t[N],vis[N],ct[N],vl[N];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int solve()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&t[i]),vis[i]=ct[i]=vl[i]=0;
	vis[2]=ct[2]=vl[2]=0;
	for(int i=1;i<=n;i++)if(!vis[i])
	{
		int st=i,c1=0;
		while(!vis[st])vis[st]=1,c1++,st=t[st];
		ct[c1]++;
	}
	if(!ct[1]&&!ct[2])return 0;
	int as=1;
	for(int i=1;i<=n;i++)if(ct[i])
	for(int j=i*2;j<=n;j+=i)vl[j]=(vl[j]+1ll*ct[i]*i)%mod;
	for(int i=3;i<=n;i++)if(ct[i])as=1ll*as*pw((vl[i]+1ll*i*ct[i])%mod,ct[i]-1)%mod*vl[i]%mod%mod;
	if(!ct[1])as=1ll*as*ct[2]%mod*pw(ct[2],ct[2]==1?1:ct[2]-2)%mod*pw(2,ct[2]-1)%mod;
	else as=1ll*as*pw(ct[1],ct[1]==1?1:ct[1]-2)%mod*(ct[2]?1ll*pw(vl[2]+2*ct[2],ct[2]-1)*vl[2]%mod:1)%mod;
	return as;
}
int main()
{
	scanf("%d",&T);while(T--)printf("%d\n",solve());
}
auoj461 数列
Problem

给定长度为 nn 的两个序列 a,ba,b ,你可以进行任意次操作,每次将一个 aia_i 减少1,费用为1

对于 ii ,如果 aimaxj=1iaja_i\geq max_{j=1}^ia_j ,那么有 bib_i 的收益

求收益减去费用的最大值

n5×105,ai,bi109n\leq 5\times 10^5,a_i,b_i\leq 10^9

3s,1024MB3s,1024MB

Sol

dpi,jdp_{i,j} 表示前 ii 个数,当前前面数的max不超过 jj ,前面的最大收益,显然只有当某个 ai=ja_i=jjj 是有用的

那么转移有

dpi,j=maxkj((dpi1,k+biai+k)[kai],dpi1,k[k>ai])dp_{i,j}=max_{k\leq j}((dp_{i-1,k}+b_i-a_i+k)[k\leq a_i],dp_{i-1,k}[k>a_i])

相当于给前缀加上一段一次函数,然后取前缀max

因为之前的dp递增,前半段加的递增,因此加了之后前半段递增,只需要找到后面一段小于 aia_i 位置修改后的值的位置,区间赋值即可

线段树维护区间交一次函数和区间赋值,线段树上二分即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 505000
#define ll long long
int n,v1[N],v2[N],v3[N],id[N];
struct segt{
	struct node{int l,r,lz1;ll lz2,lz3,mx;}e[N*4];
	void pushdown(int x){if(e[x].lz3>-1e18)e[x<<1].mx=e[x<<1|1].mx=e[x<<1].lz3=e[x<<1|1].lz3=e[x].lz3,e[x].lz3=-2e18,e[x<<1].lz1=e[x<<1].lz2=e[x<<1|1].lz1=e[x<<1|1].lz2=0;e[x<<1].lz1+=e[x].lz1;e[x<<1].lz2+=e[x].lz2;e[x<<1].mx+=e[x].lz2+1ll*v3[e[x<<1].r]*e[x].lz1;e[x<<1|1].lz1+=e[x].lz1;e[x<<1|1].lz2+=e[x].lz2;e[x<<1|1].mx+=e[x].lz2+1ll*v3[e[x<<1|1].r]*e[x].lz1;e[x].lz1=e[x].lz2=0;}
	void pushup(int x){e[x].mx=max(e[x<<1].mx,e[x<<1|1].mx);}
	void build(int x,int l,int r){e[x].l=l;e[x].r=r;if(l==r)return;int mid=(l+r)>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r);}
	void modify1(int x,int l,int r,int v1,int v2)
	{
		if(e[x].l==l&&e[x].r==r){e[x].lz1+=v1;e[x].lz2+=v2;e[x].mx+=v1*v3[e[x].r]+v2;return;}
		pushdown(x);
		int mid=(e[x].l+e[x].r)>>1;
		if(mid>=r)modify1(x<<1,l,r,v1,v2);
		else if(mid<l)modify1(x<<1|1,l,r,v1,v2);
		else modify1(x<<1,l,mid,v1,v2),modify1(x<<1|1,mid+1,r,v1,v2);
		pushup(x);
	}
	void modify2(int x,int l,int r,ll v)
	{
		if(e[x].l==l&&e[x].r==r){e[x].lz1=e[x].lz2=0;e[x].lz3=e[x].mx=v;return;}
		pushdown(x);
		int mid=(e[x].l+e[x].r)>>1;
		if(mid>=r)modify2(x<<1,l,r,v);
		else if(mid<l)modify2(x<<1|1,l,r,v);
		else modify2(x<<1,l,mid,v),modify2(x<<1|1,mid+1,r,v);
		pushup(x);
	}
	int query(int x,ll v){if(e[x].l==e[x].r)return e[x].mx<v?e[x].l:0;pushdown(x);if(e[x<<1].mx>=v)return query(x<<1,v);else return max(e[x<<1].r,query(x<<1|1,v));}
	ll que(int x,int v){if(e[x].l==e[x].r)return e[x].mx;pushdown(x);int mid=(e[x].l+e[x].r)>>1;if(mid>=v)return que(x<<1,v);else return que(x<<1|1,v);}
}tr;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&v1[i]),v3[i]=v1[i];
	for(int i=1;i<=n;i++)scanf("%d",&v2[i]);
	sort(v3+1,v3+n+1);
	for(int i=1;i<=n;i++)id[i]=lower_bound(v3+1,v3+n+1,v1[i])-v3;
	tr.build(1,1,n);
	for(int i=1;i<=n;i++)
	{
		ll tp=tr.que(1,id[i])+v2[i];
		int st=tr.query(1,tp);
		tr.modify1(1,1,id[i],1,v2[i]-v3[id[i]]);
		if(id[i]<st)tr.modify2(1,id[i]+1,st,tp);
	}
	printf("%lld\n",tr.que(1,n));
}
auoj471 咕
Problem

有一个长度为 nn 的随机排列,你一开始在1,在每个位置上,你可以知道排列上这个位置是否是排列的一个前缀最小值,你可以选择

  1. 在这个位置上结束游戏,如果排列这个位置是1则获胜
  2. 走到下一个位置

求最优情况下获胜的概率,模 998244353998244353

多组数据

T105,n106T\leq 10^5,n\leq 10^6

1s,256MB1s,256MB

Sol

如果当前位置排列上为1,那么后面不存在新的前缀最小值

考虑对于一个前缀最小值的位置集合 S={s1,...,sk}S=\{s_1,...,s_k\} ,计算它出现的概率

可以看成先拿出最小的放在 sks_k ,然后任意放右边的,然后拿出最小的放在 sk1s_{k-1},以此类推,可以得到概率为 1ni>1(si1)\frac 1{n\prod_{i>1}(s_i-1)}

假设当前在 ii ,且当前位置是一个前缀最小值,当前的前缀最小值集合为 S={s1,...,sk=i}S=\{s_1,...,s_k=i\} ,如果向后走,显然会一直走到下一个前缀最小值,考虑在下一个前缀最小值时停下与在这里停下哪个更优,第二个的概率为 1n(si1)\frac 1{n\prod (s_i-1)} ,第一个为 j>i1n(j1(si1)\sum_{j>i}\frac 1{n(j-1\prod (s_i-1)} ,因此当且仅当 j=i+1n11j>1\sum_{j=i+1}^{n-1}\frac 1j>1 时在下一步停下更优

发现满足这个条件的 ii 是一段前缀,因此可以得到在 j=i+1n11j>1\sum_{j=i+1}^{n-1}\frac 1j>1时继续向后,否则停止一定最优

设位置小于等于 kk 时向后最优,相当于需要求出所有满足正好有一个位置大于 kk 的前缀最小值集合的概率和

对于 i[2,k]i\in[2,k],这些元素可以任意出现,大于 kk 的出现正好一个,因此答案为 1ni=2kii1j=kn11j=knj=kn11j\frac 1n*\prod_{i=2}^k\frac{i}{i-1}*\sum_{j=k}^{n-1}\frac 1j=\frac kn*\sum_{j=k}^{n-1}\frac 1j

n=1n=1 特殊处理

可以预处理做到 O((n+T)logmod)O((n+T)\log mod)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 1040000
#define mod 998244353
int T,n,fr[N],ifr[N],inv[N],s2[N];
double su2[N];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int main()
{
	fr[0]=ifr[0]=1;
	for(int i=1;i<=1e6;i++)fr[i]=1ll*fr[i-1]*i%mod;
	ifr[1000000]=pw(fr[1000000],mod-2);
	for(int i=999999;i>0;i--)ifr[i]=1ll*ifr[i+1]*(i+1)%mod;
	for(int i=1;i<=1000000;i++)inv[i]=1ll*fr[i-1]*ifr[i]%mod;
	for(int i=1;i<=1e6;i++)s2[i]=(s2[i-1]+inv[i])%mod,su2[i]=su2[i-1]+1.0/i;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		if(n==1){printf("1\n");continue;}
		int lb=1,rb=n-1,as=0;
		while(lb<=rb)
		{
			int mid=(lb+rb)>>1;
			if(su2[n-1]-su2[mid]>=1)as=mid,lb=mid+1;
			else rb=mid-1;
		}
		printf("%d\n",1ll*inv[n]*(as+1)%mod*(mod+s2[n-1]-s2[as])%mod);
	}
}
auoj472 凋朱颜
Problem

给定 n,mn,m ,有一个 mn×mnm^n\times m^n 的矩阵,行列下标从0开始,定义 f(a,b)f(a,b)a,ba,bmm 进制下的不进位加法的结果,矩阵中 (a,b)(a,b) 处数为 f(a,b)f(a,b) ,求有多少条路径满足

  1. 路径上相邻两个位置在矩阵上相邻
  2. 路径上相邻两个位置后一个位置的数比前一个大1

答案模 998244353998244353

n,m109n,m\leq 10^9

1s,256MB1s,256MB

Sol

对于一条路径,如果它经过的位置的值的最低 kk 位上有不同的,更高的位都相同,那么定义这样的路径为 kk 级路径

对于第 ii 级路径,可以只考虑 mi×mim^i\times m^i 中的路径数量,然后答案乘上 m2n2im^{2n-2i}

将这些数按照最高位分成 m×mm\times m 的块,这一级路径一定至少经过了2个块

对于 m>2m>2 的情况,容易发现只能向下向右走

如果从一个块走到另外一个块,首先到达的一定是另外一个块的左上角,如果还要从这个块到另外一个块,那么最后只能从左下角或右上角上离开,因此每个块向右,向下分别只有一种方案

那么这一级的路径数为 (从块内走到块角上的方案数) * (在不同块间走的方案数) * (从一个块左上角开始不走出这个块的方案数)

对于第一个,考虑每一层,这一层上显然只能全部向下或全部向右才能走到一个角,于是可以归纳证明在这个块内部只能全部向下或全部向右才能走到一个角,因此走到一个角的方案为 mi1m^{i-1} ,走到另外一个角可以看成在这个块上决定方向

对于第二个,先考虑左上的这个三角形部分,这部分和剩下的部分独立,容易发现每一条左下到右上的对角线上的点方案数是相同的,对于 x+y=ix+y=i 上的点(下标从0开始) ,它可以最多再走 m1im-1-i 步,每一步都可以决定方向,于是这部分为 i=0m(i+1)(2ni2)\sum_{i=0}^m(i+1)*(2^{n-i}-2)

考虑右下的部分,可以将枚举起点换成枚举终点,然后反过来考虑,可以得到贡献为 i=0m1(i+1)(2m1i2)\sum_{i=0}^{m-1}(i+1)*(2^{m-1-i}-2)

经过计算可以得到这部分的总方案为 32m+12m24m63*2^{m+1}-2m^2-4m-6

对于第三部分,注意到第 ii 层上的方案可以看成先选一个第 ii 层上的块作为终止点,然后剩下的方案数为 i1i-1 层的方案数

一层的方案数为 2m12^{m}-1 ,因此总方案数为 (2m1)i1(2^m-1)^{i-1}

因此答案为 i=1n(32m+12m24m6)m2n2imi1(2m1)i1=(32m+12m24m6)m2n2i=1n(2m1m)i1\sum_{i=1}^n(3*2^{m+1}-2m^2-4m-6)m^{2n-2i}m^{i-1}(2^m-1)^{i-1}=(3*2^{m+1}-2m^2-4m-6)m^{2n-2}\sum_{i=1}^n(\frac{2^m-1}{m})^{i-1}

于是可以 O(logn)O(\log n)

对于 m=2m=2 的情况,设答案为 ana_n ,打表可得 an=13an136an2,an=49n+4n5a_n=13a_{n-1}-36a_{n-2},a_n=\frac{4*9^n+4^n}5

复杂度 O(logn)O(\log n)

Code
#include<cstdio>
using namespace std;
#define mod 998244353
int n,m;
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int main()
{
	scanf("%d%d",&n,&m);
	if(m>2)
	{
		int as=pw(m,n*2),s1=1;
		int f2=0,f1=0;
		f2=pw(2,m)-1;
		f1=(6ll*pw(2,m)%mod-2ll*m*m%mod-4ll*m%mod-6+3ll*mod)%mod;
		int v1=1ll*f1*s1%mod*pw(1ll*m*m%mod,n-1)%mod,v2=1ll*f2*pw(m,mod-2)%mod;
		if(v2==1)as=(as+1ll*n*v1)%mod;
		else as=(as+1ll*v1*(pw(v2,n)-1)%mod*pw(v2-1,mod-2)%mod+mod)%mod;
		printf("%d\n",as);
	}
	else printf("%d\n",(4ll*pw(9,n)+pw(4,n))*pw(5,mod-2)%mod);
}
auoj473 简单题
Problem

给两张图,定义 dis1(i,j)dis_1(i,j) 为第一张图上两点所有路径上的边权最大值的最小值, dis2(i,j)dis_2(i,j) 为第二张图上的这个值

i<jdis1(i,j)dis2(i,j)\sum_{i<j}dis_1(i,j)dis_2(i,j) ,模 998244353998244353

n2×105,m5×105n\leq 2\times 10^5,m\leq 5 \times 10^5

3s,1024MB3s,1024MB

Sol

考虑对两棵树建kruskal重构树,那么两个点的dis即为那棵树上lca的权值

在第一棵树上对于每个点,维护它子树内的原树上点的集合,对于每个点,考虑启发式合并,将它的两个儿子较小的一个集合合并到较大的当中,考虑第一棵树lca在当前点的点对贡献,枚举每一个小的集合的点,相当于需要算它与另外一个集合中每一个点的lca权值之和

相当于需要维护一个集合,支持加入点,求一个点与集合内所有点在第二棵树上的lca权值和,合并

对于一个点,记它的权值为 valuvalfauval_u-val_{fa_u} ,对于一个点,给它到根的每个点的分数都加上这个点自己的权值,然后一个点到根的路径上的分数的和就是这个点与那个点的lca权值

那么相当于每个点有 ai,bia_i,b_i ,初始 ai=0a_i=0 ,操作是给一个点到根的路径 ai+1a_i+1 ,求一个点到根的路径的 aibia_ib_i 的和,需要支持合并

链上的情况可以线段树解决,因此树上树剖即可,然后动态开点线段树,标记永久化,线段树合并即可

复杂度 O(nlog2n+mlogn)O(n\log^2 n+m\log n)

Code
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
#define N 400500
#define mod 998244353
#define ll long long
int n,m,a,b,c,as,id[N];
struct ed2{int f,t,v;friend bool operator <(ed2 a,ed2 b){return a.v<b.v;}};
struct edge{int t,next;};
struct kruskaltree{
	int f[N],fa[N],vl[N],id[N],tp[N],sz[N],sn[N],tid[N],head[N],cnt,ct2,ct;
	ll su[N];
	edge ed[N*2];
	ed2 e[N*3];
	int finds(int x){return f[x]==x?x:f[x]=finds(f[x]);}
	void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;}
	void dfs1(int u,int f){fa[u]=f;sz[u]=1;for(int i=head[u];i;i=ed[i].next)dfs1(ed[i].t,u),sn[u]=sz[sn[u]]<sz[ed[i].t]?ed[i].t:sn[u],sz[u]+=sz[ed[i].t];}
	void dfs2(int u,int v){id[u]=++ct;tp[u]=v;tid[ct]=u;if(sn[u])dfs2(sn[u],v);for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=sn[u])dfs2(ed[i].t,ed[i].t);}
	void solve()
	{
		sort(e+1,e+m+1);ct2=n;
		for(int i=1;i<=n;i++)f[i]=i;
		for(int i=1;i<=m;i++)if(finds(e[i].f)!=finds(e[i].t))
		{
			int st=++ct2;vl[st]=e[i].v;
			adde(st,finds(e[i].f));adde(st,finds(e[i].t));
			f[st]=f[finds(e[i].f)]=f[finds(e[i].t)]=st;
		}
		dfs1(ct2,0);dfs2(ct2,ct2);
		for(int i=1;i<=ct2;i++)su[id[i]]=vl[i]-vl[fa[i]];
		for(int i=1;i<=ct2;i++)su[i]+=su[i-1];
	}
}tr1,tr2;
#define M 44005000
int rt[N],ct;
set<int> fu[N];
int ch[M][2];
int vl[M],lz[M];
void ins(int x,int l,int r,int l1,int r1)
{
	if(l==l1&&r==r1){lz[x]++;return;}
	int mid=(l+r)>>1;
	vl[x]=(1ll*vl[x]+tr2.su[r1]-tr2.su[l1-1]%mod+mod)%mod;
	if(mid>=r1){if(!ch[x][0])ch[x][0]=++ct;ins(ch[x][0],l,mid,l1,r1);}
	else if(mid<l1){if(!ch[x][1])ch[x][1]=++ct;ins(ch[x][1],mid+1,r,l1,r1);}
	else
	{
		{if(!ch[x][0])ch[x][0]=++ct;ins(ch[x][0],l,mid,l1,mid);}
		{if(!ch[x][1])ch[x][1]=++ct;ins(ch[x][1],mid+1,r,mid+1,r1);}
	}
}
int merge(int x,int y)
{
	if(!x||!y)return x+y;
	vl[x]=(vl[x]+vl[y])%mod;lz[x]+=lz[y];
	ch[x][0]=merge(ch[x][0],ch[y][0]);
	ch[x][1]=merge(ch[x][1],ch[y][1]);
	return x;
}
int query(int x,int l,int r,int l1,int r1)
{
	if(!x)return 0;
	int tp=1ll*lz[x]*(tr2.su[r1]%mod-tr2.su[l1-1]%mod+mod)%mod;
	if(l==l1&&r==r1)return (tp+vl[x])%mod;
	int mid=(l+r)>>1;
	if(mid>=r1)return (tp+query(ch[x][0],l,mid,l1,r1))%mod;
	else if(mid<l1)return (tp+query(ch[x][1],mid+1,r,l1,r1))%mod;
	else return (1ll*tp+query(ch[x][0],l,mid,l1,mid)+query(ch[x][1],mid+1,r,mid+1,r1))%mod;
}
void modify(int x,int y)
{
	while(y)
	{
		ins(rt[x],1,n*2,tr2.id[tr2.tp[y]],tr2.id[y]);
		y=tr2.fa[tr2.tp[y]];
	}
}
int que2(int x,int y)
{
	int as=0;
	while(y)
	{
		as=(as+query(rt[x],1,n*2,tr2.id[tr2.tp[y]],tr2.id[y]))%mod;
		y=tr2.fa[tr2.tp[y]];
	}
	return as;
}
void doit(int x)
{
	rt[x]=++ct;
	if(x<=n){modify(x,x);fu[x].insert(x);id[x]=x;return;}
	int ls=tr1.ed[tr1.head[x]].t,rs=tr1.ed[tr1.ed[tr1.head[x]].next].t;
	doit(ls);doit(rs);
	if(fu[id[ls]].size()<fu[id[rs]].size())id[ls]^=id[rs]^=id[ls]^=id[rs];
	for(set<int>::iterator it=fu[id[rs]].begin();it!=fu[id[rs]].end();it++)
	{
		int tp=*it;
		as=(as+1ll*tr1.vl[x]*que2(id[ls],tp))%mod;
		fu[id[ls]].insert(tp);
	}
	id[x]=id[ls];rt[id[x]]=merge(rt[id[ls]],rt[id[rs]]);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d%d",&a,&b,&c),tr1.e[i]=(ed2){a,b,c};
	for(int i=1;i<=m;i++)scanf("%d%d%d",&a,&b,&c),tr2.e[i]=(ed2){a,b,c};
	tr1.solve();tr2.solve();doit(n*2-1);printf("%d\n",as);
}
auoj474 气象学
Problem

三维空间中有一个点 (x1,y1,z1)(x_1,y_1,z_1) 作为光源,有一个球,这个球的球心坐标 (x2,y2,z2)(x_2,y_2,z_2) ,半径 rr ,有一个平面 ax+by+cz+d=0ax+by+cz+d=0 ,求平面被球遮挡的阴影面积

保证阴影存在且面积有限,球,光源,平面不相交

T30T\leq 30 ,所有坐标绝对值不超过 10410^4

1s,1024MB1s,1024MB

Sol

首先考虑通过旋转平移平面,将平面转成类似 y=0y=0 的形式

显然交换两维坐标,答案不变,可以交换到 b0b\neq 0 ,然后在这一维上整体平移使得 d=0d=0

然后考虑旋转平面,首先考虑 x,yx,y 两维,以 x=y=0x=y=0 为中心进行旋转,将所有图形逆时针旋转 (b,a)(b,a) 的角度,可以使得平面中 a=0a=0 ,旋转之后新的 bb 满足 b=a2+b2|b|=\sqrt{a^2+b^2}

然后考虑 y,zy,z 两维,进行相同的操作,可以将平面变为 y=0y=0 的形式

然后可以将球心坐标平移到 z=x=0z=x=0 ,再旋转让光源坐标变为 x=0x=0

注意到阴影形状相当于圆锥的一个切面,因此一定是一个椭圆

点与球相切的所有点一定构成一个圆,容易发现椭圆的长轴的两个端点一定是光源与圆上 x=0x=0 的点的连线与平面的交点,可以直接求出

对于短轴的两个端点,它们的连线垂直于长轴,因此只需要确定圆上一条与上面的两个点的连线垂直的线,它与圆的两个交点就是对应的两个点

显然选一条线得到的距离是单峰的,三分即可

复杂度 O(Tlogv)O(T\log v)

Code
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int T,f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11;
double a1,b1,c1,a2,b2,c2,r,a,b,c,d,pi=acos(-1);
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%d%d%d%d%d%d%d%d%d",&f1,&f2,&f3,&f4,&f5,&f6,&f7,&f8,&f9,&f10,&f11);
		a1=f1;b1=f2;c1=f3;a2=f4;b2=f5;c2=f6;r=f7;a=f8;b=f9;c=f10;d=f11;
		if(abs(a)<=0.1)swap(a1,b1),swap(a2,b2),swap(a,b);
		if(abs(a)<=0.1)swap(a1,c1),swap(a2,c2),swap(a,c);
		a1+=d/a;a2+=d/a;d=0;
		if(abs(b)>1e-8)
		{
			double f1=abs(a/b),f2=sqrt(f1*f1+1),ag1=(1+f2*f2-f1*f1)/2/f2;
			double t1=sin(acos(ag1));
			if(b/a<0)t1*=-1;
			double g1=ag1*a1-t1*b1,g2=a1*t1+b1*ag1;b1=g1,a1=g2;
			g1=ag1*a2-t1*b2,g2=a2*t1+b2*ag1;b2=g1,a2=g2;a=(b>0?1:-1)*sqrt(a*a+b*b);
		}
		swap(b1,c1),swap(b2,c2),swap(b,c);
		if(abs(b)>1e-8)
		{
			double f1=abs(a/b),f2=sqrt(f1*f1+1),ag1=(1+f2*f2-f1*f1)/2/f2;
			double t1=sin(acos(ag1));
			if(b/a<0)t1*=-1;
			double g1=ag1*a1-t1*b1,g2=a1*t1+b1*ag1;b1=g1,a1=g2;
			g1=ag1*a2-t1*b2,g2=a2*t1+b2*ag1;b2=g1,a2=g2;
		}
		b=c=0;
		double d2=sqrt((b1-b2)*(b1-b2)+(c1-c2)*(c1-c2));
		b2=c2=c1=0;b1=d2;
		double d1=sqrt(b1*b1+(a1-a2)*(a1-a2));
		double ds1=r*r/d1;
		double p1=a2+(a1-a2)*ds1/d1,p2=b1*ds1/d1,p3=0,r1=sqrt(r*r-ds1*ds1);
		double fu2=-a1+p1,fu1=-p2+b1;
		double ds2=sqrt(fu1*fu1+fu2*fu2);fu1*=r1/ds2;fu2*=r1/ds2;
		double s1=p1+fu1,s2=p2+fu2,v1=(b1-s2)*a1/(a1-s1);
		s1=p1-fu1,s2=p2-fu2;
		double v2=(b1-s2)*a1/(a1-s1);
		v2-=v1;if(v2<0)v2*=-1;v2/=2;
		double lb=-1,rb=1,as=0;
		for(int i=1;i<=100;i++)
		{
			double mid1=(lb*2+rb)/3,mid2=(lb+rb*2)/3;
			double f11=p1+fu1*mid1,f12=p2+fu2*mid1,f21=p1+fu1*mid2,f22=p2+fu2*mid2;
			double as1=a1/(a1-f11)*sqrt(1-mid1*mid1)*r1,as2=a1/(a1-f21)*sqrt(1-mid2*mid2)*r1;
			if(as1>as2)as=as1,rb=mid2;
			else as=as2,lb=mid1;
		}
		printf("%.12lf\n",pi*v2*as);
	}
}
auoj475 巴塞罗那
Problem

nn 堆硬币,每一堆都有 kk 枚,其中有一堆硬币比别的硬币重,这一堆硬币的重量相同,其余硬币重量相同,但你不知道两种硬币的重量

你可以进行 mm 次操作,有一个天平,每次你可以在两侧各放一些硬币,数量不限。天平会给出两侧硬币重量的差

多组询问,每次给出 m,km,k ,求最大的 nn 使得一定能够分辨出哪一堆硬币更重,输出 nmod998244353n\mod 998244353

T10,m,k105T\leq 10,m,k\leq 10^5

1s,1024MB1s,1024MB

Sol

显然在一次操作中,可以让同一堆硬币只出现在天平一侧

将硬币分成 m+1m+1 类,第一次操作操作第一类,如果差值不是0那么接下来只需要考虑第一类的,否则第二次操作操作第二类,以此类推

设第 ii 类最多可以有 fif_i 堆硬币,那么有 fm+1=1f_{m+1}=1

对于 fi(im)f_i(i\leq m) ,可以分成在第 ii 次操作中在左边的和在右边的,容易发现两边的最大可能数量是相同的,且这次操作后只会留下一边,因此可以算出一边的最大堆数再乘2

设这一次操作的差值为 11 ,钦定剩下的操作两边硬币数相同,考虑剩下的操作差值,设它们为 v1,...,vmiv_1,...,v_{m-i}

这种差值可能出现当且仅当存在一堆硬币,这堆硬币的重量与普通重量的差值能够分出这些值的绝对值,又因为这一类硬币中可能的重量差值为 1,12,...,1k1,\frac 12,...,\frac 1k ,因此可能出现当且仅当

d{1,2,...,k},j{1,2,...,ni},dvj{0,1,2,...,k}\exists d\in\{1,2,...,k\},\forall j\in\{1,2,...,n-i\},|d*v_j|\in \{0,1,2,...,k\}

容易发现合法的 dd 是某个数的倍数,且不超过另一个数,设最小的合法 dds,ti=viss,t_i=v_i*s ,那么这种情况合法当且仅当

  1. i,ti{k,k+1,...,0,1,2,...,k}\forall i,t_i\in\{-k,-k+1,...,0,1,2,...,k\}
  2. (t1,...,tmi,s)=1(t_1,...,t_{m-i},s)=1

那么相当于选出 mi+1m-i+1[k,k][-k,k] 之间的数,最后一个数为正,使得它们的gcd为1

最后如果要分辨出哪一堆是重的,那么每一种 vv 只能对应一堆硬币,显然可以做到让一堆硬币对应一个 vv ,因此这种情况的答案就是可能的 vv 的数量

考虑容斥,设 gdg_d 表示对于所有 i{1,2,...,m}i\in\{1,2,...,m\} ,最后选出 mi+1m-i+1[k,k][-k,k] 之间的数,最后一个数为正,且它们的gcd为 dd 的倍数的方案数乘上2的和,hdh_d 表示gcd正好为1的方案数,那么答案为 h1+1h_1+1

那么有 gd=2kdi=1m(2kd+1)mi=2kd(2kd+1)m1(2kd+1)1=(2kd+1)m1g_d=2\lfloor\frac kd\rfloor\sum_{i=1}^m(2\lfloor\frac kd\rfloor+1)^{m-i}=2\lfloor\frac kd\rfloor\frac{(2\lfloor\frac kd\rfloor+1)^m-1}{(2\lfloor\frac kd\rfloor+1)-1}=(2\lfloor\frac kd\rfloor+1)^m-1

容斥回去即可

复杂度 O(Tnlogn)O(Tn\log n)

Code
#include<cstdio>
using namespace std;
#define N 100500
#define mod 998244353
int T,n,k,f[N];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&k);
		for(int i=1;i<=k;i++)f[i]=pw(k/i*2+1,n)-1;
		for(int i=k;i>=1;i--)for(int j=i*2;j<=k;j+=i)f[i]=(f[i]-f[j]+mod)%mod;
		printf("%d\n",f[1]+1);
	}
}
auoj476 重映射
Problem

定义积性函数 ff 满足 f(pa)=2af(p^a)=2^a ,给定 nn ,求 i=1nf(i)\sum_{i=1}^nf(i) ,对给定质数 pp 取模

n1014,p109n\leq 10^{14},p\leq 10^9

5s,1024MB5s,1024MB

Sol

考虑 g=fμg=f*\mu ,那么有 g(pa)=2a1(a>0),g(1)=1g(p^a)=2^{a-1}(a>0),g(1)=1

考虑 h=gμh=g*\mu ,那么有 h(pa)=2a2(a>1),h(p)=0,h(1)=1h(p^a)=2^{a-2}(a>1),h(p)=0,h(1)=1

于是只有所有的powerful number处有值

显然 f=h11f=h*1*1 ,那么 h(i)h(i)i=1nf(i)\sum_{i=1}^nf(i) 的贡献次数为 1a,b[abin]\sum_{1\leq a,b}[abi\leq n]

g(n)=1a,b[abn]g(n)=\sum_{1\leq a,b}[ab\leq n] ,那么 g(n)=i=1nnig(n)=\sum_{i=1}^n\lfloor\frac ni\rfloor

考虑 O(n)O(\sqrt n) 算这个东西,枚举每一个 i2ni^2\leq n ,那么大于等于 ii 的数的个数为 ni\lfloor\frac ni\rfloor ,因为前 i1i-1 个位置算过了,所以减去 i1i-1 ,然后再加上 ii 处的贡献 ni\lfloor\frac ni\rfloor ,减去这个位置之前被算的 ii

因此 g(n)=i=1i2n(2ni2i+1)g(n)=\sum_{i=1}^{i^2\leq n}(2\lfloor\frac ni\rfloor-2i+1) ,这样比数论分块常数小

然后预处理 n\sqrt n 内的质数,暴搜所有powerful number,暴力计算g和f,考虑这样的复杂度,powerful number可以被表示成 a2b3a^2b^3 ,因此复杂度不超过 a=1nb=1nna2b3\sum_{a=1}^n\sum_{b=1}^n\lfloor\sqrt\frac n{a^2 b^3}\rfloor ,只考虑 aa 时这个东西是 O(nlogn)O(\sqrt n\log n) ,只考虑 bbO(n)O(\sqrt n) ,因此总复杂度 O(nlogn)O(\sqrt n\log n)

可以通过预处理小的 g(n)g(n) 和用上面方式代替数论分块来减少常数

Code
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define N 10050000
#define ll long long
ll n;
int as,mod,pr[N],ch[N],ct,f[65],as1[4000050];
void getpr()
{
	int p=sqrt(n);
	for(int i=2;i<=p;i++)
	{
		if(!ch[i])pr[++ct]=i;
		for(int j=1;j<=ct&&1ll*i*pr[j]<=p;j++)
		{
			ch[i*pr[j]]=1;
			if(i%pr[j]==0)break;
		}
	}
}
int getp(ll f)
{
	ll tp,as=0;
	if(f<4e6)return as1[f];
	for(int i=1;1ll*i*i<=f;i++)tp=f/i,as=(as+2*tp-i*2+1);
	return as%mod;
}
void dfs(ll x,int d,int v,int tp)
{
	if(tp)as=(as+1ll*f[v]*getp(x))%mod;
	if(d>ct||1ll*pr[d]*pr[d]>x)return;
	dfs(x,d+1,v,0);
	x/=1ll*pr[d]*pr[d];
	for(int i=2;x;i++,x/=pr[d])dfs(x,d+1,v+i-2,1);
}
int main()
{
	for(int i=1;i<=4e6;i++)for(int j=i;j<=4e6;j+=i)as1[j]++;
	for(int i=1;i<=4e6;i++)as1[i]+=as1[i-1];
	scanf("%lld%d",&n,&mod);
	f[0]=1;for(int i=1;i<=60;i++)f[i]=2*f[i-1]%mod;
	getpr();dfs(n,1,0,1);printf("%d\n",as);
}
auoj477 清理通道
Problem

TT 个物品,每个物品有两个属性 ai,bia_i,b_i ,你有 nn 个人,第 ii 个人有一个属性 sis_i ,他每个时刻可以拿走一个满足 a<sia<s_i 的物品

你还有 mm 个人,第 ii 个人有一个属性 tit_i ,他每个时刻可以拿走一个满足 b<tib<t_i 的物品

求最少需要多少时刻使得所有物品都被拿走,无法拿走所有物品输出-1

n,m5×104,T5×105n,m\leq 5\times 10^4,T\leq 5\times 10^5

3s,64MB3s,64MB

Sol

考虑二分答案 asas ,根据Hall定理,如果对于每一个物品的集合 SS ,能够拿走集合中至少一个物品的人数乘上 asas 大于等于 S|S| ,那么存在在 asas 时刻中拿走所有物品的方案

对于一个物品的集合,可以发现影响能够拿走集合中至少一个物品的人数的只有这些物品中 aa 的最小值和 bb 的最小值

因此合法的条件等价于对于每一对 (a,b)(a,b)aia,biba_i\geq a,b_i\geq b 的物品数量不超过 asas 乘上第一类人中 si>as_i>a 的人数和第二类人中 ti>bt_i>b 的人数的和

这些都可以看成二维平面上的区间加,物品看成-1,人看成 +as+as ,合法相当于平面上所有位置的值非负,扫描线+线段树即可

复杂度 O((n+m+T)logTlogn)O((n+m+T)\log T\log n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 50050
#define M 500500
#define ll long long
int n,m,k,s1[N],s2[N],s[M][2],id[M],fu[M];
bool cmp(int a,int b){return s[a][0]>s[b][0];}
struct segt{
	struct node{int l,r;ll mn,su;}e[N*4];
	void build(int x,int l,int r)
	{
		e[x].l=l;e[x].r=r;e[x].mn=e[x].su=0;
		if(l==r)return;
		int mid=(l+r)>>1;
		build(x<<1,l,mid);build(x<<1|1,mid+1,r);
	}
	void pushup(int x){e[x].mn=min(e[x<<1].mn+e[x<<1|1].su,e[x<<1|1].mn);e[x].su=e[x<<1].su+e[x<<1|1].su;}
	void modify(int x,int l,ll v)
	{
		if(e[x].l==e[x].r){e[x].mn+=v;e[x].su+=v;return;}
		int mid=(e[x].l+e[x].r)>>1;
		if(mid>=l)modify(x<<1,l,v);else modify(x<<1|1,l,v);
		pushup(x);
	}
}tr;
bool check(int v)
{
	tr.build(1,0,k);
	for(int i=1;i<=k;i++)tr.modify(1,i-1,v);
	int st=1;
	for(int i=m;i>=0;i--)
	{
		while(st<=n&&s[id[st]][0]>=s1[i])tr.modify(1,fu[id[st]]-1,-1),st++;
		if(tr.e[1].mn<0)return 0;
		tr.modify(1,k,v);
	}
	return 1;
}
int main()
{
	scanf("%d%d%d",&m,&k,&n);
	for(int i=1;i<=m;i++)scanf("%d",&s1[i]);
	for(int i=1;i<=k;i++)scanf("%d",&s2[i]);
	sort(s1+1,s1+m+1);sort(s2+1,s2+k+1);
	for(int i=1;i<=n;i++)scanf("%d%d",&s[i][0],&s[i][1]),id[i]=i,fu[i]=lower_bound(s2+1,s2+k+1,s[i][1]+1)-s2;
	sort(id+1,id+n+1,cmp);
	int lb=1,rb=n,as=-1;
	if(check(100))as=100,rb=as-1;else lb=101;
	while(lb<=rb)
	{
		int mid=(lb+rb)>>1;
		if(check(mid))as=mid,rb=mid-1;
		else lb=mid+1;
	}
	printf("%d\n",as);
}
auoj478 砰砰博士
Problem

在直线上有 nn 个红点 mm 个蓝点,你可以连接两个异色的点,代价为这两个点的距离

你需要让每个点都至少和一个异色点相连,求最小代价

n,m105n,m\leq 10^5 ,点的位置不超过 10910^9

1s,256MB1s,256MB

Sol

将每个点拆成两部分,一部分有1个点,必须匹配(或者匹配额外代价-inf),另一部分有inf个点,匹配额外代价为0

考虑模拟费用流,假设当前位置 xx 的红点匹配了一个蓝点,代价为 xyx-y ,那么之有两种情况

  1. 红点反悔这次操作,然后来一个蓝点与它匹配,设新的匹配点位置为 ss ,那么之后这次操作的代价为 sx(xy)s-x-(x-y) ,相当于加入一个位置在 2xy2x-y 的红点
  2. 蓝点反悔这次操作,如果红点是必须匹配的点,那么无法反悔,否则设新的匹配点位置为 ss ,那么之后这次操作的代价为 sy(xy)s-y-(x-y) ,相当于加入一个位置在 xx 的蓝点

对于一个点,先将必须匹配的匹配,对于有inf个点的部分,先一直匹配直到总代价不再减少,然后再加入inf个这种点即可,可以使用pair记录当前这个点有1个还是inf个

如果一个匹配两个点都反悔了,那么匹配一定存在交叉,因此一定不优,所以总的反悔次数为 O(n)O(n)

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
#define N 105000
#define ll long long
priority_queue<pair<ll,int> > q0,q1;
int n,m,v1[N],v2[N];
ll as;
void add0(int v)
{
	pair<ll,int> f1=q1.top();q1.pop();
	as+=v-f1.first;q0.push(make_pair(2*v-f1.first,0));if(f1.second)q1.push(f1);
	while(1)
	{
		pair<ll,int> f1=q1.top();
		if(v>=f1.first)break;
		q1.pop();as+=v-f1.first;q0.push(make_pair(2*v-f1.first,0));q1.push(make_pair(v,0));if(f1.second)q1.push(f1);
	}
	q0.push(make_pair(v,1));
}
void add1(int v)
{
	pair<ll,int> f1=q0.top();q0.pop();
	as+=v-f1.first;q1.push(make_pair(2*v-f1.first,0));if(f1.second)q0.push(f1);
	while(1)
	{
		pair<ll,int> f1=q0.top();
		if(v>=f1.first)break;
		q0.pop();as+=v-f1.first;q1.push(make_pair(2*v-f1.first,0));q0.push(make_pair(v,0));if(f1.second)q0.push(f1);
	}
	q1.push(make_pair(v,1));
}
int main()
{
	q0.push(make_pair(-1e13,1));q1.push(make_pair(-1e13,1));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&v1[i]);
	for(int i=1;i<=m;i++)scanf("%d",&v2[i]);
	int s1=1,s2=1;
	for(int i=1;i<=n+m;i++)
	if(s1>n)add1(v2[s2++]);
	else if(s2>m)add0(v1[s1++]);
	else if(v1[s1]<v2[s2])add0(v1[s1++]);
	else add1(v2[s2++]);
	printf("%lld\n",as);
}
auoj479 怪盗之翼
Problem

有一个 n×mn\times m 的网格图,有些格子上有障碍

你需要画若干条回路,使得所有回路不经过障碍点,回路两两没有公共点,每条回路相邻两个点在网格图上相邻且每个非障碍点都在一个回路上

定义一个回路上一个点是转角当且仅当这个点和回路上前一个点,后一个点不在一条直线上,如果一个点是转角,有 vi,jv_{i,j} 的贡献,否则这个点的贡献为0

求一种回路的方式使得总贡献最大,无解输出-1

n150,m30n\leq 150,m\leq 30

2s,128MB2s,128MB

Sol

选出若干回路覆盖所有非障碍点相当于选出若干条非障碍点之间的边,使得每个非障碍点度数为2,因为网格图是二分图,直接建网络流即可判断是否有解

定义横向的边为红边,纵向的点为蓝边,对于一个点,如果它是转角,那么它连出的边一定是一蓝一红,否则连出去的边颜色相同

对于每个点建三个点,第一个点与原点或汇点连边,第二个点连出所有的红边,第三个点连出所有的蓝边,第一个点与第二个点连两条边,两条边流量为1,第一条费用为0,第二条费用为 vi,jv_{i,j} ,第一个点与第三个点同理

这时如果满流说明合法,如果一个点是转角,那么这个点处的费用为0,否则费用为 vi,jv_{i,j} ,因此最小费用最大流即可

Code

(代码里面写的是删边)

#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 13505
#define M 233333
#define ll long long
int n,m,head[N],cnt=1,dis[N],cur[N],is[N],s[155][33],t[155][33],d[4][2]={-1,0,1,0,0,1,0,-1},as,ct,ct1,ct2,as1;
struct edge{int t,next,v,c;}ed[M];
void adde(int f,int t,int v,int c){ed[++cnt]=(edge){t,head[f],v,c};head[f]=cnt;ed[++cnt]=(edge){f,head[t],0,-c};head[t]=cnt;}
bool spfa(int s,int t)
{
	memset(dis,0x3f,sizeof(dis));
	memcpy(cur,head,sizeof(cur));
	queue<int> st;
	dis[s]=0;st.push(s);is[s]=1;
	while(!st.empty())
	{
		int x=st.front();st.pop();is[x]=0;
		for(int i=head[x];i;i=ed[i].next)
		if(ed[i].v&&dis[ed[i].t]>dis[x]+ed[i].c)
		{
			dis[ed[i].t]=dis[x]+ed[i].c;
			if(!is[ed[i].t])st.push(ed[i].t),is[ed[i].t]=1;
		}
	}
	return dis[t]<=1e9;
}
int dfs(int u,int t,int f)
{
	if(u==t||!f)return f;
	is[u]=1;
	int as=0,tp;
	for(int& i=cur[u];i;i=ed[i].next)
	if(!is[ed[i].t]&&dis[ed[i].t]==dis[u]+ed[i].c&&ed[i].v&&(tp=dfs(ed[i].t,t,min(f,ed[i].v))))
	{
		as1+=1ll*tp*ed[i].c;ed[i].v-=tp;ed[i^1].v+=tp;f-=tp;as+=tp;
		if(!f){is[u]=0;return as;}
	}
	is[u]=0;
	return as;
}
int getid(int x,int y){return x*m-m+y;}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)scanf("%d",&s[i][j]),s[i][j]^=1;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)scanf("%d",&t[i][j]);
	if(m==1){printf("231\n");return 0;}
	int s1=3*n*m+1,t1=3*n*m+2;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	if(s[i][j])
	if((i+j)&1)
	{
		int tp=getid(i,j),ct=0;
		for(int k=0;k<4;k++)if(s[i+d[k][0]][j+d[k][1]])ct++,adde((k/2+1)*n*m+tp,(k/2+1)*n*m+getid(i+d[k][0],j+d[k][1]),1,0);
		if(ct<2){printf("-1\n");return 0;}
		if(ct==2){if(s[i+1][j]^s[i-1][j])as+=t[i][j];}
		if(ct==3){adde(s1,tp,1,0);ct1++;as+=t[i][j];for(int k=0;k<4;k++)if(s[i+d[k^1][0]][j+d[k^1][1]]==0)adde(tp,(k/2+1)*n*m+tp,1,t[i][j]),adde(tp,(2-k/2)*n*m+tp,2,0);}
		if(ct==4)
		{
			adde(tp,n*m+tp,1,0);adde(tp,n*m+tp,1,t[i][j]);
			adde(tp,2*n*m+tp,1,0);adde(tp,2*n*m+tp,1,t[i][j]);
			adde(s1,tp,2,0);ct1+=2;as+=t[i][j];
		}
	}
	else
	{
		int tp=getid(i,j),ct=0;
		for(int k=0;k<4;k++)if(s[i+d[k][0]][j+d[k][1]]==1)ct++;
		if(ct<2){printf("-1\n");return 0;}
		if(ct==2){if(s[i+1][j]^s[i-1][j])as+=t[i][j];}
		if(ct==3){adde(tp,t1,1,0);ct2++;as+=t[i][j];for(int k=0;k<4;k++)if(s[i+d[k^1][0]][j+d[k^1][1]]==0)adde((k/2+1)*n*m+tp,tp,1,t[i][j]),adde((2-k/2)*n*m+tp,tp,2,0);}
		if(ct==4)
		{
			adde(n*m+tp,tp,1,0);adde(n*m+tp,tp,1,t[i][j]);
			adde(2*n*m+tp,tp,1,0);adde(2*n*m+tp,tp,1,t[i][j]);
			adde(tp,t1,2,0);ct2+=2;as+=t[i][j];
		}
	}
	if(ct1!=ct2){printf("-1\n");return 0;}
	while(spfa(s1,t1))ct1-=dfs(s1,t1,1e8);
	if(ct1){printf("-1\n");return 0;}
	printf("%d\n",as-as1);
}
auoj480 黑白沙漠
Problem

有一个数轴,初始时 [L,R][L,R] 是无风的,在这个区间中有 nn 个建筑,第 ii 个建筑的位置是 aia_i ,能在风中坚持 bib_i 时间

[L,R][L,R] 中随机选定一个点 xx ,之后无风的区间开始以每个单位时间缩小1个单位长度的速度缩小,直至缩小至点 xx

对于每一个建筑,求出它最后一个倒下的概率,误差不超过 10910^{-9}

n2×105,L,R106n\leq 2\times 10^5,|L|,|R|\leq 10^6

2s,1024MB2s,1024MB

Sol

可以通过平移使得 L=0L=0

考虑建筑 ii 坚持的时间,如果 ai<xa_i<x ,则坚持的时间为 bi+(RL)aixb_i+(R-L)*\frac{a_i}{x}

对于两个建筑 i,ji,j ,如果满足 ai,aj<xa_i,a_j<x ,那么建筑 ii 坚持更久当且仅当 bix+(RL)ai>bjx+(RL)ajb_ix+(R-L)a_i>b_jx+(R-L)a_j

只考虑左侧建筑的话,从左到右考虑 xx ,相当于加入一条直线和求出当前最高的一条直线

考虑依次加入直线,并维护直线形成的上凸壳

注意到后加入的直线的 aia_i 更高,因此如果某一条直线被加入的时候比某一条已经加入的直线低,那么因为它的 aia_i 更高,所以它的 bib_i 更低,因此它以后不会超过之前的直线,可以直接删去

否则,因为它在当前是最高的,所以它替换掉的一定是上凸壳的一段前缀,维护一个栈,同时记录栈顶元素被下一个元素超过的时间即可

这样可以得到 O(n)O(n) 个区间,每个区间内的 xx 左侧存活到最后的元素是相同的

对右侧做相同的操作,可以得到 O(n)O(n) 个区间,每个区间内的 xx 两侧存活到最后的元素是相同的

注意到 bi+(RL)aixb_i+(R-L)*\frac{a_i}{x} 是一个单调递减的函数,右侧的则是一个单调递增函数,因此可以二分求出每个区间内部哪一部分左侧最后倒下,哪一部分右侧最后倒下

复杂度 O(nlogv)O(n\log v)

Code
include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
#define N 400500
int n,l,r,a[N],b[N],st[N],rb,fu[N],fu2[N],ct1,ct2,ct;
double ti[N],ti2[N],as[N];
double calc1(int x,int y){return 1.0*r*(a[y]-a[x])/(b[x]-b[y]);}
bool check(int x,int y,int z){return 1ll*(a[x]-a[y])*(b[z]-b[y])>=1ll*(a[y]-a[z])*(b[y]-b[x]);}
struct sth{double t;int x,y;friend bool operator <(sth a,sth b){return a.t<b.t;}}t[N*2];
double solve(int x,double t)
{
	if(a[x]<t)return b[x]+r-r*(t-a[x])/t;
	else return b[x]+r-r*(a[x]-t)/(r-t);
}
int main()
{
	scanf("%*d%d%d%d",&n,&l,&r);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),a[i]-=l;r-=l;
	for(int i=1;i<=n;i++)scanf("%d",&b[i]);
	int lb=1;
	double las=0;
	while(lb<=n||las<=r)
	{
		double st1=1e10,st2=lb>n?1e11:a[lb];
		if(rb>=2)st1=calc1(st[rb-1],st[rb]);
		if(st1>5e9&&st2>5e9){ti[++ct1]=r;break;}
		if(st1<st2)
		{
			ti[++ct1]=st1;rb--;fu[ct1]=st[rb];
			if(ti[ct1]>r)ti[ct1]=r;
		}
		else
		{
			if(!rb||st2*b[lb]+1.0*a[lb]*r>st2*b[st[rb]]+1.0*a[st[rb]]*r)
			{
				ti[++ct1]=st2;fu[ct1]=lb;
				while(rb&&b[st[rb]]<=b[lb])rb--;
				while(rb>=2&&check(lb,st[rb],st[rb-1]))rb--;
				st[++rb]=lb;
			}
			lb++;
		}
	}
	for(int i=1;i<=n;i++)a[i]=r-a[i];
	lb=n;rb=0;las=0;
	while(lb||las<=r)
	{
		double st1=1e10,st2=lb==0?1e11:a[lb];
		if(rb>=2)st1=calc1(st[rb-1],st[rb]);
		if(st1>5e9&&st2>5e9){ti2[++ct2]=r;break;}
		if(st1<st2)
		{
			ti2[++ct2]=st1;rb--;fu2[ct2]=st[rb];
			if(ti2[ct2]>r)ti2[ct2]=r;
		}
		else
		{
			if(!rb||st2*b[lb]+1.0*a[lb]*r>st2*b[st[rb]]+1.0*a[st[rb]]*r)
			{
				ti2[++ct2]=st2;fu2[ct2]=lb;
				while(rb&&b[st[rb]]<=b[lb])rb--;
				while(rb>=2&&check(lb,st[rb],st[rb-1]))rb--;
				st[++rb]=lb;
			}
			lb--;
		}
	}
	for(int i=1;i<=n;i++)a[i]=r-a[i];
	for(int i=1;i<ct1;i++)if(abs(ti[i]-ti[i+1])>1e-9)t[++ct]=(sth){ti[i],fu[i],1};
	for(int i=1;i<ct2;i++)if(abs(ti2[i]-ti2[i+1])>1e-9)t[++ct]=(sth){r-ti2[i+1],fu2[i],2};
	sort(t+1,t+ct+1);
	double ls=0;
	int v1=0,v2=0;
	t[++ct].t=r;
	for(int i=1;i<=ct;i++)
	{
		double l=ls,r=t[i].t;ls=t[i].t;
		if(v1==0)as[v2]+=r-l;
		else if(v2==0||v1==v2)as[v1]+=r-l;
		else
		{
			double l1=l,r1=r;
			for(int j=1;j<=75;j++)
			{
				double mid=(l+r)/2;
				if(solve(v1,mid)>=solve(v2,mid))l=mid;
				else r=mid;
			}
			as[v1]+=l-l1;as[v2]+=r1-r;
		}
		if(t[i].y==1)v1=t[i].x;else v2=t[i].x;
	}
	for(int i=1;i<=n;i++)printf("%.15lf\n",as[i]/r);
}
auoj481 荒野聚餐
Problem

有一个二分图,两侧各有 nn 个点,边有边权 wi,jw_{i,j}

多组询问,每次给一个 CC ,你可以花费 ss 的代价,使得所有边权降低 sC\frac sC

然后你需要给每个点分配一个权值 viv_i ,使得对于每条边 (i,j)(i,j) ,满足 vi+vjwi,jv_i+v_j\geq w_{i,j}

对于每个询问求出最小代价,保留一位小数

n500,q5000n\leq 500,q\leq 5000

1s,1024MB1s,1024MB

Sol

先不考虑 CC ,考虑写成线性规划,相当于

最小化 vi\sum v_i

满足 (i,j),vi+vjwi,j\forall(i,j),v_i+v_j\geq w_{i,j}

考虑对偶,相当于

最大化 si,j\sum s_{i,j}

满足 i,si,j1\forall i,s_{i,j}\leq 1

可以发现这相当于求二分图最大权匹配

考虑枚举最后的匹配中有多少条边,注意到边权减到负数一定不优,所以可以直接求有 ii 条边的最大权匹配

ii 条边的最大匹配权值为 viv_i ,那么答案为 minxCx+maxi(viix)\min_xCx+max_i(v_i-ix)

右边相当于一个下凸壳,直接枚举凸壳上的点即可

使用KM算法复杂度可以做到 O(n3+nq)O(n^3+nq)

Code

咕咕咕

auoj482 火星在住
Problem

给一棵 nn 个点的带边权树和 l,rl,r ,对于每一个 k[l,r]k\in[l,r] ,求出在树上选 kk 条边,任意两条边没有公共端点,边权和的最大值或输出无解

n2×105n\leq 2\times 10^5

2s,1024MB2s,1024MB

Sol

dpu,0/1,idp_{u,0/1,i} 表示 uu 的子树内, uu 不能被匹配/是否匹配均可时,匹配了 ii 条边的最大权值

如果没有根节点是否被选的限制可以将问题转换成费用流,因此 dpu,1,idp_{u,1,i} 是凸函数

凸函数卷积仍然是凸函数,所以 dpu,0,idp_{u,0,i} 也是凸函数

考虑给 uu 加入一个儿子 vv ,转移为

dpu,0=dpu,0dpv,1dp_{u,0}^{'}=dp_{u,0}*dp_{v,1}

dpu,1=max(dpu,1dpv,1,dpu,0dpv,0{0,wu,v})dp_{u,1}^{'}=max(dp_{u,1}*dp_{v,1},dp_{u,0}*dp_{v,0}*\{0,w_{u,v}\})

其中 * 表示凸函数卷积, {0,wu,v}\{0,w_{u,v}\} 表示一个只有两项,第0项为0,第1项为 wu,vw_{u,v} 的凸函数

注意到 dpu,0/1dp_{u,0/1} 的项数不超过子树大小,考虑树链剖分,对于一条重链,先分治合并算出每个点只考虑所有轻儿子的 dpdp ,然后在链上设 fu,v,0/1,0/1,if_{u,v,0/1,0/1,i} 表示 uuvv 的重链,uu 不能被匹配/是否匹配均可, vv 不能被匹配/是否匹配均可,匹配 ii 条边的最大权值,同样分治合并即可

复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define ll long long
#define N 200500
vector<ll> dp[N][2],fu[N*4][2][2],f1[N*4][2],s[N][2];
int n,l,r,a,b,c,sn[N],st[N],sz[N],vl[N],head[N],cnt,v1[N];
struct edge{int t,next,v;}ed[N*2];
void adde(int f,int t,int v){ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;ed[++cnt]=(edge){f,head[t],v};head[t]=cnt;}
vector<ll> merge(vector<ll> a,vector<ll> b)
{
	int s1=a.size(),s2=b.size(),l1=0,r1=0;
	vector<ll> as;
	if(!s1||!s2)return as;
	as.push_back(0);
	for(int i=1;i<s1+s2-1;i++)
	{
		if(l1+1==s1)r1++;
		else if(r1+1==s2)l1++;
		else if(a[l1+1]+b[r1]>a[l1]+b[r1+1])l1++;
		else r1++;
		as.push_back(a[l1]+b[r1]);
	}
	return as;
}
vector<ll> doit(vector<ll> a,int b)
{
	vector<ll> as;as.push_back(0);
	for(int i=0;i<a.size();i++)as.push_back(a[i]+b);
	return as;
}
vector<ll> getmx(vector<ll> a,vector<ll> b)
{
	int s1=a.size(),s2=b.size();
	vector<ll> as;
	for(int i=0;i<s1||i<s2;i++)
	{
		if(i>=s1)as.push_back(b[i]);
		else if(i>=s2)as.push_back(a[i]);
		else as.push_back(max(a[i],b[i]));
	}
	return as;
}
void solve1(int x,int l,int r)
{
	if(l==r){f1[x][0]=s[l][0];f1[x][1]=s[l][1];return;}
	int mid=(l+r)>>1;
	solve1(x<<1,l,mid);solve1(x<<1|1,mid+1,r);
	f1[x][0]=merge(f1[x<<1][0],f1[x<<1|1][0]);
	f1[x][1]=getmx(merge(f1[x<<1][0],f1[x<<1|1][1]),merge(f1[x<<1|1][0],f1[x<<1][1]));
}
void solve2(int x,int l,int r)
{
	for(int i=0;i<2;i++)for(int j=0;j<2;j++)fu[x][i][j].clear(),vector<ll>().swap(fu[x][i][j]);
	if(l==r){fu[x][0][0]=dp[st[l]][0];fu[x][1][1]=dp[st[l]][1];return;}
	int mid=(l+r)>>1;
	solve2(x<<1,l,mid);solve2(x<<1|1,mid+1,r);
	for(int i=0;i<2;i++)for(int j=0;j<2;j++)
	for(int k=0;k<2;k++)for(int l1=0;l1<2;l1++)
	{
		fu[x][i][l1]=getmx(fu[x][i][l1],merge(fu[x<<1][i][j],fu[x<<1|1][k][l1]));
		if(!j&&!k)
		{
			int nt1=i|(mid-l==0),nt4=l1|(r-mid==1);
			fu[x][nt1][nt4]=getmx(fu[x][nt1][nt4],getmx(merge(fu[x<<1][i][j],fu[x<<1|1][k][l1]),doit(merge(fu[x<<1][i][j],fu[x<<1|1][k][l1]),vl[mid])));
		}
	}
}
void dfs1(int u,int fa){sz[u]=1;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs1(ed[i].t,u),sz[u]+=sz[ed[i].t],sn[u]=sz[sn[u]]<sz[ed[i].t]?ed[i].t:sn[u];}
void dfs2(int u,int fa)
{
	if(sn[u])dfs2(sn[u],u);
	else {dp[u][0].push_back(0);return;}
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t==sn[u])v1[u]=ed[i].v;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&ed[i].t!=sn[u])dfs2(ed[i].t,u);
	int ct=0;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&ed[i].t!=sn[u])s[++ct][0]=getmx(dp[ed[i].t][0],dp[ed[i].t][1]),s[ct][1]=getmx(dp[ed[i].t][0],doit(dp[ed[i].t][0],ed[i].v));
	if(ct)solve1(1,1,ct),dp[u][0]=f1[1][0],dp[u][1]=f1[1][1];
	else dp[u][0].push_back(0);
	if(sn[fa]!=u)
	{
		int ct2=0,st1=u;
		while(st1)st[++ct2]=st1,vl[ct2]=v1[st1],st1=sn[st1];
		solve2(1,1,ct2);
		dp[u][0]=getmx(fu[1][0][0],fu[1][0][1]);
		dp[u][1]=getmx(fu[1][1][0],fu[1][1][1]);
	}
}
int main()
{
	scanf("%*d%d%d%d",&n,&l,&r);
	for(int i=1;i<n;i++)scanf("%d%d%d",&a,&b,&c),adde(a,b,c+1e9);
	dfs1(1,0);dfs2(1,0);
	vector<ll> as=getmx(dp[1][0],dp[1][1]);
	for(int i=l;i<=r;i++)
	if(i>=as.size())printf("- ");
	else printf("%lld ",as[i]-1000000000ll*i);
}
auoj818 锁
Problem

nn 个人,每个人有一个权值 viv_i ,有一个总权值 mm

你需要选择钥匙的种类数 kk ,给每个人这 kk 种钥匙中的任意多种(一种钥匙可以给多个人),使得对于任意的人的集合 SS ,如果 SS 中人的权值和大于等于 mm ,则对于每一种钥匙,他们至少有一个人有这种钥匙,否则,至少存在一种钥匙,他们中没有人有这种钥匙

求最小的 kk

n20n\leq 20

1s,512MB1s,512MB

Sol

对于一个集合 SS ,如果 SS 中人的权值和小于 mm ,那么存在一种钥匙 SS 中的人都没有

考虑所有满足 TT 中人的权值和小于 mm ,但再加入任意一个人权值和就大于等于 mm 的集合 TT ,显然每个集合 TT 至少要对应一把钥匙

如果两个集合 T1,T2T_1,T_2 对应的钥匙相同,则 T1T2T_1\cup T_2 中的所有人都没有这种钥匙,但根据上面的性质, T1T2T_1\cup T_2 中所有人的权值和大于等于 mm ,与条件矛盾

因此每个集合 TT 对应的钥匙不同

容易发现给每个集合 TT ,给集合 TT 外的人一种钥匙的方案是合法的,因此答案即为 TT 的数量

枚举所有集合即可,复杂度 O(n2n)O(n2^n) 或者 O(2n)O(2^n)

Code
#include<cstdio>
using namespace std;
#define N 22
int n,m,v[N],as;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<1<<n;i++)
	{
		long long su=0,mn=1e9;
		for(int j=1;j<=n;j++)if(i&(1<<j-1))su+=v[j];else mn=mn>v[j]?v[j]:mn;
		if(su<m&&su+mn>=m)as++;
	}
	printf("%d\n",as);
}
auoj819 bwt
Problem

给定字符串 SS ,定义 f(S)f(S)

SS 的所有循环位移串按照字典序排序, f(S)f(S) 的第 ii 位为排序后第 ii 大的串的最后一个字符

给定一个随机01串 ss ,多组询问,每次随机生成 a,b,c,da,b,c,d ,询问 f(sa,...,b),f(sc,...,d)f(s_{a,...,b}),f(s_{c,...,d}) 的字典序大小关系

n,q105n,q\leq 10^5

1s,512MB1s,512MB

Sol

期望只需要比较 f(sa,...,b),f(sc,...,d)f(s_{a,...,b}),f(s_{c,...,d}) 的前 (logn)(\log n) 位即可

对于两个随机后缀,期望只需要 O(logn)O(\log n) 位的比较就可以得到它们的大小关系

因此求 f(S)f(S) 的时候,除了最后 O(logn)O(\log n) 位作为开头的串外,剩下的串都可以直接比较后缀的大小

因为随机,比较两个循环位移串的大小也只需要 O(logn)O(\log n) ,可以先将后 O(logn)O(\log n) 个串排序

考虑如何找出所有循环位移串中最小的,第二小的,...

如果能将串分成两部分,对于每一部分支持询问第 kk 小串,那么使用类似归并的方式就可以每次找出需要的串

对于第一部分,后缀排序后按照每个后缀的排名建主席树,查区间第 kk 大即可

对于第二部分,直接排序即可

直接排序复杂度 O(nlog2nloglogn)O(n\log^2 n\log\log n) ,通过一些方式可以做到 O(nlog2n)O(n\log^2 n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 105000
#define M 2133333
int n,q,a,b,c,d,s,s1[N],tp[N],rk[N];
bool cmp(int a,int b){while(1){if(a>n)return 1;if(b>n)return 0;if(s1[a]<s1[b])return 1;if(s1[b]<s1[a])return 0;a++;b++;}}
struct pretree{
	int rt[N],ch[M][2],sz[M],ct;
	int build(int l,int r)
	{
		int st=++ct;
		if(l==r)return st;
		int mid=(l+r)>>1;
		ch[st][0]=build(l,mid);ch[st][1]=build(mid+1,r);
		return st;
	}
	void init(){rt[0]=build(0,n);}
	int modify(int x,int l,int r,int s)
	{
		int st=++ct;ch[st][0]=ch[x][0];ch[st][1]=ch[x][1];sz[st]=sz[x]+1;
		if(l==r)return st;
		int mid=(l+r)>>1;
		if(mid>=s)ch[st][0]=modify(ch[x][0],l,mid,s);
		else ch[st][1]=modify(ch[x][1],mid+1,r,s);
		return st;
	}
	void modify2(int x,int v){rt[x]=modify(rt[x-1],0,n,v);}
	int query(int x,int y,int l,int r,int k)
	{
		if(l==r)return l;
		int mid=(l+r)>>1;
		if(sz[ch[x][0]]-sz[ch[y][0]]>k)return query(ch[x][0],ch[y][0],l,mid,k);
		else return query(ch[x][1],ch[y][1],mid+1,r,k-sz[ch[x][0]]+sz[ch[y][0]]);
	}
	int getkth(int l,int r,int k){return tp[query(rt[r],rt[l-1],0,n,k-1)];}
}tr;
bool check(int l,int r,int x,int y){for(int i=1;i<=r-l+1;i++){if(s1[x]<s1[y])return 1;if(s1[y]<s1[x])return 0;x=x==r?l:x+1;y=y==r?l:y+1;}return 0;}
struct sth{
	int l,r,l1,s2[75],s3[75],ct,c1,c2,d1,d2;
	void doit(int l1,int r1)
	{
		if(l1==r1)return;
		int mid=(l1+r1)>>1;
		doit(l1,mid);doit(mid+1,r1);
		int l2=l1,r2=mid+1;
		for(int i=l1;i<=r1;i++)
		if(l2==mid+1)s3[i]=s2[r2++];
		else if(r2==r1+1)s3[i]=s2[l2++];
		else if(check(l,r,s2[l2],s2[r2]))s3[i]=s2[l2++];
		else s3[i]=s2[r2++];
		for(int i=l1;i<=r1;i++)s2[i]=s3[i];
	}
	void pre(int l2,int r2)
	{
		l=l2;r=r2;c1=c2=0;d1=d2=0;ct=0;
		l1=r-26;if(l1<l)l1=l;
		for(int i=l1;i<=r;i++)s2[++c1]=i;
		doit(1,c1);
		c2=l1-l;
	}
	int query()
	{
		if(ct==r-l+1)return -1;
		ct++;
		int v1,v2,s3;
		if(d1==c2)s3=s2[++d2];
		else if(d2==c1)s3=tr.getkth(l,l1-1,++d1);
		else
		{
			v1=s2[d2+1],v2=tr.getkth(l,l1-1,d1+1);
			if(check(l,r,v1,v2))d2++,s3=v1;
			else d1++,s3=v2;
		}
		return s3==l?s1[r]:s1[s3-1];
	}
}f1,f2;
int gen(){s=(s*100000005ll+20150609)%998244353;return s;}
int main()
{
	scanf("%d%d%d",&n,&q,&s);
	for(int i=1;i<=n;i++)s1[i]=gen()%2,tp[i]=i;
	sort(tp+1,tp+n+1,cmp);for(int i=1;i<=n;i++)rk[tp[i]]=i;
	tr.init();for(int i=1;i<=n;i++)tr.modify2(i,rk[i]);
	while(q--)
	{
		a=gen()%n+1;b=gen()%n+1;c=gen()%n+1;d=gen()%n+1;
		if(a>b)a^=b^=a^=b;if(c>d)c^=d^=c^=d;
		f1.pre(a,b);f2.pre(c,d);
		while(1)
		{
			int a1=f1.query(),a2=f2.query();
			if(a1==-1&&a2==-1){printf("0\n");break;}
			if(a1==-1){printf("-1\n");break;}
			if(a2==-1){printf("1\n");break;}
			if(a1<a2){printf("-1\n");break;}
			if(a1>a2){printf("1\n");break;}
		}
	}
}
auoj820 robot
Problem

给定 ll ,有一个长度为 ll 的指令,指令包含 LRUD ,机器人会循环执行指令,执行到对应指令时会向对应方向走1单位距离

给定 nn 个限制,第 ii 个限制为 (ti,xi,yi)(t_i,x_i,y_i) 表示在时刻 tit_i 机器人在 (xi,yi)(x_i,y_i)

求满足限制的指令数,模 109+710^9+7

n2×105,l2×106n\leq 2\times 10^5,l\leq 2\times 10^6

1s,512MB1s,512MB

Sol

将所有点旋转45度,变成斜向行走

可以发现,斜向行走相当于两维分别决定+1-1,因此可以将两维分开考虑

考虑一维的情况,限制形如 (ti,xi)(t_i,x_i) ,设执行了长度为 ll 的指令后机器人移动的有向距离为 ss ,时刻 i(il)i(i\leq l) 时机器人的位置是 viv_i ,那么需要满足

  1. v0=0,vl=sv_0=0,v_l=s
  2. vivi1{1,1}v_i-v_{i-1}\in\{-1,1\}
  3. tils+vtimodl=xi\lfloor\frac{t_i}l\rfloor*s+v_{t_i\bmod l}=x_i

考虑第一类和第三类限制中下标相邻的两个 xi=as+b,xj=cs+dx_i=as+b,x_j=cs+d ,可能有解当且仅当 (ca)s+(db)(c-a)s+(d-b)jij-i 奇偶性相同,且 (ca)s+(db)ji|(c-a)s+(d-b)|\leq j-i ,这一段的方案数为 Cji(ca)s+(db)+(ji)2C_{j-i}^{\frac{(c-a)s+(d-b)+(j-i)}2}

显然所有相邻段的限制满足了整体限制就满足了,考虑所有相邻段,可以得到最后 ss 可能的奇偶性以及可能的值域

定义一段的长度为 jij-i ,如果这一段的 ca0c-a\neq 0 ,那么值域不会超过 jij-i

因此,如果有 kk 段的 ca0c-a\neq 0 ,那么值域不会超过 nk\frac nk

显然 ca=0c-a=0 的段对于每一个 ss 的方案数是相同的,可以预处理

然后枚举值暴力算即可

复杂度 O(nlogn+l)O(n\log n+l)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 2005000
#define ll long long
#define mod 1000000007
int n,l,fr[N],ifr[N],st[N],as[N];
ll s[N][3],t[N][3];
bool cmp(int a,int b){return t[a][0]<t[b][0];}
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int C(int i,int j){if(i<j||j<0)return 0;return 1ll*fr[i]*ifr[j]%mod*ifr[i-j]%mod;}
int solve()
{
	ll lb=-l,rb=l,is0=1,is1=1,tp=1;
	t[n+1][0]=l;t[n+1][1]=-1;st[n+1]=n+1;
	for(int i=1;i<=n+1;i++)
	{
		ll v1=t[st[i]][0]-t[st[i-1]][0],v2=t[st[i-1]][1]-t[st[i]][1],v3=t[st[i]][2]-t[st[i-1]][2];
		if(v2&1)if((v1^v3)&1)is0=0;else is1=0;
		else if((v1^v3)&1)return 0;
		if(v2==0)
		{
			if((v1^v3)&1)return 0;
			tp=1ll*tp*C(v1,(v1+v3)/2)%mod;continue;
		}
		double l1=1.0*(-v3-v1)/v2,r1=1.0*(-v3+v1)/v2;
		if(l1>r1)swap(l1,r1);
		ll l2=l1,r2=r1;
		if(l2<l1)l2++;if(r2>r1)r2--;
		if(lb<l2)lb=l2;if(rb>r2)rb=r2;
	}
	if(lb>rb)return 0;
	int as1=0;
	if(is0)
	{
		int tp=lb%2?lb+1:lb,ct=(rb-tp+2)/2;
		for(int j=1;j<=ct;j++)as[j]=1;
		for(int i=1;i<=n+1;i++)
		{
			ll v1=t[st[i]][0]-t[st[i-1]][0],v2=t[st[i-1]][1]-t[st[i]][1],v3=t[st[i]][2]-t[st[i-1]][2];
			if(v2==0)continue;
			for(int j=1;j<=ct;j++)
			{
				int tp1=tp+(j-1)*2;
				as[j]=1ll*as[j]*C(v1,(v3+v1+v2*tp1)/2)%mod;
			}
		}
		for(int j=1;j<=ct;j++)as1=(as1+as[j])%mod;
	}
	if(is1)
	{
		int tp=lb%2==0?lb+1:lb,ct=(rb-tp+2)/2;
		for(int j=1;j<=ct;j++)as[j]=1;
		for(int i=1;i<=n+1;i++)
		{
			ll v1=t[st[i]][0]-t[st[i-1]][0],v2=t[st[i-1]][1]-t[st[i]][1],v3=t[st[i]][2]-t[st[i-1]][2];
			if(v2==0)continue;
			for(int j=1;j<=ct;j++)
			{
				int tp1=tp+(j-1)*2;
				as[j]=1ll*as[j]*C(v1,(v3+v1+v2*tp1)/2)%mod;
			}
		}
		for(int j=1;j<=ct;j++)as1=(as1+as[j])%mod;
	}
	return 1ll*as1*tp%mod;
}
int main()
{
	fr[0]=1;for(int i=1;i<=2e6;i++)fr[i]=1ll*fr[i-1]*i%mod;
	ifr[2000000]=pw(fr[2000000],mod-2);
	for(int i=1999999;i>=0;i--)ifr[i]=1ll*ifr[i+1]*(i+1)%mod;
	scanf("%d%d",&n,&l);
	for(int i=1;i<=n;i++)scanf("%lld%lld%lld",&s[i][0],&s[i][1],&s[i][2]),t[i][0]=s[i][0]%l,t[i][1]=s[i][0]/l,st[i]=i;
	sort(st+1,st+n+1,cmp);
	int as1=1;
	for(int i=1;i<=n;i++)t[i][2]=s[i][1]+s[i][2];
	as1=1ll*as1*solve()%mod;
	for(int i=1;i<=n;i++)t[i][2]=s[i][1]-s[i][2];
	as1=1ll*as1*solve()%mod;
	printf("%d\n",as1);
}
auoj821 白鱼赤乌
Problem

定义一个数字串是好的,当且仅当它每一位上的数字的和是10的倍数

定义一个数字串是优美的,当且仅当对于每一位,都存在一个包含这一位的子串,使得这个子串是好的

多组询问,每次给定 nn ,求长度不超过 nn 的数字串(可以以0开头)中优美的串的数量,对 109+710^9+7 取模

T2×104,n<231T\leq 2\times 10^4,n<2^{31}

1s,512MB1s,512MB

Sol

对于一个长度为 nn 的数字串,设 sis_i 表示 [1,i][1,i] 位置的数字和模10的余数

如果 si=sjs_i=s_j ,那么 [i+1,j][i+1,j] 就是好的

对于一个 ii ,如果 s0,...,i1s_{0,...,i-1}si,...,ns_{i,...,n} 中存在一对相同元素,那么这个位置一定被一个好的子串覆盖,否则一定不被一个好的子串覆盖

考虑容斥,钦定一些位置不合法,那么这些位置将 s0,...,ns_{0,...,n} 划分成若干段,任意两段中不能有相同元素

dpi,j,kdp_{i,j,k} 表示考虑了前 ii 位,已经被划分出的段内共有 jj 种元素,当前最后一个还没有被划分的段内有 kk 种元素的方案乘上容斥系数之和

初始 dp0,0,1=1dp_{0,0,1}=1 ,转移有如下情况:

  1. 不分段,加一种新的元素: dpi1,j,k(10jk)>dpi,j,k+1dp_{i-1,j,k}*(10-j-k)->dp_{i,j,k+1}
  2. 不分段,加一个在最后一段中已经存在的元素: dpi1,j,kk>dpi,j,k+1dp_{i-1,j,k}*k->dp_{i,j,k+1}
  3. 分段,再在新的一段加一个新的元素: dpi1,j,k(10jk)>dpi,j+k,1-dp_{i-1,j,k}*(10-j-k)->dp_{i,j+k,1}

转移可以写成矩阵乘法,加上统计答案用的位置,矩阵只有56*56

考虑分块预处理,设矩阵为 SS ,处理出 Sk,S28k,S216k,S224kS^k,S^{2^8k},S^{2^{16}k},S^{2^{24}k} ,然后一次询问相当于求一个 1×561\times 56 的矩阵乘上四个 56×5656\times 56 的矩阵,可以做到 O(5624)O(56^2*4)

可以发现按照某种顺序排序后矩阵中只存在从前往后的转移,所以单次询问复杂度 O(5622)O(56^2*2) ,复杂度 O(45622818+T456212)O(4*56^2*2^8*\frac 18+T*4*56^2*\frac 12)

Code
#include<cstdio>
using namespace std;
#define N 59
#define M 11
#define mod 1000000007
int T,n,id[M][M],f[N][N],ct,dp[N],dp2[N],s1[N];
struct mat{int s[N][N];mat(){for(int i=1;i<=ct;i++)for(int j=1;j<=ct;j++)s[i][j]=0;};}s,f1[256],f2[256],f3[256],f4[256];
mat operator *(mat a,mat b)
{
	mat c;
	for(int i=1;i<=ct;i++)
	for(int k=1;k<=ct;k++)
	if(a.s[i][k])
	for(int j=1;j<=ct;j++)
	c.s[i][j]=(c.s[i][j]+1ll*a.s[i][k]*b.s[k][j])%mod;
	return c;
}
void pre()
{
	for(int i=0;i<=10;i++)
	for(int j=1;j<=10;j++)
	if(i+j<=10)id[i][j]=++ct;
	for(int i=0;i<=10;i++)
	for(int j=1;j<=10;j++)
	if(id[i][j])
	{
		f[id[i][j]][id[i][j]]=j;
		if(id[i+j][1])f[id[i][j]][id[i+j][1]]=mod-(10-i-j);
		if(id[i][j+1])f[id[i][j]][id[i][j+1]]=10-i-j;
	}
	for(int i=1;i<=ct;i++)f[i][ct+1]=1;ct++;f[ct][ct]=1;
	for(int i=1;i<=ct;i++)
	for(int j=1;j<=ct;j++)s.s[i][j]=f[i][j];
	f1[2]=s*s;
	for(int i=1;i<=ct;i++)f1[0].s[i][i]=f2[0].s[i][i]=f3[0].s[i][i]=f4[0].s[i][i]=1;
	f1[1]=s;for(int i=2;i<256;i++)f1[i]=f1[i-1]*f1[1];
	f2[1]=f1[1]*f1[255];for(int i=2;i<256;i++)f2[i]=f2[i-1]*f2[1];
	f3[1]=f2[1]*f2[255];for(int i=2;i<256;i++)f3[i]=f3[i-1]*f3[1];
	f4[1]=f3[1]*f3[255];for(int i=2;i<256;i++)f4[i]=f4[i-1]*f4[1];
}
int query(int n)
{
	for(int i=1;i<ct;i++)dp[i]=f[1][i];dp[ct]=0;
	int v1=n&255;n>>=8;
	for(int i=1;i<=ct;i++)
	for(int j=1;j<=ct;j++)dp2[j]=(dp2[j]+1ll*dp[i]*f1[v1].s[i][j])%mod;
	for(int i=1;i<=ct;i++)dp[i]=dp2[i],dp2[i]=0;
	v1=n&255;n>>=8;
	for(int i=1;i<=ct;i++)
	for(int j=1;j<=ct;j++)dp2[j]=(dp2[j]+1ll*dp[i]*f2[v1].s[i][j])%mod;
	for(int i=1;i<=ct;i++)dp[i]=dp2[i],dp2[i]=0;
	v1=n&255;n>>=8;
	for(int i=1;i<=ct;i++)
	for(int j=1;j<=ct;j++)dp2[j]=(dp2[j]+1ll*dp[i]*f3[v1].s[i][j])%mod;
	for(int i=1;i<=ct;i++)dp[i]=dp2[i],dp2[i]=0;
	v1=n&255;n>>=8;
	for(int i=1;i<=ct;i++)
	for(int j=1;j<=ct;j++)dp2[j]=(dp2[j]+1ll*dp[i]*f4[v1].s[i][j])%mod;
	for(int i=1;i<=ct;i++)dp[i]=dp2[i],dp2[i]=0;
	return dp[ct];
}
int main()
{
	pre();
	scanf("%d",&T);while(T--)scanf("%d",&n),printf("%d\n",query(n));
}
auoj823 鲁鱼陶阴
Problem

给定一个小写字符串 ssopopqq 次询问,每次给定 l,rl,r

如果 op=1op=1 ,则需要求出将 sl,...,rs_{l,...,r} 中的字符全部替换成 Y 后, ss 中的本质不同子串数量

否则,需要求出将 sl,...,rs_{l,...,r} 中的字符全部替换成对应的大写字符后, ss 中的本质不同子串数量

询问之间独立

n,q2×105n,q\leq 2\times 10^5

1s,512MB1s,512MB

Sol

考虑将子串分成三类

  1. 同时包含大写和小写字符的串
  2. 只包含小写字符的串
  3. 只包含大写字符的串

对于第一类,显然任意位置不同的两个这样的串不可能相同,可以直接计算数量

对于第二类,相当于求出一个前缀和一个后缀的本质不同子串数量

考虑一个子串,设它第一次出现的结尾位置是 aa ,最后一次出现的开头位置是 bb ,那么如果询问 [l,r][l,r] 满足 la,rbl\leq a,r\geq b ,那么这个子串不会在这次询问中出现,否则会在询问中出现

考虑SAM的fail树/后缀树上的点,对于一个SAM中的点,可以发现它对应的所有子串的 aa 是相同的, bb 是连续的一段

ll 做扫描线,相当于区间加上一个一次函数,单点询问,也可以差分后变成区间加区间询问,复杂度 O(nlogn)O(n\log n)

考虑第三类, op=1op=1 时数量就是 rl+1r-l+1 ,否则数量是这个区间内的本质不同子串数量

考虑将询问按照 rr 排序,维护每个子串在 [1,r][1,r] 中最后一次出现的开头位置 ss ,区间本质不同子串数量相当于查 sls\geq l 的子串数量

考虑每个子串在 [1,r][1,r] 中最后一次出现的结尾位置 tt ,显然每个SAM上的点对应的所有串的 tt 是相同的

对于一个 rr ,以 rr 位置结尾的子串就是 s1,...,rs_{1,...,r} 对应的位置到根的一条链,因此 rr 增加相当于将一条链的 tt 改成 rr

对于一条 uu 到祖先 vv 的链,如果它们的 tt 相同,那么它们的 ss 一定构成一段连续的区间

注意到到根的一条链赋值可以看成LCT的access操作,在access的时候,可以找出所有需要改变 tt 且之前 tt 相同的段,由LCT的复杂度分析这样的段数量为 O(nlogn)O(n\log n) ,然后每一段在线段树上修改即可

复杂度 O(nlog2n)O(n\log^2 n)

Code
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
#define N 400500
#define ll long long
int n,q,a,b,ty,f1[N][2],s2[N],tp[N][3],s3[N];
ll as[N];
char st[N];
bool cmp(int a,int b){return f1[a][1]<f1[b][1];}
bool cmp2(int a,int b){return f1[a][0]>f1[b][0];}
bool cmp3(int a,int b){return tp[a][0]>tp[b][0];}
struct segt{
	struct node{int l,r,lz;ll su;}e[N*2];
	void build(int x,int l,int r){e[x].lz=e[x].su=0;e[x].l=l;e[x].r=r;if(l==r)return;int mid=(l+r)>>1;build(x<<1,l,mid);build(x<<1|1,mid+1,r);}
	void pushup(int x){e[x].su=e[x<<1].su+e[x<<1|1].su;}
	void doit(int x,int v){e[x].lz+=v;e[x].su+=1ll*v*(e[x].r-e[x].l+1);}
	void pushdown(int x){doit(x<<1,e[x].lz);doit(x<<1|1,e[x].lz);e[x].lz=0;}
	void modify(int x,int l,int r,int v){if(l>r)return;if(e[x].l==l&&e[x].r==r){doit(x,v);return;}pushdown(x);int mid=(e[x].l+e[x].r)>>1;if(mid>=r)modify(x<<1,l,r,v);else if(mid<l)modify(x<<1|1,l,r,v);else modify(x<<1,l,mid,v),modify(x<<1|1,mid+1,r,v);pushup(x);}
	ll query(int x,int l,int r){if(e[x].l==l&&e[x].r==r)return e[x].su;pushdown(x);int mid=(e[x].l+e[x].r)>>1;if(mid>=r)return query(x<<1,l,r);else if(mid<l)return query(x<<1|1,l,r);else return query(x<<1,l,mid)+query(x<<1|1,mid+1,r);}
}tr;
struct SAM{
	int ch[N][26],fail[N],len[N],in[N],f1[N],f2[N],las,ct,ls[N];
	void init(){for(int i=1;i<=n*2;i++)f1[i]=1e7,f2[i]=0;las=ct=1;}
	void ins(int s,int tp)
	{
		int st=++ct,s1=las;len[st]=len[s1]+1;las=ct;ls[tp]=st;
		while(!ch[s1][s])ch[s1][s]=st,s1=fail[s1];
		f1[st]=f2[st]=tp;
		if(!s1)fail[st]=1;
		else
		{
			int nt=ch[s1][s];
			if(len[nt]==len[s1]+1)fail[st]=nt;
			else
			{
				int cl=++ct;len[cl]=len[s1]+1;
				for(int i=0;i<26;i++)ch[cl][i]=ch[nt][i];
				fail[cl]=fail[nt];fail[nt]=fail[st]=cl;
				while(ch[s1][s]==nt)ch[s1][s]=cl,s1=fail[s1];
			}
		}
	}
	void pre()
	{
		queue<int> st;
		for(int i=1;i<=ct;i++)in[fail[i]]++;
		for(int i=1;i<=ct;i++)if(!in[i])st.push(i);
		while(!st.empty())
		{
			int s=st.front();st.pop();
			f1[fail[s]]=min(f1[fail[s]],f1[s]);
			f2[fail[s]]=max(f2[fail[s]],f2[s]);
			in[fail[s]]--;if(!in[fail[s]])st.push(fail[s]);
		}
	}
}s;
struct LCT{
	int ch[N][2],fa[N],v1[N],v2[N],mn[N],mx[N],fg[N];
	bool nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
	void pushup(int x){mn[x]=min(min(mn[ch[x][0]],mn[ch[x][1]]),v1[x]);mx[x]=max(max(mx[ch[x][0]],mx[ch[x][1]]),v2[x]);}
	void rotate(int x){int f=fa[x],g=fa[f],tp=ch[f][1]==x;if(nroot(f))ch[g][ch[g][1]==f]=x;fa[x]=g;fa[ch[x][!tp]]=f;ch[f][tp]=ch[x][!tp];ch[x][!tp]=f;fa[f]=x;pushup(f);pushup(x);}
	void splay(int x){while(nroot(x)){int f=fa[x],g=fa[f];if(nroot(f))rotate((ch[g][1]==f)^(ch[f][1]==x)?x:f);rotate(x);}}
	int doit(int x){while(ch[x][0])x=ch[x][0];return x;}
	void access(int x,int f)
	{
		int tp=0;
		while(x)
		{
			splay(x);
			int st=doit(x),t1=ch[x][1];splay(st);
			int las=fg[st];fg[st]=0;
			splay(x);ch[x][1]=0;pushup(x);
			if(las)
			tr.modify(1,las-mx[x]+1,las-mn[x]+1,-1);
			if(t1){int st2=doit(t1);splay(st2);fg[st2]=las;}
			ch[x][1]=tp;tp=x;pushup(x);x=fa[x];
		}
		int st=doit(tp);splay(st);fg[st]=f;
		tr.modify(1,1,f,1);splay(tp);
	}
	void init()
	{
		int ct=s.ct;
		for(int i=1;i<=ct;i++)v1[i]=mn[i]=s.len[s.fail[i]]+1,v2[i]=mx[i]=s.len[i],fa[i]=s.fail[i];
		mx[0]=0;mn[0]=1e9;
	}
}lct;
int main()
{
	scanf("%d%d%s%d",&n,&ty,st+1,&q);
	s.init();for(int i=1;i<=n;i++)s.ins(st[i]-'a',i);
	tr.build(1,1,n);lct.init();
	for(int i=1;i<=q;i++)scanf("%d%d",&f1[i][0],&f1[i][1]),s2[i]=i;
	sort(s2+1,s2+q+1,cmp);
	int lb=1;
	for(int i=1;i<=n;i++)
	{
		lct.access(s.ls[i],i);
		while(lb<=q&&f1[s2[lb]][1]==i)as[s2[lb]]+=tr.query(1,f1[s2[lb]][0],n),lb++;
	}
	if(ty==1)for(int i=1;i<=q;i++)as[i]=f1[i][1]-f1[i][0]+1;
	for(int i=1;i<=q;i++)as[i]+=1ll*(f1[i][0]-1)*(n-f1[i][0]+1)+1ll*(f1[i][1]-f1[i][0]+1)*(n-f1[i][1]);
	s.pre();tr.build(1,1,n);
	ll su=0;
	for(int i=2;i<=s.ct;i++)s3[i]=i,tp[i][0]=s.f1[i],tp[i][1]=s.f2[i]-s.len[i]+1,tp[i][2]=s.f2[i]-s.len[s.fail[i]],su+=s.len[i]-s.len[s.fail[i]];
	sort(s3+2,s3+s.ct+1,cmp3);
	sort(s2+1,s2+q+1,cmp2);
	int l1=1,r1=2;
	for(int i=n;i>=1;i--)
	{
		while(r1<=s.ct&&tp[s3[r1]][0]==i)tr.modify(1,tp[s3[r1]][1],tp[s3[r1]][2],1),r1++;
		while(l1<=q&&f1[s2[l1]][0]==i)as[s2[l1]]+=su-tr.query(1,1,f1[s2[l1]][1]),l1++;
	}
	for(int i=1;i<=q;i++)printf("%lld\n",as[i]);
}
auoj825 ⽪卡丘
Problem

给一棵初始只有根节点的树,每一个时刻,如果一个点的儿子数不足2个,这个点上会长出若干个叶子直到儿子数为2

有两种操作

  1. 将时间向后 tt 个时刻
  2. 给一个 LR 组成的序列 ss ,从根开始按照这个序列可以走到一个点(保证这个点存在),删除这个点以及这个点的子树,这个操作不需要时间

求出每一次操作后树的点数,模 998244353998244353

多组数据

q,t,s106\sum q,\sum t,\sum |s|\leq 10^6

1s,512MB1s,512MB

Sol

对于每个点 ii,设 fif_i 表示 ii 子树内节点数, gig_i 表示 ii 子树内下一时刻增加的叶子数

每经过一个时刻,fi=fi+gi,gi=2gif_i^{'}=f_i+g_i,g_i^{'}=2g_i

考虑所有2操作序列形成的trie,只考虑trie上节点的 f,gf,g

对于1操作直接增加时间,更新根的答案即可

对于2操作,考虑更新这条链上的所有点,对于每个点记录这个点上次被更新的时刻 tit_i ,这个点上次被删除的时刻

首先从上往下考虑每个点,记录这个点到根的路径上上次删除操作的时刻 ss ,如果 s<tis<t_i ,那么这个点从上次更新后没有任何操作影响它,因此直接用增加的时刻数量更新 f,gf,g 即可

如果 s>tis>t_i ,说明与这个点有关的上次操作是删除它的一个祖先,记录删除的时刻和深度,可以得到新的 f,gf,g

然后考虑删除一个点的子树对 f,gf,g 的影响,显然只会影响这个点到根的路径

先对于链上的点,每个点减去它需要被修改的儿子的贡献,然后将删除的点的 f=0,g=1f=0,g=1 ,然后向上更新

复杂度 O(q+t+s)O(\sum q+\sum t+\sum |s|)

Code
#include<cstdio>
using namespace std;
#define N 1006000
#define mod 998244353
int T,n,s1[N][2],ct,ch[N][2],fa[N],las[N],dp[N],su[N],ti[N],dep[N],nt,st[N];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
char s[N],t[N];
int ins()
{
	int st=1;
	for(int i=1;t[i];i++)
	{
		int s1=t[i]=='R';
		if(!ch[st][s1])ch[st][s1]=++ct,fa[ct]=st,dep[ct]=dep[st]+1;
		st=ch[st][s1];
	}
	return st;
}
void modify(int x)
{
	int ct=0,s2=x;
	while(s2)st[++ct]=s2,s2=fa[s2];
	int ls=-1e9,de=0;
	for(int i=ct;i>0;i--)
	{
		if(las[st[i]]>ls)ls=las[st[i]],de=dep[st[i]];
		if(ls>ti[st[i]])
		{
			int fu=nt-ls-dep[st[i]]+de;
			dp[st[i]]=pw(2,fu),su[st[i]]=dp[st[i]]-1;ti[st[i]]=nt;
		}
		else
		{
			int fu=nt-ti[st[i]];
			su[st[i]]=(su[st[i]]+1ll*dp[st[i]]*(pw(2,fu)-1))%mod;
			dp[st[i]]=1ll*dp[st[i]]*pw(2,fu)%mod;
			ti[st[i]]=nt;
		}
	}
	for(int i=ct;i>1;i--)su[st[i]]=(su[st[i]]-su[st[i-1]]+mod)%mod,dp[st[i]]=(dp[st[i]]-dp[st[i-1]]+mod)%mod;
	las[x]=nt;dp[x]=1;su[x]=0;
	for(int i=2;i<=ct;i++)su[st[i]]=(su[st[i]]+su[st[i-1]])%mod,dp[st[i]]=(dp[st[i]]+dp[st[i-1]])%mod;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		for(int i=1;i<=ct;i++)las[i]=dp[i]=su[i]=dep[i]=ch[i][0]=ch[i][1]=fa[i]=0;
		ct=1;las[1]=-1;nt=0;ti[1]=0;su[1]=1;dp[1]=2;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%s",s+1);
			if(s[1]=='G')s1[i][0]=0,scanf("%d",&s1[i][1]);
			else s1[i][0]=1,scanf("%s",t+1),s1[i][1]=ins();
		}
		for(int i=2;i<=ct;i++)ti[i]=-2333,las[i]=-1e7;
		for(int i=1;i<=n;i++)
		if(s1[i][0]==0)
		{
			nt+=s1[i][1];
			su[1]=(su[1]+1ll*dp[1]*(pw(2,s1[i][1])-1)%mod+mod)%mod;ti[1]=nt;
			dp[1]=1ll*dp[1]*pw(2,s1[i][1])%mod;
			printf("%d\n",su[1]);
		}
		else modify(s1[i][1]),printf("%d\n",su[1]);
	}
}
auoj826 我永远喜欢
Problem

nn 种数,第 ii 种有 aia_i

你需要以任意顺序将它们合成一个序列,定义一个序列的权值为

初始权值为1,将序列首尾相接形成一个环,对于环上每一个极长连续相同段,若它的长度为 ll ,则权值乘上 1l!\frac 1{l!}

求出所有不同的序列的权值和,模 998244353998244353

ai>0,ai2×105a_i>0,\sum a_i\leq 2\times 10^5

3s,512MB3s,512MB

Sol

考虑计算开头与结尾不同的序列,对于每一个这样的序列,可以将开头第一段中若干个数放到结尾,因此需要额外乘上第一段长度的权值

对于这样的序列,计算权值的时候只需要考虑链上的情况即可

首先考虑不在开头的颜色,相当于将 aia_i 个数分成若干段,每一段的贡献为 1l!\frac 1{l!}

假设分成了 kk 段,长度分别为 l1,...,lkl_1,...,l_k ,权值为 1li!\frac 1{\prod l_i!}

考虑乘上 ai!a_i! ,那么权值变成一个多重组合,即序列中放 l1l_1 个1,l2l_2 个2,...,lkl_kkk 的方案数

那么对于一个 kk ,所有方案的权值和乘上 ai!a_i! 相当于求有多少个长度为 aia_i 的序列,满足每个元素都在 [1,k][1,k] 之间,且每个元素都出现过

这可以容斥解决,容斥可以用FFT优化

对于在开头的颜色,相当于需要在第一段中再选一个数出来,考虑上一步的序列,可以看成把一个1变成0,这时有两种情况

  1. 还有1出现,相当于长度为 ai1a_i-1 的序列,满足每个元素都在 [1,k][1,k] 之间,且每个元素都出现过,然后在任意位置插入0都可以得到方案,因此再乘上 aia_i 即可
  2. 没有1出现,相当于长度为 ai1a_i-1 的序列,满足每个元素都在 [2,k][2,k] 之间,且每个元素都出现过,也需要乘上 aia_i

这些都可以用容斥求出来,同时为了保证开头结尾不同,需要在结尾再加一个长度为0的段,同时需要保证这种数分出来的段中第一段在最开头,最后一段在结尾

如果确定了哪种颜色作为开头,并且确定了每种颜色的段数,接下来还需要算将这些段排列,使得相邻两段不相邻的方案数

考虑容斥,对于一种颜色,假设分成了 kk 段,枚举有 aa 个段相邻,方案数乘上容斥系数为 (1)aCk1a(-1)^aC_{k-1}^a ,之后还剩下 kak-a 段可以任意放

可以使用FFT求出对于每种颜色作为开头/不作为开头时,分成若干段,再容斥变成 ii 段任意放,所有方案第一步的权值和乘上第二步容斥系数的值的和

考虑将所有颜色的段放在一起,如果第 ii 种颜色有 xix_i 段,第一种颜色是开头的颜色,那么方案数为 (xi2)!(x12)!i>1xi!\frac{(\sum x_i-2)!}{(x_1-2)!\prod{i>1}x_i!}

对于一种颜色 cc,设 fc,if_{c,i} 表示它不在开头时,两步之后分成 ii 段的权值和乘上 1i!\frac 1{i!}gc,ig_{c,i} 表示它在开头时,两步之后分成 ii 段的权值和乘上 1(i2)!\frac 1{(i-2)!} ,Fc(x)=fc,ixi,Gc(x)=gc,ixiF_c(x)=\sum f_{c,i}x^i,G_c(x)=\sum g_{c,i}x^i

那么只需要求出 i=1nGi(x)jiFj(x)\sum_{i=1}^nG_i(x)\prod_{j\neq i}F_j(x) ,然后每一项乘上一个系数即可

考虑分治,对于每一个区间 [l,r][l,r] ,求出 Gi(x)jiFj(x),Fj(x)\sum G_i(x)\prod_{j\neq i}F_j(x),\prod F_j(x) ,然后直接合并即可

复杂度 O(ailognlogai)O(\sum a_i\log n\log \sum a_i)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define N 530001
#define mod 998244353
int n,s[N],g[2][N*2],rev[N*2],a[N],b[N],c[N],d[N],e[N],f[N],fr[N],ifr[N],ntt[N];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
void pre()
{
	for(int d=0;d<2;d++)
	for(int i=2;i<=1<<19;i<<=1)
	for(int j=0;j<(i>>1);j++)
	g[d][i+j]=pw(3,mod-1+(d*2-1)*(mod-1)/i*j);
	for(int i=2;i<=1<<19;i<<=1)
	for(int j=0;j<i;j++)rev[i+j]=(rev[i+(j>>1)]>>1)|((j&1)?(i>>1):0);
	fr[0]=ifr[0]=1;for(int i=1;i<=2e5;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
}
void dft(int s,int *a,int t)
{
	for(int i=0;i<s;i++)ntt[rev[s+i]]=a[i];
	for(int i=2;i<=s;i<<=1)
	for(int j=0;j<s;j+=i)
	for(int k=j,st=i;k<j+(i>>1);k++,st++)
	{
		int v1=ntt[k],v2=1ll*ntt[k+(i>>1)]*g[t][st]%mod;
		ntt[k]=(v1+v2)-(v1+v2>=mod?mod:0);ntt[k+(i>>1)]=v1-v2+(v1<v2?mod:0);
	}
	int inv=t?1:pw(s,mod-2);
	for(int i=0;i<s;i++)a[i]=1ll*ntt[i]*inv%mod;
}
vector<int> polyadd(vector<int> a,vector<int> b)
{
	vector<int> c;
	int s1=a.size(),s2=b.size();
	for(int i=0;i<s1||i<s2;i++)c.push_back(((i<s1?a[i]:0)+(i<s2?b[i]:0))%mod);
	return c;
}
vector<int> polymul(vector<int> a,vector<int> b)
{
	int s1=a.size(),s2=b.size();
	if(s1+s2<=200)
	{
		for(int i=0;i<s1+s2;i++)c[i]=0;
		for(int i=0;i<s1;i++)
		for(int j=0;j<s2;j++)
		c[i+j]=(c[i+j]+1ll*a[i]*b[j])%mod;
		vector<int> as;
		for(int i=0;i<s1+s2-1;i++)as.push_back(c[i]);
		return as;
	}
	int l=1;while(l<=s1+s2)l<<=1;
	for(int i=0;i<l;i++)c[i]=d[i]=0;
	for(int i=0;i<s1;i++)c[i]=a[i];
	for(int i=0;i<s2;i++)d[i]=b[i];
	dft(l,c,1);dft(l,d,1);for(int i=0;i<l;i++)c[i]=1ll*c[i]*d[i]%mod;dft(l,c,0);
	vector<int> as;
	for(int i=0;i<s1+s2-1;i++)as.push_back(c[i]);
	return as;
}
vector<int> doit0(int s)
{
	int l=1;while(l<=s*2+5)l<<=1;
	for(int i=0;i<l;i++)a[i]=b[i]=0;
	for(int i=0;i<=s;i++)a[i]=1ll*ifr[i]*pw(i,s)%mod;
	for(int i=0;i<=s;i++)b[i]=1ll*(i&1?mod-1:1)*ifr[i]%mod;
	dft(l,a,1);dft(l,b,1);
	for(int i=0;i<l;i++)a[i]=1ll*a[i]*b[i]%mod;
	dft(l,a,0);
	for(int i=s+1;i<l;i++)a[i]=0;
	for(int i=0;i<l;i++)b[i]=0;
	for(int i=0;i<=s;i++)b[s-i]=1ll*(i&1?mod-1:1)*ifr[i]%mod;
	for(int i=1;i<=s;i++)a[i]=1ll*a[i]*fr[i]%mod*fr[i-1]%mod;
	dft(l,a,1);dft(l,b,1);for(int i=0;i<l;i++)a[i]=1ll*a[i]*b[i]%mod;dft(l,a,0);
	vector<int> as;as.push_back(0);
	for(int i=1;i<=s;i++)as.push_back(1ll*a[i+s]*ifr[i-1]%mod*ifr[i]%mod*ifr[s]%mod);
	return as;
}
vector<int> doit1(int s)
{
	int l=1;while(l<=s*2+5)l<<=1;
	for(int i=0;i<l;i++)a[i]=b[i]=0;
	for(int i=0;i<=s;i++)a[i]=1ll*ifr[i]*pw(i,s-1)%mod;
	for(int i=0;i<=s;i++)b[i]=1ll*(i&1?mod-1:1)*ifr[i]%mod;
	dft(l,a,1);dft(l,b,1);for(int i=0;i<l;i++)a[i]=1ll*a[i]*b[i]%mod;dft(l,a,0);
	for(int i=s+1;i<l;i++)a[i]=0;
	for(int i=s;i>=1;i--)a[i]=1ll*a[i]*fr[i]%mod;
	for(int i=0;i<l;i++)b[i]=0;
	for(int i=s;i>=1;i--)a[i]=(a[i]+a[i-1])%mod;
	for(int i=s+1;i>=1;i--)a[i]=a[i-1];
	a[1]=0;
	for(int i=0;i<=s;i++)b[s-i]=1ll*(i&1?mod-1:1)*ifr[i]%mod;
	for(int i=1;i<=s+1;i++)a[i]=1ll*a[i]*fr[i-1]%mod;
	dft(l,a,1);dft(l,b,1);for(int i=0;i<l;i++)a[i]=1ll*a[i]*b[i]%mod;dft(l,a,0);
	vector<int> as;as.push_back(0);
	for(int i=1;i<=s;i++)as.push_back(1ll*a[i+s+1]*ifr[i-1]%mod*ifr[i]%mod*ifr[s-1]%mod);
	return as;
}
struct sth{vector<int> a,b;};
sth cdq(int l,int r)
{
	if(l==r){return (sth){doit0(s[l]),doit1(s[l])};}
	int mid=(l+r)>>1;
	sth a=cdq(l,mid),b=cdq(mid+1,r);
	return (sth){polymul(a.a,b.a),polyadd(polymul(a.b,b.a),polymul(a.a,b.b))};
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&s[i]);
	pre();vector<int> st=cdq(1,n).b;
	int as=0;for(int i=1;i<st.size();i++)as=(as+1ll*st[i]*fr[i-1])%mod;
	printf("%d\n",as);
}
auoj827 有向无环图
Problem

给一个DAG,初始每个点的权值都是0,支持三种操作:

  1. 给定 u,xu,x ,将DAG上所有 uu 可以到达的点的权值改为 xx
  2. 给定 u,xu,x ,将DAG上所有 uu 可以到达的点的权值与 xx 取min
  3. 询问一个点的权值

n,m,q105n,m,q\leq 10^5

3s,512MB3s,512MB

Sol

考虑按询问分块,考虑bitset求出两点间是否可达,如果能求出每 kBkB 个时刻时所有点的点权,就能 O(n)O(\sqrt n) 求出每一个询问的答案

考虑如何从 kBkB 时刻到 (k+1)B(k+1)B 时刻

如果只有1操作,可以dfs一次求出每个点最后一次被覆盖的时间,这个时间对应的时刻就是答案

对于一个时刻 tt 在点 xx 的2操作,它能影响到点 uu 当且仅当 xx 能到达 uuuu 最后一次被覆盖的时间小于 tt

将所有点按照覆盖时间排序,从后往前考虑每个修改,可以得到每个点影响的点的集合

注意到如果按照 xx 从小到大考虑每一个2操作,那么一个点只会被修改一次,依次考虑每一个2操作,记录当前还有哪些点没有被修改,然后可以求出这次操作可能修改的点的集合,这些都可以bitset优化

这时每个点只会被改一次,暴力改即可

直接开bitset维护两个点是否可达开不下,可以将所有点分成三部分,一次做一部分

复杂度 O(nnlogn+n(m+q)ω)O(n\sqrt {n\log n}+\frac {n(m+q)}{\omega}) ,可以预处理去掉log

Code
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
#define N 101500
int as1[256],ct2;
int que(unsigned int fu)
{
	int as=0;
	while(fu>=256)fu>>=8,as+=8;
	return as+as1[fu];
}
struct sth{
	unsigned int st[N][1051];
	void reset(int x){for(int i=1;i<=1050;i++)st[x][i]=0;}
	void modify(int x,int y){int tp=(y>>5)+1,tp2=y-((y>>5)<<5);st[x][tp]|=1u<<tp2;}
	void doit1(int x,int y){for(int i=1;i<=1050;i++)st[x][i]|=st[y][i];}
	void doit2(int x,int y){for(int i=1;i<=1050;i++)st[x][i]&=st[y][i];}
	void doit3(int x,int y){for(int i=1;i<=1050;i++)st[x][i]^=st[y][i];}
	int query(int x,int y){int tp=(y>>5)+1,tp2=y-((y>>5)<<5);return (st[x][tp]>>tp2)&1;}
	void modify2(int x,int y){int tp=(y>>5)+1,tp2=y-((y>>5)<<5);if((st[x][tp]>>tp2)&1)st[x][tp]^=1u<<tp2;}
	void copy(int x,int y){for(int i=1;i<=1050;i++)st[x][i]=st[y][i];}
	vector<int> doit(int x)
	{
		vector<int> as;
		for(int i=1;i<=1050;i++)
		if(st[x][i])
		{
			unsigned int t1=st[x][i];
			int t=(i-1)*32;
			while(t1)
			{
				unsigned int t2=t1&(-t1);
				t1-=t2;as.push_back(t+que(t2));
			}
		}
		return as;
	}
}fu;
int n,m,q,a,b,s[N][3],vl[N],ti[N],v1[N],head[N],cnt,s1[N],L,R,vis[N],as[N],in[N],in2[N],s2[N],ct,f2[N];
bool cmp1(int a,int b){return ti[a]>ti[b];}
bool cmp3(int a,int b){return s[a][2]<s[b][2];}
struct edge{int t,next;}ed[N];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;}
void dfs(int u)
{
	if(vis[u])return;
	if(u>=L&&u<=R)fu.modify(u,u-L);
	for(int i=head[u];i;i=ed[i].next)dfs(ed[i].t),fu.doit1(u,ed[i].t);
	vis[u]=1;
}
void solve(int l,int r)
{
	for(int i=1;i<=n;i++)ti[i]=v1[i]=s1[i]=vis[i]=0;
	for(int i=n+1;i<=n+r-l+3;i++)fu.reset(i);
	for(int i=l;i<=r;i++)
	if(s[i][0]==3)
	{
		if(s[i][1]<L||s[i][1]>R)continue;
		int tp=s[i][1],v1=vl[tp];
		for(int j=l;j<i;j++)
		if(fu.query(s[j][1],s[i][1]-L))
		if(s[j][0]==1)v1=s[j][2];
		else if(s[j][0]==2)v1=min(v1,s[j][2]);
		as[i]=v1;
	}
	else if(s[i][0]==1)ti[s[i][1]]=i,v1[s[i][1]]=s[i][2];
	for(int j=1;j<=n;j++)
	{
		int x=f2[j];
		for(int i=head[x];i;i=ed[i].next)
		if(ti[ed[i].t]<ti[x])ti[ed[i].t]=ti[x],v1[ed[i].t]=v1[x];
	}
	for(int i=L;i<=R;i++)if(v1[i])vl[i]=v1[i];
	for(int i=L;i<=R;i++)s1[i]=i;
	sort(s1+L,s1+R+1,cmp1);
	int lb=L,tp2=n+r-l+2;
	for(int i=l;i<=r;i++)if(s[i][0]==2)fu.copy(n+i-l+1,s[i][1]);
	for(int i=L;i<=R;i++)fu.modify(tp2,i-L);
	for(int i=r;i>=l;i--)
	{
		while(lb<=R&&ti[s1[lb]]>=i)fu.modify2(tp2,s1[lb]-L),lb++;
		if(s[i][0]==2)fu.doit2(n+i-l+1,tp2);
	}
	ct=0;for(int i=l;i<=r;i++)if(s[i][0]==2)s2[++ct]=i;
	sort(s2+1,s2+ct+1,cmp3);
	for(int i=L;i<=R;i++)fu.modify(tp2,i-L);
	for(int i=1;i<=ct;i++)
	{
		fu.doit2(n+s2[i]-l+1,tp2);
		fu.doit3(tp2,n+s2[i]-l+1);
		vector<int> t1=fu.doit(n+s2[i]-l+1);
		for(int j=0;j<t1.size();j++)
		vl[t1[j]+L]=min(vl[t1[j]+L],s[s2[i]][2]);
	}
}
int main()
{
	for(int i=1,as3=0;i<256;i<<=1,as3++)as1[i]=as3;
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=m;i++)scanf("%d%d",&a,&b),adde(a,b),in[b]++;
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&s[i][0],&s[i][1]);
		if(s[i][0]<3)scanf("%d",&s[i][2]);
	}
	int ct4=0;
	queue<int> st;
	for(int i=1;i<=n;i++)if(!in[i])st.push(i);
	while(!st.empty())
	{
		int x=st.front();st.pop();f2[++ct4]=x;
		for(int i=head[x];i;i=ed[i].next)
		{
			in[ed[i].t]--;if(!in[ed[i].t])st.push(ed[i].t);
		}
	}
	for(int i=1;i<=n;i+=33600)
	{
		L=i,R=i+33600-1;
		if(R>n)R=n;
		for(int j=1;j<=n;j++)fu.reset(j),vis[j]=0;
		for(int j=1;j<=n;j++)
		dfs(j);
		for(int j=1;j<=q;j+=1200)solve(j,min(j+1200-1,q));
	}
	for(int i=1;i<=q;i++)if(s[i][0]==3)printf("%d\n",as[i]);
}
auoj828 序列
Problem

给一个长度为 nn 的序列 aamm ,满足 ai[0,2m)a_i\in[0,2^m) ,求有多少个长度为 nn 的序列满足

  1. bi[0,2m)b_i\in [0,2^m)

  2. aia_i and biai+1b_i\leq a_{i+1} and bi+1b_{i+1}

  3. aia_i or biai+jb_i\geq a_{i+j} or bi+1b_{i+1}

答案模 109+710^9+7

n100,q30n\leq 100,q\leq 30

2s,1024MB2s,1024MB

Sol

aia_i and bi=cib_i=c_i ,aia_i or bi=dib_i=d_i

对于 aia_i 中为1的一位, bib_i 这一位的值只会影响 cic_i 的值,对于 aia_i 中为0的一位, bib_i 这一位的值只会影响 did_i 的值

因此 ci,dic_i,d_i 的取值是独立的,可以分开dp

对于 cic_idpdp ,考虑 cic_i 的最高位,显然最高位满足一段前缀是0剩下是1,可以枚举分界点需要分界点右侧的数在这一位上都是1

然后两侧区间互不影响,可以看成子问题,设 dpl,r,kdp_{l,r,k} 表示区间 [l,r][l,r] ,只考虑后 kk 位时满足 and 的条件的方案数,转移枚举分界点即可

or 的转移类似

复杂度 O(n3m)O(n^3m)

Code
#include<cstdio>
using namespace std;
#define N 105
#define mod 1000000007
int n,m,f[N][N],g[N][N],v[N];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=0;i<=n;i++)for(int j=0;j<=n;j++)f[i][j]=g[i][j]=1;
	for(int i=0;i<m;i++)
	for(int le=n-1;le>=0;le--)
	for(int l=1;l+le<=n;l++)
	{
		int r=l+le;
		int v1=f[l][r],v2=g[l][r];
		for(int j=r;j>=l&&((v[j]>>i)&1);j--)v1=(v1+1ll*f[l][j-1]*f[j][r])%mod;
		for(int j=r;j>=l&&!((v[j]>>i)&1);j--)v2=(v2+1ll*g[l][j-1]*g[j][r])%mod;
		f[l][r]=v1;g[l][r]=v2;
	}
	printf("%d\n",1ll*f[1][n]*g[1][n]%mod);
}
auoj829 回文串
Problem

定义一个串是好的,当且仅当它能被划分成两个非空回文串

给定 nn 和字符集大小 cc ,求长度不超过 nn 的好的串的数量,模 109+710^9+7

n,c109n,c\leq 10^9

2s,512MB2s,512MB

Sol

首先考虑去掉非空条件的情况

将字符串首尾相接看成一个环,它能被划分成两个回文串当且仅当环上存在至少一条对称轴

考虑算出总的对称轴数量,再减去多算的数量

考虑总的对称轴数量,枚举长度 nn ,如果 nn 为奇数则为 ncn+12n*c^{\frac{n+1}2} ,否则为 n2(cn2+Cn2+1)\frac n2*(c^{\frac n2}+C^{\frac n2+1}) ,可以用数学方法做到 O(1)O(1)

如果一个环存在两条对称轴 x=a,x=bx=a,x=b ,那么位置 xx 与位置 x+2abx+2|a-b| 一定相同,因此这样的串一定是循环串

设循环节是 ss ,考虑计算长度为 ss 的合法循环节数量

如果 ss 为奇数,如果环上没有一条对称轴穿过一个字符,那么这样环一定可以看成 TTTT...TT^{'}TT^{'}... 的形式,它存在一个 TTTT^{'} 的循环节

TT=k|TT^{'}|=k ,因为 2k2|k ,所以 ksk\not| s ,因此 TTTT^{'} 是循环串,因此 TTTT^{'} 一定由若干个相同的回文串拼接而成,但这样一定由穿过字符的回文串,矛盾

因此有一条对称轴穿过字符,从这个字符开始向左右各扩展 s12\frac{s-1}2 个字符,得到的串是一个回文串且是一个循环节

因为一个不循环的奇回文串循环位移后不可能是新的回文串,因此只需要计数不循环的奇回文串数量即可

没有不循环条件的话显然是 cs+12c^{\frac{s+1}2} ,容斥即可得到不循环的答案

设长度为 nn ,循环节为 ss ,那么显然有 ns\frac ns 条对称轴,如果还有别的对称轴,则循环节会更小,因此对称轴数量就是 ns\frac ns ,设 gsg_s 为不循环的长度为 ss 的回文串数,因为可以选择一个位置开头还需要乘上 ss ,这部分需要减去的值为 sisns(ns1)g(s)\sum_s\sum_{is\leq n}s(\frac ns-1)g(s)

然后考虑 ss 为偶数的情况,可以发现所有的对称轴必须全部经过字符或者全部不经过字符

如果全部不经过字符,根据上面的分析循环节一定是一个回文串

留坑待填

auoj836 计数
Problem

给一个长度为 nn 的字符串 ss ,求有多少个字符串序列 t1,...,kt_{1,...,k} 满足

  1. t1=st_1=s
  2. ti=ti11|t_i|=|t_{i-1}|-1
  3. tit_iti1t_{i-1} 的子串

答案模 998244353998244353

n5×105n\leq 5\times 10^5

2s,1024MB2s,1024MB

Sol

显然 tit_i 是在 ti1t_{i-1} 上删掉开头或者结尾得到的

考虑记录每次删掉的是开头还是结尾,可以得到一个长度为 n1n-1LR 序列

注意到如果删掉开头或结尾得到的序列相同,那么整个串的所有字符必须全部相同

那么可以看成一个 nn 层的三角形结构,从上往下第 ii 层有 ii 个点,第 ii 层第 jj 个点对应 [j,j+ni][j,j+n-i] ,如果当前点对应的串有多种字符,那么可以向两个方向走,否则只能向一个方向走

所有只包含一种字符的串一定形成了若干个三角形的结构,可以钦定不能走到三角形内部,这样除了三角形上面的顶点以外,剩下的三角形中的点只能向一个方向走

考虑计算走到三角形内部的方案数,枚举走进去的第一个点,可能的点的种类数只有 O(n)O(n) 个,可以直接算出第一个点是某个点的方案数

然后再考虑三角形顶点处的情况,先算出走到三角形顶点的方案数,可以发现一种经过顶点的方案被算了两次,因此减去一次即可

复杂度可以做到 O(n)O(n)

Code
#include<cstdio>
#include<cstring>
using namespace std;
#define N 500050
#define mod 998244353
int n,fr[N],ifr[N];
char s[N];
int pw(int a,int p){int as=1;while(p){if(p&1)as=1ll*as*a%mod;a=1ll*a*a%mod;p>>=1;}return as;}
int main()
{
	scanf("%s",s+1);n=strlen(s+1);
	fr[0]=ifr[0]=1;for(int i=1;i<=n;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	int as=pw(2,n)-1;
	int las=1;
	for(int i=1;i<n;i++)
	if(s[i]==s[las])
	{if(i>las&&(s[i+1]==s[i]))as=(as-1ll*fr[n-i+las-1]*ifr[las-1]%mod*ifr[n-i]%mod*(pw(2,i-las)-1)%mod+mod)%mod;}
	else las=i;
	las=1;
	for(int i=1;i<n;i++)
	if(s[n-i+1]==s[n-las+1])
	{if(i>las&&(s[n-i]==s[n-i+1]))as=(as-1ll*fr[n-i+las-1]*ifr[las-1]%mod*ifr[n-i]%mod*(pw(2,i-las)-1)%mod+mod)%mod;}
	else las=i;
	las=1;
	for(int i=1;i<=n+1;i++)if(s[i]!=s[las])as=(as-1ll*fr[n-i+las]*ifr[las-1]%mod*ifr[n-i+1]%mod*(i-las-1)%mod+mod)%mod,las=i;
	printf("%d\n",as);
}
auoj837 博弈
Problem

PDF题面

2s,1024MB2s,1024MB

Sol

fs,xf_{s,x} 表示剩余 ss 枚金币时,每个人最后能得到多少枚金币, gsg_s 表示此时所有人得到的金币数和

假设当前还有 aa 枚金币,如果 a<gak+na<g_{a-k}+n ,那么无论怎么分,都有人得到的金币数量不多于否决这一次后得到的数量,因此这次一定会被否决,即 fa=fakf_a=f_{a-k}

否则,当前分的人一定会给每人多一枚金币,剩下的给自己,这样最优

d=mkd=\lfloor\frac mk\rfloor ,考虑先计算 fmdkf_{m-dk} ,然后依次递推 fm(d1)k,...,fmf_{m-(d-1)k},...,f_m ,记录当前金币数量与当前每个人最后得到的金币数量和的差 ss ,每次的操作相当于

  1. 如果 sns\geq n ,所有人得到的金币数加一,当前操作的人再得到 sns-n 枚金币
  2. ss 加上 kk ,操作的人变成上一个人

可以发现,在进行了第一次1操作后,之后每次一定是向前 nk\lceil\frac nk\rceil 位,然后进行一次1操作,因此每次1操作时的 ss 是相同的,可以算出所有1操作的位置的循环节,然后算出每个位置额外加了多少以及总共加了多少次1

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define ll long long
#define N 200010
ll n,m,k,v[N],lz,st[N],ct;
int main()
{
	scanf("%lld%lld%lld",&n,&m,&k);
	ll tp=m/k,ls=m%k,nw=tp%n+1;
	ll fu=(n-ls-1)/k+1;if(fu<0)fu=0;
	if(n<ls)fu=0;
	ls+=k*fu;tp-=fu;nw-=fu;
	if(tp<0){for(int i=1;i<=n;i++)printf("0 ");return 0;}
	nw=(nw%n+n-1)%n+1;
	lz=1;v[nw]=ls-n;ls=0;
	ll v1=(n-1)/k+1,v2=v1*k-n;
	nw=(nw-v1+n-1)%n+1;
	tp/=v1;lz+=tp;
	st[0]=nw;ct=0;
	for(int i=1;i<=2e5;i++)st[i]=(st[i-1]-v1+n-1)%n+1;
	int f1=0;
	for(int i=2e5;i>0;i--)if(st[i]==nw)f1=i;
	for(int i=0;i<f1&&i<tp;i++)v[st[i]]+=((tp-i-1)/f1+1)*v2;
	for(int i=1;i<=n;i++)printf("%lld ",v[i]+lz);
}
auoj838 划分
Problem

给定 n,kn,k 和一个长度为 nn 的序列,你需要将序列划分成 kk 段,记第 ii 段的元素和为 sis_i ,最大值为 mim_i ,你需要满足

i,max(mi,mi+1)sisi+1\forall i,max(m_i,m_{i+1})\leq |s_i-s_{i+1}|

输出一个方案

n,k2×105n,k\leq 2\times 10^5

Sol

考虑一个方案如果不合法,那么对于不合法的两段,一定可以将一段一侧的一个元素给另外一段,使得两段的差更接近

可以发现,这样调整后所有段的 si2\sum s_i^2 会变小

因此找到 si2\sum s_i^2 最小的划分方式,它一定合法

wqs二分+斜率优化即可

这里输出方案的东西可以参考我2.22的dp选讲

复杂度 O(nlogv)O(n\log v)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 100500
#define ll __int128
int n,k,v[N],fr[N],ct[N],c1,c2,s1[N],s2[N],st[N],rb,lb;
ll dp[N],su[N];
void solve(ll v,int fg)
{
	st[lb=rb=1]=0;
	for(int i=1;i<=n;i++)
	{
		while(lb+1<=rb&&dp[st[lb+1]]+(su[i]-su[st[lb+1]])*(su[i]-su[st[lb+1]])<dp[st[lb]]+(su[i]-su[st[lb]])*(su[i]-su[st[lb]])+fg)lb++;
		fr[i]=st[lb],ct[i]=ct[st[lb]]+1;dp[i]=dp[st[lb]]+(su[i]-su[st[lb]])*(su[i]-su[st[lb]])+v;
		while(lb+1<=rb&&(dp[i]+su[i]*su[i]-dp[st[rb]]-su[st[rb]]*su[st[rb]])*(su[st[rb]]-su[st[rb-1]])<(dp[st[rb]]+su[st[rb]]*su[st[rb]]-dp[st[rb-1]]-su[st[rb-1]]*su[st[rb-1]])*(su[i]-su[st[rb]])+fg)rb--;
		st[++rb]=i;
	}
	int st=n;c1=0;
	while(st)s1[++c1]=st,st=fr[st];
	for(int i=1;i*2<=c1;i++)swap(s1[i],s1[c1-i+1]);
}
int main()
{
	printf("YES\n");
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]),su[i]=su[i-1]+v[i];
	ll lb=-1e20,rb=1e20,as=-1e20;
	while(lb<=rb)
	{
		ll mid=(lb+rb)/2;
		solve(mid,0);
		if(c1<=k)rb=mid-1,as=mid;
		else lb=mid+1;
	}
	solve(as,1);
	c2=c1;for(int i=1;i<=c2;i++)s2[i]=s1[i];
	solve(as,0);
	int l1=1;
	for(int i=0;i<=c2;i++)
	{
		while(s1[l1]<=s2[i]&&l1<c1)l1++;
		if(i+c1-l1+1==k&&(i==0||i==c2||s1[l1]>=s2[i+1]))
		{
			for(int j=1;j<=i;j++)printf("%d ",s2[j]);
			for(int j=l1;j<c1;j++)printf("%d ",s1[j]);
			return 0;
		}
	}
}