集训队作业 2019 题解

其实是集训队作业总结

题目完成度:134/150

题解完成度:133/134

1 CF549E Sasha Circles
Problem

给两个点集 A,BA,B ,坐标为整数,求是否存在一个圆,包含所有 AA 的点且不包含任何一个 BB 的点

n,xi,yi104n,|x_i|,|y_i|\leq 10^4

2s,512MB2s,512MB

Sol

不会

2 CF674G Choosing Ads
Problem

给一个序列,多次询问,每一次询问一个区间里面出现次数大于等于区间长度的 L%L\% 的数,可以额外输出不合法的数

有区间赋值的操作

n1.5×105,L20n\leq 1.5\times 10^5,L\geq 20

4s,512MB4s,512MB

Sol

考虑一种求大于一半的数的方法:每次加入一个数时,如果和原有的数不同就两种都拿掉一个,相同就加入,最后剩下的可能是答案

考虑扩展到多个数的情况,考虑这样的算法

加入当前数时,如果有一个相同的就直接加入,如果当前种数小于 100/L100/L 就加入,否则将每一种数删掉一个

考虑一种数出现了 xx 次,总共 yy 个数

ff 为当前这种数数量, gg 为剩下的数量,k=100/Lk=100/L

那么有这几种情况

加入这种数:

  1. f=f+1f=f+1
  2. g=gkg=g-k

加入其它数:

  1. g=g+1g=g+1
  2. g=gkg=g-k
  3. f=f1,g=g(k1)f=f-1,g=g-(k-1)

设这些操作有 a,b,c,d,ea,b,c,d,e

那么有

a+b=xa+b=x

a+b+c+d+e=ya+b+c+d+e=y

aea\geq e

ck(b+d)+(k1)ec\geq k(b+d)+(k-1)e

如果 kxykx\geq y ,那么

(k1)(a+b)c+d+ekb+kd+d+ke(k-1)(a+b)\geq c+d+e\geq kb+kd+d+ke

kab+kb+kd+d+keka\geq b+kb+kd+d+ke

(k+1)a(k+1)(b+d+e)(k+1)a\geq (k+1)(b+d+e)

ab+d+ea\geq b+d+e

如果 b+d=0b+d=0 ,那么有 a=x,e(yx)/ka=x,e\leq (y-x)/k

这时因为 x0x\geq 0 ,那么有 a>ea>e

否则一定有 a>ea>e ,这说明了最后 f>0f>0

因此,所有合法的数都会出现在答案中

使用线段树维护序列,对于两个区间,可以 k2k^2 暴力合并

复杂度 O(nk2logn)O(nk^2\log n)

Code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 150050
int n,m,v[N],cl[N],as[11],ct,p,l1,r1,a,b,c,d,t,is2[N];
struct sth{int s[6][2];};
sth operator *(sth a,sth b)
{
	for(int i=0;i<t;i++)
	for(int j=0;j<t;j++)
	if(a.s[i][0]==b.s[j][0])a.s[i][1]+=b.s[j][1],b.s[j][1]=0;
	for(int j=0;j<t;j++)
	if(b.s[j][1])
	{
		int tp=b.s[j][1],fg=1;
		for(int i=0;i<t;i++)
		if(tp>a.s[i][1])tp=a.s[i][1];
		for(int i=0;i<t;i++)
		{
			a.s[i][1]-=tp;
			if(a.s[i][1]==0&&fg)a.s[i][0]=b.s[j][0],a.s[i][1]=b.s[j][1]-tp,fg=0;
		}
	}
	return a;
}
struct segtree{
	struct node{int l,r,lz;sth as;}e[N*4];
	void pushup(int x){e[x].as=e[x<<1].as*e[x<<1|1].as;}
	void pushdown(int x,int s)
	{
		if(e[x].lz==-1)return;
		memset(e[x<<1].as.s,0,sizeof(e[x<<1].as.s));
		memset(e[x<<1|1].as.s,0,sizeof(e[x<<1|1].as.s));
		e[x<<1].as.s[0][0]=e[x<<1|1].as.s[0][0]=e[x].lz;
		e[x<<1].as.s[0][1]=(s+1)>>1;
		e[x<<1|1].as.s[0][1]=s>>1;
		e[x<<1].lz=e[x<<1|1].lz=e[x].lz;
		e[x].lz=-1;
	}
	void build(int x,int l,int r)
	{
		e[x].l=l;e[x].r=r;e[x].lz=-1;
		if(l==r){e[x].as.s[0][0]=v[l];e[x].as.s[0][1]=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){memset(e[x].as.s,0,sizeof(e[x].as.s));e[x].as.s[0][0]=v;e[x].as.s[0][1]=e[x].r-e[x].l+1;e[x].lz=v;return;}
		pushdown(x,e[x].r-e[x].l+1);
		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);
	}
	sth query(int x,int l,int r)
	{
		if(e[x].l==l&&e[x].r==r)
		{
			return e[x].as;
		}
		pushdown(x,e[x].r-e[x].l+1);
		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);
	}
}st;
int main()
{
	scanf("%d%d%d",&n,&m,&p);t=100/p;
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	st.build(1,1,n);
	while(m--)
	{
		scanf("%d",&a);
		if(a==1)
		{
			scanf("%d%d%d",&b,&c,&d);
			st.modify(1,b,c,d);
		}
		else
		{
			scanf("%d%d",&l1,&r1);
			ct=0;
			sth tp=st.query(1,l1,r1);
			printf("%d ",100/p);
			for(int i=1;i<=100/p;i++)printf("%d ",tp.s[i-1][0]);
			printf("\n");
		}
	}
}
3 ARC103F Distance Sum
Problem

有一棵树,给出每个点到其它点的距离和,还原这棵树或者输出无解

所有距离和不同, n105n\leq 10^5

2s,512MB2s,512MB

Sol

考虑以重心为根后,每个点和它父亲的距离差一定是 n2szun-2*sz_u

按距离和从大到小确定每个点的父亲,因为距离唯一所以父亲唯一,因此可以直接确定,最后确定根的答案是否合法即可

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

Code
#include<cstdio>
#include<map>
#include<algorithm>
#define N 1000400
using namespace std;
int n,v2[N],fa[N];
long long v[N],sz[N],ds[N];
map<long long,int> tp;
bool cmp(int a,int b){return v[a]<v[b];}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&v[i]),tp[v[i]]=i,v2[i]=i,sz[i]=1;
	sort(v2+1,v2+n+1,cmp);
	for(int i=n;i>1;i--)
	{
		long long tp2=v[v2[i]]-n+sz[v2[i]]*2;
		if(!tp[tp2]){printf("-1\n");return 0;}
		int sb=tp[tp2];
		ds[sb]+=ds[v2[i]]+sz[v2[i]];
		sz[sb]+=sz[v2[i]];
		fa[v2[i]]=sb;
	}
	if(ds[v2[1]]!=v[v2[1]]){printf("-1\n");return 0;}
	for(int i=1;i<=n;i++)if(fa[i])printf("%d %d\n",i,fa[i]);
}
4 CF594E Cutting the Line
Problem

你有一个字符串 ss

你可以将其划分成若干段,段数小于等于 kk ,然后选择一些段翻转再按原顺序拼起来

求可能得到的最小字典序字符串

n5×106n\leq 5\times 10^6

2s,1024MB2s,1024MB

Sol

不会

5 AGC034F RNG and XOR
Problem

你有一个随机数生成器,它有 AiA_i 的概率生成 ii ,生成范围为 [0,2n1][0,2^n-1]

初始为0,每次异或上随机出的数,求对于每个数,第一次变成这个数的期望时间,模 998244353998244353

n18,1Ai1000n\leq 18,1\leq A_i\leq 1000

3s,1024MB3s,1024MB

Sol

考虑倒着做,相当于求每个数期望变成0的时间

先令 Ai=Ai/AjA_i=A_i/\sum A_j

那么有 dpi=1+jdpjAij(i>0)dp_i=1+\sum_j dp_j A_{i\oplus j}(i>0)

可以看成集合幂级数的xor卷积

{dp0,dp1,...,dp2n1}{A0.A1,...,A2n1}={?,dp11,dp21,...,dp2n11}\{dp_0,dp_1,...,dp_{2^n-1}\}\oplus \{A_0.A_1,...,A_{2^n-1}\}=\{?,dp_1-1,dp_2-1,...,dp_{2^n-1}-1\}

因为中间的和是1,所以可以得到 ?=2n1?=2^n-1

那么有 {dp0,dp1,...,dp2n1}{A01.A11,...,A2n11}={2n1,1,1,...,1}\{dp_0,dp_1,...,dp_{2^n-1}\}\oplus \{A_0-1.A_1-1,...,A_{2^n-1}-1\}=\{2^n-1,-1,-1,...,-1\}

FWT逆卷积即可

注意到对后面两个FWT后0项都为0,无法求逆

注意到IFWT其它项对0项的系数很好搞,并且有 dp0=0dp_0=0 ,因此可以求出那个系数

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

Code
#include<cstdio>
using namespace std;
#define N 270001
#define mod 998244353
int a[N],b[N],n,su;
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 fwt(int *a,int n,int f)
{
	for(int l=2;l<=n;l<<=1)
	for(int j=0;j<n;j+=l)
	for(int k=j;k<j+(l>>1);k++)
	if(f)
	{
		int t1=a[k],t2=a[k+(l>>1)];
		a[k]=(t1+t2)%mod;
		a[k+(l>>1)]=(t1-t2+mod)%mod;
	}
	else
	{
		int t1=a[k],t2=a[k+(l>>1)];
		a[k]=1ll*(t1+t2)*(mod+1)/2%mod;
		a[k+(l>>1)]=1ll*(t1-t2+mod)*(mod+1)/2%mod;
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=0;i<1<<n;i++)scanf("%d",&a[i]),su+=a[i];
	for(int i=0;i<1<<n;i++)a[i]=1ll*a[i]*pw(su,mod-2)%mod-(i==0),b[i]=i==0?(1<<n)-1:mod-1;
	fwt(a,1<<n,1);fwt(b,1<<n,1);
	for(int i=0;i<1<<n;i++)a[i]=1ll*b[i]*pw(a[i],mod-2)%mod;
	fwt(a,1<<n,0);
	int tp=a[0];
	fwt(a,1<<n,1);
	a[0]=(a[0]+mod-1ll*tp*(1<<n)%mod)%mod;
	fwt(a,1<<n,0);
	for(int i=0;i<1<<n;i++)printf("%d\n",a[i]);
}
6 AGC030D Inversion Sum
Problem

给一个序列 AA ,有 qq 次操作,每次有 1/21/2 的概率交换 ai,bia_i,b_i 位置上的数,求最后的期望逆序对数乘 2q2^q109+710^9+7

n,q3000n,q\leq 3000

Sol

fi,jf_{i,j} 表示 Ai>AjA_i>A_j 的概率

对于一次操作,有

fx,ai=fx,bi=(fx,ai+fx,bi)/2(xai,bi)f_{x,a_i}^{'}=f_{x,b_i}^{'}=(f_{x,a_i}+f_{x,b_i})/2(x\neq a_i,b_i)

fai,x=fbi,x=(fai,x+fbi,x)/2(xai,bi)f_{a_i,x}^{'}=f_{b_i,x}^{'}=(f_{a_i,x}+f_{b_i,x})/2(x\neq a_i,b_i)

fai,bi=fbi,ai=(fai,bi+fbi,ai)/2f_{a_i,b_i}^{'}=f_{b_i,a_i}^{'}=(f_{a_i,b_i}+f_{b_i,a_i})/2

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

Code
#include<cstdio>
using namespace std;
#define N 3005
#define mod 1000000007
#define inv 500000004
int n,dp[N][N],v[N],q,a,b;
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)dp[i][j]=(v[i]<v[j]);
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&a,&b);
		int tp=dp[a][b]+dp[b][a];
		for(int j=1;j<=n;j++)
		{
			dp[j][a]=dp[j][b]=1ll*(dp[j][a]+dp[j][b])*inv%mod;
			dp[a][j]=dp[b][j]=1ll*(dp[a][j]+dp[b][j])*inv%mod;
		}
		dp[a][b]=dp[b][a]=1ll*tp*inv%mod;
	}
	int as=0;
	for(int i=2;i<=n;i++)
	for(int j=1;j<i;j++)as=(as+dp[i][j])%mod;
	for(int i=1;i<=q;i++)as=as*2%mod;
	printf("%d\n",as);
}
7 CF575E Spectator Riots
Problem

有一个 105×10510^5\times 10^5 的矩形

场地中有 nn 个人,每个人有初始位置 (xi,yi)(x_i,y_i) 的速度 pp ,他可能会出现在所有矩形内满足 xxi+yyip|x-x_i|+|y-y_i|\leq p 的整点 (x,y)(x,y)

你需要选择三个点,使得这三个点上都可能有人出现,并且三点外接圆圆内期望人数最多,并且这个外接圆是所有满足前两个条件的外接圆中半径最大的

输出选择的点

n105n\leq 10^5

1s,256MB1s,256MB

Sol

不会

8 AGC035C Skolem XOR Tree
Problem

2n2n 个点,第 i,i+ni,i+n 的点的权值为 ii

你需要构造一棵树,使得 第 i,i+ni,i+n 的点路径上的点权异或和为 ii

n105n\leq 10^5

2s,1024MB2s,1024MB

Sol

注意到 x(x1)x1=xx\oplus (x\oplus 1) \oplus x \oplus 1=x

考虑这样构造

2i  2i+1  1  2i+n  2i+1+n2i\ -\ 2i+1\ -\ 1\ -\ 2i+n\ -\ 2i+1+n

对于 1,1+n1,1+n ,可以构造 1  2  3  1+n  2+n  3+n1\ -\ 2\ - \ 3\ -\ 1+n\ -\ 2+n\ -\ 3+n

这样可以解决 nn 为奇数的情况

如果 n=2kn=2^k ,显然无解

否则,一定可以找到 x,y<nx,y<n ,xy1=nx\oplus y\oplus 1=n

因此接上去即可

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
int n;
int main()
{
	scanf("%d",&n);
	if(n<3){printf("No\n");return 0;}
	if(n&1)
	{
		printf("Yes\n");
		printf("%d %d\n",1,2);printf("%d %d\n",2,3);printf("%d %d\n",3,n+1);printf("%d %d\n",n+1,n+2);printf("%d %d\n",n+2,n+3);
		for(int i=4;i<=n;i+=2)printf("%d %d\n",i,i+1),printf("%d %d\n",i+n,i+n+1),printf("%d %d\n",i,1),printf("%d %d\n",i+n+1,1);
	}
	else
	{
		for(int i=1;i<n;i++)
		if((n^i^1)<n)
		{
			int st=n^i^1;
			printf("Yes\n");
			printf("%d %d\n",1,2);printf("%d %d\n",2,3);printf("%d %d\n",3,n+1);printf("%d %d\n",n+1,n+2);printf("%d %d\n",n+2,n+3);
			for(int j=4;j<n;j+=2)printf("%d %d\n",j,j+1),printf("%d %d\n",j+n,j+n+1),printf("%d %d\n",j,1),printf("%d %d\n",j+n+1,1);
			printf("%d %d\n",n,(i&1)?i+n:i);printf("%d %d\n",n*2,(st&1)?st+n:st);
			return 0;
		}
		printf("No\n");
	}
}
9 AGC026F Manju Game
Problem

有一个长度为 nn 的序列,每个位置有分数 AiA_i

两人轮流操作,每次只能拿走上一个被拿走的位置相邻的位置中的分数

如果没有这样的数或者这是第一次操作,则可以任选一个位置拿走

求最优策略下先后手得分

n3×105,1Ai1000n\leq 3\times 10^5,1\leq A_i\leq 1000

2s,1024MB2s,1024MB

Sol

首先考虑 nn 为偶数的情况

显然,先手得分不低于所有奇数位置和与所有偶数位置和的max

考虑先手选了一个数,后手一定可以通过选择一个方向使得这个方向选完后后手先手

假设先手选了奇数位置,那么在这一段先手得分为所有奇数位置和,在剩下的因为后手先,所以先手得分不会超过这一段的奇数位置和

因此先手总得分不超过奇数位置和

因此,可以发现 nn 为偶数时,最优得分为所有奇数位置和与所有偶数位置和的max

考虑 nn 是奇数的情况,如果先手选了奇数位置,那么通过和上面一样的分析可以得到先手得分不高于所有奇数位置和

如果先手选了偶数位置,可以看成先手将序列分成两段,后手选一段,先手分数加上其余的偶数位置和,然后对这一段继续

对于先手在每一段的选择方式,可以看成一棵树

对于一个点,如果先手选奇数,那么这个点是叶子,否则这个点的儿子是分出的两个区间

因为先手的得分是最后选奇数的区间的奇数位置和加上剩下的偶数位置和,可以看成所有偶数位置和加上这一段的奇数位置和减偶数位置和

那么后手相当于要最小化这一段的奇数位置和减偶数位置和

这个博弈过程可以看成在树上走,一直到一个叶子停止

显然后手最小化的结果是所有叶子这个值的最小值

注意到所有叶子相当于把序列从一些偶数位置分割成了若干段,因此先手的策略相当于将序列从一些偶数位置分割成若干段,使得每一段的奇数位置和减偶数位置和的最小值最大

二分答案,设 dpidp_i 表示位置i是否可能是一个划分方案中一个位置的结尾,注意到转移过程只和 sumjsumi+1sum_j-sum_{i+1} 的大小有关,因此可以贪心记录所有 dpi=1dp_i=1 中最小的 sumi+1sum_{i+1} 即可

复杂度 O(nlogai)O(n\log \sum a_i)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 300060
int n,v[N];
long long su[N],su1,su2;
bool check(long long l)
{
	long long mn=0;
	for(int i=1;i<=n;i+=2)
	if(su[i]-mn>=l)
	{
		if(mn>su[i+1])mn=su[i+1];
		if(i==n)return 1;
	}
	return 0;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	if(~n&1)
	{
		long long s1=0,s2=0;
		for(int i=1;i<=n;i+=2)s1+=v[i],s2+=v[i+1];
		printf("%lld %lld\n",max(s1,s2),min(s1,s2));
	}
	else
	{
		for(int i=1;i<=n;i++)su1+=v[i]*(i&1),su[i]=su[i-1]+v[i]*(i&1?1:-1),su2+=v[i]*(~i&1);
		long long lb=0,rb=2e10,as=0;
		while(lb<=rb)
		{
			long long mid=(lb+rb)>>1;
			if(check(mid))as=mid,lb=mid+1;
			else rb=mid-1;
		}
		printf("%lld %lld\n",su2+as,su1-as);
	}
}
10 CF607E Cross Sum
Problem

nn 条直线,每条直线为 1000y=aix+bi1000y=a_ix+b_i

这些直线的交点构成了一个可重集 SS

给出两个数 p,qp,q 表示给定点坐标是 (p/1000,q/1000)(p/1000,q/1000)

再给出一个 kk ,定义 SS 中每个点的权值为这个点到给定点的欧几里得距离

SS 中前 kk 小权值的和

n5×104,k3×107n\leq 5\times 10^4,k\leq 3\times 10^7

7s,512MB7s,512MB

Sol

首先考虑二分第 kk 小权值,然后暴力找出所有点计算

但是因为可能出现 n2n^2 个点位置相同的情况,考虑二分一个数 dd ,使得距离小于等于 dd 的不超过 kk 个,但距离小于等于 d+ϵd+\epsilon 的超过 kk 个( dd 可能是负数)

二分 dd ,以给定点画一个圆,显然,如果交点在圆内,那么这两条直线都和圆有交

求出每条直线与圆相交的弦后,显然两条直线交点在圆内当且仅当对应弦相交

如果对于每条弦,记录它的两个端点对应圆心的方位角,那么与一条弦相交的弦一定在若干个二维区间中

可以二维数点统计对数,这部分复杂度为 O(nlogn)O(n\log n)

然后考虑计算答案

如果直接用主席树找出所有对数,复杂度可能达到 $O(k\log n) $ ,跑不过去

考虑线段树套vector,每次询问直接在vector上二分然后遍历,这样复杂度为 O(nlog2n+k)O(n\log^2 n+k)

复杂度 O(nlognlogV+k)O(n\log n\log V+k)

Code
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#define ld double
using namespace std;
#define N 100050
long double pi=acos(-1),p[N][2],f[N],s1[N],s2[N];
struct point{ld x,y;}s;
point operator +(point a,point b){return (point){a.x+b.x,a.y+b.y};}
point operator -(point a,point b){return (point){a.x-b.x,a.y-b.y};}
point operator *(point a,ld b){return (point){a.x*b,a.y*b};}
struct vec{point s,t;ld a,b,c,d,e;}v[N];
point intersect(vec a,vec b){ld tp=(a.e-b.e)/(b.d-a.d);return (point){tp,a.e+tp*a.d};}
ld dist(point a,vec b){return (b.a*a.x+b.b*a.y+b.c)/sqrt(b.a*b.a+b.b*b.b);}
ld dist(point a,point b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}
int n,a,b,c,k,cnt,is[N],p2[N][2],t1,t2,fu[N],fr[N],id2[N][2];
int id[N*32],ch[N*32][2],sz[N*32],ct,ct2,rt[N],su1;
int build(int l,int r)
{
	int st=++ct;
	id[st]=ch[st][0]=ch[st][1]=sz[st]=id[st]=0;
	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(){ct=ct2=0;rt[0]=build(1,n);}
int add(int x,int l,int r,int v,int s)
{
	int st=++ct;
	ch[st][0]=ch[x][0];
	ch[st][1]=ch[x][1];
	sz[st]=sz[x]+1;
	id[st]=0;
	if(l==r){id[st]=s;return st;}
	int mid=(l+r)>>1;
	if(mid>=v)ch[st][0]=add(ch[x][0],l,mid,v,s);
	else ch[st][1]=add(ch[x][1],mid+1,r,v,s);
	return st;
}
void adds(int v,int s){rt[ct2+1]=add(rt[ct2],1,n,v,s);ct2++;}
int que1(int x,int x2,int l,int r,int l1,int r1)
{
	if(l==l1&&r==r1)return sz[x]-sz[x2];
	if(!x)return 0;
	int mid=(l+r)>>1;
	if(mid>=r1)return que1(ch[x][0],ch[x2][0],l,mid,l1,r1);
	else if(mid<l1)return que1(ch[x][1],ch[x2][1],mid+1,r,l1,r1);
	else return que1(ch[x][0],ch[x2][0],l,mid,l1,mid)+que1(ch[x][1],ch[x2][1],mid+1,r,mid+1,r1);
}
struct sth{int x,id;friend bool operator <(sth a,sth b){return a.x<b.x;};};
struct node{int l,r;vector<sth> sb;}e[N*4];
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 ins(int x,int l,sth a)
{
	e[x].sb.push_back(a);
	if(e[x].l==e[x].r)return;
	int mid=(e[x].l+e[x].r)>>1;
	if(mid>=l)ins(x<<1,l,a);
	else ins(x<<1|1,l,a);
}
double que(int x,int l,int r,int l1,int r1,int id)
{
	if(!e[x].sb.size())return 0;
	if(e[x].l==l&&e[x].r==r)
	{
		int lb=0,rb=e[x].sb.size()-1,as=rb+1,as2=-1;
		while(lb<=rb)
		{
			int mid=(lb+rb)>>1;
			if(e[x].sb[mid].x>=l1)as=mid,rb=mid-1;
			else lb=mid+1;
		}
		lb=0,rb=e[x].sb.size()-1;
		while(lb<=rb)
		{
			int mid=(lb+rb)>>1;
			if(e[x].sb[mid].x<=r1)as2=mid,lb=mid+1;
			else rb=mid-1;
		}
		double as1=0;
		for(int j=as;j<=as2;j++)
		{
			int t=id,q=e[x].sb[j].id;
			if(t==q)continue;
			double x1=(v[t].e-v[q].e)/(v[q].d-v[t].d),y=v[q].d*x1+v[q].e;su1++;
			as1+=sqrt((s.x-x1)*(s.x-x1)+(s.y-y)*(s.y-y));
		}
		return as1;
	}
	int mid=(e[x].l+e[x].r)>>1;
	if(mid>=r)return que(x<<1,l,r,l1,r1,id);
	else if(mid<l)return que(x<<1|1,l,r,l1,r1,id);
	else return que(x<<1,l,mid,l1,r1,id)+que(x<<1|1,mid+1,r,l1,r1,id);
}
long long check(ld r1)
{
	ct=0;cnt=0;t1=t2=0;
	long long su=0,c1=0,c2=0,c3=0;
	ld as=0;
	for(int i=1;i<=n;i++)is[i]=0;
 	for(int i=1;i<=n;i++)
	{
		ld di=dist(s,v[i]);
		if(di<0)di=-di;
		if(di>r1)continue;
		is[i]=1;c3++;
		if(di<=1e-14)
		{
			ld a=pi-atan2(v[i].a,v[i].b);
			if(a>pi)a-=pi;
			p[i][0]=a;
			p[i][1]=a+pi;
			continue;
		}
		long double st=atan2(v[i].b,v[i].a)+pi;
		if(st>pi)st-=pi;
		ld st1=v[i].d*s.x+v[i].e;
		if(st1>s.y)st+=pi;
		ld d1=sqrt(r1*r1-di*di),f1=atan2(d1,di);
		long double t1=st-f1,t2=st+f1;
		if(t1<0)t1+=pi*2;
		if(t2>2*pi)t2-=pi*2;
		if(t1>t2)swap(t1,t2);
		p[i][0]=t1;
		p[i][1]=t2;
	}
	init();
	for(int i=1;i<=n;i++)if(is[i])s1[++t1]=p[i][0]-1e-18,s2[++t2]=p[i][1]-1e-18;
	sort(s1+1,s1+t1+1);sort(s2+1,s2+t1+1);
	for(int i=1;i<=n;i++)if(is[i])
	{
		int tp=lower_bound(s1+1,s1+t1+1,p[i][0])-s1-1,tp2=lower_bound(s2+1,s2+t1+1,p[i][1])-s2-1;
		id2[i][0]=tp;id2[i][1]=tp2;
		fu[tp]=tp2;fr[tp]=i;
	}
	for(int i=1;i<=c3;i++)adds(fu[i],fr[i]);
	for(int i=1;i<=n;i++)if(is[i])
	{
		int tp1=1,tp2=id2[i][0]-1,tp3=id2[i][0],tp5=lower_bound(s1+1,s1+t1+1,p[i][1]+2e-18)-s1-1;
		int v1=lower_bound(s2+1,s2+t1+1,p[i][0]-2e-18)-s2,v2=id2[i][1],v3=v2;
		su+=que1(rt[tp3],0,1,n,v1,v2)+que1(rt[tp5],rt[tp2],1,n,v3,n);
	}
	return su/2-c3;
}
ld solve(ld r1)
{
	ct=0;cnt=0;t1=t2=0;
	long long c1=0,c2=0,c3=0;
	ld as=0;
	for(int i=1;i<=n;i++)is[i]=0;
 	for(int i=1;i<=n;i++)
	{
		long double di=dist(s,v[i]);
		if(di<0)di=-di;
		if(di>r1)continue;
		is[i]=1;c3++;
		if(di<=1e-14)
		{
			ld a=pi-atan2(v[i].a,v[i].b);
			if(a>pi)a-=pi;
			p[i][0]=a;
			p[i][1]=a+pi;
			continue;
		}
		long double st=atan2(v[i].b,v[i].a)+pi;
		if(st>pi)st-=pi;
		ld st1=v[i].d*s.x+v[i].e;
		if(st1>s.y)st+=pi;
		long double d1=sqrt(r1*r1-di*di),f1=atan2(d1,di);
		long double t1=st-f1,t2=st+f1;
		if(t1<0)t1+=pi*2;
		if(t2>2*pi)t2-=pi*2;
		if(t1>t2)swap(t1,t2);
		p[i][0]=t1;
		p[i][1]=t2;
	}
	init();
	for(int i=1;i<=n;i++)if(is[i])s1[++t1]=p[i][0]-1e-18,s2[++t2]=p[i][1]-1e-18;
	sort(s1+1,s1+t1+1);sort(s2+1,s2+t1+1);
	for(int i=1;i<=n;i++)if(is[i])
	{
		int tp=lower_bound(s1+1,s1+t1+1,p[i][0])-s1-1,tp2=lower_bound(s2+1,s2+t1+1,p[i][1])-s2-1;
		fu[tp]=tp2;fr[tp]=i;id2[i][0]=tp;id2[i][1]=tp2;
	}
	build(1,1,n);
	for(int i=1;i<=c3;i++)ins(1,i,(sth){fu[i],fr[i]});
	for(int i=1;i<n*4;i++)sort(e[i].sb.begin(),e[i].sb.end());
	for(int i=1;i<=n;i++)if(is[i])
	{
		int tp1=1,tp2=id2[i][0]-1,tp3=id2[i][0],tp5=lower_bound(s1+1,s1+t1+1,p[i][1]+2e-18)-s1-1;
		int v1=lower_bound(s2+1,s2+t1+1,p[i][0]-2e-18)-s2,v2=id2[i][1],v3=v2;
		as+=que(1,1,tp3,v1,v2,i)+que(1,tp2+1,tp5,v3,n,i);
	}
	as=as/2+(k-su1/2)*r1;
	if(as<0)as=0;
	return as;
}
int main()
{
	scanf("%d%d%d%d",&n,&a,&b,&k);
	int fg2=0;s.x=a/1000.0;s.y=b/1000.0;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&a,&b);v[i].s=(point){10*1.0,(a*10+b)/1000.0};v[i].t=(point){10*-1.0,(-a*10+b)/1000.0};
		v[i].a=a/1000.0;v[i].b=-1;v[i].c=b/1000.0;
		v[i].d=a/1000.0;v[i].e=b/1000.0;
	}
	ld lb=-1e-7,rb=3e9,as=lb;
	for(int r=1;r<=65;r++)
	{
		ld mid=(lb+rb)/2;
		long long tp=check(mid);
		if(tp<k)as=mid;
		if(tp<k)lb=mid;
		else rb=mid;
	}
	printf("%.15lf\n",(double)solve(as));
}
11 AGC038E Gachapon
Problem

你有一个随机数生成器,它有 Ai/AjA_i/\sum A_j 的概率生成 ii

求期望生成多少次后,对于第 ii 个数,它至少被生成了 BiB_i

答案模 998244353998244353

Ai,Bi400,1Ai,Bi,n400\sum A_i,\sum B_i\leq 400,1\leq A_i,B_i,n\leq 400

3s,1024MB3s,1024MB

Sol

每个限制相互独立,考虑Min-Max容斥

假设当前枚举一个集合 SS ,假设只考虑集合内的数的生成,期望满足至少一个限制的时间为 TT ,那么总的期望时间显然是 Tai/sumiSaiT*\sum a_i/sum_{i\in S}a_i

根据期望线性性,算期望时间可以看成算时刻0,1,...还不满足条件的概率之和

对于枚举的一个集合 SS ,设 fi,jf_{i,j} 表示考虑了集合前 ii 种数,当前选了 jj 个的方案的概率和

那么有 fi,j=k=0min(j,Bi1)fi1,jCjk(Ai/lSAl)kf_{i,j}=\sum_{k=0}^{min(j,B_i-1)}f_{i-1,j}*C_j^k*(A_i/\sum_{l\in S}A_l)^k

考虑对所有集合一起dp,设 dpi,j,0/1,kdp_{i,j,0/1,k} 表示前 ii 种数,当前选了 jj 个,当前集合内选了奇数/偶数种数,当前集合内选的每一种数的 AiA_i 之和是 kk 的方案的概率和

转移方程类似

复杂度 O(Ai(Bi)2)O(\sum A_i(\sum B_i)^2)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 410
#define mod 998244353
int dp[N][N],v[N],r[N],n,c[N][N],su,p[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",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&v[i],&r[i]),su+=v[i];
	dp[0][0]=1;
	for(int i=0;i<=400;i++)c[i][0]=c[i][i]=1;
	for(int i=1;i<=400;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++)
	{
		p[0]=1;
		for(int j=1;j<=r[i];j++)p[j]=1ll*p[j-1]*v[i]%mod;
		for(int j=400-v[i];j>=0;j--)
		for(int k=400-r[i];k>=0;k--)
		if(dp[j][k])
		for(int l=0;l<r[i];l++)
		dp[j+v[i]][k+l]=(dp[j+v[i]][k+l]+1ll*dp[j][k]*c[k+l][l]%mod*p[l]%mod*(mod-1))%mod;
	}
	int as=0;
	for(int i=1;i<=400;i++)
	for(int j=0;j<=400;j++)
	as=(as+1ll*dp[i][j]*pw(pw(i,mod-2),j+1)%mod*su)%mod;
	printf("%d\n",mod-as);
}
12 AGC030C Coloring Torus
Problem

给定 kk ,你需要构造一个 n×nn\times n 的棋盘并染色,满足

  1. n500n\leq 500
  2. 只出现 [1,k][1,k] 的颜色且每种颜色至少出现一次
  3. 对于每一对颜色 (i,j)(i,j) ,每一个 颜色 ii 的格子上下左右中颜色 jj 的数量相同(第一行的上为最后一行,其它类似)

k1000k\leq 1000

2s,1024MB2s,1024MB

Sol

一种暴力的构造:

1 2 3 4
2 3 4 1
3 4 1 2
4 1 2 3

因为 n=k/2n=k/2 ,考虑用 2×22\times 2 的东西代替每个数

于是有

1  2  5  6  9  10 13 14
3  4  7  8  11 12 15 16
5  6  9  10 13 14 1  2  
7  8  11 12 15 16 3  4  
9  10 13 14 1  2  5  6  
11 12 15 16 3  4  7  8  
13 14 1  2  5  6  9  10 
15 16 3  4  7  8  11 12 

这样就解决了 n=4kn=4k 的情况

对于其余的情况,容易发现在这里面, 14和15四周的颜色种数是相同的,然后 10,11 6,7 2,3都是相同的

k12k\geq 12 时,这样一定能够找出至少三种颜色合并

k11k\leq 11 直接暴力构造

复杂度 O(k2)O(k^2)

Code
#include<cstdio>
using namespace std;
#define N 1040
int n,fg,as[N][N];
int main()
{
	scanf("%d",&n);
	if(n<=9)
	{
		printf("%d\n",n);
		for(int i=1;i<=n;i++,printf("\n"))
		for(int j=1;j<=n;j++)
		printf("%d ",(i+j-2)%n+1);
		return 0;
	}
	if(n%4)fg=4-n%4,n+=fg;
	n/=2;
	for(int i=1;i<=n/2;i++)
	for(int j=1;j<=n/2;j++)
	{
		int st=(i+j-2)%(n/2)+1;
		as[i*2-1][j*2-1]=st*4-3;
		as[i*2-1][j*2]=st*4-2;
		as[i*2][j*2-1]=st*4-1;
		as[i*2][j*2]=st*4;
	}
	if(fg)
	{
		for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			if(as[i][j]>3)as[i][j]--;
			else if(as[i][j]==3)as[i][j]=2;
		}
		fg--;
	}
	if(fg)
	{
		for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			if(as[i][j]>6)as[i][j]--;
			else if(as[i][j]==6)as[i][j]=5;
		}
		fg--;
	}
	if(fg)
	{
		for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			if(as[i][j]>9)as[i][j]--;
			else if(as[i][j]==9)as[i][j]=8;
		}
		fg--;
	}
	printf("%d\n",n);
	for(int i=1;i<=n;i++,printf("\n"))
	for(int j=1;j<=n;j++)
	printf("%d ",as[i][j]);
}
13 CF611G New Year and Cake
Problem

给一个凸多边形,有 n(n3)/2n*(n-3)/2 种沿对角线划分的方式,对于一种方式,它的权值为分出的两块的大小的差的2倍

求所有方式的权值和模 109+710^9+7

4n5×105,xi,yi1094\leq n\leq 5\times 10^5,|x_i|,|y_i|\leq 10^9

2s,256MB2s,256MB

Sol

可以看成 n(n3)sizen*(n-3)*size 减去所有分出的小于一半的块面积的4倍,再减去所有面积等于一半的块面积的2倍

首先考虑怎么算分出的一块的答案

可以将其划分成若干个一个点和一条边组成的三角形

可以发现,对于一条固定的边,如果点坐标是 (x,y)(x,y) ,构成的面积一定形如 ax+by+cax+by+c

因此,对于每个点考虑它向顺时针方向分出的小于等于一半的块,可以通过一个指针加上线段树 O(nlogn)O(n\log n) 求出每个点顺时针能分出哪些小于一半的块

然后考虑对于一个点如果分出小于一半的最多到 rr ,每一个三角形的贡献是 (ri+1)(r-i+1) ,那么相当于 (ri+1)(aix+biy+ci)\sum (r-i+1)(a_ix+b_iy+c_i)

那么线段树上维护 ai,bi,ci,iai,ibi,icia_i,b_i,c_i,ia_i,ib_i,ic_i 的和即可

复杂度 O(nlogn)O(n\log n) ,直接记录前缀和可以做到 O(n)O(n)

Code

这个代码非常奇怪

#include<cstdio>
using namespace std;
#define N 500050
#define mod 1000000007
long long s[N][2],su[N][3],tsz,as1;
int n;
struct node{int l,r,sz;long long s1,s2,s3,s4,s5,s6;}e[N*4];
void build(int x,int l,int r)
{
	e[x].l=l;e[x].r=r;e[x].sz=r-l+1;
	if(e[x].l==e[x].r){e[x].s1=e[x].s4=(mod-(1ll*s[l][0]*s[l==n?1:l+1][1]-1ll*s[l==n?1:l+1][0]*s[l][1])%mod)%mod;e[x].s2=e[x].s5=(mod+s[l][0]-s[l==n?1:l+1][0])%mod;e[x].s3=e[x].s6=(s[l==n?1:l+1][1]-s[l][1]+mod)%mod;return;}
	int mid=(l+r)>>1;
	build(x<<1,l,mid);
	build(x<<1|1,mid+1,r);
	e[x].s1=(e[x<<1].s1+e[x<<1|1].s1)%mod;
	e[x].s2=(e[x<<1].s2+e[x<<1|1].s2)%mod;
	e[x].s3=(e[x<<1].s3+e[x<<1|1].s3)%mod;
	e[x].s4=(e[x<<1].s4+e[x<<1].s1*e[x<<1|1].sz+e[x<<1|1].s4)%mod;
	e[x].s5=(e[x<<1].s5+e[x<<1].s2*e[x<<1|1].sz+e[x<<1|1].s5)%mod;
	e[x].s6=(e[x<<1].s6+e[x<<1].s3*e[x<<1|1].sz+e[x<<1|1].s6)%mod;
}
int query(int x,int l,int r,int s1,int s2)
{
	if(l>r)return 0;
	if(e[x].l==l&&e[x].r==r)
	return (1ll*s2*e[x].s2+1ll*s1*e[x].s3+e[x].s1)%mod;
	int mid=(e[x].l+e[x].r)>>1;
	if(mid>=r)return query(x<<1,l,r,s1,s2);
	else if(mid<l)return query(x<<1|1,l,r,s1,s2);
	else return (query(x<<1,l,mid,s1,s2)+query(x<<1|1,mid+1,r,s1,s2))%mod;
}
int query2(int x,int l,int r,int s1,int s2,int ct)
{
	if(l>r)return 0;
	if(e[x].l==l&&e[x].r==r)return (1ll*s2*e[x].s5+1ll*s1*e[x].s6+e[x].s4+1ll*(ct-1)*query(x,l,r,s1,s2))%mod;
	int mid=(e[x].l+e[x].r)>>1;
	if(mid>=r)return query2(x<<1,l,r,s1,s2,ct);
	else if(mid<l)return query2(x<<1|1,l,r,s1,s2,ct);
	else return (query2(x<<1,l,mid,s1,s2,ct+r-mid)+query2(x<<1|1,mid+1,r,s1,s2,ct))%mod; 
}
long long doit(int x,int y)
{
	x^=y^=x^=y;
	if(y<1)return 0;
	if(x>y)return su[y][0]-(s[y==n?1:y+1][0]-s[1][0])*s[x][1]-(s[1][1]-s[y==n?1:y+1][1])*s[x][0];
	return su[y][0]-(s[y==n?1:y+1][0]-s[x==n?1:x+1][0])*s[x][1]-(s[x==n?1:x+1][1]-s[y==n?1:y+1][1])*s[x][0];
}
long long solve(int l,int r)
{
	if(l==r)return 0;
	if(r>l)return doit(r-1,l)-doit(l,l);
	else return doit(r-1,l)+doit(n,l)-doit(l,l);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld%lld",&s[i][0],&s[i][1]);
		if(i>1)su[i-1][0]=su[i-2][0]+s[i][0]*s[i-1][1]-s[i-1][0]*s[i][1];
	}
	su[n][0]=su[n-1][0]+(s[1][0]*s[n][1]-s[n][0]*s[1][1]);
	for(int i=1;i<=n;i++)
	tsz+=s[i][0]*s[i==1?n:i-1][1]-s[i==1?n:i-1][0]*s[i][1];
	build(1,1,n);
	solve(7,3);
	for(int i=1;i<=n;i++)
	{
		int lb=i,rb=i+n-1,as=lb;
		while(lb<=rb)
		{
			int mid=(lb+rb)>>1;
			if((unsigned long long)solve(i,(mid-1)%n+1)*2+(mid>n)<=tsz)as=mid,lb=mid+1;
			else rb=mid-1;
		}
		if(as<=n)as1=(as1+query2(1,i,as-1,(s[i][0]%mod+mod)%mod,(s[i][1]%mod+mod)%mod,1)+mod)%mod;
		else as1=(as1+query2(1,i,n,(s[i][0]%mod+mod)%mod,(s[i][1]%mod+mod)%mod,as-n)+query2(1,1,as-n-1,(s[i][0]%mod+mod)%mod,(s[i][1]%mod+mod)%mod,1))%mod;
	}
	as1=(1ll*n*(n-3)%mod*(mod+1)/2%mod*(tsz%mod)%mod-as1*2)%mod;
	printf("%d\n",(as1+mod)%mod);
}
14 AGC030D Manhattan Max Matching
Problem

2n2n 个位置

首先,对于 ini\leq n ,在 (xi,yi)(x_i,y_i)viv_i 个红点

对于 i>ni> n ,在 (xi,yi)(x_i,y_i)viv_i 个蓝点,总点数相等

两个点的匹配权值为两点曼哈顿距离,求红点和蓝点的最大权匹配

n1000,vi10,xi,yi109n\leq 1000,v_i\leq 10,|x_i|,|y_i|\leq 10^9

5s,1024MB5s,1024MB

Sol

考虑 (x1,y1),(x2,y2)(x_1,y_1),(x_2,y_2) 的曼哈顿距离为 max(x1+y1x2y2,x1y1x2+y1,x1+y1+x2y2,x1y1+x2+y2)max(x_1+y_1-x_2-y_2,x_1-y_1-x_2+y_1,-x_1+y_1+x_2-y_2,-x_1-y_1+x_2+y_2)

可以看成有四种匹配方式,每个红蓝点对有每种匹配方式有一个权值

考虑对于每个匹配方式建一个点,然后每个红蓝点向匹配方式连边

如果出现了一种没有取到max的匹配,它一定不是最大匹配

因此直接最大流即可

Code
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 2050
struct edge{int t,next,v,c;}ed[N*30];
int n,a,b,c,head[N],ct,cnt,vis2[N],vis3[N],vis[N];
long long dis[N],as;
void adde(int f,int t,int w,int c){ed[++cnt]=(edge){t,head[f],w,c};head[f]=cnt;ed[++cnt]=(edge){f,head[t],0,-c};head[t]=cnt;}
void bfs2(int t)
{
	memset(vis2,0,sizeof(vis2));
	queue<int> tp;
	tp.push(t);
	vis2[t]=1;
	while(!tp.empty())
	{
		int x=tp.front();tp.pop();
		for(int i=head[x];i;i=ed[i].next)
		if(ed[i^1].v)
		if(!vis2[ed[i].t])vis2[ed[i].t]=1,tp.push(ed[i].t);
	}
}
bool spfa_is_dead(int s,int t)
{
	bfs2(t);
	memset(vis,0,sizeof(vis));
	memset(vis3,0,sizeof(vis3));
	memset(dis,0x3f,sizeof(dis));
	queue<int> tp;
	tp.push(s);
	dis[s]=0;vis[s]=1;
	while(!tp.empty())
	{
		int x=tp.front();tp.pop();
		vis[x]=0;
		for(int i=head[x];i;i=ed[i].next)
		if(ed[i].v&&vis2[ed[i].t])
		if(dis[ed[i].t]>dis[x]+ed[i].c)
		{
			dis[ed[i].t]=dis[x]+ed[i].c;
			if(!vis[ed[i].t])vis[ed[i].t]=1,tp.push(ed[i].t);
		}
	}
	return dis[t]<=1e15;
}
int dfs(int u,int t,int f)
{
	if(u==t||!f)return f;
	vis3[u]=1;
	int as1=0,tp;
	for(int i=head[u];i;i=ed[i].next)
	if(!vis3[ed[i].t]&&vis2[ed[i].t]&&ed[i].v&&dis[ed[i].t]==dis[u]+ed[i].c&&(tp=dfs(ed[i].t,t,min(ed[i].v,f))))
	{
		as1+=tp,f-=tp;
		ed[i].v-=tp,ed[i^1].v+=tp;
		as+=1ll*ed[i].c*tp;
		if(!f)return as1;
	}
	return as1;
}
long long dinic(int s,int t)
{
	while(spfa_is_dead(s,t))
	dfs(s,t,1e8);
	return as;
}
int main()
{
	scanf("%d",&n);
	ct=6;cnt=1;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		++ct;
		adde(1,ct,c,0);
		adde(ct,3,c,a+b);
		adde(ct,4,c,a-b);
		adde(ct,5,c,-a+b);
		adde(ct,6,c,-a-b);
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		++ct;
		adde(ct,2,c,0);
		adde(3,ct,c,-a-b);
		adde(4,ct,c,-a+b);
		adde(5,ct,c,a-b);
		adde(6,ct,c,a+b);
	}
	printf("%lld\n",-dinic(1,2));
}
15 AGC024F Simple Subsequence Problem
Problem

给出若干长度小于等于 nn 的字符串组成的集合 SS

求至少是其中 kk 个串的子序列的串,输出最长的中字典序最小的一个

SS 由特殊方式给出

n20n\leq 20

2s,1024MB2s,1024MB

Sol

考虑子序列自动机的过程

看成有两个串 S,TS,T ,初始 TT 为空

有三种操作:

  1. 清空 SS ,结束
  2. 如果当前 SS 有0,向 TT 末尾加一个0,删去 SS 第一个0及以前的部分
  3. 如果当前 SS 有1,向 TT 末尾加一个1,删去 SS 第一个1及以前的部分

这样从 SSTT 的路径是唯一的

dpS,Tdp_{S,T} 表示这个状态的方案数,状态数只有 n22nn^22^n

预处理2和3对于每一个 SS 的转移,复杂度 O(n22n)O(n^22^n)

Code
#include<cstdio>
using namespace std;
#define N 22
int dp[1<<N][N],n,k,v[N],nt[N][2],ct,le,lg[1<<N];
char s[1<<N];
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=0;i<=n;i++)
	{
		scanf("%s",s);
		for(int j=0;j<1<<i;j++)
		dp[j|(1<<i)][0]=s[j]=='1';
	}
	for(int i=2;i<1<<n+1;i++)lg[i]=lg[i>>1]+1;
	for(int j=(1<<n+1)-1;j>=0;j--)
	{
		int le=lg[j];
		for(int k=1;k<=le;k++)v[k]=(bool)(j&(1<<k-1));
		nt[le][0]=nt[le][1]=-1;
		for(int k=le-1;k>=0;k--)
		{
			nt[k][0]=nt[k+1][0];nt[k][1]=nt[k+1][1];
			nt[k][v[k+1]]=k+1;
		}
		for(int k=0;k<=le;k++)
		if(dp[j][k])
		{
			dp[j&((1<<k)-1)][k+1]=dp[j&((1<<k)-1)][k+1]+dp[j][k];
			if(nt[k][0]!=-1)dp[(j&((1<<k)-1))|(j>>(nt[k][0]-1)<<k)][k+1]=dp[(j&((1<<k)-1))|(j>>(nt[k][0]-1)<<k)][k+1]+dp[j][k];
			if(nt[k][1]!=-1)dp[(j&((1<<k)-1))|(j>>(nt[k][1]-1)<<k)][k+1]=dp[(j&((1<<k)-1))|(j>>(nt[k][1]-1)<<k)][k+1]+dp[j][k];
		}
	}
	for(int l=n;l>=1;l--)
	for(int j=0;j<1<<l;j++)
	if(dp[j][l+1]>=k)
	{
		for(int i=l;i>=1;i--)printf("%d",(bool)(j&(1<<i-1)));
		return 0;
	}
}
16 CF571E Geometric Progressions
Problem

nn 个首项为 aia_i ,公比为 bib_i 的等比数列

如果它们没有公共项,输出-1,否则输出最小的公共项模 109+710^9+7

n100,a,b109n\leq 100,a,b\leq 10^9

1s,256MB1s,256MB

Sol

考虑如何合并两个数列

一个这样的序列形如 piai+kbi\prod p_i^{a_i+kb_i}

合并两个序列可以得到若干个与 k1,k2k_1,k_2 相关的二元一次方程

如果有两个不同的方程,可以直接解出唯一解,然后判无解

如果只有一个,那么解出不定方程求解

有各种特殊情况

复杂度 O(n2V)O(n^2\sqrt V)

Code

没有

17 CF696F ..Dary!
Problem

给一个 nn 个点的凸多边形,你需要放两个圆心在多边形内的半径为 rr 的圆,使得每条边所在的直线一定和一个圆有交,求最小的 rr 并输出一组方案

n300n\leq 300

3s,256MB3s,256MB

Sol

假设当前确定了两个圆心,可以发现离一个圆心更近的直线是某一连续的段

考虑二分答案 xx,把直线向内平移 xx,暴力的想法是枚举区间半平面交,复杂度为 O(n3lognlogv)O(n^3\log n\log v) ,常数过大过不去

注意到如果 [l,r][l,r] 合法, [l+1,r][l+1,r] 一定合法,这样每次增加右端点,总共只需要check O(n)O(n) 次,总复杂度 O(n2lognlogv)O(n^2\log n\log v)

Code
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<ctime>
using namespace std;
#define N 1505
struct point{double x,y;}q2[N],p[N],as[N],as2,as3;
point operator +(point a,point b){return (point){a.x+b.x,a.y+b.y};}
point operator -(point a,point b){return (point){a.x-b.x,a.y-b.y};}
point operator *(point a,double b){return (point){a.x*b,a.y*b};}
struct vec{point s,t;double ag;}ed[N],ed2[N],ed3[N],q[N];
int hd,tl,n,m,f[N];
double ag[N],rag[N],pi=acos(-1);
double cross(point x,point y,point z,point l){return (y.x-x.x)*(l.y-z.y)-(l.x-z.x)*(y.y-x.y);}
bool cmp(vec a,vec b)
{
	if(abs(a.ag-b.ag)<=1e-8)return cross(a.s,a.t,a.s,b.t)<0;
	return a.ag<b.ag;
}
point ins(vec a,vec b){return a.s+(a.t-a.s)*(cross(a.s,b.s,a.s,b.t)/(cross(a.s,b.s,a.s,a.t)+cross(a.s,a.t,a.s,b.t)));}
bool si(int s1,int s2)
{
	int tp=clock();
	q[1]=ed[1];hd=tl=1;
	int v1=2,v2=1;
	if(ed[1].ag>ed2[1].ag)v1=1,v2=2,q[1]=ed2[1];
	if(abs(ed[1].ag-ed2[1].ag)<=1e-8)
	{
		if(cmp(ed[1],ed2[1])==1)q[1]=ed[1];
		else q[1]=ed2[1];
		v1=v2=2;
	}
	for(int i=2;i<=s1+s2;i++)
	{
		int fg=0;
		if(v1==s1+1&&v2==s2+1)break;
		if(v2==s2+1)fg=1;
		else if(v1==s1+1)fg=0;
		else
		{
			if(abs(ed[v1].ag-ed2[v2].ag)<=1e-8)
			if(cmp(ed[v1],ed2[v2])==1)fg=1,v2++;
			else fg=0,v1++;
			else
			if(ed[v1].ag<ed2[v2].ag)fg=1;
			else fg=0;
		}
		if(fg)
		{
			if(hd<tl&&(cross(q[hd].s,q[hd].t,q[hd+1].s,q[hd+1].t)==0||cross(q[tl-1].s,q[tl-1].t,q[tl].s,q[tl].t)==0))return 0;
			while(hd<tl&&cross(ed[v1].s,ed[v1].t,ed[v1].s,q2[tl-1])<0)tl--;
			while(hd<tl&&cross(ed[v1].s,ed[v1].t,ed[v1].s,q2[hd])<0)hd++;
			q[++tl]=ed[v1];
			if(hd<tl)q2[tl-1]=ins(q[tl-1],q[tl]);
			v1++;
		}
		else
		{
			if(hd<tl&&(cross(q[hd].s,q[hd].t,q[hd+1].s,q[hd+1].t)==0||cross(q[tl-1].s,q[tl-1].t,q[tl].s,q[tl].t)==0))return 0;
			while(hd<tl&&cross(ed2[v2].s,ed2[v2].t,ed2[v2].s,q2[tl-1])<0)tl--;
			while(hd<tl&&cross(ed2[v2].s,ed2[v2].t,ed2[v2].s,q2[hd])<0)hd++;
			q[++tl]=ed2[v2];
			if(hd<tl)q2[tl-1]=ins(q[tl-1],q[tl]);
			v2++;
		}
	}
	while(hd<tl&&cross(q[hd].s,q[hd].t,q[hd].s,q2[tl-1])<0)tl--;
	while(hd<tl&&cross(q[tl].s,q[tl].t,q[tl].s,q2[hd])<0)hd++;
	if(tl-hd<=1)return 0;
	q2[tl]=ins(q[hd],q[tl]);
	int ct=0;
	for(int i=hd;i<=tl;i++)
	{
		point a=q2[i],b=q2[i==tl?hd:i+1];
		if(!(abs(a.x-b.x)<=1e-14&&abs(a.y-b.y)<=1e-14))ct++;
	}
	return ct;
}
int cnt=0;
bool check(int l,int r,double mid)
{
	int ct=0;cnt++;
	for(int i=1;i<=n;i++)ed[++ct]=(vec){p[i],p[i==n?1:i+1],ag[i]};
	ct=0;
	for(int i=l;i<=r;i++)
	{
		ed2[++ct]=(vec){p[i%n+1],p[(i-1)%n+1],rag[(i-1)%n+1]};
		double dis=sqrt((ed2[ct].t.y-ed2[ct].s.y)*(ed2[ct].t.y-ed2[ct].s.y)+(ed2[ct].t.x-ed2[ct].s.x)*(ed2[ct].t.x-ed2[ct].s.x));
		point tp=ed2[ct].t-ed2[ct].s;tp=tp*(mid/dis);
		point tp2=(point){tp.y,-tp.x};
		ed2[ct].s=ed2[ct].s+tp2,ed2[ct].t=ed2[ct].t+tp2;
	}
	int mn1=1;
	for(int i=1;i<=n;i++)if(ed[i].ag<ed[mn1].ag)mn1=i;
	for(int i=1;i<=n;i++)ed3[i]=ed[i];
	for(int i=1;i<=n;i++)ed[(i+n-mn1)%n+1]=ed3[i];
	mn1=1;
	for(int i=1;i<=ct;i++)if(ed2[i].ag<ed2[mn1].ag)mn1=i;
	for(int i=1;i<=ct;i++)ed3[i]=ed2[i];
	for(int i=1;i<=ct;i++)ed2[(i+ct-mn1)%ct+1]=ed3[i];
	if(!si(n,ct))return 0;
	as[l]=q2[hd];return 1;
}
bool solve(double mid)
{
	for(int i=1;i<=n;i++)
	{
		int las=f[i-1]-1;
		if(las<i)las=i;
		while(1)
		if(las<n+i&&check(i,las+1,mid))las++;
		else break;
		f[i]=las;
	}
	for(int i=1;i<=n;i++)
	if(f[i]>=n+i-1){as2=as3=as[i];return 1;}
	for(int i=1;i<=n;i++)
	if(f[f[i]%n+1]>=n+i-1){as2=as[i],as3=as[f[i]%n+1];return 1;}
	return 0;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lf%lf",&p[i].x,&p[i].y);
	for(int i=1;i<=n;i++)
	{
		vec a=(vec){p[i],p[i==n?1:i+1],0};
		ag[i]=atan2(a.t.y-a.s.y,a.t.x-a.s.x);
		rag[i]=atan2(a.s.y-a.t.y,a.s.x-a.t.x);
	}
	double lb=0,rb=1e5,as=1e5;
	for(int i=1;i<=60;i++)
	{
		double mid=(lb+rb)/2;
		if(solve(mid))rb=mid,as=rb;
		else lb=mid;
	}
	printf("%.10lf\n%.10lf %.10lf\n%.10lf %.10lf\n",as,as2.x,as2.y,as3.x,as3.y);
}
18 ARC093E Bichrome Spanning Tree
Problem

给一张图,求有多少种给边染色的方案,使得两种颜色边都出现的最小生成树边权和为 KK ,模 109+710^9+7

n,m2000n,m\leq 2000

1s,1024MB1s,1024MB

Sol

首先找出图的最小生成树

如果边权和大于 KK 无解

如果等于 KK ,考虑剩下的边,有一些边满足可以替代一条原最小生成树的边,设这些边有 aa

有两种情况

  1. 这棵最小生成树所有边同色,这时剩下的边至少需要有一条异色 ,方案数为 (2a1)(2m(n1)a)(2^a-1)*(2^{m-(n-1)-a})
  2. 这棵最小生成树异色,方案数为 (2n12)2mn+1(2^{n-1}-2)*2^{m-n+1}

如果小于 KK ,设 s(u,v)s_{(u,v)} 表示这条边的边权减去生成树上对应路径边权max

设最小生成树边权和为 sumsum

显然所有 s(u,v)<Ksums_{(u,v)}<K-sum 的边必须同色,对于等于的边,至少需要一条异色,大于的边没有限制

复杂度 O(nm)O(nm) 或者 O(mlogn)O(m\log n) 或者 O(m)O(m)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 1050
#define mod 1000000007
int head[N],cnt,s[N*2][3],fa[N],v[N*2],as,n,m,s1,s2,is[N*2];
long long su;
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]);}
bool cmp(int i,int j){return s[i][2]<s[j][2];}
void dfs(int u,int fa,int t,int v){if(u==t){as=v;return;}for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u,t,max(ed[i].v,v));}
int main()
{
	scanf("%d%d%lld",&n,&m,&su);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++)scanf("%d%d%d",&s[i][0],&s[i][1],&s[i][2]),v[i]=i;
	sort(v+1,v+m+1,cmp);
	for(int i=1;i<=m;i++)if(finds(s[v[i]][0])!=finds(s[v[i]][1]))is[v[i]]=1,fa[finds(s[v[i]][0])]=finds(s[v[i]][1]),su-=s[v[i]][2],adde(s[v[i]][0],s[v[i]][1],s[v[i]][2]);
	if(su<0)printf("0");
	else
	{
		for(int i=1;i<=m;i++)if(!is[i])
		{
			as=0;dfs(s[i][0],0,s[i][1],0);
			int tp=s[i][2]-as;
			if(tp==su)s1++;
			if(tp>su)s2++;
		}
		if(su==0)
		{
			int as=0,st1=1,st2=1;
			for(int i=1;i<=m;i++)st1=st1*2%mod;
			for(int i=1;i<=m-n+2-s1;i++)st2=st2*2%mod;
			as=(st1-st2+mod)%mod;
			printf("%d\n",as);
		}
		else
		{
			int as=0,st1=1,st2=1;
			for(int i=1;i<=s1;i++)st1=st1*2%mod;
			for(int i=1;i<=s2;i++)st2=st2*2%mod;
			as=2ll*(st1-1)*st2%mod;
			printf("%d\n",as);
		}
	}
}
19 CF573E Bear and Bowling
Problem

你有一个长度为 nn 的序列 aa ,你需要选出一个子序列 bb ,使 ibi\sum i*b_i 最大,求最大值

n105,ai107n\leq 10^5,|a_i|\leq 10^7

6s,512MB6s,512MB

Sol

不会

20 CF704E Iron Man
Problem

有一棵 nn 个点的树

mm 个人,第 ii 个人在时刻 tit_i 出现在 sis_i ,以 viv_i 条边每秒的速度向 tit_i 移动,到达终点后消失

求最早的存在两个人位置相同的时刻(可能是实数),没有输出-1

n,m105,vi1000n,m\leq 10^5,v_i\leq 1000

5s,256MB5s,256MB

Sol

考虑树剖,化为链上的情况

对于一条链的情况,考虑按照时间加入/删除点

维护当前点的顺序

注意到如果出现相交,相当于顺序交换

显然顺序交换只可能在相邻两个间出现

每次加入/删除时维护相邻两个相交的时间,每一个操作时判断这时是否出现相交即可

因为有各种一个点的情况,最好使用分数类实现,或者实数+多个精度可以爆过去

复杂度 O(mlogmlogn)O(m\log m\log n)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
#define N 200050
int head[N],cnt,dep[N],tp[N],id[N],sz[N],son[N],n,m,a,b,c,d,ct,f[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){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];if(sz[ed[i].t]>sz[son[u]])son[u]=ed[i].t;}}
void dfs2(int u,int v,int fa){f[u]=fa;tp[u]=v;dep[u]=dep[fa]+1;if(u==v)id[u]=++ct;if(son[u])dfs2(son[u],v,u);for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&ed[i].t!=son[u])dfs2(ed[i].t,ed[i].t,u);}
struct sth{double f,t;int s,v;};
vector<sth> v[N];
struct sth2{double t;int s,v,id,r;friend bool operator <(sth2 a,sth2 b){return a.t<b.t;}};
vector<sth2> tp1;
void solve(int l,int r,double s,int v1)
{
	int s1=l,s2=r;
	while(tp[s1]!=tp[s2])
	{
		if(dep[tp[s1]]<dep[tp[s2]])s1^=s2^=s1^=s2;
		s1=f[tp[s1]];
	}
	int le=dep[l]+dep[r]-min(dep[s1],dep[s2])*2;
	double st=s,et=s+1.0*le/v1;
	int fg=0;
	while(tp[l]!=tp[r])
	{
		if(dep[tp[l]]<dep[tp[r]])l^=r^=l^=r,fg^=1;
		int s3=id[tp[l]];
		if(!fg)v[s3].push_back((sth){st,st+1.0*(dep[l]-dep[tp[l]]+1)/v1,dep[l]-dep[tp[l]]+1,-v1}),st+=1.0*(dep[l]-dep[tp[l]]+1)/v1;
		else v[s3].push_back((sth){et-1.0*(dep[l]-dep[tp[l]]+1)/v1,et,0,v1}),et-=1.0*(dep[l]-dep[tp[l]]+1)/v1;
		l=f[tp[l]];
	}
	int s3=id[tp[l]];
	if(fg)l^=r^=l^=r;
	if(dep[l]<dep[r])v[s3].push_back((sth){st,st+1.0*(dep[r]-dep[l])/v1,dep[l]-dep[tp[l]]+1,v1});
	else v[s3].push_back((sth){st,st+1.0*(dep[l]-dep[r])/v1,dep[l]-dep[tp[l]]+1,-v1});
}
int ch[N][2],rt,fa[N],ct1,id2[N];
double s[N],vl[N],ti,mn=1e18;
priority_queue<double> as,as2;
bool cmp(int a,int b){return s[a]+ti*vl[a]<s[b]+ti*vl[b];}
double calc(int a,int b){if(vl[a]-vl[b]<1e-7&&vl[b]-vl[a]<1e-7)if(s[a]-s[b]<1e-7&&s[b]-s[a]<1e-7)return ti;else return 1e18;double tp=(s[a]-s[b])/(vl[b]-vl[a]);if(tp<ti)return 1e18;return tp;}
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;}
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 ins(int x,int y){int tp=cmp(x,y);if(!ch[x][tp]){ch[x][tp]=y;fa[y]=x;splay(y,0);return y;}return ins(ch[x][tp],y);}
double doit(int s1)
{
	tp1.clear();
	int sz=v[s1].size();
	for(int i=0;i<sz;i++)tp1.push_back((sth2){v[s1][i].f-1e-9,v[s1][i].s,v[s1][i].v,i,1}),tp1.push_back((sth2){v[s1][i].t,v[s1][i].s,v[s1][i].v,i,-1});
	sort(tp1.begin(),tp1.end());
	ch[1][0]=ch[2][1]=ch[2][0]=0;s[1]=-1e18,s[2]=1e18;vl[1]=vl[2]=0;ch[1][1]=2,fa[2]=1;fa[1]=0;rt=1;ct1=2;
	while(!as.empty())as.pop();while(!as2.empty())as2.pop();
	as.push(-calc(1,2));ti=0;
	for(int i=0;i<sz*2;i++)
	{
		while(as2.size()&&as.top()-as2.top()<=1e-7)as.pop(),as2.pop();
		ti=tp1[i].t;
		if(as.size()&&-as.top()<=tp1[i].t+1e-9)return -as.top();
		if(tp1[i].r==1)
		{
			int st=++ct1;
			fa[st]=ch[st][0]=ch[st][1]=0;s[st]=tp1[i].s-tp1[i].v*ti,vl[st]=tp1[i].v;id2[tp1[i].id]=st;
			ins(rt,st);
			int s1=ch[st][0],s2=ch[st][1];
			while(ch[s1][1])s1=ch[s1][1];
			while(ch[s2][0])s2=ch[s2][0];
			splay(s1);splay(s2);
			as2.push(-calc(s1,s2));
			as.push(-calc(s1,st));
			as.push(-calc(st,s2));
		}
		else
		{
			int st=id2[tp1[i].id];
			splay(st);
			int s1=ch[st][0],s2=ch[st][1];
			while(ch[s1][1])s1=ch[s1][1];
			while(ch[s2][0])s2=ch[s2][0];
			splay(s1);splay(s2,s1);
			as.push(-calc(s1,s2));
			as2.push(-calc(s1,st));
			as2.push(-calc(st,s2));
			ch[s2][0]=fa[st]=0;
		}
	}
	return 1e18;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs1(1,0);dfs2(1,1,0);
	for(int i=1;i<=m;i++)scanf("%d%d%d%d",&a,&b,&c,&d),solve(c,d,a,b);
	for(int i=1;i<=n;i++)if(tp[i]==i){double tp=doit(id[i]);if(tp<mn)mn=tp;}
	if(mn<1e18)printf("%.15lf\n",mn);else printf("-1\n");
}
21 ARC103D Robot Arms
Problem

给出 nn 个点的坐标,你需要构造一个 mm 以及 l1,...,lml_1,...,l_m ,满足:

  1. m40m\leq 40
  2. 对于每一个点,存在一个长度为 mm 的包含 LRUD 的字符串,使得从原点开始,每一段按照字符对应的方向行走 lil_i 单位长度,最后可以到达这个点

需要输出 m,lm,l 和每个点的方案或者输出无解

n1000,xi,yi109n\leq 1000,|x_i|,|y_i|\leq 10^9

2s,1024MB2s,1024MB

Sol

mmlogV\log V 级别,考虑倍增构造

显然所有点 x+yx+y 奇偶性不同时无解

如果所有点都是奇数,下一步可以通过放一个1将其全部变成 x+y=4k+2x+y=4k+2 型,然后全部除以2,变成坐标更小的奇数

但如果是偶数,直接除以2可能出现奇偶性的问题

如果都是偶数,先放一个1变成全是奇数的情况,然后按照奇数构造

复杂度 O(nlogV)O(n\log V)

Code
#include<cstdio>
using namespace std;
#define N 1050
#define K 36
char as[N][K];
int p[N][2],n,is1,is2;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&p[i][0],&p[i][1]);p[i][0]*=-1,p[i][1]*=-1;
		if((p[i][0]+p[i][1])&1)is1=1;
		else is2=1;
	}
	if(is1&&is2){printf("-1\n");return 0;}
	if(is1)
	{
		printf("31\n");
		for(int i=0;i<=30;i++)printf("%d ",1<<i);
		printf("\n");
		for(int i=0;i<30;i++)
		{
			int le=1<<i;
			for(int j=1;j<=n;j++)
			{
				if(p[j][0]>>i&1)
				{
					if(p[j][1]>>i&2)
					{
						int tp1=p[j][0]+le;
						if(tp1>>i&2)p[j][0]-=le,as[j][i]='L';
						else p[j][0]+=le,as[j][i]='R';
					}
					else
					{
						int tp2=p[j][0]-le;
						if(tp2>>i&2)p[j][0]-=le,as[j][i]='L';
						else p[j][0]+=le,as[j][i]='R';
					}
				}
				else
				{
					if(p[j][0]>>i&2)
					{
						int tp1=p[j][1]+le;
						if(tp1>>i&2)p[j][1]-=le,as[j][i]='D';
						else p[j][1]+=le,as[j][i]='U';
					}
					else
					{
						int tp2=p[j][1]-le;
						if(tp2>>i&2)p[j][1]-=le,as[j][i]='D';
						else p[j][1]+=le,as[j][i]='U';
					}
				}
			}
			for(int i=1;i<=n;i++)
			if(p[i][1])as[i][30]=p[i][1]>0?'D':'U';else as[i][30]=p[i][0]>0?'L':'R';
		}
		for(int i=1;i<=n;i++)printf("%s\n",as[i]);
	}
	else
	{
		printf("32\n1 ");
		for(int i=0;i<=30;i++)printf("%d ",1<<i);
		printf("\n");
		for(int i=1;i<=n;i++)p[i][0]--,as[i][0]='L';
		for(int i=0;i<30;i++)
		{
			int le=1<<i;
			for(int j=1;j<=n;j++)
			{
				if(p[j][0]>>i&1)
				{
					if(p[j][1]>>i&2)
					{
						int tp1=p[j][0]+le;
						if(tp1>>i&2)p[j][0]-=le,as[j][i+1]='L';
						else p[j][0]+=le,as[j][i+1]='R';
					}
					else
					{
						int tp2=p[j][0]-le;
						if(tp2>>i&2)p[j][0]-=le,as[j][i+1]='L';
						else p[j][0]+=le,as[j][i+1]='R';
					}
				}
				else
				{
					if(p[j][0]>>i&2)
					{
						int tp1=p[j][1]+le;
						if(tp1>>i&2)p[j][1]-=le,as[j][i+1]='D';
						else p[j][1]+=le,as[j][i+1]='U';
					}
					else
					{
						int tp2=p[j][1]-le;
						if(tp2>>i&2)p[j][1]-=le,as[j][i+1]='D';
						else p[j][1]+=le,as[j][i+1]='U';
					}
				}
			}
			for(int i=1;i<=n;i++)if(p[i][1])as[i][31]=p[i][1]>0?'D':'U';else as[i][31]=p[i][0]>0?'L':'R';
		}
		for(int i=1;i<=n;i++)printf("%s\n",as[i]);
	}
}
22 CF627F Island Puzzle
Problem

有一棵树,上面有 [1,n1][1,n-1] 的所有数和一个空位

你可以加0或1条边,使得存在一种数的移动方式能从初始状态到目标状态

不能有两个数同时在一个点

你需要输出加边数最小且边数最小的情况下移动次数最小的加边方案和移动次数

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

2s,256MB2s,256MB

Sol

如果不加边,空位的移动方式唯一,这时很好判断合法性

如果加一条边,考虑先把空位移过去,然后一定是进行如下操作:

把空位移到环上,进行操作,然后空位移回去

注意到这样会改变的点只可能有环上除了离终点最近的点以外的点,因此可以求出所有移过去后需要改变的点,如果这些点分成了三条或以上路径无解,如果分成两条方案唯一,如果一条则需要判一下哪一边的子树内有终点

这样就唯一确定了连的边

然后考虑这个环上的情况,枚举两种旋转方向搞一下就行了

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define N 200500
int v1[N],v2[N],v3[N],s,t,head[N],cnt,is[N],fr[N],ct,st[N],fg,s1,s2,s3,s4,s5,a,b,st2[N],ct2,vl[N],c1,ins[N],n,fr2[N],is2[N],d1,is3[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)
{
	if(!fg)st[++ct]=u;
	if(u==t)fg=1;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa)dfs(ed[i].t,u);
	if(!fg)ct--;
}
bool check(int l,int r)
{
	t=r;ct=0;fg=0;dfs(l,0);
	for(int i=1;i<=n;i++)ins[i]=0;
	for(int i=1;i<=ct;i++)ins[st[i]]=1;
	for(int i=1;i<=n;i++)if(is[i]&&!ins[i])return 0;
	return 1;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){scanf("%d",&v1[i]);if(!v1[i])s=i;}
	for(int i=1;i<=n;i++){scanf("%d",&v2[i]);if(!v2[i])t=i;}
	int v7=t;
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs(s,0);s3=s,s4=t;
	for(int i=1;i<=n;i++)v3[i]=v1[i];
	for(int i=1;i<ct;i++)v3[st[i]]=v3[st[i+1]];
	v3[st[ct]]=0;
	int fg1=0;
	for(int i=1;i<=n;i++)
	if(v2[i]!=v3[i])
	{
		fg1=1;
		is[i]=1;
		for(int j=head[i];j;j=ed[j].next)fr[ed[j].t]++;
	}
	if(!fg1){printf("0 %d\n",ct-1);return 0;}
	for(int i=1;i<=n;i++)if(is[i]&&fr[i]<=1)vl[++c1]=i;
	if(c1>4){printf("-1\n");return 0;}
	for(int i=1;i<=c1&&fg1==1;i++)
	for(int j=i+1;j<=c1;j++)
	if(check(vl[i],vl[j])){s1=vl[i],s2=vl[j];fg1=2;break;}
	t=s2;ct=fg=0;dfs(s1,0);for(int i=1;i<=ct;i++)is3[st[i]]=1;
	if(fg1==1){printf("-1\n");return 0;}
	int v4=0;for(int i=1;i<=ct;i++)if(!is[st[i]]){if(v4){printf("-1\n");return 0;}v4=st[i];}
	if(!v4)
	{
		t=s1;ct=fg=0;dfs(v7,0);
		if(ct>1&&!is[st[ct-1]])v4=st[ct-1],s1=v4;
		else
		{
			t=s2;ct=fg=0;dfs(v7,0);
			if(ct>1&&!is[st[ct-1]])v4=st[ct-1],s2=v4;
			else
			{
				int st2=0,st3=(s1==v7?s2:s1);
				for(int i=head[st3];i;i=ed[i].next)
				if(!is[ed[i].t]){v4=ed[i].t,st2=1;if(s1==s)s2=v4;else s1=v4;}
				if(!st2){printf("-1\n");return 0;}
			}
		}	
	}
	else
	{
		t=v4;ct=0;fg=0;dfs(s,0);
		for(int i=1;i<=ct;i++)if(is3[st[i]]){v4=st[i];break;}
	}
	t=v4;ct=0;fg=0;dfs(s,0);
	for(int i=1;i<=n;i++)v3[i]=v1[i];
	for(int i=1;i<ct;i++)v3[st[i]]=v3[st[i+1]];
	v3[st[ct]]=0;
	long long as=ct-1;
	t=s2;ct=0;fg=0;dfs(s1,0);
	int ct1=0,ct2=0,fg2=-1,tp=0;
	for(int i=1;i<=ct;i++)fr2[v2[st[i]]]=i,is2[st[i]]=1;
	for(int i=1;i<=ct;i++)
	{
		if(!v3[st[i]])continue;
		int nt=fr2[v3[st[i]]];fr2[v3[st[i]]]=0;
		if(!nt){printf("-1\n");return 0;}
		int st=i-nt+(i<nt?ct:0);
		if(fg2==-1)fg2=st,ct1=1;
		else
		{
			if(fg2==st)ct1++;
			else if(fg2==st-1)ct2++;
			else if(fg2==st+1){if(ct2){printf("-1\n");return 0;}ct2=ct1;ct1=1,fg2--;}
			else if(fg2==0&&st+1==ct){if(ct2){printf("-1\n");return 0;}ct2=ct1,ct1=1,fg2=ct-1;}
			else if(st==0&&fg2==ct-1)ct2++;
			else {printf("-1\n");return 0;}
		}
	}
	for(int i=1;i<=ct;i++)v3[st[i]]=v2[st[i]];
	long long as1=1ll*ct1*fg2+1ll*ct2*(fg2+1),as2=1ll*ct1*(ct-fg2)+1ll*ct2*(ct-fg2-1);
	if(as2<as1)as1=as2;
	as+=as1;
	for(int i=1;i<=n;i++)if(fr2[v2[i]])s5=i;
	v3[s5]=0;
	t=s4;ct=0;fg=0;
	dfs(s5,0);
	for(int i=1;i<ct;i++)v3[st[i]]=v3[st[i+1]];
	v3[st[ct]]=0;
	for(int i=1;i<=n;i++)if(v2[i]!=v3[i]){printf("-1\n");return 0;}
	as+=ct-1;
	if(s1>s2)s1^=s2^=s1^=s2;
	printf("%d %d %lld\n",s1,s2,as);
}
23 AGC035D Add and Remove
Problem

有长度为 nn 的序列,你每次可以选一个中间的数,将它两侧的数加上它,然后删掉它

求最后剩下两个数的最小值

n18,Ai109n\leq 18,A_i\leq 10^9

2s,1024MB2s,1024MB

Sol

考虑最后一个删的数,可以发现这个数对和的贡献是左右两个数对答案贡献的和,然后左右两侧剩下的是独立的

dpl,r,x,ydp_{l,r,x,y} 表示区间 [l,r][l,r] ,左边数的贡献是 xx ,右边贡献是 yy ,当前的最小值

转移有 dpl,l+1,x,y=Alx+Al+1ydp_{l,l+1,x,y}=A_l*x+A_{l+1}*y

dpl,r,x,y=minl<i<rdpl,i,x,x+y+dpi,r,x+y,yAi(x+y)dp_{l,r,x,y}=\min_{l<i<r} dp_{l,i,x,x+y}+dp_{i,r,x+y,y}-A_i*(x+y)

转移时直接搜,复杂度不超过 O(n32n2)O(n^32^{n-2})

Code
#include<cstdio>
using namespace std;
#define ll long long
ll n,v[19],ct;
ll Min(ll a,ll b){return a<b?a:b;}
ll DP(int l,int r,int s1,int s2)
{
	if(l+1==r)return v[l]*s1+v[r]*s2;
	long long as=2e18;
	for(int i=l+1;i<r;i++)as=Min(as,DP(l,i,s1,s1+s2)+DP(i,r,s1+s2,s2)-v[i]*(s1+s2));
	return as;
}
int main(){scanf("%lld",&n);for(int i=1;i<=n;i++)scanf("%lld",&v[i]);printf("%lld\n",DP(1,n,1,1));}
24 AGC033F Adding Edges
Problem

有一棵树和一张图,反复进行如下操作:

选出三个点 a,b,ca,b,c ,使得图中 (a,b),(b,c)(a,b),(b,c) 有边, (a,c)(a,c) 无边,在树中这三个点按某种顺序排列在一条链上,在图中加边 (a,c)(a,c)

求最后图的边数

n,m2000n,m\leq 2000

2s,1024MB2s,1024MB

Sol

不会

25 CF538G Berserk Robot
Problem

有一个长度为 ll 的包含 LRUD 的指令序列,一个机器人从原点开始,循环执行指令

给出 nn 个限制,要求时刻 tit_i 时机器人在 (xi,yi)(x_i,y_i)

构造一组

合法解或者返回无解

n2×105,l2×106,xi,yi,ti1018n\leq 2\times 10^5,l\leq 2\times 10^6,|x_i|,|y_i|,t_i\leq 10^{18}

2s,256MB2s,256MB

Sol

一个二维游走的套路:旋转45度,变为两维分别决定+1-1的问题

因此可以将问题分成两个一维问题

考虑一维的情况,设 ii 步之后停留在 viv_i ,那么如果一个限制是 ti,xit_i,x_i ,相当于时刻 timodlt_i \bmod l 时在 xivltilx_i-v_l*\left\lfloor\frac{t_i}{l}\right\rfloor ,即 vtimodl=xivltilv_{t_i\bmod l}=x_i-v_l*\left\lfloor\frac{t_i}{l}\right\rfloor

同时还有 v0=0v_0=0 的限制

这时因为时刻都在 [0,l][0,l] 中,没有其余的限制,只需要对于相邻两个 i,ji,j ,满足 ijvjvijii-j\leq v_j-v_i\leq j-i 和奇偶性即可

枚举 vlv_l 的奇偶性,暴力解方程即可得到一个 vlv_l 的解

然后贪心构造即可

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

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 200060
#define ll long long
#define fail {printf("NO\n");exit(0);}
struct sth{ll x,y,t;friend bool operator <(sth a,sth b){return a.t<b.t;}}s[N];
ll n,l,v[N][3],as[N*10],fg1,su[N*10][2];
char vl[5]="LUDR";
void solve(int vl)
{
	int ct=n+2,as1=0,as2=0;
	if(l&1)as2=1;
	else as1=1;
	s[n+1]=(sth){0,0,0};
	s[n+2]=(sth){0,1,l};
	double smn=-l,smx=l;
	sort(s+1,s+ct+1);
	for(int i=2;i<=ct;i++)
	{
		ll s1=s[i].x-s[i-1].x,s2=s[i].y-s[i-1].y,t1=s[i].t-s[i-1].t;
		if(s2&1)
		{
			if((s1-t1)&1)as2=1;
			else as1=1;
		}
		else if((s1-t1)&1)fail;
		if(s2==0){if(s1<-t1||s1>t1)fail;continue;}
		double mn=1.0*(-t1-s1)/s2,mx=1.0*(t1-s1)/s2;
		if(mn>mx)swap(mn,mx);
		if(smn<mn)smn=mn;
		if(smx>mx)smx=mx;
	}
	ll ans=(ll)(smn-1e-6)+1;
	if(smn-1e-6<=0)ans=smn;
	if(as1&&as2)fail;
	if(ans&1)if(as1)ans++;
	if(~ans&1)if(as2)ans++;
	if(ans>smx)fail;
	for(int i=2;i<=ct;i++)
	{
		ll s1=s[i].x-s[i-1].x+(s[i].y-s[i-1].y)*ans,t1=s[i].t-s[i-1].t;
		ll v1=(t1-s1)/2;
		for(int j=s[i-1].t+1;j<=s[i-1].t+v1;j++)as[j]-=vl;
	}
}
int main()
{
	scanf("%lld%lld",&n,&l);for(int i=1;i<=l;i++)as[i]=3;
	for(int i=1;i<=n;i++)scanf("%lld%lld%lld",&v[i][0],&v[i][1],&v[i][2]);
	for(int i=1;i<=n;i++)s[i].x=v[i][1]+v[i][2],s[i].y=-v[i][0]/l,s[i].t=v[i][0]%l;
	solve(1);
	for(int i=1;i<=n;i++)s[i].x=v[i][1]-v[i][2],s[i].y=-v[i][0]/l,s[i].t=v[i][0]%l;
	solve(2);
	for(int i=1;i<=l;i++)
	{
		if(as[i]==0)su[i][0]=su[i-1][0]-1,su[i][1]=su[i-1][1];
		if(as[i]==1)su[i][0]=su[i-1][0],su[i][1]=su[i-1][1]+1;
		if(as[i]==2)su[i][0]=su[i-1][0],su[i][1]=su[i-1][1]-1;
		if(as[i]==3)su[i][0]=su[i-1][0]+1,su[i][1]=su[i-1][1];
	}
	for(int i=1;i<=n;i++)
	{
		ll asx=v[i][0]/l*su[l][0]+su[v[i][0]%l][0],asy=v[i][0]/l*su[l][1]+su[v[i][0]%l][1];
		if(asx!=v[i][1]||asy!=v[i][2])fail;
	}
	for(int i=1;i<=l;i++)printf("%c",vl[as[i]]);
}
26 CF674D Bearish Fanpages
Problem

nn 个公司,有 tit_i 个人会访问第 ii 个公司的主页

每个公司关注了另外一个公司,保证不存在互相关注

对于一个公司,它会显示自己的,它关注的公司以及关注它的公司的广告

如果一共有 kk 个,那么每一个其它的广告会有 tik\left\lfloor\frac{t_i}{k}\right\rfloor 个人访问,剩下的人会访问这家公司的广告

有三种操作:

更改一个公司的关注

输出一家公司广告的访问量

输出所有公司广告访问量的最大最小值

n,q105n,q\leq 10^5

5s,256MB5s,256MB

Sol

考虑从 u>vu->v 更改到 u>tu->t 的影响

可以发现只会影响 u,v,tu,v,t , u,v,tu,v,t 关注的公司,所有关注 v,tv,t 的公司

并且对于后面一部分,所有关注 vv 的公司的修改量是一样的,另外一个也一样

对于每一个 uu ,对所有关注 uu 的公司维护minmax,然后所有的minmax拿出来再minmax,这样复杂度即为 O((n+q)logn)O((n+q)\log n)

Code
#include<cstdio>
#include<set>
#include<queue>
using namespace std;
#define N 1000500
int n,m,a,b,c,f[N],in[N];
long long v[N],su[N],fg[N];
multiset<long long> s[N],v1[N];
priority_queue<long long> tp1,tp2,tp3,tp4;
void del(int i){if(in[i]>2)tp2.push(-*s[i].begin()-fg[i]),tp4.push(*s[i].rbegin()+fg[i]);}
void ins(int i){if(in[i]>2)tp1.push(-*s[i].begin()-fg[i]),tp3.push(*s[i].rbegin()+fg[i]);}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%lld",&v[i]),in[i]=2;
	for(int i=1;i<=n;i++)scanf("%d",&f[i]),in[f[i]]++,v1[f[i]].insert(i);
	for(int i=1;i<=n;i++)
	{
		long long tp=v[i],s1=tp/in[i];
		for(multiset<long long>::iterator it=v1[i].begin();it!=v1[i].end();it++)
		su[*it]+=s1,tp-=s1;
		su[f[i]]+=s1,tp-=s1;
		su[i]+=tp;
	}
	for(int i=1;i<=n;i++)s[f[i]].insert(su[i]);
	for(int i=1;i<=n;i++)if(v1[i].size())ins(i);
	while(m--)
	{
		scanf("%d",&a);
		if(a==1)
		{
			scanf("%d%d",&b,&c);
			int l1=f[b],l2=f[l1],l3=f[l2];
			del(l1);
			s[l1].erase(s[l1].find(su[b]));
			su[b]+=fg[l1];su[b]-=v[l1]/in[l1];
			long long vl1=v[l1]/(in[l1]-1)-v[l1]/in[l1],vl2=v[l1]-v[l1]/(in[l1]-1)*(in[l1]-2)-v[l1]+v[l1]/in[l1]*(in[l1]-1);
			in[l1]--;fg[l1]+=vl1;
			del(l2);s[l2].erase(s[l2].find(su[l1]));
			su[l1]+=vl2-v[b]/in[b];s[l2].insert(su[l1]);ins(l2);
			del(l3);s[l3].erase(s[l3].find(su[l2]));
			su[l2]+=vl1;s[l3].insert(su[l2]);ins(l3);
			ins(l1);f[b]=c;
			l1=f[b],l2=f[l1],l3=f[l2];
			del(l1);
			su[b]-=fg[l1];su[b]+=v[l1]/in[l1];
			s[l1].insert(su[b]);
			vl1=-v[l1]/in[l1]+v[l1]/(in[l1]+1),vl2=-v[l1]+v[l1]/in[l1]*(in[l1]-1)+v[l1]-v[l1]/(in[l1]+1)*in[l1];
			in[l1]++;fg[l1]+=vl1;
			del(l2);s[l2].erase(s[l2].find(su[l1]));
			su[l1]+=vl2+v[b]/in[b];s[l2].insert(su[l1]);ins(l2);
			del(l3);s[l3].erase(s[l3].find(su[l2]));
			su[l2]+=vl1;s[l3].insert(su[l2]);ins(l3);
			ins(l1);
		}
		else if(a==2)scanf("%d",&b),printf("%lld\n",su[b]+fg[f[b]]);
		else
		{
			while(tp1.size()&&tp2.size()&&tp1.top()==tp2.top())tp1.pop(),tp2.pop();
			while(tp3.size()&&tp4.size()&&tp3.top()==tp4.top())tp3.pop(),tp4.pop();
			printf("%lld %lld\n",-tp1.top(),tp3.top());
		}
	}
}
27 ARC101F Robots and Exits
Problem

nn 个机器人和 mm 个出口,有两种操作:

  1. 所有机器人坐标+1
  2. 所有机器人坐标-1

如果一个机器人到达了一个出口,它就会从那里离开

一直执行操作直到所有机器人离开

对于每个机器人,记录它离开的位置

求记录的种数模 109+710^9+7

n,m105n,m\leq 10^5

2s,1024MB2s,1024MB

Sol

对于只有一侧有出口的可以直接无视

注意到只需要关注向左走的最大距离 aa 和向右走的最大距离 bb

显然两个都是递增的,放在二维平面上,可以看成从 (0,0)(0,0) 向右上走

对于每个点,记录两侧出口距离 (x,y)(x,y)

一个机器人离开当且仅当 axa\geq x 或者 byb\geq y

可以发现, (a,b)(a,b) 变换形成的折线上面部分和线上横向部分的点从左侧离开,其余从右侧离开

xx 排序,考虑dp,设 dpidp_{i} 表示上一个从右侧离开的折线边界上的点是 ii 的方案数,暴力枚举转移下一个点复杂度为 O(n2)O(n^2)

注意到转移条件只和 yy 相关,可以线段树/树状数组优化

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

Code
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
#define N 100500
#define mod 1000000007
multiset<int> tp;
int n,v[N],l[N],r[N],s[N],m,a;
struct node{int l,r,su;}e[N*4];
void pushup(int x){e[x].su=(e[x<<1].su+e[x<<1|1].su)%mod;}
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 add(int x,int s,int v){if(e[x].l==e[x].r){e[x].su=(e[x].su+v)%mod;return;}int mid=(e[x].l+e[x].r)>>1;if(mid>=s)add(x<<1,s,v);else add(x<<1|1,s,v);pushup(x);}
int que(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 que(x<<1,l,r);else if(mid<l)return que(x<<1|1,l,r);return (que(x<<1,l,mid)+que(x<<1|1,mid+1,r))%mod;}
bool cmp(int i,int j){return l[i]==l[j]?r[i]>r[j]:l[i]<l[j];}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	tp.insert(-1e9-10);tp.insert(2e9+5);
	for(int i=1;i<=m;i++)scanf("%d",&a),tp.insert(a);
	for(int i=1;i<=n;i++)
	{
		multiset<int>::iterator it=tp.lower_bound(v[i]);
		r[i]=*it-v[i];it--;l[i]=v[i]-*it;s[i]=i;v[i]=r[i];
	}
	sort(s+1,s+n+1,cmp);sort(v+1,v+n+2);
	build(1,1,n+1);add(1,1,1);
	for(int i=1;i<=n;i++)
	if(l[s[i]]!=l[s[i-1]]||r[s[i]]!=r[s[i-1]])
	{
		int t=lower_bound(v+1,v+n+2,r[s[i]])-v;
		if(r[s[i]]>1e9||l[s[i]]>1e9)continue;
		add(1,t,que(1,1,t-1));
	}
	printf("%d\n",que(1,1,n+1));
}
28 CF566C Logistical Questions
Problem

给一棵带点权和边权的数,求一个点 uu ,使得 disu,x3/2vx\sum dis_{u,x}^{3/2}*v_x 最小,输出最小值和选点

n2×105,0vi108n\leq 2\times 10^5,0\leq v_i\leq 10^8

2s,512MB2s,512MB

Sol

多个凸函数的和一定是凸函数,可以贪心找最小

考虑当前选的是 uu ,向 vv 移动是否优秀

如果在 u>vu->v 上点 uu 处求导,可以得到 3/2(xsubtreeofvdisx,u1/2xsubtreeofvdisx,u1/2)3/2*(\sum_{x\notin subtree of v} dis_{x,u}^{1/2}-\sum_{x\in subtree of v} dis_{x,u}^{1/2})

因此,可以在一个点用 O(n)O(n) 的时间确定向哪个子树更优秀

考虑点分治,每次一定能将合法范围减半,复杂度 O(nlogn)O(n\log n)

Code
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define N 205000
int n,dis[N],head[N],vis[N],sz[N],vl,tp,as,as1,v[N],a,b,c,cnt;
double su[N],su2[N],as2=1e25;
struct edge{int t,next,l;}ed[N*2];
void adde(int f,int t,int l){ed[++cnt]=(edge){t,head[f],l};head[f]=cnt;ed[++cnt]=(edge){f,head[t],l};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(!vis[ed[i].t]&&ed[i].t!=fa)dfs1(ed[i].t,u),mx=max(mx,sz[ed[i].t]),sz[u]+=sz[ed[i].t];
	mx=max(mx,vl-sz[u]);
	if(mx<tp)tp=mx,as=u;
}
void dfs2(int u,int fa)
{
	su[u]=pow(dis[u],1.5)*v[u];
	su2[u]=pow(dis[u],0.5)*v[u];
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dis[ed[i].t]=dis[u]+ed[i].l,dfs2(ed[i].t,u),su[u]+=su[ed[i].t],su2[u]+=su2[ed[i].t];
}
void solve(int x)
{
	dis[x]=0;dfs2(x,0);vis[x]=1;
	if(as2>su[x])as2=su[x],as1=x;
	for(int i=head[x];i;i=ed[i].next)
	if(su2[x]<2*su2[ed[i].t]&&!vis[ed[i].t])
	{
		dfs1(x,0);
		tp=1e7,vl=sz[ed[i].t];dfs1(ed[i].t,0);
		solve(as);
		break;
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<n;i++)scanf("%d%d%d",&a,&b,&c),adde(a,b,c);
	solve(1);printf("%d %.15lf\n",as1,as2);
}
29 CF700E Cool Slogans
Problem

有一个字符串 tt ,求最大的 kk ,使得存在 s1,...,sks_1,...,s_k 满足

  1. sis_itt 的子串
  2. sis_isi+1s_{i+1} 中至少出现两次

t2×105|t|\leq 2\times 10^5

4s,512MB4s,512MB

Sol

如果 sis_i 不是 si+1s_{i+1} 的前缀,删去 si+1s_{i+1} 的第一个字符一定更优秀

因此可以令 sis_isi+1s_{i+1} 的前缀

建出后缀树,设 dpxdp_x 表示最大的 kk 满足存在这个点对应字符串为 sks_k 的一组 s1,...,sks_1,...,s_k

可以证明,只考虑每个点最长的串一定最优

显然一个点只能从它的祖先转移,可持久化线段树合并维护每个点的endpos集合,对于一个串 [l,r][l,r] 和一个长度为len的祖先,显然祖先对应字符串在这个串中的匹配位置为endpos中在 [l+len1,r][l+len-1,r] 中的位置,那么线段树上查即可

显然每个点只能从一段祖先中转移过来

考虑维护每个点能被转移到的深度最大的点,对于一个点,如果某个点能转移到它父亲,那它一定能转移到这个点,那么可以从它父亲的能转移到的最大的点开始向下依次check即可

复杂度 O(tlogt+26t)O(|t| \log |t|+26|t|)

Code
#include<cstdio>
#include<queue>
using namespace std;
#define N 400050
#define M 11000500
int rt[N],ch[M][2],sz[M],ct,n,dp[N],pt[N],f[N][21],head[N],cnt,dep[N],in[N],qu[N],ct2,as;
char v[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 SAM{
	int ch[N][26],fail[N],len[N],las,ct,st1[N],ct2;
	void ins(int t)
	{
		int s1=las,st=++ct;las=ct;st1[++ct2]=st;len[st]=ct2;
		while(!ch[s1][t]&&s1)ch[s1][t]=st,s1=fail[s1];
		if(!ch[s1][t]){fail[st]=1;return;}
		int tp=ch[s1][t];
		if(len[tp]==len[s1]+1)fail[st]=tp;
		else
		{
			int 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][t]==tp)ch[s1][t]=cl,s1=fail[s1];
		}
	}
}s;
void init()
{
	for(int i=1;i<=n;i++)
	{
		int t=s.st1[i],st=++ct;
		rt[t]=st,sz[st]=1;
		int lb=1,rb=n;
		while(lb!=rb)
		{
			int mid=(lb+rb)>>1;
			if(mid>=i)rb=mid,ch[st][0]=++ct,sz[ct]=1,st=ct;
			else lb=mid+1,ch[st][1]=++ct,sz[ct]=1,st=ct;
		}
	}
}
int merge(int s,int t)
{
	if(!s||!t)return s+t;
	int st=++ct;sz[st]=sz[s]+sz[t];
	ch[st][0]=merge(ch[s][0],ch[t][0]);ch[st][1]=merge(ch[s][1],ch[t][1]);
	return st;
}
int query(int x,int l,int r,int l1,int r1)
{
	if(!x)return 0;
	if(l==l1&&r==r1)return sz[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 query(ch[x][0],l,mid,l1,mid)+query(ch[x][1],mid+1,r,mid+1,r1);
}
void topsort()
{
	for(int i=1;i<=s.ct;i++)in[s.fail[i]]++;
	queue<int> tp;
	for(int i=1;i<=s.ct;i++)if(!in[i])tp.push(i);
	while(!tp.empty())
	{
		int t=tp.front();tp.pop();qu[++ct2]=t;
		rt[s.fail[t]]=merge(rt[s.fail[t]],rt[t]);
		in[s.fail[t]]--;
		if(!in[s.fail[t]])tp.push(s.fail[t]);
	}
}
void dfs(int u,int fa){f[u][0]=fa;dep[u]=dep[fa]+1;for(int i=1;i<=19;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)dfs(ed[i].t,u);}
int doit(int x,int d){for(int i=19;i>=0;i--)if(d>>i&1)x=f[x][i];return x;}
int doit2(int x,int l,int r){if(l==r)return l;int mid=(l+r)>>1;if(ch[x][0])return doit2(ch[x][0],l,mid);else return doit2(ch[x][1],mid+1,r);}
int main()
{
	scanf("%d%s",&n,v+1);s.las=s.ct=1;
	for(int i=1;i<=n;i++)s.ins(v[i]-'a');
	init();topsort();
	for(int i=2;i<=s.ct;i++)adde(s.fail[i],i);dfs(1,0);
	for(int i=ct2-2;i>=1;i--)
	{
		int tp=qu[i],las=pt[s.fail[tp]],tp1=doit2(rt[tp],1,n);
		while(1)
		{
			int nt=doit(tp,dep[tp]-dep[las]-1);
			if(nt==1||query(rt[nt],1,n,tp1-s.len[tp]+s.len[nt],tp1)>=2)las=nt;
			else break;
		}
		pt[tp]=las;dp[tp]=dp[las]+1;if(as<dp[tp])as=dp[tp];
	}
	printf("%d\n",as);
}
30 ARC092F Two Faced Edges
Problem

给一张 nn 个点 mm 条边的有向图,求每条边翻转后强连通分量数是否改变

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

5s,512MB5s,512MB

Sol

首先求出原图的scc

考虑一条连接两个scc的边 u>vu->v ,如果从 uuvv 存在大于一条路径,那么翻转后scc一定会减少,否则scc一定不变

这部分可以对于每个点dfs求出,复杂度 O(nm)O(nm)

考虑每个scc内部,先求出一棵dfs树,对于一条非树边 u>vu->v ,在树上可以看成 lca>u,lca>vlca -> u,lca->v 两部分

翻转后,考虑分裂成多个scc的条件

可以发现一定是在 lca>ulca->u 的路径上有一个点,这个点dfn=low,也就是子树内的点没有一条连向子树外

在原图中,相当于子树内的点只有一条连向子树外

对于一条非树边 u>vu->v ,它对 [u,lca)[u,lca) 这些点都满足是一条子树内的点连向子树外的边,可以树上差分求出每个点子树内的情况

然后对于每条边暴力判断路径上有没有只有一条的点,复杂度 O(nm)O(nm)

对于scc的dfs树边,因为只有 O(n)O(n) 条,暴力判断即可

总复杂度 O(nm)O(nm)

Code
#include<cstdio>
#include<stack>
#include<ctime>
using namespace std;
#define N 1050
#define M 200500
int head[N],cnt,scc[N],dfn[N],low[N],f[N][13],s[M][2],as[M],vl[N],ct,ct2,vis[N][N],id[N],ct1,dep[N],is[N],n,m,ct3;
struct edge{int t,next,id;}ed[M];
void adde(int f,int t,int id){ed[++cnt]=(edge){t,head[f],id};head[f]=cnt;}
stack<int> tp,tp2,v[N],tp3;
void dfs1(int u)
{
	dfn[u]=low[u]=++ct1;
	tp.push(u);
	for(int i=head[u];i;i=ed[i].next)
	{
		if(!dfn[ed[i].t])dfs1(ed[i].t),low[u]=min(low[u],low[ed[i].t]);
		else if(!scc[ed[i].t])low[u]=min(low[u],dfn[ed[i].t]);
	}
	if(dfn[u]==low[u])
	{
		int s=tp.top(),id=++ct2;tp.pop();
		scc[s]=id;
		while(s!=u)
		s=tp.top(),tp.pop(),scc[s]=id;
	}
}
void dfs2(int u,int s){for(int i=head[u];i;i=ed[i].next)if(!vis[s][ed[i].t])vis[s][ed[i].t]=1,dfs2(ed[i].t,s);else vis[s][ed[i].t]++;}
int LCA(int x,int y){if(dep[x]<dep[y])x^=y^=x^=y;for(int i=10;i>=0;i--)if(dep[x]-dep[y]>=(1<<i))x=f[x][i];if(x==y)return x;for(int i=10;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];return f[x][0];}
void dfs3(int u,int fa)
{
	dfn[u]=1;dep[u]=dep[fa]+1;f[u][0]=fa;vl[u]=0;for(int i=1;i<=10;i++)f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=ed[i].next)
	if(!dfn[ed[i].t])tp.push(ed[i].id),dfs3(ed[i].t,u),vl[u]+=vl[ed[i].t];
	else{int s=LCA(u,ed[i].t);vl[u]++;vl[s]--;tp2.push(ed[i].id);}
}
bool check(int x)
{
	for(int i=1;i<=n;i++)dfn[i]=scc[i]=head[i]=0;
	cnt=0;ct2=0;
	for(int i=1;i<=m;i++)if(i!=x)adde(s[i][0],s[i][1],-1);else adde(s[i][1],s[i][0],-2);
	for(int i=1;i<=n;i++)if(!dfn[i])dfs1(i);
	return ct2!=ct3;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d",&s[i][0],&s[i][1]),adde(s[i][0],s[i][1],1);
	for(int i=1;i<=n;i++)if(!dfn[i])dfs1(i);ct3=ct2;
	for(int i=1;i<=n;i++)head[i]=0;cnt=0;
	for(int i=1;i<=m;i++)if(scc[s[i][0]]!=scc[s[i][1]])adde(scc[s[i][0]],scc[s[i][1]],-3);
	for(int i=1;i<=ct2;i++)dfs2(i,i);
	for(int i=1;i<=n;i++)head[i]=0;cnt=0;
	for(int i=1;i<=m;i++)if(scc[s[i][0]]!=scc[s[i][1]])as[i]=vis[scc[s[i][0]]][scc[s[i][1]]]>1;else v[scc[s[i][0]]].push(i);
	for(int i=1;i<=ct2;i++)
	{
		for(int j=1;j<=n;j++)dfn[j]=0,head[j]=0;
		ct1=0;cnt=0;
		for(int j=1;j<=n;j++)if(scc[j]==i)id[j]=++ct1;
		while(!v[i].empty())
		{
			int q=v[i].top();v[i].pop();
			adde(id[s[q][0]],id[s[q][1]],q);
		}
		dfs3(1,0);
		while(!tp2.empty())
		{
			int st=tp2.top();tp2.pop();
			int l=id[s[st][0]],r=id[s[st][1]];
			r=LCA(l,r);
			int fg=0;
			while(l!=r)fg|=(vl[l]==1),l=f[l][0];
			as[st]=fg;
		}
	}
	while(!tp.empty()){int st=tp.top();tp.pop();as[st]=check(st);}
	for(int i=1;i<=m;i++)printf("%s\n",as[i]?"diff":"same");
}
31 CF566E Restoring Map
Problem

有一棵 nn 个点的树,给出每个点与其距离小于等于2的点集合,集合被打乱顺序给出,你需要还原这棵树

n1000n\leq 1000

2s,256MB2s,256MB

Sol

考虑一条链 abcda-b-c-d ,显然 aa 的集合和 dd 的集合的交只有 b,cb,c

容易发现,如果两个集合交大小为2,这两个点一定有边

于是可以枚举集合求交,还原除了叶子向父亲的边以外的边

这时如果还原的边数为0,原图一定是菊花,容易发现任意一个菊花都是合法的,于是随便输出一个即可

对于目前还原出来的树,对于每个点求出距离为1的点的集合 SuS_u

考虑一个叶子 uu 距离为2的集合 ,它等于 SfaS_{fa} 并上fa的所有儿子

于是 uu 的集合 Sfa=Sfa∩ S_{fa}=S_{fa}

如果还原的边数大于1,可以发现满足这样的 fafa 只有一个,于是容易还原出每个儿子

对于最后一种情况,找到一个大小不是 nn 的集合,它一定包含那两个点和某个点的所有儿子,剩下的即为另外一个的儿子

复杂度 O(n3/32)O(n^3/32)

Code
#include<cstdio>
#include<bitset>
using namespace std;
#define N 1056
bitset<N> s[N],v[N],t,p;
int n,a,b,is[N],fg[N][N],f1,f2;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		while(a--)scanf("%d",&b),s[i].set(b,1);
		v[i].set(i,1);
	}
	for(int i=1;i<=n;i++)
	for(int j=i+1;j<=n;j++)
	{
		t=s[i]&s[j];
		if(t.count()==2)
		{
			int s1=-1,s2=0;
			for(int k=1;k<=n;k++)
			if(t[k])
			{
				if(s1==-1)s1=k;
				else s2=k;
			}
			if(fg[s1][s2])continue;
			printf("%d %d\n",s1,s2);f1=s1,f2=s2;
			v[s1].set(s2,1);
			v[s2].set(s1,1);
			is[s1]=is[s2]=fg[s1][s2]=fg[s2][s1]=1;
			p.set(s1,1);p.set(s2,1);
		}
	}
	if(!p.count())
	{
		for(int i=2;i<=n;i++)printf("1 %d\n",i);
		return 0;
	}
	if(p.count()==2)
	{
		int m1=0,m2=0,v1,fg=-1;
		for(int i=1;i<=n;i++)
		{
			int vl=s[i].count();
			if(vl>m1)m1=vl,v1=i;
			else if(vl>m2)m2=vl;
		}
		for(int i=1;i<=n;i++)if(s[i].count()!=n){
		int as=0;
		for(int j=1;j<=n;j++)if(s[i][j]&&j!=f1&&j!=f2&&!is[j])as=j;
		if(fg==-1)fg=as;
		if(s[i][fg])printf("%d %d\n",as,f1);
		else printf("%d %d\n",as,f2);is[as]=1;}
		return 0;
	}
	for(int i=1;i<=n;i++)
	if(!is[i])
	{
		int as=0,mn=1e9;
		for(int j=1;j<=n;j++)
		if(s[j][i]&&(s[j]&p).count()<mn)
		mn=(s[j]&p).count(),as=j;
		for(int j=1;j<=n;j++)
		if(v[j]==(s[as]&p))
		{printf("%d %d\n",i,j);break;}
	}
}
32 AGC034E Complete Compress
Problem

给一棵树,有一些点上有一个棋子,每次可以选两个距离大于1的棋子,将这两个棋子向对方方向移动1格,求最少操作次数使得所有棋子到一个点上或者输出无解

n2000n\leq 2000

3s,1024MB3s,1024MB

Sol

枚举最后在哪个点 uu

考虑一个点的每一个儿子,如果没有一个儿子子树内需要的次数大于总次数一半,那么可以将所有棋子移到这个点,否则必须要其它的棋子和它们移动

fif_i 表示 ii 的子树至少还需要多少次其它棋子和它们操作才能全部到 ii ,gig_i 表示 ii 子树内最多可以做多少次其它棋子和它们操作使得它们全部到 ii

如果存在 vv, fv>jsoni,jvgjf_v>\sum_{j\in son_i,j\neq v} g_j ,那么 fjf_j 为这两者差

否则 fi=gimod2f_i=g_i\bmod 2

如果 fu=0f_u=0 ,那么步数为 gug_u ,否则不可能全部在 uu

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

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 2050
struct edge{int t,next;}ed[N*2];
int head[N],cnt,n,mn[N],mx[N],sz[N],a,b,as=1e9;
char is[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 dfs(int u,int fa)
{
	sz[u]=(is[u]=='1');mn[u]=mx[u]=0;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u),mx[u]+=mx[ed[i].t]+sz[ed[i].t],sz[u]+=sz[ed[i].t];
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)mn[u]=max(mn[u],mn[ed[i].t]+mx[ed[i].t]-mx[u]+sz[ed[i].t]*2);
	if(!mn[u])mn[u]=mx[u]&1;
}
int main()
{
	scanf("%d%s",&n,is+1);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	for(int i=1;i<=n;i++)dfs(i,0),as=min(as,mn[i]?1000000000:mx[i]/2);
	printf("%d\n",as>1e8?-1:as);
}
33 AGC022D Shopping
Problem

nn 个超市在一条数轴上,每个超市有购物时间,有一辆电车从0时刻在0开始沿 [0,L][0,L] 循环运行,速度为1,求在每个超市购物一次最后回到1的最短时间

n3×105,L109n\leq 3\times 10^5,L\leq 10^9

2s,256MB2s,256MB

Sol

首先可以将每个点的用时对 2L2L 取模,最后加上

一种基础的方式是从左到右遍历每一个,需要特判最后一个

考虑中间每个点从左侧进来,如果从右侧出去一定列车会跑1个来回

如果从左侧出去,可能会让列车少1/2来回

从右侧进来同理

如果一个靠左的点满足右侧进右侧出可以少1/2,另外一个靠右的点满足左侧进左侧出可以少1/2,那么将它们配对则可以少1个来回

那么可以贪心计算最多能配多少对,记录当前还未配对的左侧点数和已经向左配对且可以向右配对的点数即可

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define N 300500
int st[N],v[N],s[N],n,l;
long long as;
int main()
{
	scanf("%d%d",&n,&l);as=l*2;
	for(int i=1;i<=n;i++)scanf("%d",&s[i]);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<=n;i++)
	{
		as+=(1ll*v[i]+l*2-1)/(l*2)*l*2;
		v[i]=(v[i]-1)%(l*2)+1;
		if(v[i]<=s[i]*2)st[i]++;
		if(v[i]<=(l-s[i])*2)st[i]+=2;
	}
	int l1=0,l2=0;
	for(int i=1;i<n;i++)
	if((st[i]&2)&&l1)l1--,as-=l*2,l2+=st[i]&1;
	else if((st[i]&2)&&l2)l1++,l2--,l2+=st[i]&1;
	else l1+=st[i]&1;
	if((st[n]&2))as-=l*2;
	printf("%lld\n",as);
}
34 CF613E Puzzle Lover
Problem

给一个 2×n2\times n 的字符矩阵和一个长度为 mm 的字符串,求有多少条路径满足

  1. 路径不重复经过点
  2. 路径相邻两个位置相邻
  3. 将路径上的字符接起来后为给定的字符串

109+710^9+7

n,m2000n,m\leq 2000

2s,256MB2s,256MB

Sol

考虑从左到右路径的形状

..v<s.>>v..>>>v..
..>>>>^.>>>^.t<..

可以将其分成三部分:

  1. 从起点向左走,再改变方向走相同的长度
  2. 向右,上,下走
  3. 向右走一段长度在改变方向走相同的长度

对于1可以枚举走的区间,hash解决

对于2,考虑设 dpi,j,kdp_{i,j,k} 表示当前在 (i,j)(i,j) ,当前匹配到 kk 位的方案数,转移枚举下一列怎么走即可

对于3使用和1相同的方式

然后换方向再做一次

如果一条路径只有part1或者3,它会在两个方向被重复计数,所以需要减去

如果 m2m\leq 2 ,可能出现各种奇怪的情况,这时暴力即可

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

Code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define mod 998244353
#define ll long long
#define N 2050
#define md 1000000007
char a[2][N],c[N];
int dp[N][2][N],n,m;
ll ha[N][2],rha[N][2],ha2[N],pw[N],ch=131,as;
int getha(int i,int j,int k){return ((ha[j][k]-ha[i-1][k]*pw[j-i+1]%mod)%mod+mod)%mod;}
int getrha(int i,int j,int k){return ((rha[i][k]-rha[j+1][k]*pw[j-i+1]%mod)%mod+mod)%mod;}
int getcha(int i,int j){return ((ha2[j]-ha2[i-1]*pw[j-i+1]%mod)%mod+mod)%mod;}
int main()
{
	scanf("%s%s%s",a[0]+1,a[1]+1,c+1);
	n=strlen(a[0]+1);m=strlen(c+1);
	if(m==1)
	{
		for(int i=1;i<=n;i++)as+=(a[0][i]==c[1])+(a[1][i]==c[1]);
		printf("%d\n",as);return 0;
	}
	if(m==2)
	{
		for(int i=1;i<=n;i++)
		{
			if(a[0][i]==c[1]&&a[0][i-1]==c[2])as++;
			if(a[0][i]==c[1]&&a[0][i+1]==c[2])as++;
			if(a[0][i]==c[1]&&a[1][i]==c[2])as++;
			if(a[1][i]==c[1]&&a[1][i-1]==c[2])as++;
			if(a[1][i]==c[1]&&a[1][i+1]==c[2])as++;
			if(a[1][i]==c[1]&&a[0][i]==c[2])as++;
		}
		printf("%d\n",as);return 0;
	}
	pw[0]=1;
	for(int i=1;i<=2000;i++)pw[i]=pw[i-1]*ch%mod;
	for(int i=1;i<=n;i++)ha[i][0]=(ha[i-1][0]*ch+a[0][i])%mod,ha[i][1]=(ha[i-1][1]*ch+a[1][i])%mod;
	for(int i=1;i<=m;i++)ha2[i]=(ha2[i-1]*ch+c[i])%mod;
	for(int i=n;i>=1;i--)rha[i][0]=(rha[i+1][0]*ch+a[0][i])%mod,rha[i][1]=(rha[i+1][1]*ch+a[1][i])%mod;
	for(int i=1;i<=n;i++)
	for(int j=i;j<=n;j++)
	if(2*(j-i+1)<=m&&i!=j)
	{
		if(getrha(i,j,1)==getcha(1,j-i+1)&&getha(i,j,0)==getcha(j-i+2,2*(j-i+1)))
		dp[j][0][2*(j-i+1)]++;
		if(getrha(i,j,0)==getcha(1,j-i+1)&&getha(i,j,1)==getcha(j-i+2,2*(j-i+1)))
		dp[j][1][2*(j-i+1)]++;
	}
	for(int i=1;i<=n;i++)
	{
		if(a[0][i]==c[1])dp[i][0][1]++;
		if(a[1][i]==c[1])dp[i][1][1]++;
		if(a[0][i]==c[1]&&a[1][i]==c[2])dp[i][1][2]++;
		if(a[1][i]==c[1]&&a[0][i]==c[2])dp[i][0][2]++;
	}
	for(int i=2;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		if(a[0][i]==c[j+1])dp[i][0][j+1]=(dp[i][0][j+1]+dp[i-1][0][j])%md;
		if(a[1][i]==c[j+1])dp[i][1][j+1]=(dp[i][1][j+1]+dp[i-1][1][j])%md;
		if(a[0][i]==c[j+1]&&a[1][i]==c[j+2])dp[i][1][j+2]=(dp[i][1][j+2]+dp[i-1][0][j])%md;
		if(a[1][i]==c[j+1]&&a[0][i]==c[j+2])dp[i][0][j+2]=(dp[i][0][j+2]+dp[i-1][1][j])%md;
	}
	for(int i=1;i<=n;i++)
	for(int j=i;j<=n;j++)
	if(2*(j-i+1)<m&&i!=j)
	{
		if(getha(i,j,0)==getcha(m-(j-i+1)*2+1,m-(j-i+1))&&getrha(i,j,1)==getcha(m-(j-i+1)+1,m))as=(as+dp[i-1][0][m-2*(j-i+1)])%md;
		if(getha(i,j,1)==getcha(m-(j-i+1)*2+1,m-(j-i+1))&&getrha(i,j,0)==getcha(m-(j-i+1)+1,m))as=(as+dp[i-1][1][m-2*(j-i+1)])%md;
	}
	for(int i=1;i<=n;i++)as=(as+dp[i][0][m]+dp[i][1][m])%md;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	dp[i][0][j]=dp[i][1][j]=0;
	for(int i=1;i*2<=n;i++)swap(a[0][i],a[0][n-i+1]),swap(a[1][i],a[1][n-i+1]);
	for(int i=1;i<=n;i++)ha[i][0]=(ha[i-1][0]*ch+a[0][i])%mod,ha[i][1]=(ha[i-1][1]*ch+a[1][i])%mod;
	for(int i=n;i>=1;i--)rha[i][0]=(rha[i+1][0]*ch+a[0][i])%mod,rha[i][1]=(rha[i+1][1]*ch+a[1][i])%mod;
	for(int i=1;i<=n;i++)
	for(int j=i;j<=n;j++)
	if(2*(j-i+1)<=m&&i!=j)
	{
		if(getrha(i,j,1)==getcha(1,j-i+1)&&getha(i,j,0)==getcha(j-i+2,2*(j-i+1)))dp[j][0][2*(j-i+1)]++;
		if(getrha(i,j,0)==getcha(1,j-i+1)&&getha(i,j,1)==getcha(j-i+2,2*(j-i+1)))dp[j][1][2*(j-i+1)]++;
	}
	for(int i=1;i<=n;i++)
	{
		if(a[0][i]==c[1])dp[i][0][1]++;
		if(a[1][i]==c[1])dp[i][1][1]++;
		if(a[0][i]==c[1]&&a[1][i]==c[2])dp[i][1][2]++;
		if(a[1][i]==c[1]&&a[0][i]==c[2])dp[i][0][2]++;
	}
	for(int i=2;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		if(a[0][i]==c[j+1])dp[i][0][j+1]=(dp[i][0][j+1]+dp[i-1][0][j])%md;
		if(a[1][i]==c[j+1])dp[i][1][j+1]=(dp[i][1][j+1]+dp[i-1][1][j])%md;
		if(a[0][i]==c[j+1]&&a[1][i]==c[j+2])dp[i][1][j+2]=(dp[i][1][j+2]+dp[i-1][0][j])%md;
		if(a[1][i]==c[j+1]&&a[0][i]==c[j+2])dp[i][0][j+2]=(dp[i][0][j+2]+dp[i-1][1][j])%md;
	}
	for(int i=1;i<=n;i++)
	for(int j=i;j<=n;j++)
	if(2*(j-i+1)<m&&i!=j)
	{
		if(getha(i,j,0)==getcha(m-(j-i+1)*2+1,m-(j-i+1))&&getrha(i,j,1)==getcha(m-(j-i+1)+1,m))as=(as+dp[i-1][0][m-2*(j-i+1)])%md;
		if(getha(i,j,1)==getcha(m-(j-i+1)*2+1,m-(j-i+1))&&getrha(i,j,0)==getcha(m-(j-i+1)+1,m))as=(as+dp[i-1][1][m-2*(j-i+1)])%md;
	}
	for(int i=1;i<=n;i++)as=(as+dp[i][0][m]+dp[i][1][m])%md;
	printf("%d\n",(as+md)%md);
}
35 AGC033E Go around a Circle
Problem

圆上有 nn 段圆弧,每段圆弧可以被染成红色或者蓝色

给定一个颜色串 SS ,求有多少种染色方式满足以下条件:

从每个点开始,都存在一种每一步选择顺时针或者逆时针走,一共走 S|S| 步的方案,使得这样走记录下每一次经过的那一段圆弧所得到的颜色串为 SS

旋转之后相同的两种方案算作不同的方案,答案模 998244353998244353

n,S2×105n,|S|\leq 2\times 10^5

1s,1024MB1s,1024MB

Sol

从我集训队作业part3抄过来的

rr 代表红色, bb 代表蓝色

不妨设 SS 的第一个颜色为 rr ,否则交换所有的 rrbb ,答案不变

首先,如果环上有两段连续的 bb ,那么从这两段中间的那个点出发,无论如何第一步不可能是 rr 。因此,环上每一段极长 bb 的长度都是 11

如果 SS 中没有 bb ,因为从每一个点开始,它两侧一定存在一段是 rr ,接下来只需要沿着这一段来回走,就可以满足限制,所以所有满足上一个条件的染色方案都合法

对于这种情况,可以枚举环上第一段的颜色,设 dpi,0/1dp_{i,0/1} 表示考虑染了前 ii 段,且每一段和上一段都不同时是 bb ,第 ii 段是 rr 或者 bb 的方案数。直接转移就可以做到 O(n)O(n)

如果 SS 中有 bb ,设在第一个 bb 之前有 llrr

从奇偶性考虑,如果环上存在一段极长的 rr 长度是偶数,那么从这一段内的每个点出发,到这一段 rr 的两个端点需要走的步数的奇偶性是相同的。此时考虑前两个点 a1,a2a_1,a_2 。如果 ll 是奇数,那么因为从 a1a_1 出发到两个端点的所有路径都需要偶数步,且在前 ll 步中不可能走出这一段,因此最后一定只能停留在这一段内的非端点。这时,因为 SS 下一步是 bb ,而非端点两侧都是 rr ,矛盾。ll 是偶数时从 a2a_2 出发会导致矛盾

因此,每一段极长的 rr 长度都是奇数

从长度考虑,如果当前一个极长的 rr 的长度是 dd ,由上面的分析可以得到 dd 一定是奇数。设这些点为 a1,a2,,ad+1a_1,a_2,\dots,a_{d+1}

如果 ll 是奇数,从 a1a_1 开始,在 ll 步后由于奇偶性只能走到一个端点 ad+1a_{d+1} ,此时有 ldl \geq d ,因此 dld \leq l

如果 ll 是偶数,从 a2a_2 开始,在 ll 步后只能走到 ad+1a_{d+1} 。此时有 ld1l \geq d-1 ,因此 dl+1d \leq l+1

因为 dd 必须是奇数,所以 ll 是奇数的情况 d=l+1d=l+1 是不合法的,所以这里的限制相当于 dl+1d\leq l+1

如果 ll 是奇数,考虑点 aia_i 。如果 ii 是奇数,它只能走到 ad+1a_{d+1} 。此时可以先走到 ad+1a_{d+1},此时剩下的步数 l(d+1i)l-(d+1-i) 一定是非负偶数,所以可以在一段上来回走直到 ll

如果 ii 是偶数,它只能走到 a1a_1 。此时先走到 a1a_1,此时剩下的步数 l(i1)l-(i-1) 一定是非负偶数,同样合法

ll 是偶数的情况同理

因此,满足上面的限制的一段极长的 rr 一定满足从这一段中每个点开始走 ll 步后可以到达唯一一个端点

在走完 S|S| 中第一段 rr 后,对于每一段环上的极长 rr ,考虑这一段的前两个点,因为奇偶性这两个点能到达的端点一定不同

于是这时对于所有的端点都存在一个点,使这个点在这时只能走到这个端点

考虑 S|S| 剩余的部分:

如果 S|S| 接着是一段长度是奇数的 rr ,那么对于每段极长的 rr ,它的左端点在这一段后只可能走到右端点,右端点只可能走到左端点

如果 S|S| 接着是一段长度是偶数的 rr ,那么对于每段极长的 rr ,它的左端点在这一段后只可能走到左端点,右端点只可能走到右端点

如果 S|S| 接着是一个 bb ,那么对于环上每一个 bb ,它两侧一定有两个端点,而这两个端点这一步一定只能互相走到对方

因此,可以证明走到 SS 中每一段极长 rr 的结尾或者 bb 之后时,对于所有的端点都存在一个点,使这个点在这时只能走到这个端点

接着考虑 SS 剩余部分的限制

SS 第二段极长的 rr 开始考虑每一段极长的 rr ,如果当前这一段后面没有 bb ,那么因为每个点两侧至少有一个 rr ,所以这一段不会造成额外的限制

否则,设 SS 中这一段长度为 xx

如果 xx 为偶数,因为考虑的是 SS 的极长段,所以在这之前一定是走到了一段的端点,这时它两侧一定只有一段是 rr 。而因为环上每一个极长 rr 段的长度都是奇数,所以它不能走到这一个极长段的另外一个端点。而因为它现在在这一侧的端点,所以只需要在一个 rr 上来回走就行了。所以只需要极长 rr 段的长度大于等于 11 ,相当于没有限制

如果 xx 为奇数,那么一定只能从一个端点到这一段的另一个端点,这时这一段的长度 dxd\leq x。因为上面说明了对于每个端点,都存在一个点,从这个点出发在这时只能走到这个端点,所以每一段的长度都必须 x\leq x

上面所有对环上极长 rr 段的长度的限制是不能超过 S|S| 第一段 rr 的长度 +1+1 和其余所有后面有 bb 的极长 rr 段中所有长度是奇数的段的长度。设这些的值的 maxmaxee

考虑满足目前限制的一种染色方案。对于 S|S| 的第一段长度为 llrr ,之前已经说明了在这一部分从每个点出发一定可以走到一个端点

对于剩余的部分,考虑以下方案:

如果 S|S| 接着是一段长度是奇数的 rr ,那么走到它所在环上极长 rr 段的另外一个端点,因为环上这一段 rr 长度 dSd\leq S 中这一段 rr 的长度 xx 且它们奇偶性相同,所以一定可以走到另外一个端点

如果 S|S| 接着是一段长度是偶数的 rr ,只需要在它两侧唯一的那一个 rr 上来回走,这之后它会停在和走这一段之前相同的端点

如果 S|S| 接着是一个 bb ,那么只需要走到它两侧唯一的那一个 bb 的另一侧,另一侧一定是一个端点

因此满足以上限制的染色方案一定合法,所以这样的染色方案即为所有合法染色方案

那么题目限制变为极长 bb 段的长度只能是 11 ,极长 rr 段长度小于等于 ee 且是奇数

考虑将每一段 rr 和这一段后面的那个 bb 一起考虑,相当于分成若干个长度小于等于 e+1e+1 且为偶数的段

考虑链上的 dpdpdpidp_i 表示上一段结尾为 ii 的方案数,有 dpi=j=2,2jmin(e+1,i)dpijdp_i=\sum_{j=2,2|j}^{min(e+1,i)}dp_{i-j}

可以前缀和优化,复杂度 O(n)O(n)

对于环的情况,可以枚举 11 号点所在极长 rr 段的长度,然后剩下部分是一条链,所以有ans=j=2,2je+1jdpnjans=\sum_{j=2,2|j}^{e+1}j*dp_{n-j}

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define mod 1000000007
#define N 200050
int n,m,dp[N][2][2],fg,mx,fg2,dp2[N],su[N],as;
char s[N];
int main()
{
	scanf("%d%d%s",&n,&m,s+1);
	for(int i=2;i<=m;i++)if(s[i]!=s[i-1])fg=1;
	if(!fg)
	{
		dp[1][1][1]=dp[1][0][0]=1;
		for(int i=2;i<=n;i++)
		dp[i][0][0]=(dp[i-1][0][0]+dp[i-1][1][0])%mod,
		dp[i][0][1]=(dp[i-1][0][1]+dp[i-1][1][1])%mod,
		dp[i][1][0]=dp[i-1][0][0],
		dp[i][1][1]=dp[i-1][0][1];
		printf("%lld\n",(1ll*dp[n][0][0]+dp[n][0][1]+dp[n][1][0])%mod);
		return 0;
	}
	int su1=0;
	for(int i=1;i<=m;i++)
	if(s[i]==s[1])su1++;
	else
	{
		if((su1&1)&&mx>su1)mx=su1;
		if(!fg2)mx=su1+1;
		fg2=1;su1=0;
	}
	if(mx>n)mx=n;
	if(~mx&1)mx--;
	dp2[1]=1;su[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(i-mx>3)dp2[i]=(su[i-2]-su[i-mx-3]+mod)%mod;
		else dp2[i]=su[i-2];
		su[i]=(su[i-2]+dp2[i])%mod;
	}
	for(int i=1;i<=mx;i+=2)as=(as+1ll*dp2[n-i]*(i+1))%mod;
	printf("%d\n",as);
}
36 AGC021F Trinity
Problem

有一个 n×mn\times m 的矩阵,每个格子可能是黑色或者白色,记录

AiA_i 表示第 ii 行最左边一个黑色格子的位置,如果没有则为 00

BiB_i 表示第 ii 列最上面一个黑色格子的位置,如果没有则为 00

CiC_i 表示第 ii 列最下面一个黑色格子的位置,如果没有则为 00

求可能的 {A,B,C}\{A,B,C\} 的组数,模 998244353998244353

n8000,m200n\leq 8000,m\leq 200

6s,256MB6s,256MB

Sol

从左往右考虑每一列

fi,jf_{i,j} 表示考虑了左边 ii 列,当前有 jj 行的 AiA_i 已经决定了,当前的方案数

考虑下一行的情况

考虑 fi,j>fi+1,j+kf_{i,j}->f_{i+1,j+k} 的转移,将这一行最左边最右边的黑色格子和新加入行的黑色格子染成红色,设染了 xx 个格子,在 x=k+2,kx=k+2,k 时它唯一对应一种原来的情况,当 x=k+1x=k+1 时,若 k>0k>0 ,有两种情况,否则有一种情况

因此 k>0k>0 的转移系数为 Cj+kk+2+2Cj+kk+1+Cj+kk=Cj+k+2k+2C_{j+k}^{k+2}+2C_{j+k}^{k+1}+C_{j+k}^k=C_{j+k+2}^{k+2}

k=0k=0 时为 Cj2+Cj1+Cj0C_j^2+C_j^1+C_j^0

对于大于的部分,可以写成卷积的形式,NTT优化

复杂度 O(nmlogn)O(nm\log n)

Code
#include<cstdio>
using namespace std;
#define N 16400
#define M 204
#define mod 998244353
int dp[M][N],n,m,fr[N],ifr[N],rev[N],a[N],b[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;}
int c(int a,int b){if(a<b)return 0;return 1ll*fr[a]*ifr[b]%mod*ifr[a-b]%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:0),ntt[rev[i]]=a[i];
	for(int i=2;i<=s;i<<=1)
	{
		int st=pw(3,(mod-1)/i);
		if(t==-1)st=pw(st,mod-2);
		for(int j=0;j<s;j+=i)
		for(int k=j,t=1;k<j+(i>>1);k++,t=1ll*t*st%mod)
		{
			int v1=ntt[k],v2=1ll*ntt[k+(i>>1)]*t%mod;
			ntt[k]=(v1+v2)%mod;ntt[k+(i>>1)]=(v1-v2+mod)%mod;
		}
	}
	int inv=t==-1?pw(s,mod-2):1;
	for(int i=0;i<s;i++)a[i]=1ll*ntt[i]*inv%mod;
}
int main()
{
	scanf("%d%d",&n,&m);
	dp[0][0]=1;
	fr[0]=ifr[0]=1;for(int i=1;i<=8020;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	for(int i=1;i<=m;i++)
	{
		int l=1;while(l<=n*2)l<<=1;for(int j=0;j<l;j++)a[j]=b[j]=0;
		for(int j=0;j<=n;j++)a[j]=1ll*dp[i-1][j]*ifr[j]%mod;
		for(int j=1;j<=n;j++)b[j]=ifr[j+2];
		dft(l,a,1);dft(l,b,1);for(int j=0;j<l;j++)a[j]=1ll*a[j]*b[j]%mod;dft(l,a,-1);
		for(int j=0;j<=n;j++)dp[i][j]=(1ll*a[j]*fr[j+2]+1ll*(1ll*j*(j-1)%mod*499122177%mod+j+1)%mod*dp[i-1][j])%mod;
	}
	int as=0;
	for(int j=0;j<=n;j++)as=(as+1ll*dp[m][j]*c(n,j))%mod;
	printf("%d\n",as);
}
37 CF528C Data Center Drama
Problem

给一张无向连通图,你需要加尽量少的边,再给每条边定向,使得每个点的入度出度都是偶数,输出方案

允许重边,自环

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

2s,256MB2s,256MB

Sol

首先每个点的度数必须是偶数,因为入度之和为偶数,所以边数也必须是偶数

找出度数是奇数的点,两两配对加边

如果这时边数为奇数,再加一个自环

考虑构造定向,取一棵生成树,非树边任意定向,对于每一个不是根的点,先确定它的子树内的边,然后可以通过调整它向父亲边的方向来满足条件,因为边数为偶数,所以当除了根以外的点都满足要求时,根一定满足要求

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

Code
#include<cstdio>
using namespace std;
#define N 400050
int fa[N],head[N],cnt,n,h[N][2],in[N],m,as[N][2],ct,ls,s[N][2];
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 finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
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);
		s[u][0]^=s[ed[i].t][1];
		s[u][1]^=s[ed[i].t][0];
	}
	if(u!=1)
	{
		if(s[u][0])as[++ct][0]=u,as[ct][1]=fa;
		else as[++ct][0]=fa,as[ct][1]=u;
	}
	else if(s[u][0])as[++ct][0]=1,as[ct][1]=1;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d",&h[i][0],&h[i][1]),in[h[i][0]]++,in[h[i][1]]++;
	for(int i=1;i<=n;i++)
	{
		fa[i]=i;
		if(in[i]&1)
		if(ls)h[++m][0]=ls,h[m][1]=i,ls=0;
		else ls=i;
	}
	for(int i=1;i<=m;i++)
	if(finds(h[i][0])!=finds(h[i][1]))adde(h[i][0],h[i][1]),fa[finds(h[i][0])]=finds(h[i][1]);
	else s[h[i][0]][0]^=1,s[h[i][1]][1]^=1,as[++ct][0]=h[i][0],as[ct][1]=h[i][1];
	dfs(1,0);
	printf("%d\n",ct);
	for(int i=1;i<=ct;i++)printf("%d %d\n",as[i][0],as[i][1]);
}
38 AGC038F Two Permutations
Problem

有两个排列 p,qp,q ,你可以构造两个排列 a,ba,b ,满足

  1. ai=ia_i=iai=pia_i=p_i
  2. bi=ib_i=ibi=qib_i=q_i

求所有情况中, aabb 最多有多少个位置不同

n105n\leq 10^5

10s,1024MB10s,1024MB

Sol

p,qp,q 写成若干置换环的形式,显然每个环只有两种情况

依次考虑每个位置

如果 pi=i,qi=ip_i=i,q_i=i ,答案减1

如果 pi=i,qiip_i=i,q_i\neq i ,那么如果 qiq_i 所在的环不动,答案减1

如果 qi=i,piiq_i=i,p_i\neq i ,那么如果 pip_i 所在的环不动,答案减1

如果 pii,qii,pi=qip_i\neq i,q_i\neq i,p_i=q_i ,那么如果两个环都动或者都不动,答案减1

对于其它情况,如果两个环都不动,答案减1

考虑最小割,将 qq 中环的每个状态反过来,那么相当于若干个如果 AABB 不动答案减1的限制,可以看成一个最小割的模型

于是可以建图跑网络流解决

Code
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 200500
int head[N],cnt=1,dis[N],p[N],q[N],ct,id[N],id2[N],as,vl[N],cur[N],vl2[N],n;
struct edge{int t,next,v;}ed[N*5];
void adde(int f,int t,int v){ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;ed[++cnt]=(edge){f,head[t],0};head[t]=cnt;}
bool bfs(int s,int t)
{
	queue<int> v;
	memset(dis,-1,sizeof(dis));
	memcpy(cur,head,sizeof(cur));
	v.push(s);dis[s]=1;
	while(!v.empty())
	{
		int r=v.front();v.pop();
		for(int i=head[r];i;i=ed[i].next)
		if(dis[ed[i].t]==-1&&ed[i].v)
		{dis[ed[i].t]=dis[r]+1,v.push(ed[i].t);if(ed[i].t==t)return 1;}
	}
	return 0;
}
int dfs(int u,int t,int f)
{
	if(!f||(u==t))return f;
	int as=0,tp;
	for(int &i=cur[u];i;i=ed[i].next)
	if(ed[i].v&&dis[ed[i].t]==dis[u]+1&&(tp=dfs(ed[i].t,t,min(ed[i].v,f))))
	{
		ed[i].v-=tp,ed[i^1].v+=tp,as+=tp,f-=tp;
		if(!f)return as;
	}
	return as;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&p[i]),p[i]++;
	for(int i=1;i<=n;i++)scanf("%d",&q[i]),q[i]++;
	for(int i=1;i<=n;i++)if(!id[i]){id[i]=++ct;for(int j=p[i];j!=i;j=p[j])id[j]=ct;}
	for(int i=1;i<=n;i++)if(!id2[i]){id2[i]=++ct;for(int j=q[i];j!=i;j=q[j])id2[j]=ct;}
	for(int i=1;i<=n;i++)
	if(p[i]==i&&q[i]==i)as++;
	else if(p[i]==i)vl[id2[i]]++;
	else if(q[i]==i)vl2[id[i]]++;
	else if(p[i]==q[i])adde(id[i],id2[i],1),adde(id2[i],id[i],1);
	else adde(id[i],id2[i],1);
	for(int i=1;i<=n*2;i++){if(vl[i])adde(n*2+1,i,vl[i]);if(vl2[i])adde(i,n*2+2,vl2[i]);}
	while(bfs(n*2+1,n*2+2))as+=dfs(n*2+1,n*2+2,1e7);
	printf("%d\n",n-as);
}
39 AGC029E Wandering TKHS
Problem

有一棵树,你每次会选择与选择过的点相邻的编号最小的未选择点,求从每个点开始,需要选择多少次才会到1

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

2s,1024MB2s,1024MB

Sol

考虑一个点到根路径上最大的一个点,在选择它之前一定选择了从起点开始只经过小于它的点所能到达的所有点

然后考虑接下来的过程,因为剩下的点小于最大点,所以最大点往下的都不会再被选择,因此可以看成从最大点的父亲开始选择

因此可以从上往下dfs求每个点的答案,相当于 nn 次求从一个点开始只经过小于 vv 的点所能到达的点数

如果起点权值小于 vv , 可以直接从小到大问,并查集解决

如果大于 vv ,因为每个起点只会问一次,所以可以遍历每条边解决

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

Code
#include<cstdio>
#include<queue>
using namespace std;
#define N 200050
int fa[N],sz[N],head[N],cnt,mx[N],f[N],ti=1,as[N],n,a,b,fr[N],vl[N];
struct edge{int t,next;}ed[N*2];
priority_queue<pair<int,int> > st;
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
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){f[u]=fa;mx[u]=mx[f[u]],fr[u]=fr[f[u]];if(mx[u]<fa)mx[u]=fa,fr[u]=u;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs1(ed[i].t,u);}
int que(int x){if(x<ti)return sz[finds(x)]-1;int as=0;for(int i=head[x];i;i=ed[i].next)if(ed[i].t<ti)as+=sz[finds(ed[i].t)];return as;}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;
	dfs1(1,0);
	for(int i=head[1];i;i=ed[i].next)st.push(make_pair(-1,ed[i].t));
	while(!st.empty())
	{
		int las=ti;
		ti=-st.top().first;int s=st.top().second;st.pop();
		for(int i=las;i<ti;i++)
		for(int j=head[i];j;j=ed[j].next)if(ed[j].t<i){int a=finds(i),b=finds(ed[j].t);fa[a]=b,sz[b]+=sz[a];}
		as[s]+=as[mx[s]]+que(s)+1-vl[fr[s]];
		for(int i=head[s];i;i=ed[i].next)if(ed[i].t!=f[s])st.push(make_pair(-mx[ed[i].t],ed[i].t)),vl[ed[i].t]=ed[i].t<ti?sz[finds(ed[i].t)]:0;
	}
	for(int i=2;i<=n;i++)printf("%d ",as[i]);
}
40 CF611H New Year and Forgotten Tree
Problem

有一棵树,现在给出每条边两个点的编号的位数,求一个合法解或输出无解

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

7s,256MB7s,256MB

Sol

先处理掉位数相同的边,对于剩下的构造

对于每一种位数的点,设一个为关键点

如果一个叶子的父亲不是关键点,可以把它调到关键点

因此可以认为每个非关键点的父亲都是关键点

因为关键点只有6个,暴搜这6个点树的形态,然后网络流将剩下的边分给每一个非关键点即可

Code
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define N 26
#define M 1206
char t[N];
int n,m,s[N][N],tp[N],e[M][2],fa[N],head[N],cnt,dis[N],cur[N],st[N],ct,is[N],v[N]={0,1,10,100,1000,10000,100000},v2[N]={0,1,10,100,1000,10000,100000};
struct edge{int t,next,v;}ed[M];
void adde(int f,int t,int v){ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;ed[++cnt]=(edge){f,head[t],0};head[t]=cnt;}
bool bfs(int s,int t)
{
	queue<int> tp;tp.push(s);memset(dis,-1,sizeof(dis));dis[s]=0;memcpy(cur,head,sizeof(cur));
	while(!tp.empty())
	{
		int r=tp.front();tp.pop();
		for(int i=head[r];i;i=ed[i].next)
		if(ed[i].v&&dis[ed[i].t]==-1)
		{
			dis[ed[i].t]=dis[r]+1;tp.push(ed[i].t);
			if(ed[i].t==t)return 1;
		}
	}
	return 0;
}
int dfs(int u,int t,int f)
{
	if(!f||u==t)return f;
	int as=0,tp;
	for(int &i=cur[u];i;i=ed[i].next)
	if(ed[i].v&&dis[ed[i].t]==dis[u]+1&&(tp=dfs(ed[i].t,t,min(ed[i].v,f))))
	{
		ed[i].v-=tp;ed[i^1].v+=tp;
		f-=tp,as+=tp;
		if(!f)return as;
	}
	return as;
}
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
void doit(int a,int b,int l1,int l2)
{
	if(!s[l1][l2])l1^=l2^=l1^=l2,a^=b^=a^=b;
	printf("%d %d\n",a,b);s[l1][l2]--;
}
void dfs2(int d,int v1)
{
	if(d==m)
	{
		int su=0;
		for(int i=1;i<=m;i++)fa[i]=i;
		for(int i=1;i<m;i++)fa[finds(e[st[i]][0])]=finds(e[st[i]][1]);
		for(int i=2;i<=m;i++)if(finds(i)!=finds(1))return;
		for(int i=1;i<=ct+m+2;i++)head[i]=0;cnt=1;
		for(int i=1;i<=ct;i++)adde(ct+m+1,i,s[e[i][0]][e[i][1]]+s[e[i][1]][e[i][0]]-is[i]),adde(i,e[i][0]+ct,1e9),adde(i,e[i][1]+ct,1e9);
		for(int i=1;i<=m;i++)adde(i+ct,ct+m+2,tp[i]-1),su+=tp[i]-1;
		while(bfs(ct+m+1,ct+m+2))su-=dfs(ct+m+1,ct+m+2,1e9);
		if(su)return;
		for(int i=1;i<m;i++)doit(v[e[st[i]][0]],v[e[st[i]][1]],e[st[i]][0],e[st[i]][1]);
		for(int i=1;i<=m;i++)while(s[i][i])doit(v[i],++v2[i],i,i);
		for(int i=1;i<=m;i++)
		for(int j=head[ct+i];j;j=ed[j].next)
		if(ed[j].t!=ct+m+2)
		for(int k=1;k<=ed[j].v;k++)doit(++v2[i],v[e[ed[j].t][1]+e[ed[j].t][0]-i],i,e[ed[j].t][1]+e[ed[j].t][0]-i);
		exit(0);
	}
	for(int i=v1+1;i<=ct;i++)if(s[e[i][0]][e[i][1]]+s[e[i][1]][e[i][0]])st[d]=i,is[i]=1,dfs2(d+1,i),is[i]=0;
}
int main()
{
	scanf("%d",&n);
	int st=n;while(st)st/=10,m++;
	for(int i=1;i<n;i++)
	{
		int s1=0,s2=0;
		scanf("%s",t+1);s1=strlen(t+1);
		scanf("%s",t+1);s2=strlen(t+1);
		s[s1][s2]++;
	}
	for(int i=1;i<=n;i++)
	{
		int st=i,vl=0;
		while(st)st/=10,vl++;
		tp[vl]++;
	}
	for(int i=1;i<=m;i++)tp[i]-=s[i][i];
	for(int i=1;i<=m;i++)if(tp[i]<=0){printf("-1\n");return 0;}
	for(int i=1;i<=m;i++)
	for(int j=i+1;j<=m;j++)
	e[++ct][0]=i,e[ct][1]=j;
	dfs2(1,0);printf("-1\n");
}
41 AGC036D Negative Cycle
Problem

有一张 nn 个点的图,有一些边:

对于 1i<n1\leq i <n ,有一条 ii 连向 i+1i+1 的有向边,边权为 00 ,这些边不能被删除

对于 1i<jn1\leq i<j \leq n ,有一条 ii 连向 jj 的有向边,边权为 1-1 ,删除代价为 Ai,jA_{i,j}

对于 1j<in1\leq j<i \leq n ,有一条 ii 连向 jj 的有向边,边权为 11 ,删除代价为 $A_{i,j} $

你需要删去一些边,使图不存在负环,求最小删除代价和

n500,0Ai,j109n\leq 500,0\leq A_{i,j}\leq 10^9

2s,1024MB2s,1024MB

Sol

图没有负环说明图中存在最短路

因为第一类边不能被删除,所以从 11 出发可以到达所有点,设 11ii 的最短路长度为 pip_i ,由于有第一类边,所以 pipi+1p_i \geq p_{i+1}

考虑 11nn 的最短路,因为边权都是 0,1,10,1,-1 ,所以在这条路径上一定存在 pi=0,1,2,,pnp_i=0,-1,-2,\dots,p_n 的点,结合 pipi+1p_i \geq p_{i+1} 可以得到 pipi+1+1p_i\leq p_{i+1}+1

假设当前已经决定了 pp ,考虑有哪些边必须被删掉:

对于 i<ji < j 的边 (i,j)(i,j) ,如果 pi1<pjp_i-1<p_j ,那么如果存在这条边就不满足 pp 是最短路长度

对于 i>ji > j 的边 (i,j)(i,j) ,如果 pi>pj+1p_i>p_j+1 ,那么如果存在这条边就不满足 pp 是最短路长度

因为0Ai,j0 \leq A_{i,j} ,其余的边都保留下来一定最优

其余的边都保留下来后,所有满足 pi=pj+1p_i=p_j+1 的边 (i,j)(i,j) 都被保留下来了,因此所有的 pip_i 都可以达到

上面两个条件都只和 pipjp_i-p_j11 的大小关系有关,设 dpl,rdp_{l,r} 表示考虑前 rr 个点,满足 pl=prp_l=p_r ,并且 pr+1<pr,pl1>prp_{r+1}<p_r,p_{l-1}>p_r ,只考虑前 rr 个点间的边时的最小代价

考虑从 [l,r][l,r] 转移到 [r+1,k][r+1,k] ,计算额外需要的代价

对于一个点 j(r+1jk)j (r+1\leq j\leq k)

考虑 (i,j),i<j(i,j),i<j 的边,这条边可以被保留的条件是 pipj+1p_i\geq p_j+1 ,因此 iri\leq r ,所以需要删掉 r<i<jr<i<j 的边

考虑 (j,i),i<j(j,i),i<j 的边,这条边可以被保留的条件是 pipj+1p_i \leq p_j +1 ,因此 ili\geq l ,所以需要删掉 1il11\leq i \leq l-1 的边

相当于需要删去满足 r<i<j<kr<i<j<k 的边 (i,j)(i,j)1il1,r+1jk1\leq i \leq l-1,r+1 \leq j \leq k 的边 (j,i)(j,i)

对于 i<ji<ji>ji>j 的边权分别二维前缀和后,就可以 O(1)O(1) 计算一次转移的代价, dpdp 复杂度 O(n3)O(n^3)

Code
#include<cstdio>
using namespace std;
#define N 505
long long f[N][N],su1[N][N],su2[N][N],dp[N][N],n,su;
long long Max(long long a,long long b){return a>b?a:b;}
int main()
{
	scanf("%lld",&n);for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(i!=j)scanf("%lld",&f[i][j]),su+=f[i][j];
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)su1[i][j]=f[i][j]*(i<j)+su1[i][j-1]+su1[i-1][j]-su1[i-1][j-1],su2[i][j]=f[i][j]*(i>j)+su2[i][j-1]+su2[i-1][j]-su2[i-1][j-1];
	for(int i=1;i<=n;i++)dp[1][i]=su2[i][i];
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)for(int k=j+1;k<=n;k++)dp[j+1][k]=Max(dp[j+1][k],dp[i][j]+su1[j][k]-su1[j][j]+su2[k][k]-su2[k][i-1]-su2[j][k]+su2[j][i-1]);
	long long as=0;for(int i=1;i<=n;i++)as=Max(as,dp[i][n]);printf("%lld\n",su-as);
}
42 AGC027E ABBreviate
Problem

有一个包含 ab 的字符串,你每次可以

  1. 选择相邻的 bb 替换成 a
  2. 选择相邻的 aa 替换成 b

求可以出现的字符串总数,模 109+710^9+7

n105n\leq 10^5

2s,1024MB2s,1024MB

Sol

考虑最后的序列,一个字符一定对应原来的一段

a 为1, b 为2,那么可以发现操作前后权值和模2不变

因此,一段变成的字符是固定的

又因为必须有相邻的两个相同才能操作,所以一段中必须要有相邻两个相同

注意到有相邻两个相同的一段如果权值模3余1或2,如果当前有两种字符,一定存在形如 abbbba 的东西,这样进行操作后还是有两个相邻的相同,否则,如果当前长度小于3就做完了,如果长度大于3那么操作左边两个后剩下的还是有相同的,因此一定可以变成一个字符

考虑子序列自动机dp的方法, dpidp_i 表示以 ii 结尾的串有多少个,记录原串的前缀和,每次相当于找某个位置右边第一个前缀和模3是某个数的位置,可以快速求出

因为所有位置和模3确定,所以如果前面确定了最后一位就确定了,所以答案相当于每一个从当前到结尾的区间合法的位置的dp和

复杂度 O(n)O(n)

Code
#include<cstdio>
#include<cstring>
using namespace std;
#define N 100500
#define mod 1000000007
int n,nt[N],su[N],nt2[N][3],as,dp[N];
char s[N];
int main()
{
	scanf("%s",s+1);n=strlen(s+1);
	for(int i=1;i<=n;i++){su[i]=(su[i-1]+s[i]-'a'+1)%3;}
	nt[n+1]=n+1;nt2[n+1][0]=nt2[n+1][1]=nt2[n+1][2]=n+1;
	for(int i=n;i>=0;i--)
	{
		nt[i]=nt[i+1];nt2[i][0]=nt2[i+1][0];nt2[i][1]=nt2[i+1][1];nt2[i][2]=nt2[i+1][2];
		if(s[i]==s[i+1])nt[i]=i+1;
		nt2[i][su[i+1]]=i+1;
	}
	dp[0]=1;
	for(int i=0;i<=n;i++)
	{
		int tp=nt[i]-1;
		if(s[i+1]=='a')dp[nt2[tp][(2+su[i])%3]]=(dp[nt2[tp][(2+su[i])%3]]+dp[i])%mod;else dp[i+1]=(dp[i+1]+dp[i])%mod;
		if(s[i+1]=='b')dp[nt2[tp][(1+su[i])%3]]=(dp[nt2[tp][(1+su[i])%3]]+dp[i])%mod;else dp[i+1]=(dp[i+1]+dp[i])%mod;
		if(!(su[n]-su[i])%3&&i)as=(as+dp[i])%mod;
	}
	if(nt[0]==n+1)as=1;
	printf("%d\n",as);
}
43 CF626G Raffles
Problem

nn 个正整数 viv_i ,你可以设置 nn 个数 gig_i ,满足

  1. git\sum g_i \leq t
  2. givig_i\leq v_i
  3. aigi/(gi+vi)\sum a_i*g_i/(g_i+v_i) 最大

有多次修改,每次让一个 viv_i 加一或减一,输出每次修改后的最大值

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

5s,256MB5s,256MB

Sol

考虑给 gig_i 加一,权值会加 vi/(gi+vi)(gi+vi+1)v_i/(g_i+v_i)(g_i+v_i+1)

显然这个值单调递减,因此可以 O(tlogn)O(t\log n) 求出一组询问的答案

考虑一次修改,如果 viv_i 加一,如果原来 gi=0g_i=0 显然不会改变 gg ,否则考虑之前 gig_i 的最后一个增量是 vi/(vi+gi1)(vi+gi1+1)v_i/(v_i+g_i-1)(v_i+g_i-1+1) ,现在 gi2>gi1g_i-2 ->g_i-1 时的增量是 (vi+1)/(vi+1+gi2)(vi+1+gi2+1)(v_i+1)/(v_i+1+g_i-2)(v_i+1+g_i-2+1) ,显然后者大于前者,因此 gig_i 最多减少1

如果原来 si=vis_i=v_i ,此时 gig_i 可能加1

如果 viv_i 减一,现在 gi+1>gi+2g_i+1->g_i+2 的增量是 (vi1)/(vi1+gi+1)(vi1+gi+1+1)(v_i-1)/(v_i-1+g_i+1)(v_i-1+g_i+1+1) ,原来 gi>gi+1g_i->g_i+1 的增量是 vi/(vi+gi)(vi+gi+1)v_i/(v_i+g_i)(v_i+g_i+1)

因此此时 gig_i 最多增加1

因此每次只需要 O(1)O(1) 次修改 gg ,复杂度 O((t+q)logn)O((t+q)\log n)

Code
#include<cstdio>
#include<queue>
#define N 205000
using namespace std;
struct sth{double s;int v,x,y;};
bool operator <(sth a,sth b){return a.s==b.s?(a.v<b.v):a.s<b.s;}
priority_queue<sth> tp1,tp2,tp3,tp4;
int n,m,q,a,b,v[N],r[N],s[N],su;
double as;
void pre()
{
	while(!tp2.empty()&&!tp1.empty()&&tp2.top().v==tp1.top().v&&tp1.top().s==tp2.top().s)tp1.pop(),tp2.pop();
	while(!tp4.empty()&&!tp3.empty()&&tp4.top().v==tp3.top().v&&tp3.top().s==tp4.top().s)tp3.pop(),tp4.pop();
}
void del(int b)
{
	as-=1.0*v[b]*s[b]/(s[b]+r[b]);
	if(s[b])tp4.push((sth){-1.0*v[b]*r[b]/(r[b]+s[b]-1)/(s[b]+r[b]),b,s[b],r[b]});
	if(s[b]<r[b])tp2.push((sth){1.0*v[b]*r[b]/(r[b]+s[b]+1)/(s[b]+r[b]),b,s[b]+1,r[b]});
	pre();
}
void ins(int b)
{
	as+=1.0*v[b]*s[b]/(s[b]+r[b]);
	if(s[b])tp3.push((sth){-1.0*v[b]*r[b]/(r[b]+s[b]-1)/(s[b]+r[b]),b,s[b],r[b]});
	if(s[b]<r[b])tp1.push((sth){1.0*v[b]*r[b]/(r[b]+s[b]+1)/(s[b]+r[b]),b,s[b]+1,r[b]});
	pre();
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<=n;i++)scanf("%d",&r[i]);
	for(int i=1;i<=n;i++)tp1.push((sth){1.0*v[i]/(r[i]+1),i,1,r[i]});
	for(int i=1;i<=m&&!tp1.empty();i++)
	{
		su++;
		sth vl=tp1.top();tp1.pop();
		s[vl.v]++;
		if(s[vl.v]<r[vl.v])
		tp1.push((sth){1.0*v[vl.v]*r[vl.v]/(r[vl.v]+s[vl.v]+1)/(s[vl.v]+r[vl.v]),vl.v,s[vl.v]+1,r[vl.v]});
	}
	for(int i=1;i<=n;i++)as+=1.0*v[i]*s[i]/(s[i]+r[i]);
	for(int i=1;i<=n;i++)if(s[i])
	tp3.push((sth){-1.0*v[i]*r[i]/(r[i]+s[i]-1)/(s[i]+r[i]),i,s[i],r[i]});
	while(q--)
	{
		if(a)printf("%.15lf\n",as);
		scanf("%d%d",&a,&b);
		if(a==1)
		{
			if(su<m)del(b),s[b]++,r[b]++,su++,ins(b);
			else if(!s[b]){del(b);r[b]++;ins(b);continue;}
			else
			{
				int st=b;
				del(b),r[b]++,s[b]--,su--,ins(b);
				if(!tp1.size())continue;
				b=tp1.top().v;
				del(b),s[b]++,su++,ins(b);
				if(s[st]==r[st]-1)
				{
					b=tp3.top().v;
					del(b),s[b]--,su--,ins(b);
					b=tp1.top().v;
					del(b),s[b]++,su++,ins(b);
				}
			}
		}
		else
		{
			if(s[b]==r[b])
			{
				del(b),s[b]--,r[b]--,su--,ins(b);
				if(!tp1.size())continue;
				b=tp1.top().v;
				del(b),s[b]++,su++,ins(b);
			}
			else
			{
				int st=b;
				b=tp3.top().v;
				del(b);if(b!=st)del(st);
				s[b]--;su--;r[st]--;
				ins(b);if(b!=st)ins(st);
				b=tp1.top().v;
				del(b),s[b]++,su++,ins(b);
			}
		}
	}
	printf("%.15lf\n",as);
}
44 CF666D Chain Reaction
Problem

有4个机器人,你可以给每一个一个移动方向和距离,你需要让它们最后的坐标形成边平行于坐标轴的正方形,求最大距离的最小值或者返回无解

tt 组数据

t50t\leq 50

3s,256MB3s,256MB

Sol

枚举四个的方向以及最后在正方形上的位置

有两种情况:

如果左右方向和上下方向都存在,那么至少能确定两个点,从而确定最后的坐标点,暴力讨论即可

如果只有一个方向,那么正方形的边长可以确定,注意到正方形坐标与一个点移动的距离显然是一个V型函数,取max后也是这样的函数,因此可以三分答案+大讨论

复杂度 O(t4!44logV)O(t4!4^4\log V)

Code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int n,s[5][2],mn,as[5][2],T,s1[5][2],is[5],r[5][4],as2[5][2];
int doit1(int x,int y){return max(max(max(s1[1][1]-x,x-s1[1][1]),max(s1[2][1]-y,y-s1[2][1])),max(max(s1[3][1]-x,x-s1[3][1]),max(s1[4][1]-y,y-s1[4][1])));}
int doit2(int x,int y){return max(max(max(s1[1][0]-x,x-s1[1][0]),max(s1[2][0]-y,y-s1[2][0])),max(max(s1[3][0]-x,x-s1[3][0]),max(s1[4][0]-y,y-s1[4][0])));}
void solve(int d1,int d2,int d3,int d4,int v1,int v2,int v3,int v4)
{
	s1[v1][0]=s[1][0];s1[v1][1]=s[1][1];
	s1[v2][0]=s[2][0];s1[v2][1]=s[2][1];
	s1[v3][0]=s[3][0];s1[v3][1]=s[3][1];
	s1[v4][0]=s[4][0];s1[v4][1]=s[4][1];
	if(s1[1][0]==s1[2][0]&&s1[3][0]==s1[4][0])
	{
		memcpy(as2,s1,sizeof(as2));
		int t=s1[3][0]-s1[1][0];
		if(t<0)t=-t;
		int lb=-4e8,rb=4e8;
		while(lb<rb)
		{
			int mid=(lb+rb)>>1,mid1=mid+1;
			if(doit1(mid,mid+t)<doit1(mid1,mid1+t))rb=mid1;
			else lb=mid;
			if(doit1(mid,mid+t)==doit1(mid1,mid1+t)){lb=mid;rb=mid1;break;}
			if(doit1(mid-1,mid-1+t)==doit1(mid,mid+t)){lb=mid-1;rb=mid;break;}
			if(doit1(mid-1,mid-1+t)>doit1(mid,mid+t)&&doit1(mid,mid+t)<doit1(mid1,mid1+t)){lb=rb=mid;break;}
		}
		int tp=doit1(lb,lb+t);
		if(tp<mn)
		{
			mn=tp;
			s1[1][1]=s1[3][1]=lb;s1[2][1]=s1[4][1]=lb+t;
			as[1][0]=s1[v1][0];as[1][1]=s1[v1][1];
			as[2][0]=s1[v2][0];as[2][1]=s1[v2][1];
			as[3][0]=s1[v3][0];as[3][1]=s1[v3][1];
			as[4][0]=s1[v4][0];as[4][1]=s1[v4][1];
		}
		memcpy(s1,as2,sizeof(s1));
	}
	if(s1[1][1]==s1[2][1]&&s1[3][1]==s1[4][1])
	{
		memcpy(as2,s1,sizeof(as2));
		int t=s1[3][1]-s1[1][1];
		if(t<0)t=-t;
		int lb=-4e8,rb=4e8;
		while(lb<rb)
		{
			int mid=(lb+rb)>>1,mid1=mid+1;
			if(doit2(mid,mid+t)<doit2(mid1,mid1+t))rb=mid1;
			else lb=mid;
			if(doit2(mid,mid+t)==doit2(mid1,mid1+t)){lb=mid,rb=mid1;break;}
			if(doit2(mid-1,mid-1+t)==doit2(mid,mid+t)){lb=mid-1,rb=mid;break;}
			if(doit2(mid-1,mid-1+t)>doit2(mid,mid+t)&&doit2(mid,mid+t)<doit2(mid1,mid1+t)){lb=rb=mid;break;}
		}
		int tp=doit2(lb,lb+t);
		if(tp<mn)
		{
			mn=tp;
			s1[1][0]=s1[3][0]=lb;s1[2][0]=s1[4][0]=lb+t;
			as[1][0]=s1[v1][0];as[1][1]=s1[v1][1];
			as[2][0]=s1[v2][0];as[2][1]=s1[v2][1];
			as[3][0]=s1[v3][0];as[3][1]=s1[v3][1];
			as[4][0]=s1[v4][0];as[4][1]=s1[v4][1];
		}
		memcpy(s1,as2,sizeof(s1));
	}
	int ct=0;is[1]=is[2]=is[3]=is[4]=-1e9;
	int r1=(d1&1)?(d1==1?1:-1):0,r2=(d2&1)?(d2==1?1:-1):0;
	if(!r1&&!r2&&s1[1][1]!=s1[2][1])return;
	if(r1&&r2)
	{
		r[++ct][0]=1;r[ct][1]=2;
		if(r1+r2)r[ct][2]=1,r[ct][3]=(s1[2][1]-s1[1][1])*r1;
		else r[ct][2]=2,r[ct][3]=r1*(s1[2][1]-s1[1][1]);
	}
	else
	if(r1){is[1]=(s1[2][1]-s1[1][1])/r1;if(is[1]<0)return;}
	else if(r2){is[2]=(s1[1][1]-s1[2][1])/r2;if(is[2]<0)return;}
	r1=(d3&1)?(d3==1?1:-1):0,r2=(d4&1)?(d4==1?1:-1):0;
	if(!r1&&!r2&&s1[3][1]!=s1[4][1])return;
	if(r1&&r2)
	{
		r[++ct][0]=3;r[ct][1]=4;
		if(r1+r2)r[ct][2]=1,r[ct][3]=(s1[4][1]-s1[3][1])*r1;
		else r[ct][2]=2,r[ct][3]=r1*(s1[4][1]-s1[3][1]);
	}
	else
	if(r1){is[3]=(s1[4][1]-s1[3][1])/r1;if(is[3]<0)return;}
	else if(r2){is[4]=(s1[3][1]-s1[4][1])/r2;if(is[4]<0)return;}
	r1=(~d2&1)?(d2==2?1:-1):0,r2=(~d3&1)?(d3==2?1:-1):0;
	if(!r1&&!r2&&s1[2][0]!=s1[3][0])return;
	if(r1&&r2)
	{
		r[++ct][0]=2;r[ct][1]=3;
		if(r1+r2)r[ct][2]=1,r[ct][3]=(s1[3][0]-s1[2][0])*r1;
		else r[ct][2]=2,r[ct][3]=r1*(s1[3][0]-s1[2][0]);
	}
	else
	if(r1){is[2]=(s1[3][0]-s1[2][0])/r1;if(is[2]<0)return;}
	else if(r2){is[3]=(s1[2][0]-s1[3][0])/r2;if(is[3]<0)return;}
	r1=(~d4&1)?(d4==2?1:-1):0,r2=(~d1&1)?(d1==2?1:-1):0;
	if(!r1&&!r2&&s1[4][0]!=s1[1][0])return;
	if(r1&&r2)
	{
		r[++ct][0]=4;r[ct][1]=1;
		if(r1+r2)r[ct][2]=1,r[ct][3]=(s1[1][0]-s1[4][0])*r1;
		else r[ct][2]=2,r[ct][3]=r1*(s1[1][0]-s1[4][0]);
	}
	else
	if(r1){is[4]=(s1[1][0]-s1[4][0])/r1;if(is[4]<0)return;}
	else if(r2){is[1]=(s1[4][0]-s1[1][0])/r2;if(is[1]<0)return;}
	if(ct==0)
	{
		if(~d1&1)s1[1][0]+=is[1]*((d1&2)?1:-1);else s1[1][1]+=is[1]*((d1&2)?-1:1);
		if(~d2&1)s1[2][0]+=is[2]*((d2&2)?1:-1);else s1[2][1]+=is[2]*((d2&2)?-1:1);
		if(~d3&1)s1[3][0]+=is[3]*((d3&2)?1:-1);else s1[3][1]+=is[3]*((d3&2)?-1:1);
		if(~d4&1)s1[4][0]+=is[4]*((d4&2)?1:-1);else s1[4][1]+=is[4]*((d4&2)?-1:1);
		if(s1[2][0]-s1[1][0]==s1[3][1]-s1[2][1])
		{
			int tp=max(max(is[1],is[2]),max(is[3],is[4]));
			if(tp<mn)
			{
				mn=tp;
				as[1][0]=s1[v1][0];as[1][1]=s1[v1][1];
				as[2][0]=s1[v2][0];as[2][1]=s1[v2][1];
				as[3][0]=s1[v3][0];as[3][1]=s1[v3][1];
				as[4][0]=s1[v4][0];as[4][1]=s1[v4][1];
			}
		}
		return;
	}
	else if(ct==1)
	{
		if(is[1]>-5e8)if(~d1&1)s1[1][0]+=is[1]*((d1&2)?1:-1);else s1[1][1]+=is[1]*((d1&2)?-1:1);
		if(is[2]>-5e8)if(~d2&1)s1[2][0]+=is[2]*((d2&2)?1:-1);else s1[2][1]+=is[2]*((d2&2)?-1:1);
		if(is[3]>-5e8)if(~d3&1)s1[3][0]+=is[3]*((d3&2)?1:-1);else s1[3][1]+=is[3]*((d3&2)?-1:1);
		if(is[4]>-5e8)if(~d4&1)s1[4][0]+=is[4]*((d4&2)?1:-1);else s1[4][1]+=is[4]*((d4&2)?-1:1);
		if(r[1][0]==1)
		{
			int asy=-s1[2][0]+s1[1][0]+s1[3][1]-s1[2][1];
			int r1=(d1&1)?(d1==1?1:-1):0;asy/=r1;
			if(asy<0)return;
			is[1]=asy;
			if(r[1][2]==1)asy-=r[1][3];
			else asy=r[1][3]-asy;
			if(asy<0)return;
			is[2]=asy;
			if(~d1&1)s1[1][0]+=is[1]*((d1&2)?1:-1);else s1[1][1]+=is[1]*((d1&2)?-1:1);
			if(~d2&1)s1[2][0]+=is[2]*((d2&2)?1:-1);else s1[2][1]+=is[2]*((d2&2)?-1:1);
		}
		else if(r[1][0]==3)
		{
			int asy=s1[2][0]-s1[1][0]+s1[2][1]-s1[3][1];
			int r1=(d3&1)?(d3==1?1:-1):0;asy/=r1;
			if(asy<0)return;
			is[3]=asy;
			if(r[1][2]==1)asy-=r[1][3];
			else asy=r[1][3]-asy;
			if(asy<0)return;
			is[4]=asy;
			if(~d3&1)s1[3][0]+=is[3]*((d3&2)?1:-1);else s1[3][1]+=is[3]*((d3&2)?-1:1);
			if(~d4&1)s1[4][0]+=is[4]*((d4&2)?1:-1);else s1[4][1]+=is[4]*((d4&2)?-1:1);
		}
		else if(r[1][0]==2)
		{
			int asy=s1[3][1]-s1[2][1]+s1[1][0]-s1[2][0];
			int r1=(~d2&1)?(d2==2?1:-1):0;asy/=r1;
			if(asy<0)return;
			is[2]=asy;
			if(r[1][2]==1)asy-=r[1][3];
			else asy=r[1][3]-asy;
			if(asy<0)return;
			is[3]=asy;
			if(~d2&1)s1[2][0]+=is[2]*((d2&2)?1:-1);else s1[2][1]+=is[2]*((d2&2)?-1:1);
			if(~d3&1)s1[3][0]+=is[3]*((d3&2)?1:-1);else s1[3][1]+=is[3]*((d3&2)?-1:1);
		}
		else
		{
			int asy=-s1[3][1]+s1[2][1]+s1[2][0]-s1[1][0];
			int r1=(~d1&1)?(d1==2?1:-1):0;asy/=r1;
			if(asy<0)return;
			is[1]=asy;
			if(r[1][2]==1)asy+=r[1][3];
			else asy=r[1][3]-asy;
			if(asy<0)return;
			is[4]=asy;
			if(~d1&1)s1[1][0]+=is[1]*((d1&2)?1:-1);else s1[1][1]+=is[1]*((d1&2)?-1:1);
			if(~d4&1)s1[4][0]+=is[4]*((d4&2)?1:-1);else s1[4][1]+=is[4]*((d4&2)?-1:1);
		}
		if(s1[2][0]-s1[1][0]==s1[3][1]-s1[2][1])
		{
			int tp=max(max(is[1],is[2]),max(is[3],is[4]));
			if(tp<mn)
			{
				mn=tp;
				as[1][0]=s1[v1][0];as[1][1]=s1[v1][1];
				as[2][0]=s1[v2][0];as[2][1]=s1[v2][1];
				as[3][0]=s1[v3][0];as[3][1]=s1[v3][1];
				as[4][0]=s1[v4][0];as[4][1]=s1[v4][1];
			}
		}
		return;
	}
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		mn=1e9;
		scanf("%d%d%d%d%d%d%d%d",&s[1][0],&s[1][1],&s[2][0],&s[2][1],&s[3][0],&s[3][1],&s[4][0],&s[4][1]);
		for(int v1=1;v1<=4;v1++)
		for(int v2=1;v2<=4;v2++)
		for(int v3=1;v3<=4;v3++)
		for(int v4=1;v4<=4;v4++)
		if(v1!=v2&&v1!=v3&&v1!=v4&&v2!=v3&&v2!=v4&&v3!=v4)
		for(int d1=1;d1<=4;d1++)
		for(int d2=1;d2<=4;d2++)
		for(int d3=1;d3<=4;d3++)
		for(int d4=1;d4<=4;d4++)
		solve(d1,d2,d3,d4,v1,v2,v3,v4);
		if(mn>5e8)printf("-1\n");
		else printf("%d\n%d %d\n%d %d\n%d %d\n%d %d\n",mn,as[1][0],as[1][1],as[2][0],as[2][1],as[3][0],as[3][1],as[4][0],as[4][1]);
	}
}
45 ARC102F Revenge of BBuBBBlesort!
Problem

你有一个排列,你每次可以选择相邻的 a,b,ca,b,c ,满足 a>b>ca>b>c ,然后交换 a,ca,c

求是否能给排列排好序

n3×105n\leq 3\times 10^5

2s,1024MB2s,1024MB

Sol

如果有一位的奇偶性对不上显然无解

如果对 pi1,pi,pi+1p_{i-1},p_i,p_{i+1} 进行了操作,之后 pi1>pip_{i-1}>p_i

在左边操作, pi1p_{i-1} 只会增大

因此如果以 ii 为中心进行了操作, i1i-1 为中心不能有操作

因此操作可以被分成若干段,每一段里面沿所有奇数/偶数为中心进行操作

容易将每一段分开,只需要判断一段交换所有奇数/偶数是否合法

考虑只进行偶数位的操作,对于每次交换,只有当两个数都向应该去的方向移动时,这次交换是合法的

因此这相当于冒泡排序这题的结论,只需求出这一段的最长下降子序列长度是否小于3即可

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
int n,a,b,c,l1,l2;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		if((a^i)&1){printf("No\n");return 0;}
		if(a>i||(l1&&!((l1^a)&1))){if(!l1)l1=a;else if(a<l2){printf("No\n");return 0;}else if(a<l1)l2=a;else l1=a;}
		if(i&1){if(b<a)b=a;}else if(c<a)c=a;
		if(b>i&&c>i){printf("No\n");return 0;}
		if(b<=i&&c<=i)l1=l2=0;
	}
	printf("Yes\n");
}
46 CF605E Intergalaxy Trips
Problem

有一张有向完全图,每一天,每条边都有一个概率开放,通过一条边需要一天,你可以在原地等待,求期望从 11nn 的时间

n1000n\leq 1000

2s,256MB2s,256MB

Sol

fxf_x 表示 xxnn 的期望用时

显然, xx 点只会向 ff 更小的点走

注意到如果按照 ff 从小到大加入每一个 xx 的出边,那么可以快速维护每加入一个后当前的 ff

一开始 fn=0f_n=0 ,每次选择当前没选的里面当前 ff 最小的,然后更新

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

Code
#include<cstdio>
using namespace std;
#define N 1050
int s[N][N],n,vis[N];
double as[N],ls[N];
int main()
{
	scanf("%d",&n);
	if(n==1){printf("0\n");return 0;}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	scanf("%d",&s[i][j]);
	for(int i=1;i<n;i++)as[i]=0,ls[i]=1-s[i][n]/100.0;
	vis[n]=1;
	for(int i=1;i<n;i++)
	{
		int su=1;
		for(int j=1;j<=n;j++)if((as[su]+1)/(1-ls[su])>(as[j]+1)/(1-ls[j])&&!vis[j])su=j;
		vis[su]=1;
		for(int j=1;j<=n;j++)if((as[su]+1)/(1-ls[su])<(as[j]+1)/(1-ls[j]))
		as[j]+=ls[j]*s[j][su]/100*(as[su]+1)/(1-ls[su]),ls[j]-=ls[j]*s[j][su]/100;
	}
	printf("%.10lf\n",(as[1]+1)/(1-ls[1]));
}
47 AGC031E Snuke the Phantom Thief
Problem

nn 个宝石,每个宝石有价值,有一些要求,有四种

  1. x=ix=i 左侧不能选超过 vv
  2. x=ix=i 右侧不能选超过 vv
  3. y=iy=i 下方不能选超过 vv
  4. y=iy=i 上方不能选超过 vv

求能选的最大价值

n80n\leq 80

5s,1024MB5s,1024MB

Sol

限制形如 选的x坐标中第 kk 大/小的在某个范围内

枚举选的个数,可以求出对于x坐标第 ii 大的,坐标范围 [li,ri][l_i,r_i] ,y坐标也一样

显然有 lili+1,riri+1l_i\leq l_{i+1},r_i\leq r_{i+1} ,因此对于任意一种 xi[li,ri]x_i\in [l_i,r_i] 的方式,即使 xix_i 顺序不对也合法

因此可以不考虑顺序,相当于把每个坐标分配一个x坐标的编号和y坐标的编号,每个编号的坐标有一个区间的限制

建费用流,源点向每一个x坐标编号连边,每一个编号向合法的宝石连边,每个宝石拆点连费用,每个宝石向合法的y坐标编号连边,每一个y坐标编号向汇点连边

Code
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define N 421
#define M 38300
int s[N][3],v[N][2],n,m,l[N][2],r[N][2],head[N],cur[N],cnt,in[N],vis[N];
char c[12];
long long dis[N],c1[N],as1,as;
struct edge{int t,next,v;long long c;}ed[M];
void adde(int f,int t,int v,long long 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> tp;
	tp.push(s);dis[s]=0;in[s]=1;
	while(!tp.empty())
	{
		int r=tp.front();tp.pop();in[r]=0;
		for(int i=head[r];i;i=ed[i].next)
		if(ed[i].v&&dis[ed[i].t]<dis[r]+ed[i].c)
		{
			dis[ed[i].t]=dis[r]+ed[i].c;
			if(!in[ed[i].t])in[ed[i].t]=1,tp.push(ed[i].t);
		}
	}
	return dis[t]>-1e18;
}
int dfs(int u,int t,int f)
{
	if(!f||u==t)return f;
	vis[u]=1;
	int tp,as=0;
	for(int& i=cur[u];i;i=ed[i].next)
	if(!vis[ed[i].t]&&ed[i].v&&dis[ed[i].t]==dis[u]+ed[i].c&&(tp=dfs(ed[i].t,t,min(f,ed[i].v))))
	{
		ed[i].v-=tp;ed[i^1].v+=tp;
		f-=tp,as+=tp;as1+=ed[i].c*tp;
		if(!f){vis[u]=0;return as;}
	}
	vis[u]=0;return as;
}
int dinic(int s,int t){int tp=0;while(spfa(s,t))tp+=dfs(s,t,1e8);return tp;}
int main()
{
	scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d%d%lld",&v[i][0],&v[i][1],&c1[i]);
	scanf("%d",&m);for(int i=1;i<=m;i++){scanf("%s%d%d",c+1,&s[i][1],&s[i][2]);if(c[1]=='U')s[i][0]=0;if(c[1]=='D')s[i][0]=1;if(c[1]=='R')s[i][0]=2;if(c[1]=='L')s[i][0]=3;}
	for(int i=1;i<=n;i++)
	{
		cnt=1;for(int j=1;j<=402;j++)head[j]=0;
		for(int j=1;j<=i;j++)l[j][0]=1,l[j][1]=100,r[j][0]=1,r[j][1]=100;
		for(int j=1;j<=m;j++)if(i>s[j][2])
		{
			int tp=s[j][2];
			if(s[j][0]==1)
			{
				for(int k=1;k<=i-tp;k++)l[k][0]=max(l[k][0],s[j][1]+1);
			}
			if(s[j][0]==0)
			{
				for(int k=tp+1;k<=i;k++)l[k][1]=min(l[k][1],s[j][1]-1);
			}
			if(s[j][0]==3)
			{
				for(int k=1;k<=i-tp;k++)r[k][0]=max(r[k][0],s[j][1]+1);
			}
			if(s[j][0]==2)
			{
				for(int k=tp+1;k<=i;k++)r[k][1]=min(r[k][1],s[j][1]-1);
			}
		}
		int s1=401,t=402;
		for(int j=1;j<=i;j++)adde(s1,j,1,0);
		for(int j=1;j<=i;j++)
		for(int k=l[j][0];k<=l[j][1];k++)
		adde(j,k+100,1,0);
		for(int j=1;j<=n;j++)adde(v[j][1]+100,v[j][0]+200,1,c1[j]);
		for(int j=1;j<=i;j++)
		for(int k=r[j][0];k<=r[j][1];k++)
		adde(k+200,j+300,1,0);
		for(int j=1;j<=i;j++)adde(j+300,t,1,0);
		as1=0;int tp2=dinic(s1,t);
		if(tp2==i)as=max(as,as1);
	}
	printf("%lld\n",as);
}
48 AGC028D Chords
Problem

在环上有 2n2n 个点,现在已经连了 kk 对点

求出对于剩下 2(nk)2(n-k) 个点的所有连边方式,得到的线段的连通块个数的和,模 109+710^9+7

n300n\leq 300

2s,1024MB2s,1024MB

Sol

一个连通块由若干点和线段组成

考虑这些点中编号最小最大的 l,rl,r ,在原图中, [l,r][l,r] 的点一定和其余的点不连通,且 l,rl,r 连通

可以发现,这样的一对 l,rl,r 一定对应一个连通块

dpl,rdp_{l,r} 表示这一对 l,rl,r 出现的方案数,转移时首先任意连,然后减去不连通的情况,这种情况可以枚举l所在的连通块 [l,k][l,k] ,右边任意连

复杂度 O(n3)O(n^3)

Code
#include<cstdio>
using namespace std;
#define N 605
#define mod 1000000007
int dp[N][N],n,m,a,b,t[N],s[N],su[N],as;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d",&a,&b),t[a]=b,t[b]=a;
	s[0]=1;for(int i=2;i<=n*2;i++)s[i]=1ll*s[i-2]*(i-1)%mod;
	for(int i=1;i<=n*2;i++)su[i]=su[i-1]+!t[i];
	for(int l=1;l<=n*2;l++)
	for(int i=1;i+l<=n*2;i++)
	{
		int j=i+l;
		int fg=1;
		for(int k=i;k<=j;k++)if((t[k]<i||t[k]>j)&&t[k])fg=0;
		if((su[j]^su[i-1])&1)fg=0;
		if(!fg)continue;
		int tp=s[su[j]-su[i-1]];
		for(int k=i;k<j;k++)tp=(tp-1ll*dp[i][k]*s[su[j]-su[k]]%mod+mod)%mod;
		dp[i][j]=tp;as=(as+1ll*tp*s[su[n*2]-su[j]+su[i-1]])%mod;
	}
	printf("%d\n",as);
}
49 CF536D Tavas in Kansas
Problem

有一张连通无向图,每个点上有分数

两个人分别在 s,ts,t ,第一个人先手,每次每个人可以选择一个 dd ,拿走离他距离小于等于 dd 的点上的分数,每次必须拿走至少一个分数,求最优策略下先手分数与后手分数的大小关系

n2000,m105n\leq 2000,m\leq 10^5

2s,512MB2s,512MB

Sol

首先dij求出距离再离散化

dpi,j,0/1dp_{i,j,0/1} 表示先手选了距离它小于等于 ii 的,后手选了小于等于 jj 的,当前先手/后手先,当前的先手最大/最小得分

转移相当于 dpi,j,0=maxk,i<dis(s,u)k,dis(t,u)>j1>0,dpk,j,1+i<dis(s,u)k,dis(t,u)>jvudp_{i,j,0}=max_{k,\sum_{i<dis(s,u)\leq k,dis(t,u)>j}1>0},dp_{k,j,1}+\sum_{i<dis(s,u)\leq k,dis(t,u)>j}v_u

对于一个 jj ,显然随着 ii 递减,合法的最小 kk 递减,可以快速维护合法的 kk

注意到右边可以拆成 i<dis(s,u),j<dis(t,u)vuk<dis(s,u),j<dis(t,u)vu\sum_{i<dis(s,u),j<dis(t,u)}v_u-\sum_{k<dis(s,u),j<dis(t,u)}v_u

可以二维前缀和,维护 dpk,j,1k<dis(s,u),j<dis(t,u)vudp_{k,j,1}-\sum_{k<dis(s,u),j<dis(t,u)}v_u 的后缀min

后手情况类似

复杂度 O(n2+mlogm)O(n^2+m\log m)

Code
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 2050
#define M 100050
struct edge{int t,next,v;}ed[M*2];
int head[N],cnt,id1[N],id2[N],c1,v[N],n,m,a,b,c,s,t,su1[N][N],pt1[N][N],pt2[N][N],fg1,fg2,fg3;
long long dis[N],vis[N],s1[N],s2[N],su[N][N],dp[N][N][2],mx[N][N],mn[N][N];
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 dij(int s)
{
	priority_queue<pair<long long,int> > st;
	st.push(make_pair(0,s));
	memset(vis,0,sizeof(vis));
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0;
	while(!st.empty())
	{
		int v=st.top().second;st.pop();
		if(vis[v])continue;vis[v]=1;
		for(int i=head[v];i;i=ed[i].next)
		if(dis[ed[i].t]>dis[v]+ed[i].v)
		dis[ed[i].t]=dis[v]+ed[i].v,st.push(make_pair(-dis[ed[i].t],ed[i].t));
	}
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<=m;i++)scanf("%d%d%d",&a,&b,&c),adde(a,b,c);
	dij(s);for(int i=1;i<=n;i++)s1[i]=dis[i];sort(s1+1,s1+n+1);
	c1=0;dis[0]=-1;for(int i=1;i<=n;i++)if(s1[i]!=s1[i-1])s2[++c1]=s1[i];
	for(int i=1;i<=n;i++)id1[i]=upper_bound(s2+1,s2+c1+1,dis[i])-s2;
	dij(t);for(int i=1;i<=n;i++)s1[i]=dis[i];sort(s1+1,s1+n+1);
	c1=0;dis[0]=-1;for(int i=1;i<=n;i++)if(s1[i]!=s1[i-1])s2[++c1]=s1[i];
	for(int i=1;i<=n;i++)id2[i]=upper_bound(s2+1,s2+c1+1,dis[i])-s2;
	for(int i=1;i<=n;i++)su[id1[i]][id2[i]]+=v[i],su1[id1[i]][id2[i]]++;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)su[i][j]=su[i-1][j]+su[i][j-1]-su[i-1][j-1]+su[i][j],su1[i][j]=su1[i-1][j]+su1[i][j-1]-su1[i-1][j-1]+su1[i][j];
	memset(dp,-0x3f,sizeof(dp));
	memset(mx,-0x3f,sizeof(mx));
	memset(mn,-0x3f,sizeof(mn));
	for(int i=0;i<n;i++)
	{
		dp[i][n][0]=0;mn[i][n]=-su[n][n]+su[i][n];
		dp[n][i][1]=0;mx[n][i]=su[n][n]-su[n][i];
		pt1[n][i]=pt2[i][n]=n;
	}
	for(int i=n-1;i>=0;i--)
	for(int j=n-1;j>=0;j--)
	{
		int s1=pt1[i+1][j];
		while(su1[s1-1][n]-su1[s1-1][j]-su1[i][n]+su1[i][j]>0)s1--;
		pt1[i][j]=s1;
		if(su1[s1][n]-su1[s1][j]-su1[i][n]+su1[i][j]>0)
		{
			dp[i][j][0]=mx[s1][j]-su[i][n]+su[i][j];
			mn[i][j]=min(dp[i][j][0]-su[n][j]+su[i][j],mn[i][j+1]);
		}
		else mn[i][j]=mn[i][j+1];
		s1=pt2[i][j+1];
		while(su1[n][s1-1]-su1[i][s1-1]-su1[n][j]+su1[i][j]>0)s1--;
		pt2[i][j]=s1;
		if(su1[n][s1]-su1[i][s1]-su1[n][j]+su1[i][j]>0)
		{
			dp[i][j][1]=mn[i][s1]+su[n][j]-su[i][j];
			mx[i][j]=max(dp[i][j][1]+su[i][n]-su[i][j],mx[i+1][j]);
		}
		else mx[i][j]=mx[i+1][j];
	}
	if(dp[0][0][0]>0)printf("Break a heart");
	else if(dp[0][0][0]<0)printf("Cry");
	else printf("Flowers");
}
50 AGC027F Grafting
Problem

有两棵树,你可以在第一棵树上进行操作:

选择一个没有被选过的叶子,更改它的父亲

求最少要多少次操作使得两树相同或者输出无解

tt 组数据

t20,n50t\leq 20,n\leq 50

5s,1024MB5s,1024MB

Sol

显然答案不超过 nn

考虑答案小于等于 n1n-1 的情况,一定有一个点不变

从这个点开始,沿着两棵树上都有的边走,走到的点一定不会被操作

对于剩下的点,必须要操作

考虑操作的顺序

在第一棵树上,父子关系的两个点,儿子必须比父亲先操作

在第二棵树上,父子关系的两个点,父亲必须比儿子先操作

可以发现,满足以上条件的操作顺序一定合法,因此可以建边tarjan解决

考虑答案为 nn 的情况,枚举第一次操作,之后这个点不变,使用之前的方法解决即可

复杂度 O(tn3)O(tn^3)

Code
#include<cstdio>
#include<stack>
#include<queue>
#include<algorithm>
using namespace std;
#define N 105
int dfn[N],low[N],scc[N],n,m,head[N],cnt,head2[N],cnt2,f1[N],f2[N],head3[N],cnt3,vis[N],is[N],ct,ct2,fg,e1[N][2],e2[N][2],in[N],T;
struct edge{int t,next;}ed[N*10],ed2[N*10],ed3[N*10];
stack<int> tp;
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 adde2(int f,int t){ed2[++cnt2]=(edge){t,head2[f]};head2[f]=cnt2;ed2[++cnt2]=(edge){f,head2[t]};head2[t]=cnt2;}
void adde3(int f,int t){ed3[++cnt3]=(edge){t,head3[f]};head3[f]=cnt3;}
void dfs1(int u,int fa){f1[u]=fa;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs1(ed[i].t,u);}
void dfs2(int u,int fa){f2[u]=fa;for(int i=head2[u];i;i=ed2[i].next)if(ed2[i].t!=fa)dfs2(ed2[i].t,u);}
void dfs3(int u,int fa)
{
	vis[u]=1;
	for(int i=head2[u];i;i=ed2[i].next)if(ed2[i].t!=fa)is[ed2[i].t]=1;
	queue<int> tp;
	for(int i=head[u];i;i=ed[i].next)if(is[ed[i].t])tp.push(ed[i].t);
	for(int i=head2[u];i;i=ed2[i].next)if(ed2[i].t!=fa)is[ed2[i].t]=0;
	while(!tp.empty())dfs3(tp.front(),u),tp.pop();
}
void dfs4(int u)
{
	tp.push(u);
	dfn[u]=low[u]=++ct;
	for(int i=head3[u];i;i=ed3[i].next)
	{
		if(!dfn[ed3[i].t])dfs4(ed3[i].t),low[u]=min(low[u],low[ed3[i].t]);
		else if(!scc[ed3[i].t])low[u]=min(low[u],dfn[ed3[i].t]);
	}
	if(dfn[u]==low[u])
	{
		int st=++ct2,s=tp.top();
		tp.pop();scc[s]=st;
		while(s!=u)
		{
			s=tp.top();tp.pop();
			scc[s]=st,fg=1;
		}
	}
}
int check(int s)
{
	int as=0;
	for(int i=1;i<=n;i++)head[i]=head2[i]=head3[i]=scc[i]=low[i]=dfn[i]=vis[i]=0;
	ct=ct2=fg=cnt=cnt2=cnt3=0;
	for(int i=1;i<n;i++)adde(e1[i][0],e1[i][1]),adde2(e2[i][0],e2[i][1]);
	dfs1(s,0);dfs2(s,0);dfs3(s,0);
	for(int i=1;i<=n;i++)if(!vis[i]){as++;if(!vis[f1[i]])adde3(i,f1[i]);if(!vis[f2[i]])adde3(f2[i],i);}
	for(int i=1;i<=n;i++)if(!dfn[i])dfs4(i);
	if(fg)return -1;
	return as;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=1;i<n;i++)scanf("%d%d",&e1[i][0],&e1[i][1]);
		for(int i=1;i<n;i++)scanf("%d%d",&e2[i][0],&e2[i][1]);
		int mn=1e9,fg2=0;
		for(int i=1;i<=n;i++){int st=check(i);if(st!=-1&&st<mn)mn=st;}
		if(mn<1e9)printf("%d\n",mn);
		else
		{
			for(int i=1;i<=n;i++)
			{
				int st=0;
				for(int j=1;j<n;j++)if(e1[j][0]==i||e1[j][1]==i){if(st)st=-1;else st=j;}
				if(st==-1)continue;
				int l1=e1[st][0],l2=e1[st][1];
				for(int j=1;j<=n;j++)if(j!=i){e1[st][0]=i,e1[st][1]=j;if(check(i)!=-1)fg2=1;}
				e1[st][0]=l1,e1[st][1]=l2;
			}
			if(fg2)printf("%d\n",n);
			else printf("-1\n");
		}
	}
}
51 AGC024E Sequence Growing Hard
Problem

有一个初始为空的序列,你需要往里面插入 nn[1,k][1,k] 的整数,满足:

每一次插入后,序列的字典序大于插入前的字典序

设第 ii 次之后的序列为 AiA_i ,求 {A1,...,An}\{A_1,...,A_n\} 的数量,对 mm 取模

n,k300n,k\leq 300

2s,1024MB2s,1024MB

Sol

一个数必须被插在一个小于等于它的数的左边

考虑一串相同的数,插一个相同的数进去时,发现无论插在哪序列是一样的,因此可以钦定插在结尾,即一个数必须被插在一个小于它的数的左边

把每个数向插入它时它右边的数连边,可以得到一个森林

A0=0A_0={0} ,那么就可以得到一棵树

显然每个点的子树是一段区间,并且每个点权值,编号都大于它的父亲

dpi,jdp_{i,j} 表示 ii 个点,根节点权值为 jj 的答案

枚举编号最小的儿子子树的大小,那么显然编号第一个是这个点,接下来这个点子树内部的编号和其余的编号是独立的,那么有 dpi,j=l=1i1v=j+1kdpl,vdpil,jCi2j1dp_{i,j}=\sum_{l=1}^{i-1}\sum_{v=j+1}^kdp_{l,v}dp_{i-l,j}C_{i-2}^{j-1} ,答案为 dpn+1,0dp_{n+1,0}

预处理dp后缀和,复杂度 O(n3)O(n^3)

Code
#include<cstdio>
using namespace std;
#define N 305
int n,m,p,dp[N][N],su[N][N],c[N][N];
int main()
{
	scanf("%d%d%d",&n,&m,&p);n++;m++;
	for(int i=1;i<=m;i++)dp[1][i]=1,su[1][i]=i;
	for(int i=0;i<=n;i++)c[i][i]=c[i][0]=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])%p;
	for(int i=2;i<=n;i++)
	{
		for(int k=1;k<=m;k++)
		for(int j=1;j<i;j++)
		dp[i][k]=(dp[i][k]+1ll*dp[i-j][k]*su[j][k-1]%p*c[i-2][j-1])%p;
		for(int k=1;k<=m;k++)su[i][k]=(su[i][k-1]+dp[i][k])%p;
	}
	printf("%d\n",dp[n][m]);
}
52 CF553E Kyoya and Train
Problem

nn 个点和 mm 条火车线路连接,每条线路每次乘坐有 pi,j(j>0)p_{i,j}(j>0) 的概率需要 jj 时间(多次乘坐概率独立

每条铁路乘坐一次有费用

你从1去到n,如果你到达的时间大于 tt 则需要额外 xx 的代价

求最优策略期望代价

n50,m100,t20000n\leq 50,m\leq 100,t\leq 20000

8s,512MB8s,512MB

Sol

dpi,jdp_{i,j} 表示现在在 ii ,现在时间为 jj ,最优策略期望代价

枚举下一条出边,那么有 dpi,j=min(u,v)(kp(u,v),kdpv,j+k)dp_{i,j}=min_{(u,v)}(\sum_k p_{(u,v),k}dp_{v,j+k})

因为时间为正数,所以可以从大到小枚举转移,暴力复杂度 O(mt2)O(mt^2)

转移形式类似分治FFT,因此考虑分治,当前处理 [l,r][l,r] 的时间时,先计算 [mid+1,r][mid+1,r] ,然后fft算 [mid+1,r][mid+1,r][l,mid][l,mid] 每条边对应转移的贡献

转移式中可能有 j+k>tj+k>t 的部分,这一部分可以对每条边的 tt 后缀和预处理解决,如果时间大于 tt 显然走最短路到 nn

复杂度 O(mtlog2t)O(mt\log^2t)

Code
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 70000
#define M 105
double pi=acos(-1);
struct comp{double x,y;};
comp operator +(comp a,comp b){return (comp){a.x+b.x,a.y+b.y};}
comp operator -(comp a,comp b){return (comp){a.x-b.x,a.y-b.y};}
comp operator *(comp a,comp b){return (comp){a.x*b.x-a.y*b.y,a.y*b.x+b.y*a.x};}
comp fft[N],a[N],b[N];
int n,m,t,f,rev[N],v[M][3],s[M][N],dis[M],vis[M],head[M],su[M][N],cnt;
double dp[M][N],dp2[M][N];
struct edge{int t,next,v;}ed[M*2];
void adde(int f,int t,int v){ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;}
void dij(int s)
{
	priority_queue<pair<long long,int> > st;
	st.push(make_pair(0,s));
	memset(vis,0,sizeof(vis));
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0;
	while(!st.empty())
	{
		int v=st.top().second;st.pop();
		if(vis[v])continue;vis[v]=1;
		for(int i=head[v];i;i=ed[i].next)
		if(dis[ed[i].t]>dis[v]+ed[i].v)
		dis[ed[i].t]=dis[v]+ed[i].v,st.push(make_pair(-dis[ed[i].t],ed[i].t));
	}
}
void dft(int s,comp *a,int t)
{
	for(int i=0;i<s;i++)rev[i]=(rev[i>>1]>>1)+((i&1)*(s>>1)),fft[rev[i]]=a[i];
	for(int l=2;l<=s;l<<=1)
	{
		comp t1=(comp){cos(pi*2/l),t*sin(pi*2/l)};
		for(int i=0;i<s;i+=l)
		{
			comp s=(comp){1,0};
			for(int j=i;j<i+(l>>1);j++,s=s*t1)
			{
				comp s1=fft[j],s2=fft[j+(l>>1)]*s;
				fft[j]=s1+s2;fft[j+(l>>1)]=s1-s2;
			}
		}
	}
	for(int i=0;i<s;i++){a[i]=fft[i];if(t==-1)a[i].x/=s,a[i].y/=s;}
}
void cdq(int l,int r)
{
	if(l==r)
	{
		for(int i=1;i<n;i++)dp2[i][l]=1e17;
		for(int i=1;i<=m;i++)
		{
			double tp=1-su[i][t-l]/1e5;
			dp[i][l]+=tp*(f+dis[v[i][1]])+v[i][2];
			if(dp[i][l]<dp2[v[i][0]][l])dp2[v[i][0]][l]=dp[i][l];
		}
		return;
	}
	int mid=(l+r)>>1;
	cdq(mid+1,r);
	for(int i=1;i<=m;i++)
	{
		int l1=1;while(l1<(r-l)*3/2+5)l1<<=1;
		for(int j=0;j<l1;j++)a[j].x=a[j].y=b[j].x=b[j].y=0;
		for(int j=mid+1;j<=r;j++)a[j-mid].x=dp2[v[i][1]][j];
		for(int j=1;j<=r-l+1;j++)b[r-l+1-j].x=s[i][j]/1e5;
		dft(l1,a,1);dft(l1,b,1);for(int j=0;j<l1;j++)a[j]=a[j]*b[j];dft(l1,a,-1);
		for(int j=l;j<=mid;j++)dp[i][j]+=a[j+r-l+1-mid].x;
	}
	cdq(l,mid);
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&t,&f);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&v[i][0],&v[i][1],&v[i][2]);
		adde(v[i][1],v[i][0],v[i][2]);
		for(int j=1;j<=t;j++)scanf("%d",&s[i][j]),su[i][j]=su[i][j-1]+s[i][j];
	}
	dij(n);cdq(0,t);
	printf("%.10lf\n",dp2[1][0]);
}
53 AGC037F Counting of Subarrays
Problem

有一个序列,你可以进行如下操作:

选择大于等于 kk 个相邻的 aa ,将它们替换成 a+1a+1

求有多少个区间满足存在一种操作方式将其变成一个数

n2×105,k>1n\leq 2\times 10^5,k>1

2s,1024MB2s,1024MB

Sol

考虑一个区间怎么操作

如果当前还有多种数,贪心地想,每一段最小的数应该分成尽量多的这个数+1,即 aa 个数变成 ak\left\lfloor\frac{a}{k}\right\rfloor

如果只剩一种数且大于等于 kk 个就直接win了

从小到大枚举数,对于一个全部是 aa 的区间,如果 l,rl,r 都在区间内部,可以直接算出来贡献

否则,它一定包含的是这个区间的一段前缀或一段后缀,根据贪心要尽量多地变成下一个数,那么可以算出对于 1,...,lenk1,...,\left\lfloor\frac{len}{k}\right\rfloor ,有多少个前缀/后缀可以分出这么多个下一个数

那么可以看成 lenk\left\lfloor\frac{len}{k}\right\rfloor 个下一个数,每一个数有 li,ril_i,r_i ,表示有多少个 ll 贪心操作到现在以这个数为 ll , rr 同理

初始 li=ri=1l_i=r_i=1 ,每次算的时候对一段做前缀和就可以求出下一次的 li,ril_i,r_i ,算答案同理

但是这样可能算重,考虑4个1,k=2k=2 ,这时整个区间在1的时候会被算一遍,在2的时候又被算一遍

因此每一次只应该计算长度为 [k,k21][k,k^2-1] 的区间就可以了

对每种数字维护一个vector就行了

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

Code
#include<cstdio>
#include<map>
#include<vector>
#include<set>
#include<algorithm>
using namespace std;
#define N 400600
map<int,int> tp1;
set<int> tp3;
struct sth{int l,r,lb,rb;friend bool operator <(sth a,sth b){return a.l<b.l;}};
vector<sth> v[N];
int su[N][2],vl[N][2],tp[N][2],ct,n,k,s[N],cnt;
long long as;
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&s[i]);
		tp3.insert(s[i]);if(!tp1[s[i]])tp1[s[i]]=++cnt;
		v[tp1[s[i]]].push_back((sth){i,i,1,1});
	}
	int lb=*tp3.lower_bound(1);
	while(1)
	{
		int st=tp1[lb];
		sort(v[st].begin(),v[st].end());
		int sz=v[st].size(),las=v[st][0].l;
		for(int i=0;i<sz;i++)
		{
			if(ct&&v[st][i].l!=v[st][i-1].r+1)
			{
				su[ct+1][0]=su[ct+1][1]=0;
				int s1=ct/k;
				if(s1)
				{
					for(int j=1;j<=ct;j++){int r1=j+k-1;if(r1<=ct)as+=1ll*vl[j][1]*(su[ct][0]-su[r1-1][0]);}
					for(int j=1;j<s1;j++)tp[j][0]=su[(j+1)*k-1][0]-su[j*k-1][0];
					tp[s1][0]=su[ct][0]-su[s1*k-1][0];
					for(int j=1;j<s1;j++)tp[s1-j+1][1]=-su[ct-(j+1)*k+1][1]+su[ct-j*k+1][1];
					tp[1][1]=su[ct-s1*k+1][1];
					if(!tp1[lb+1])tp1[lb+1]=++cnt,tp3.insert(lb+1);
					for(int j=1;j<=s1;j++)v[tp1[lb+1]].push_back((sth){las+j-1,j==s1?v[st][i-1].r:las+j-1,tp[j][0],tp[j][1]});
					for(int j=1;j<=s1;j++)vl[j][0]=tp[j][0],vl[j][1]=tp[j][1],su[j][0]=su[j-1][0]+vl[j][0];
					for(int j=1;j<=s1;j++){int r1=j+k-1;if(r1<=s1)as-=1ll*vl[j][1]*(su[s1][0]-su[r1-1][0]);}
				}
				ct=0;las=v[st][i].l;
			}
			vl[++ct][0]=v[st][i].lb;vl[ct][1]=v[st][i].rb;
			su[ct][0]=su[ct-1][0]+vl[ct][0];su[ct][1]=su[ct-1][1]+vl[ct][1];
		}
		su[ct+1][0]=su[ct+1][1]=0;
		int s1=ct/k;
		if(s1)
		{
			for(int j=1;j<=ct;j++){int r1=j+k-1;if(r1<=ct)as+=1ll*vl[j][1]*(su[ct][0]-su[r1-1][0]);}
			for(int j=1;j<s1;j++)tp[j][0]=su[(j+1)*k-1][0]-su[j*k-1][0];
			tp[s1][0]=su[ct][0]-su[s1*k-1][0];
			for(int j=1;j<s1;j++)tp[s1-j+1][1]=-su[ct-(j+1)*k+1][1]+su[ct-j*k+1][1];
			tp[1][1]=su[ct-s1*k+1][1];
			if(!tp1[lb+1])tp1[lb+1]=++cnt,tp3.insert(lb+1);
			for(int j=1;j<=s1;j++)v[tp1[lb+1]].push_back((sth){las+j-1,j==s1?v[st][sz-1].r:las+j-1,tp[j][0],tp[j][1]});
			for(int j=1;j<=s1;j++)vl[j][0]=tp[j][0],vl[j][1]=tp[j][1],su[j][0]=su[j-1][0]+vl[j][0];
			for(int j=1;j<=s1;j++){int r1=j+k-1;if(r1<=s1)as-=1ll*vl[j][1]*(su[s1][0]-su[r1-1][0]);}
		}
		ct=0;
		set<int>::iterator it=tp3.lower_bound(lb+1);
		if(it==tp3.end())break;
		lb=*it;
	}
	printf("%lld\n",as+n);
}
54 AGC029C Lexicographic Constraints
Problem

nn 个串,每个串有长度,求最小的字符集使得存在一种方案使得这些串字典序严格递增

n2×105,l109n\leq 2\times 10^5,l\leq 10^9

2s,1024MB2s,1024MB

Sol

显然可以二分答案

二分答案后,显然的想法是每次找一个字典序合法的最小的

设当前二分的是 kk ,那么可以看成一个 kk 进制数,每次相当于删去第 lil_i 位以后的,然后给这一位加1,然后向前进位,如果在第1位进位就说明不行

如果 k=1k=1 不能直接按上面的做,显然 k=1k=1 能行当且仅当 lil_i 递增

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

Code
#include<cstdio>
#include<map>
using namespace std;
#define N 215000
map<int,int> tp;
int n,s[N];
bool check(int k)
{
	if(k==1){for(int i=2;i<=n;i++)if(s[i]<=s[i-1])return 0;return 1;}
	tp.clear();
	int ls=0;
	for(int i=1;i<=n;i++)
	{
		if(s[i]>ls){ls=s[i];continue;}
		while(1)
		{
			if(!tp.size())break;
			map<int,int>::reverse_iterator it=tp.rbegin();
			if(it->first<=s[i])break;
			tp.erase(--(it.base()));
		}
		tp[s[i]]++;
		int t=s[i];
		while(tp[t]>=k){if(t==1)return 0;tp[t]-=k,tp[t-1]++,t--;}
		ls=s[i];
	}
	return 1;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&s[i]);
	int lb=1,rb=n,as=n;
	while(lb<=rb)
	{
		int mid=(lb+rb)>>1;
		if(check(mid))as=mid,rb=mid-1;
		else lb=mid+1;
	}
	printf("%d\n",as);
}
55 CF571D Campus
Problem

nn 个宿舍,一开始第 ii 个宿舍属于第 ii 个大学和第 ii 个警卫室

有以下这些操作:

  1. 将大学 bb 合并给大学 aa
  2. 将警卫室 bb 合并给警卫室 aa
  3. 设大学 aa 当前有 xx 个宿舍,向每个属于这所大学的宿舍加入 xx 个学生
  4. 清空属于第 aa 个警卫室的宿舍的学生
  5. 询问当前第 aa 个宿舍的学生数量

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

2s,256MB2s,256MB

Sol

首先处理清空操作

如果对于每个询问,求出询问之前这个宿舍被清空的时间,就可以把询问拆成两个,之后不管清空的问题

注意到合并的关系可以看成一棵树,每个宿舍看成一个叶子,每次合并新建一个点连向合并的两个点

按照叶子dfs序排序,这样每次操作的都是一个区间

于是可以线段树维护出对于每次询问,询问前这个宿舍最后一次被清空的时间

然后考虑剩下的操作

显然大学的合并构成了另外一棵树,使用同样的方式维护区间加单点查即可

复杂度 O(qlogn+qlogq+n)O(q\log n+q\log q+n) 或者 O(qlogn+n)O(q\log n+n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 1000050
int head[N],cnt,fa[N],id2[N],id[N],ct,ct2,ct3,q[N][2],lb[N],rb[N],n,m,a,b,is[N],vis[N];
char s[2],t[N];
long long as[N],tr[N];
struct que{int t,x,id,s;friend bool operator <(que a,que b){return a.t<b.t;}}r[N];
struct edge{int t,next;}ed[N*2];
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
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){vis[u]=1;if(u<=n)id[u]=++ct3;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u);}
struct segt{int l,r,lz;}e[N*2];
void pushdown(int x){e[x<<1].lz=max(e[x].lz,e[x<<1].lz);e[x<<1|1].lz=max(e[x].lz,e[x<<1|1].lz);}
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,int s){if(e[x].l==l&&e[x].r==r){e[x].lz=s;return;}pushdown(x);int mid=(e[x].l+e[x].r)>>1;if(mid>=r)modify(x<<1,l,r,s);else if(mid<l)modify(x<<1|1,l,r,s);else modify(x<<1,l,mid,s),modify(x<<1|1,mid+1,r,s);}
int query(int x,int s){if(e[x].l==e[x].r)return e[x].lz;pushdown(x);int mid=(e[x].l+e[x].r)>>1;if(mid>=s)return query(x<<1,s);else return query(x<<1|1,s);}
void add(int x,int v){for(int i=x;i<=n;i+=i&-i)tr[i]+=v;}
long long que1(int x){long long as=0;for(int i=x;i;i-=i&-i)as+=tr[i];return as;}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%s%d",s,&a);
		if(s[0]=='U'||s[0]=='M')scanf("%d",&b);else b=0;
		q[i][0]=a;q[i][1]=b;t[i]=s[0];
	}
	for(int i=1;i<=n;i++)id2[i]=i,fa[i]=i;
	ct2=n;
	for(int i=1;i<=m;i++)if(t[i]=='M')
	{
		int ta=finds(q[i][0]),tb=finds(q[i][1]);
		int st=++ct2;
		adde(st,id2[ta]);adde(st,id2[tb]);
		id2[ta]=st;fa[tb]=ta;
	}
	for(int i=ct2;i>=1;i--)if(!vis[i])dfs(i,0);build(1,1,n);
	for(int i=1;i<=n;i++)lb[i]=rb[i]=id[i];
	for(int i=1;i<=m;i++)
	if(t[i]=='M')
	{
		if(lb[q[i][0]]<lb[q[i][1]])rb[q[i][0]]=rb[q[i][1]];
		else lb[q[i][0]]=lb[q[i][1]];
	}
	else if(t[i]=='Z')modify(1,lb[q[i][0]],rb[q[i][0]],i);
	else if(t[i]=='Q')
	{
		is[i]=1;
		int ti=query(1,id[q[i][0]]);
		r[++ct]=(que){i,q[i][0],i,1};
		r[++ct]=(que){ti-1,q[i][0],i,-1};
	}
	for(int i=1;i<=ct2;i++)vis[i]=0,head[i]=0;
	for(int i=1;i<=n;i++)id2[i]=i,fa[i]=i;
	ct2=n;ct3=0;cnt=0;
	for(int i=1;i<=m;i++)if(t[i]=='U')
	{
		int ta=finds(q[i][0]),tb=finds(q[i][1]);
		int st=++ct2;
		adde(st,id2[ta]);adde(st,id2[tb]);
		id2[ta]=st;fa[tb]=ta;
	}
	for(int i=ct2;i>=1;i--)if(!vis[i])dfs(i,0);
	for(int i=1;i<=n;i++)lb[i]=rb[i]=id[i];
	int st=1;
	sort(r+1,r+ct+1);
	while(r[st].t<1)st++;
	for(int i=1;i<=m;i++)
	{
		if(t[i]=='U')
		{
			if(lb[q[i][0]]<lb[q[i][1]])rb[q[i][0]]=rb[q[i][1]];
			else lb[q[i][0]]=lb[q[i][1]];
		}
		else if(t[i]=='A')
		{
			int st=rb[q[i][0]]-lb[q[i][0]]+1;
			add(lb[q[i][0]],st);
			add(rb[q[i][0]]+1,-st);
		}
		while(r[st].t==i&&st<=ct)
		as[r[st].id]+=que1(id[r[st].x])*r[st].s,st++;
	}
	for(int i=1;i<=m;i++)if(is[i])printf("%lld\n",as[i]);
}
56 CF708E Student's Camp
Problem

有一个 (n+2)×m(n+2)\times m 的由方块组成的长方形

每一天,将会有以下事情按顺序发生:

  1. 除了第一行和最后一行外,每行最左边的一个方块有 a/ba/b 的概率消失

  2. 除了第一行和最后一行外,每行最右边的一个方块有 a/ba/b 的概率消失

kk 天后,这个图形仍然连通的概率,模 109+710^9+7

n,m1500,k105n,m\leq 1500,k\leq 10^5

3s,256MB3s,256MB

Sol

p=a/bp=a/b

fl,rf_{l,r} 表示一行 kk 天后剩下的是区间 [l,r][l,r] 的方案数,分别考虑两边的情况有 $f_{l,r}={k \choose l-1}p{l-1}*(1-p){k-(l-1)}{k \choose m-r}*p{m-r}*(1-p){k-(m-r)} $

gi=(ki)pi(1p)kig_i={k \choose i}*p^i*(1-p)^{k-i} ,有 fl,r=gl1gmrf_{l,r}=g_{l-1}*g_{m-r}

dpi,l,rdp_{i,l,r} 表示前 ii 行连通,且第 ii 行剩下的是区间 [l,r][l,r] 的概率。

显然有转移 dpi,l,r=fl,rl1r,lr1,1l1r1mdpi1,l1,r1dp_{i,l,r}=f_{l,r}\sum_{l_1\leq r,l\leq r_1,1\leq l_1\leq r_1\leq m}dp_{i-1,l_1,r_1}

sumi=1lrmdpi,l,rsum_i=\sum_{1\leq l\leq r\leq m}dp_{i,l,r} ,考虑用所有情况减去两个区间不相交的情况,有

dpi,l,r=fl,r(sumi11l1r1<ldpi1,l1,r1r<l1r1mdpi1,l1,r1)dp_{i,l,r}=f_{l,r}(sum_{i-1}-\sum_{1\leq l_1\leq r_1<l}dp_{i-1,l_1,r_1}-\sum_{r<l_1\leq r_1\leq m}dp_{i-1,l_1,r_1})

sumli,l=1l1r1ldpi,l1,r1,sumri,r=rl1r1mdpi,l1,r1suml_{i,l}=\sum_{1\leq l_1\leq r_1\leq l}dp_{i,l_1,r_1},sumr_{i,r}=\sum_{r\leq l_1\leq r_1\leq m}dp_{i,l_1,r_1} ,那么 dpi,l,r=fl,r(sumi1sumli1,l1sumri1,r+1)dp_{i,l,r}=f_{l,r}(sum_{i-1}-suml_{i-1,l-1}-sumr_{i-1,r+1})

sumli,l=sumli,l1+j=1l(pj1pnl(sui1sumli1,j1sumri1,l+1))suml_{i,l}=suml_{i,l-1}+\sum_{j=1}^l (p_{j-1}p_{n-l}(su_{i-1}-suml_{i-1,j-1}-sumr_{i-1,l+1}))

sumli,l=sumli,l1+pnl(sui1sumri1,l+1)j=1lpj1pnlj=1lpj1sumli1,j1suml_{i,l}=suml_{i,l-1}+p_{n-l}(su_{i-1}-sumr_{i-1,l+1})\sum_{j=1}^l p_{j-1}-p_{n-l}\sum_{j=1}^l p_{j-1}suml_{i-1,j-1}

显然 sumrsumrsumlsuml 是对称的,有 sumli,l=sumri,ml+1suml_{i,l}=sumr_{i,m-l+1} ,所以只用算一遍

记录 pj1,pj1suli1,j1p_{j-1},p_{j-1}sul_{i-1,j-1} 的前缀和即可

sumi=sumli,msum_i=suml_{i,m} ,答案为 sumnsum_n

复杂度 O(nm+(m+k)logk)O(nm+(m+k)\log k)

Code
#include<cstdio>
using namespace std;
#define N 100500
#define M 1505
#define mod 1000000007
int n,m,a,b,k,fr[N],p[M],su[M],sul[M][M],sur[M][M],f[M],g[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;}
int main()
{
	scanf("%d%d%d%d%d",&n,&m,&a,&b,&k),a=1ll*a*pw(b,mod-2)%mod;
	fr[0]=1;for(int i=1;i<=k;i++)fr[i]=1ll*fr[i-1]*i%mod;
	for(int i=0;i<m&&i<=k;i++)p[i]=1ll*fr[k]*pw(fr[i],mod-2)%mod*pw(fr[k-i],mod-2)%mod*pw(a,i)%mod*pw(mod+1-a,k-i)%mod;
	su[0]=sul[0][m]=sur[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)f[j]=(f[j-1]+p[j-1])%mod,g[j]=(g[j-1]+1ll*p[j-1]*sul[i-1][j-1])%mod;
		for(int j=1;j<=m;j++)sul[i][j]=(sul[i][j-1]+1ll*p[m-j]*(su[i-1]-sur[i-1][j+1]+mod)%mod*f[j]%mod+mod-1ll*p[m-j]*g[j]%mod)%mod;
		for(int j=m;j>=1;j--)f[j]=(f[j+1]+p[m-j])%mod,g[j]=(g[j+1]+1ll*p[m-j]*sur[i-1][j+1])%mod;
		for(int j=m;j>=1;j--)sur[i][j]=(sur[i][j+1]+1ll*p[j-1]*(su[i-1]-sul[i-1][j-1]+mod)%mod*f[j]%mod+mod-1ll*p[j-1]*g[j]%mod)%mod;
		su[i]=sul[i][m];
	}
	printf("%d\n",su[n]);
}
57 AGC093F Dark Horse
Problem

2n2^n 个人随机排列,进行 nn 轮游戏:

每一次将当前序列的第1个和第2个,第3个和第4个,... 进行比赛,胜者留下,相对顺序不变

已知有 mm 个选手可以战胜1,1可以战胜剩下的选手,如果两个编号大于1的选手比赛,编号小的获胜

求使得1最后留下的排列数模 109+710^9+7

n,m16n,m\leq 16

2s,256MB2s,256MB

Sol

如果没有1,胜者显然是最小的那个

考虑1的 nn 个对手,第一个对手是1个人中最小的,第二个是2个人中最小的,...,第 nn 个是 2n12^{n-1} 个中最小的

那么相当于要求分出的这 nn 个集合的min都不是给出的 mm 个数

考虑容斥,对于一个钦定的集合,里面每个集合都必须以关键值为min

从大到小考虑,设 dpi,Sdp_{i,S} 表示当前处理到第 ii 个关键值,当前已经确定了min的集合的集合为 SS ,当前的方案数

那么 dpi,S=dpi+1,S+jSdpi+1,S/jC2n1vikS/j2k12j11dp_{i,S}=dp_{i+1,S}+\sum_{j\in S} dp_{i+1,S/j}*C_{2^n-1-v_i-\sum_{k\in S/j}2^{k-1}}^{2^{j-1}-1}

后面相当于枚举这次填了哪个集合的min,乘上放剩下数的方案数

最后贡献是 dp1,S(1)S(2n1iS2i1)!dp_{1,S}*(-1)^{|S|}*(2^n-1-\sum_{i\in S}2^{i-1})!

因为无论1在哪分出的 nn 个集合都类似,所以最后答案乘 2n2^n

暴力枚举每个集合做dp的复杂度是 O(nm3n)O(nm3^n)

显然可以把所有集合的dp一起做,复杂度 O(nm2n)O(nm2^n)

Code
#include<cstdio>
using namespace std;
#define N 17
#define mod 1000000007
int dp[1<<N],fr[1<<N],ifr[1<<N],n,m,v[N],tp[1<<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;}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d",&v[i]);
	dp[0]=1;
	fr[0]=ifr[0]=1;
	for(int i=1;i<=1<<n;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	for(int i=m;i>=1;i--)
	for(int j=(1<<n)-1;j>=0;j--)
	for(int k=1;k<=n;k++)if(!(j&(1<<k-1)))
	if((1<<n)-j-v[i]-(1<<k-1)+1>=0)
	dp[j|(1<<k-1)]=(dp[j|(1<<k-1)]+1ll*dp[j]*fr[(1<<n)-j-v[i]]%mod*ifr[(1<<n)-j-v[i]-(1<<k-1)+1]%mod*(1<<k-1))%mod;
	int as=0;tp[0]=1;
	for(int i=0;i<1<<n;i++)tp[i]=tp[i>>1]*(i&1?-1:1),as=(as+1ll*tp[i]*dp[i]%mod*fr[(1<<n)-i-1]%mod*(1<<n))%mod;
	printf("%d\n",(as+mod)%mod);
}
58 CF603E Pastoral Oddities
Problem

给一张图,一种选边的方式合法当且仅当每个点连出的被选边数量为奇数,求对于每一个 ii ,只考虑前 ii 条边,求所有合法选边方式中被选边最大边权的最小值或输出没有合法方案

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

4s,256MB4s,256MB

Sol

考虑什么情况下合法

如果一个连通块点数为偶数,取出一棵生成树,其余边不选,生成树上从下往上构造,每次满足儿子的度数奇偶性即可

如果点数为奇数,则要求度数和为奇数,显然不可能

因此当每个连通块点数都为偶数时,一定合法

设答案为 ans1,...,ansmans_1,...,ans_m ,如果 ansi1ans_i\neq -1 那么一定有 ansiansi+1ans_i\geq ans_{i+1}

那么如果当前有解了,那么当前没有用到的边不会再被用到

考虑LCT维护最小生成树,在有解之前正常维护生成树

有解之后,考虑每次删去最大边,如果删去后剩余两边点数均为偶数,那么可以删去这条边,减少答案

加入边时正常加入

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

Code
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
#define N 400050
priority_queue<pair<long long,int> > st1,st2;
priority_queue<int> v1,v2;
int fa[N],ch[N][2],mx[N],fr[N],n,m,s[N][3],vl[N],lz[N],sz[N],ls[N],st[N],ras,nw;
int Max(int x,int y){return x>y?x:y;}
bool nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
void pushup(int x){mx[x]=Max(Max(mx[ch[x][0]],mx[ch[x][1]]),vl[x]);if(mx[x]==vl[x])fr[x]=x;else if(mx[x]==mx[ch[x][0]])fr[x]=fr[ch[x][0]];else fr[x]=fr[ch[x][1]];sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+(x<=n)+ls[x];}
void pushdown(int x){if(lz[x])lz[ch[x][0]]^=1,lz[ch[x][1]]^=1,lz[x]=0,swap(ch[ch[x][0]][0],ch[ch[x][0]][1]),swap(ch[ch[x][1]][0],ch[ch[x][1]][1]);}
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;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 ct=1,st1=x;st[1]=x;
	while(nroot(st1)&&st1)st1=fa[st1],st[++ct]=st1;
	for(int i=ct;i>=1;i--)pushdown(st[i]);
	while(nroot(x)){int f=fa[x],g=fa[f];if(nroot(f))rotate((ch[f][1]==x)^(ch[g][1]==f)?x:f);rotate(x);}
}
void access(int x){int tp=0;while(x)splay(x),ls[x]=ls[x]+sz[ch[x][1]]-sz[tp],ch[x][1]=tp,pushup(x),tp=x,x=fa[x];}
int findroot(int x){access(x);splay(x);while(ch[x][0])x=ch[x][0];return x;}
void makeroot(int x){access(x);splay(x);lz[x]^=1;swap(ch[x][0],ch[x][1]);}
void split(int x,int y){makeroot(x);access(y);splay(y);}
void link(int x,int y){makeroot(x);makeroot(y);fa[x]=y;ls[y]+=sz[x];pushup(y);}
void cut(int x,int y){split(x,y);fa[x]=ch[y][0]=0;pushup(y);}
int main()
{
	scanf("%d%d",&n,&m);
	if(n&1){for(int i=1;i<=m;i++)printf("-1\n");return 0;}
	for(int i=1;i<=n;i++)sz[i]=1;
	nw=n;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&s[i][0],&s[i][1],&s[i][2]);
		vl[i+n]=s[i][2];
		if(findroot(s[i][0])!=findroot(s[i][1])){
		makeroot(s[i][0]);
		makeroot(s[i][1]);if((sz[s[i][0]]&1)&&(sz[s[i][1]]&1))nw-=2;v1.push(s[i][2]);st1.push(make_pair(s[i][2],i));
		link(s[i][0],i+n);link(s[i][1],i+n);}
		else
		{
			split(s[i][0],s[i][1]);
			int tp=fr[s[i][1]]-n;
			if(s[tp][2]>s[i][2])
			{
				st2.push(make_pair(s[tp][2],tp));
				v2.push(s[tp][2]);
				st1.push(make_pair(s[i][2],i));
				v1.push(s[i][2]);
				cut(s[tp][0],tp+n);cut(s[tp][1],tp+n);
				link(s[i][0],i+n);link(s[i][1],i+n);
			}
		}
		if(nw)printf("-1\n");
		else
		{
			while(!st1.empty())
			{
				while(st2.size()&&st1.top()==st2.top())st1.pop(),st2.pop();
				int r=st1.top().second;
				split(s[r][0],s[r][1]);
				splay(s[r][0]);
				if(sz[s[r][1]]&1)break;
				cut(s[r][0],r+n);cut(s[r][1],r+n);
				st2.push(make_pair(s[r][2],r));
				v2.push(s[r][2]);
			}
			while(v2.size()&&v1.top()==v2.top())v1.pop(),v2.pop();
			printf("%d\n",v1.top());
		}
	}
}
59 AGC036E ABC String
Problem

你有一个包含 ABC 的字符串,你需要选出一个子序列,满足

  1. 相邻两个字符不同
  2. 三种字符数量相同

输出任意一个最长的合法子序列

n106n\leq 10^6

2s,1024MB2s,1024MB

Sol

考验乱搞能力

首先原序列中相邻两个相同的可以删去一个

A 的出现次数小于等于 B 的出现次数小于等于 C 的出现次数

将原序列以 A 分开,分成若干 ...BCBC... 的段

首先考虑同时减少 BC 的数量,对于一段长度大于3的,可以在这一段里面同时删去一个 BC

然后考虑将 BC 的数量调整到尽量相同,如果当前 B>CB>C ,那么对于 CBC....BC 段可以删 BC 或者不动,对于BCB...CB 段可以删两个 B 或删一个,对于 BC...BC 段可以删一个 B

此时如果 BC 都大于 A 的数量,前面的调整之后原序列相邻两个 A 之间一定只有一个字符,所以 B+CA+1B+C\leq A+1 ,矛盾

因此现在一定有 B 的数量和 A 相同

考虑剩下的 C ,每一段一定形如 BC...CB 或者C

C 多出来 kk 个,那么 C 的数量一定大于等于 kk

因此,考虑删掉 kkAC ,然后再删掉 kkBC ,因为 BC...CB 段数小于等于总段数减去 kk ,可以发现一定可以找到这么多对

复杂度 O(n)O(n)

Code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 1000050
char s[N],t[N];
int n,su[5],fg,v,ct,s1[N],s2[N],ed[N],is;
int main()
{
	scanf("%s",s+1);n=strlen(s+1);
	for(int i=1;i<=n;i++)if(s[i]!=s[i-1])t[++v]=s[i],su[s[i]-'A'+1]++;
	if(su[1]<su[2]&&su[1]<su[3])fg=1;
	else if(su[2]<su[3])
	{
		fg=2;
		for(int i=1;i<=v;i++)if(t[i]=='A')t[i]='B';else if(t[i]=='B')t[i]='A';
		su[1]^=su[2]^=su[1]^=su[2];
	}
	else
	{
		fg=3;
		for(int i=1;i<=v;i++)if(t[i]=='A')t[i]='C';else if(t[i]=='C')t[i]='A';
		su[1]^=su[3]^=su[1]^=su[3];
	}
	is=(t[v]=='A');
	ct=1;
	for(int i=1;i<=v;i++)
	if(t[i]=='A')ct++;
	else if(t[i]=='B')s1[ct]++,ed[ct]=1;
	else s2[ct]++,ed[ct]=2;
	int v1=su[2]-su[1],v2=su[3]-su[1];
	for(int i=1;i<=ct;i++)
	{
		if(v1>0&&v2>0)
		{
			int st=min(v1,v2),st2=min(s1[i],s2[i]);
			if(s1[i]==s2[i]&&(1<i&&i<v))st2--;
			st2--;
			if(st2<0)st2=0;
			st=min(st,st2);
			v1-=st,v2-=st,s1[i]-=st,s2[i]-=st;
		}
	}
	for(int i=1;i<=ct;i++)
	{
		if(v1>0&&v2>0)
		{
			int st=min(v1,v2),st2=min(s1[i],s2[i]);
			if(s1[i]==s2[i]&&(1<i&&i<v))st2--;
			if(st2<0)st2=0;
			st=min(st,st2);
			v1-=st,v2-=st,s1[i]-=st,s2[i]-=st;
		}
		if(v1>v2){int tp=min(s1[i]+s2[i]-(1<i&&i<v),s1[i]-s2[i]+1);if(v1<tp)tp=v1;if(s1[i]<tp)tp=s1[i];if(tp<0)tp=0;s1[i]-=tp,v1-=tp;}
		else if(v1<v2){int tp=min(s1[i]+s2[i]-(1<i&&i<v),s2[i]-s1[i]+1);if(v2<tp)tp=v2;if(s2[i]<tp)tp=s2[i];if(tp<0)tp=0;v2-=tp,s2[i]-=tp;}
	}
	for(int i=1;i<=ct;i++)
	if(v1>v2){int tp=min(s1[i]+s2[i]-(1<i&&i<v),s1[i]-s2[i]+1);if(v1<tp)tp=v1;if(s1[i]<tp)tp=s1[i];if(tp<0)tp=0;s1[i]-=tp,v1-=tp;}
	else {int tp=min(s1[i]+s2[i]-(1<i&&i<v),s2[i]-s1[i]+1);if(v2<tp)tp=v2;if(s2[i]<tp)tp=s2[i];if(tp<0)tp=0;v2-=tp,s2[i]-=tp;}
	if(v1)
	{
		if(s1[1]==1&&s2[1]==0)v1--,s1[1]--;
		if(s1[ct]==1&&s2[ct]==0&&v1)s1[ct]--,v1--,is=1;
		int tp=v1,tp2=v1;
		for(int i=1;i<=ct;i++)if(s1[i]==1&&s2[i]==0&&tp)s1[i]=0,tp--;
		for(int i=1;i<=ct;i++){int fg=min(tp2,min(s1[i],s2[i]-1));if(fg<0)fg=0;s1[i]-=fg,s2[i]-=fg,tp2-=fg;}
	}
	if(v2)
	{
		if(s2[1]==1&&s1[1]==0)v2--,s2[1]--;
		if(s2[ct]==1&&s1[ct]==0&&v2)s2[ct]--,v2--,is=1;
		int tp=v2,tp2=v2;
		for(int i=1;i<=ct;i++)if(s1[i]==0&&s2[i]==1&&tp)s2[i]=0,tp--;
		for(int i=1;i<=ct;i++){
		int fg=min(tp2,min(s1[i]-1,s2[i]));if(fg<0)fg=0;
		s1[i]-=fg,s2[i]-=fg,tp2-=fg;}
	}
	for(int i=1;i<=ct;i++)
	{
		if(s1[i]+s2[i]&&i>1)printf("%c",'A'+fg-1);
		if(s1[i]>s2[i])
		{
			for(int j=1;j<=s2[i];j++)
			printf("%c",fg==2?'A':'B'),
			printf("%c",fg==3?'A':'C');
			printf("%c",fg==2?'A':'B');
		}
		else if(s1[i]<s2[i])
		{
			for(int j=1;j<=s1[i];j++)
			printf("%c",fg==3?'A':'C'),
			printf("%c",fg==2?'A':'B');
			printf("%c",fg==3?'A':'C');
		}
		else if(ed[i]==2)
		for(int j=1;j<=s2[i];j++)
		printf("%c",fg==2?'A':'B'),
		printf("%c",fg==3?'A':'C');
		else 
		for(int j=1;j<=s2[i];j++)
		printf("%c",fg==3?'A':'C'),
		printf("%c",fg==2?'A':'B');
	}
	if(is)printf("%c",'A'+fg-1);
}
60 AGC021E Ball Eat Chameleon
Problem

nn 只变色龙,每只一开始都是蓝色,一只变色龙如果吃的红球数量大于蓝球数量,它就会变成红色,如果吃的蓝球数量大于红球数量,它就会变成蓝色

你扔了 mm 个球,每一次会有随机一个变色龙吃掉这个球,求有多少种扔进去的球的颜色序列,使得最后可能每只变色龙都是红色

n,m5×105n,m\leq 5\times 10^5

2s,256MB2s,256MB

Sol

枚举扔进去的红球数 aa ,显然必须满足 an,2ama\geq n,2a\geq m

先考虑 2a>m2a>m 的情况

这时至少有 n+m2an+m-2a 只最后吃掉的红蓝球数相同,剩下的红球比蓝球多

对于这 n+m2an+m-2a 只,它最后吃掉的一定是蓝球,且它必须吃掉一个红球

那么它至少需要先吃一个红球再吃一个蓝球

那么相当于需要能配 n+m2an+m-2a 对红-蓝

设红色为+1,蓝色为-1,如果一个前缀和为 x-x 那么前面至少有 xx 个蓝色无法被配对上

因为最多有 ana-n 个蓝不被配对,相当于要求前 n+m2an+m-2a 个红色部分不存在一个前缀和小于 nan-a

对于后面的部分,因为前面有 n+m2an+m-2a 个红色,总共有 mam-a 个蓝色,因此后面前缀和不小于 n+m2a(ma)=nan+m-2a-(m-a)=n-a

因此只需要要求整体没有一个前缀和小于 nan-a

考虑折线翻折,答案为结尾是 2am2a-m 的方案数减去结尾是 m2n4a2m-2n-4a-2 的方案数

这相当于 CmaCmmn2a1C_m^a-C_m^{m-n-2a-1}

对于 2a=m2a=m 的情况,最后一个一定是蓝色球,因此可以转成 2a=m+12a=m+1 的情况,且任意一种 2a=m+12a=m+1 的情况,只需要最后一个扔给那个吃的红色比蓝色多的即可

复杂度 O(m)O(m)

Code
#include<cstdio>
using namespace std;
#define N 1000500
#define mod 998244353
int fr[N],ifr[N],n,m,as;
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);
	fr[0]=ifr[0]=1;for(int i=1;i<=m*2;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	for(int i=0;i<=m;i++)
	{
		if(i*2<m||i<n)continue;
		int s1=i*2-m,s2=m-i+s1-n;
		if(i*2==m)
		{
			as=(as+1ll*fr[m-1]*ifr[i]%mod*ifr[m-1-i])%mod;
			if(s1>=n||m-i-s2-2<0)continue;
			as=(as-1ll*fr[m-1]*ifr[i+s2+1]%mod*ifr[m-i-s2-2]%mod+mod)%mod;
			continue;
		}
		as=(as+1ll*fr[m]*ifr[i]%mod*ifr[m-i])%mod;
		if(s1>=n||m-i-s2-1<0)continue;
		as=(as-1ll*fr[m]*ifr[i+s2+1]%mod*ifr[m-i-s2-1]%mod+mod)%mod;
	}
	printf("%d\n",as);
}
61 CF590E Birthday
Problem

nn 个串,你需要选出尽量多的串,使得不存在一个串是另一个的子串,输出方案

n750,S107n\leq 750,\sum |S|\leq 10^7

3s,512MB3s,512MB

Sol

不会

62 AGC031D A Sequence of Permutations
Problem

对于两个排列 p,qp,q ,定义排列 f(p,q)f(p,q) 满足 f(p,q)pi=qif(p,q)_{p_i}=q_i

给出 a1,a2a_1,a_2 ,定义 an+2=f(an,an+1)a_{n+2}=f(a_n,a_{n+1})

aka_k

n105,k109n\leq 10^5,k\leq 10^9

2s,1024MB2s,1024MB

Sol

定义排列相乘 (fg)i=fgi(f*g)_i=f_{g_i} ,这显然有结合律

那么 fp,q=qp1f_{p,q}=q*p^{-1}

(pq)1=q1p1(p*q)^{-1}=q^{-1}p^{-1}

a1=pa_1=p

a2=qa_2=q

a3=qp1a_3=qp^{-1}

a4=qp1q1a_4=qp^{-1}q^{-1}

a5=qp1q1pq1a_5=qp^{-1}q^{-1}pq^{-1}

a6=qp1q1ppq1a_6=qp^{-1}q^{-1}ppq^{-1}

a7=qp1q1pqpq1a_7=qp^{-1}q^{-1}pqpq^{-1}

a8=qp1q1pqp1qpq1a_8=qp^{-1}q^{-1}pqp^{-1}qpq^{-1}

a9=qp1q1pqp1p1qpq1a_9=qp^{-1}q^{-1}pqp^{-1}p^{-1}qpq^{-1}

a10=qp1q1pqp1q1p1qpq1a_{10}=qp^{-1}q^{-1}pqp^{-1}q^{-1}p^{-1}qpq^{-1}

a11=qp1q1pqp1q1pq1p1qpq1a_{11}=qp^{-1}q^{-1}pqp^{-1}q^{-1}pq^{-1}p^{-1}qpq^{-1}

a12=qp1q1pqp1q1ppq1p1qpq1a_{12}=qp^{-1}q^{-1}pqp^{-1}q^{-1}ppq^{-1}p^{-1}qpq^{-1}

a13=qp1q1pqp1q1pqpq1p1qpq1a_{13}=qp^{-1}q^{-1}pqp^{-1}q^{-1}pqpq^{-1}p^{-1}qpq^{-1}

那么有 an+6=qp1q1panp1qpq1(n>1)a_{n+6}=qp^{-1}q^{-1}pa_np^{-1}qpq^{-1}(n>1)

求置换的幂即可

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define N 1000700
int n,k,ct,p[N],q[N],rp[N],rq[N],s[N],rs[N],vis[N],st[N],as[N],s1[N];
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d",&p[i]);
	for(int i=1;i<=n;i++)scanf("%d",&q[i]);
	if(k==1){for(int i=1;i<=n;i++)printf("%d ",p[i]);return 0;}
	if(k<=6)
	{
		for(int i=3;i<=k;i++)
		{
			for(int j=1;j<=n;j++)s[p[j]]=q[j],p[j]=q[j];
			for(int j=1;j<=n;j++)q[j]=s[j];
		}
		for(int i=1;i<=n;i++)printf("%d ",q[i]);return 0;
	}
	for(int i=1;i<=n;i++)rp[p[i]]=i,rq[q[i]]=i;
	for(int i=1;i<=n;i++)s1[i]=rp[q[p[rq[i]]]];
	for(int i=3;i<=(k-1)%6+7;i++)
	{
		for(int j=1;j<=n;j++)s[p[j]]=q[j],p[j]=q[j];
		for(int j=1;j<=n;j++)q[j]=s[j];
	}
	for(int i=1;i<=n;i++)if(!vis[i])
	{
		ct=0;st[ct=1]=i;vis[i]=1;
		for(int j=s1[i];j!=i;j=s1[j])st[++ct]=j,vis[j]=1;
		for(int j=1;j<=ct;j++)as[st[j]]=st[(j-2+(k-1)/6)%ct+1];
	}
	for(int i=1;i<=n;i++)rs[as[i]]=i;
	for(int i=1;i<=n;i++)printf("%d ",rs[q[as[i]]]);
}
63 AGC026E Synchronized Subsequence
Problem

有一个包含 nnAB 的字符串,你可以选出一个子序列,要求

如果原串中第 iiA 被选了,那第 iiB 也需要被选,反过来同理

求字典序最大的能选出来的子序列

n3000n\leq 3000

2s,1024MB2s,1024MB

Sol

设第 ii 对的位置为 (ai,bi)(a_i,b_i)

如果选的第一对为 (ai,bi)(a_i,b_i) ,那么

如果 ai<bia_i<b_i , 考虑所有 aj>bia_j>b_i 的对 (aj,bj)(a_j,b_j) ,这些对一定和这一对没有影响,对于 aj<bia_j<b_i 的对,加入它只会让字典序变小,因此一定不会加入

如果 ai>bia_i>b_i ,考虑下一对 (ai+1,bi+1)(a_{i+1},b_{i+1})

如果 bi+1>aib_{i+1}>a_i ,那么这后面和前面的没有影响,可以分开做

如果 bi+1<aib_{i+1}<a_i ,这一对选了更好

那么对于每一对,可以得到这一对选完之后必须选哪些对

dpidp_{i} 表示第 ii 对往后能得到的最大字典序串,转移时有两种情况:

这一对不选,那么为 dpi+1dp_{i+1}

这一对要选,那么一定是某一段内的都选/不选,剩下的与前面独立,那么从那里转移过来即可

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

Code
#include<cstdio>
using namespace std;
#define N 6005
int n,l1,l2,v[N][2],ct[N];
char s[N],dp[N][N];
bool check(int i,int j){for(int k=1;k<=n*2;k++)if(dp[i][k]<dp[j][k])return 1;else if(dp[i][k]>dp[j][k])return 0;return 0;}
int main()
{
	scanf("%d%s",&n,s+1);
	for(int i=1;i<=n*2;i++)
	if(s[i]=='a')v[++l1][0]=i;
	else v[++l2][1]=i;
	for(int i=n;i>=1;i--)
	if(v[i][0]<v[i][1])
	{
		int rb=i;
		while(rb<n&&(v[i][1]>v[rb+1][1]||v[i][1]>v[rb+1][0]))rb++;
		ct[i]=2;dp[i][1]='a';dp[i][2]='b';
		for(int j=1;j<=ct[rb+1];j++)dp[i][j+ct[i]]=dp[rb+1][j];
		ct[i]+=ct[rb+1];
		if(check(i,i+1))
		{
			ct[i]=ct[i+1];
			for(int j=1;j<=ct[i];j++)dp[i][j]=dp[i+1][j];
			dp[i][ct[i]+1]=0;
		}
	}
	else
	{
		int rb=i;
		while(rb<n&&v[rb][0]>v[rb+1][1])rb++;
		int l1=i,l2=i;
		for(ct[i]=1;ct[i]<=(rb-i+1)*2;ct[i]++)
		if(l1==rb+1)dp[i][ct[i]]='b',++l2;
		else if(l2==rb+1)dp[i][ct[i]]='a',++l1;
		else if(v[l1][0]<v[l2][1])dp[i][ct[i]]='a',++l1;
		else dp[i][ct[i]]='b',++l2;
		ct[i]--;
		for(int j=1;j<=ct[rb+1];j++)dp[i][j+ct[i]]=dp[rb+1][j];
		ct[i]+=ct[rb+1];
		if(check(i,i+1))
		{
			ct[i]=ct[i+1];
			for(int j=1;j<=ct[i];j++)dp[i][j]=dp[i+1][j];
			dp[i][ct[i]+1]=0;
		}
	}
	printf("%s\n",dp[1]+1);
}
64 CF587E Duff is Mad
Problem

给定 nn 个串 s1,...,sns_1,...,s_n ,多组询问求 i=lroccur(si,sk)\sum_{i=l}^r occur(s_i,s_k)

其中 occur(s1,s2)occur(s_1,s_2) 表示 s1s_1s2s_2 中出现的次数

n,si,q105n,\sum |s_i|,q\leq 10^5

4s,256MB4s,256MB

Sol

occur(s1,s2)occur(s_1,s_2) 相当于求出 s2s_2 的所有后缀中有多少包含 s1s_1 作为前缀

考虑把所有串带分隔符拼起来,然后对串建SA

对于一个给出的串,以它为前缀的后缀一定是排序后后缀的一段区间

一个暴力想法是将询问拆成若干 i=1roccur(si,sk)\sum_{i=1}^r occur(s_i,s_k) 的询问,从小到大加入每个串,每次是一个区间加,然后每个询问枚举 sks_k 的每个后缀求和

但这样复杂度最坏 O(qslogn)O(q|s|\log n)

考虑根号分治,将串分成 ssi|s|\geq\sqrt {\sum |s_i|}s<si|s|<\sqrt {\sum |s_i|} 的两部分

对于第一部分,因为串只有 O(si)O(\sqrt{\sum |s_i|}) 个,考虑暴力维护每个串的答案

对于每个串,维护原串属于它们的后缀,然后前缀和维护对于每个 ii ,排序后前 ii 个后缀有多少个属于这个串,这样可以 O((n+si)si)O((n+\sum |s_i|)\sqrt{\sum |s_i|}) 预处理 O(si)O(\sqrt{\sum |s_i|}) 修改每个串的答案

然后考虑第二部分的询问,如果暴力处理,有 O(n)O(n) 次修改, O((q+si)si)O((q+\sum |s_i|)\sqrt{\sum |s_i|}) 次询问,暴力询问可能不能通过

可以对询问根号平衡,分块,维护块内前缀和和块和的前缀和,那么可以 O(si)O(\sqrt{\sum |s_i|}) 修改 O(1)O(1) 询问

复杂度 O((n+q+si)si)O((n+q+\sum |s_i|)\sqrt{\sum |s_i|})

Code
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
#define N 100500
char s[N*2],t[N];
int k=350,n,m,a,b,c,su[351][N],id[N],tid[N],ct,id2[N*2],lb[N][2],lb2[N][2],le,l,ct1,id3[N*2],id4[N*2];
long long as[N],as2[N];
struct sth{int a,t,id;};
vector<sth> v[N];
struct SA{
	int a[N*4],b[N*4],c[N*4],sa[N*4],rk[N*4],su[N*4],n,m,he[N*4],f[N*2][18],lg[N*2];
	void pre()
	{
		n=le,m=255;
		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];
			m=2;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]?m-1:m++;
			m--;
		}
		for(int i=1;i<=n;i++)rk[sa[i]]=i;
		s[n+1]='%';
		for(int i=1;i<=n;i++)
		{
			if(rk[i]==1)continue;
			int tp=he[rk[i-1]]-1;
			if(tp<0)tp=0;
			while(s[i+tp]==s[sa[rk[i]-1]+tp])tp++;
			he[rk[i]]=tp;
		}
		for(int i=2;i<=n;i++)f[i][0]=he[i],lg[i]=lg[i>>1]+1;
		for(int i=1;i<=17;i++)
		for(int j=2;j+(1<<i)-1<=n;j++)
		f[j][i]=min(f[j][i-1],f[j+(1<<i-1)][i-1]);
	}
	int query1(int l,int r)
	{
		if(l==r)return 1e7;
		int a=l,b=r;
		if(a>b)b++,a^=b^=a^=b;else a++;
		int tp=lg[b-a+1];
		return min(f[a][tp],f[b-(1<<tp)+1][tp]);
	}
	pair<int,int> query2(int x,int k)
	{
		pair<int,int> as1;
		int l=1,r=x,as=x;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(query1(mid,x)>=k)as=mid,r=mid-1;
			else l=mid+1;
		}
		as1.first=as;
		l=x,r=n,as=x;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(query1(x,mid)>=k)as=mid,l=mid+1;
			else r=mid-1;
		}
		as1.second=as;
		return as1;
	}
}sa;
struct sqrttr{
	int v[N*2],su[451],k=450,bel[N*2];
	void init(){for(int i=1;i<=le+1;i++)bel[i]=(i-1)/k+1;}
	void add(int x,int s)
	{
		for(int i=x;i<=bel[x]*k;i++)v[i]+=s;
		for(int i=bel[x]+1;i<=450;i++)su[i]+=s;
	}
	int que(int x){return su[bel[x]]+v[x];}
}tr;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",t+1);l=strlen(t+1);
		lb[i][0]=ct1+1,lb[i][1]=ct1+l;
		lb2[i][0]=le+1;lb2[i][1]=le+l;
		for(int j=1;j<=l;j++)s[++le]=t[j],id3[le]=++ct1;
		s[++le]='z'+1;
		if(l>k)tid[++ct]=i,id[i]=ct;
	}
	sa.pre();tr.init();
	for(int i=1;i<=ct;i++)
	for(int j=1,tp=0;j<=le;j++)
	if(id3[sa.sa[j]])
	su[i][tp+1]=su[i][tp]+(id3[sa.sa[j]]>=lb[tid[i]][0]&&id3[sa.sa[j]]<=lb[tid[i]][1]),id4[j]=++tp;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		v[a-1].push_back((sth){c,-1,i});
		v[b].push_back((sth){c,1,i});
	}
	for(int i=1;i<=n;i++)
	{
		pair<int,int> fu=sa.query2(sa.rk[lb2[i][0]],lb2[i][1]-lb2[i][0]+1);
		tr.add(fu.first,1);
		tr.add(fu.second+1,-1);
		for(int j=1;j<=ct;j++)as2[j]+=su[j][id4[fu.second]]-su[j][id4[fu.first]-1];
		for(int j=0;j<v[i].size();j++)
		{
			if(id[v[i][j].a])as[v[i][j].id]+=v[i][j].t*as2[id[v[i][j].a]];
			else for(int k=lb2[v[i][j].a][0];k<=lb2[v[i][j].a][1];k++)as[v[i][j].id]+=v[i][j].t*tr.que(sa.rk[k]);
		}
	}
	for(int i=1;i<=m;i++)printf("%lld\n",as[i]);
}
65 CF685C Optimal Point
Problem

给出三维空间中的 nn 个点,求一个点,使得这个点与其它点的曼哈顿距离的最大值最小

多组数据

n105,xi,yi,zi1018n\leq 10^5,|x_i|,|y_i|,|z_i|\leq 10^{18}

3s,256MB3s,256MB

Sol

考虑二分答案,然后每个点的限制可以看成 x+y+z,x+yz,xy+z,xyzx+y+z,x+y-z,x-y+z,x-y-z 的限制

x+y+za1,x+yza2,xy+za3,xyza4x+y+z\geq a_1,x+y-z\geq a_2,x-y+z\geq a_3,x-y-z\geq a_4

那么 2xmax{a1+a4,a2+a3}2x\geq max\{a_1+a_4,a_2+a_3\}

对于一个合法的 xx ,一定存在一个合法的实数 y,zy,z

那么考虑合法的最小 xxx+1x+1 ,如果存在整点,那么这两个 xx 里面一定有一个有解

于是暴力贪心一下即可

复杂度 O(n+TlogV)O(\sum n+T\log V)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
long long T,s[5][2],n,a,b,c,a1,b1,c1,a2,b2,c2,s2[5][2],fg,t[10][3];
bool solve2(long long l)
{
	long long r1mn=s2[1][0]-l,r1mx=l-s2[4][0],r2mn=s2[2][0]-l,r2mx=l-s2[3][0];
	if((r1mn+r2mn)&1)
	if(r1mn<r1mx)r1mn++;
	else r2mn++;
	if(r1mn>r1mx||r2mn>r2mx)return 0;
	long long r1mn1=l-s2[4][1],r1mx1=s2[1][1]-l,r2mn1=l-s2[3][1],r2mx1=s2[2][1]-l;
	if(r1mn1>r1mn)r1mn=r1mn1;
	if(r1mx1<r1mx)r1mx=r1mx1;
	if(r2mn1>r2mn)r2mn=r2mn1;
	if(r2mx1<r2mx)r2mx=r2mx1;
	if((r1mn+r2mn)&1)
	if(r1mn<r1mx)r1mn++;
	else r2mn++;
	a2=l,b2=(r1mn+r2mn)/2,c2=r1mn-b2;
	return 1;
}
bool solve3(long long l)
{
	long long r1mn=s2[1][0]-l,r1mx=l-s2[4][0],r2mn=s2[2][0]-l,r2mx=l-s2[3][0];
	if((r1mn+r2mn)&1)
	if(r2mn<r2mx)r2mn++;
	else r1mn++;
	if(r1mn>r1mx||r2mn>r2mx)return 0;
	long long r1mn1=l-s2[4][1],r1mx1=s2[1][1]-l,r2mn1=l-s2[3][1],r2mx1=s2[2][1]-l;
	if(r1mn1>r1mn)r1mn=r1mn1;
	if(r1mx1<r1mx)r1mx=r1mx1;
	if(r2mn1>r2mn)r2mn=r2mn1;
	if(r2mx1<r2mx)r2mx=r2mx1;
	if((r1mn+r2mn)&1)
	if(r2mn<r2mx)r2mn++;
	else r1mn++;
	a2=l,b2=(r1mn+r2mn)/2,c2=r1mn-b2;
	return 1;
}
long long Abs(long long a){return a>0?a:-a;}
long long solve(long long x,long long y,long long z)
{
	return max(max(
	max(Abs(x+y+z-s[1][0]),Abs(x+y+z-s[1][1])),
	max(Abs(x+y-z-s[2][0]),Abs(x+y-z-s[2][1]))
	),max(
	max(Abs(x-y+z-s[3][0]),Abs(x-y+z-s[3][1])),
	max(Abs(x-y-z-s[4][0]),Abs(x-y-z-s[4][1]))
	));
}
bool solve(long long l)
{
	for(int i=1;i<=4;i++)s2[i][0]=s[i][1]-l,s2[i][1]=s[i][0]+l;
	for(int i=1;i<=4;i++)if(s2[i][0]>s2[i][1])return 0;
	long long tp=max(s2[4][0]+s2[1][0],s2[3][0]+s2[2][0])/2;
	if(!solve2(tp-1))if(!solve2(tp))if(!solve2(tp+1))return 0;
	if(solve(a2,b2,c2)>l)solve2(tp+1);
	if(solve(a2,b2,c2)<=l){a1=a2,b1=b2,c1=c2;return 1;}
	if(!solve3(tp-1))if(!solve3(tp))if(!solve3(tp+1))return 0;
	if(solve(a2,b2,c2)>l)solve3(tp+1);
	if(solve(a2,b2,c2)<=l){a1=a2,b1=b2,c1=c2;return 1;}
	return 0;
}
void doit(int x,int y,int z){if(solve(a1,b1,c1)>solve(a1+x,b1+y,c1+z))a1+=x,b1+=y,c1+=z;}
int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		s[1][0]=s[2][0]=s[3][0]=s[4][0]=4e18;
		s[1][1]=s[2][1]=s[3][1]=s[4][1]=-4e18;
		int ct=0;
		scanf("%lld",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%lld%lld%lld",&a,&b,&c);
			if(fg)t[++ct][0]=a,t[ct][1]=b,t[ct][2]=c;
			if(s[1][0]>a+b+c)s[1][0]=a+b+c;
			if(s[1][1]<a+b+c)s[1][1]=a+b+c;
			if(s[2][0]>a+b-c)s[2][0]=a+b-c;
			if(s[2][1]<a+b-c)s[2][1]=a+b-c;
			if(s[3][0]>a-b+c)s[3][0]=a-b+c;
			if(s[3][1]<a-b+c)s[3][1]=a-b+c;
			if(s[4][0]>a-b-c)s[4][0]=a-b-c;
			if(s[4][1]<a-b-c)s[4][1]=a-b-c;
		}
		long long lb=0,rb=4e18;
		while(lb<=rb)
		{
			long long mid=(lb+rb)>>1;
			if(solve(mid))rb=mid-1;
			else lb=mid+1;
		}
		for(int t1=1;t1<=3;t1++)
		for(int i=-1;i<=1;i++)
		for(int j=-1;j<=1;j++)
		for(int k=-1;k<=1;k++)
		doit(i,j,k);
		printf("%lld %lld %lld\n",a1,b1,c1);
	}
}
66 ARC096E Everything on It
Problem

给一个集合 {1,2,...,n}\{1,2,...,n\} ,求有多少个子集的集合满足每个元素至少出现在两个子集中,对质数 mm 取模

n3000,108m109n\leq 3000,10^8\leq m\leq 10^9

4s,512MB4s,512MB

Sol

考虑容斥,枚举有 ii 个元素出现了0次, jj 个元素出现了1次,那么对于剩下的 nijn-i-j 个元素,它们的子集可以任意选,然后枚举 jj 个元素分在几个子集里面,那么分 j>0,j=0j>0,j=0 讨论有

ans=i=0nj=1niCni+jCi+ji(1)i+j22nijk=1jSjk(2nij)k+i=0nCni(1)i22nians=\sum_{i=0}^n\sum_{j=1}^{n-i}C_n^{i+j}C_{i+j}^i(-1)^{i+j}2^{2^{n-i-j}}\sum_{k=1}^jS_j^k(2^{n-i-j})^k+\sum_{i=0}^nC_n^i(-1)^i2^{2^{n-i}} ,其中 SS 为第二类斯特林数

考虑枚举 i+ji+j ,有 ans=s=1nCns(1)s22nsj=1sCsjk=1j(2ns)kSjk+i=0nCni(1)i22nians=\sum_{s=1}^nC_n^s(-1)^s2^{2^{n-s}}\sum_{j=1}^sC_s^j\sum_{k=1}^j(2^{n-s})^kS_j^k+\sum_{i=0}^nC_n^i(-1)^i2^{2^{n-i}}

考虑 j=1sCsiSjk\sum_{j=1}^sC_{s}^iS_j^k 的组合意义,当 i=0i=0 时它等于 Si+jkS_{i+j}^k , i>0i>0 时,相当于在 i+ji+j 个元素中选出一个集合,再将剩下的分成 kk 个集合,这相当于把元素分成 k+1k+1 个集合,再选出一个集合,于是这等于 Ssk+Ssk+1(k+1)S_{s}^k+S_{s}^{k+1}*(k+1)

于是相当于 ans=s=1nCns(1)s22nsk=1s(2ns)k(Ssk+Ssk+1(k+1))+i=0nCni(1)i22nians=\sum_{s=1}^nC_n^s(-1)^s2^{2^{n-s}}\sum_{k=1}^s(2^{n-s})^k(S_s^k+S_s^{k+1}*(k+1))+\sum_{i=0}^nC_n^i(-1)^i2^{2^{n-i}}

注意到如果 k=0k=0 那么里面等于 Ss0+Ss11=1S_s^0+S_s^1*1=1 ,因此可以将前面和后面合并得到 ans=s=0nCns(1)s22nsk=0s(2ns)k(Ssk+Ssk+1(k+1))ans=\sum_{s=0}^nC_n^s(-1)^s2^{2^{n-s}}\sum_{k=0}^s(2^{n-s})^k(S_s^k+S_s^{k+1}*(k+1))

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

Code
#include<cstdio>
using namespace std;
#define N 3050
int n,p,c[N][N],s[N][N];
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;}
int main()
{
	scanf("%d%d",&n,&p);
	for(int i=0;i<=n;i++)c[i][i]=c[i][0]=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])%p;
	s[1][1]=1;s[0][0]=1;
	for(int i=2;i<=n;i++)
	for(int j=1;j<=i;j++)
	s[i][j]=(1ll*j*s[i-1][j]+s[i-1][j-1])%p;
	int as=0;
	for(int i=0;i<=n;i++)
	{
		int tp=1ll*(i&1?p-1:1)*c[n][i]%p*pw(2,pw(2,n-i,p-1),p)%p;
		int s1=1,s2=pw(2,n-i,p);
		for(int k=0;k<=i;k++)
		as=(as+1ll*tp*(s[i][k]+1ll*s[i][k+1]*(k+1)%p)%p*s1)%p,s1=1ll*s1*s2%p;
	}
	printf("%d\n",as);
}
67 CF587D Duff in Mafia
Problem

你有一张 nn 个点 mm 条边的图,每条边有颜色,你需要选出一些边,满足:

  1. 没有两条选定的边有公共顶点
  2. 没有两条颜色相同的没有选定的边有公共顶点
  3. 选出的边的边权最大值最小

输出方案或者输出无解

n,m5×104n,m\leq 5\times 10^4

6s,256MB6s,256MB

Sol

最大值最小,考虑二分答案

对于每个点的所有出边,如果有一条被选了,那么其它的都不能被选

对于每个点每种颜色的出边,如果有一条没有被选,那么其它的都要被选

对于一些边,因为它的边权很大,所以它必须不选

考虑2-SAT,但是因为度数可能很大,所以不能暴力建边

考虑将一个点的出边排成序列,那么选了一个其余都不能选,相当于一段前缀和一段后缀不能选,因此可以考虑前缀和建边,新建 2m2m 个虚点,第 ii 个虚点连向第 i1i-1 个虚点和对应第 ii 条边不选的点, 后缀和部分同理

对于每种颜色,可以再进行一次这样的操作,但注意到如果一个点有三条颜色相同的出边一定无解因此这部分可以暴力

然后直接2-SAT构造方案即可

复杂度 O((n+m)logV)O((n+m)\log V)

Code
#include<cstdio>
#include<vector>
#include<cstring>
#include<set>
#include<algorithm>
#include<stack>
using namespace std;
#define N 500500
int s[N][4],n,m,head[N],cnt,ct,dfn[N],low[N],scc[N],s1[N],ct1,ct2;
struct edge{int t,next;}ed[N*4];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;}
stack<int> tp;
vector<int> st[N];
set<int> vl;
bool cmp(int i,int j){return s[i][2]<s[j][2];}
void dfs1(int u)
{
	dfn[u]=low[u]=++ct1;
	tp.push(u);
	for(int i=head[u];i;i=ed[i].next)
	{
		if(!dfn[ed[i].t])dfs1(ed[i].t),low[u]=min(low[u],low[ed[i].t]);
		else if(!scc[ed[i].t])low[u]=min(low[u],dfn[ed[i].t]);
	}
	if(dfn[u]==low[u])
	{
		int s=tp.top(),id=++ct2;tp.pop();
		scc[s]=id;
		while(s!=u)
		s=tp.top(),tp.pop(),scc[s]=id;
	}
}
bool check(int v)
{
	memset(head,0,sizeof(head));
	memset(dfn,0,sizeof(dfn));
	memset(scc,0,sizeof(scc));
	cnt=0;
	ct=m*2;ct1=0;ct2=0;
	for(int i=1;i<=m;i++)if(s[i][3]<=v)st[s[i][0]].push_back(i),st[s[i][1]].push_back(i);
	for(int i=1;i<=n;i++)
	{
		int sz=st[i].size();
		for(int j=0;j<sz;j++)
		{
			adde(ct+j+1,st[i][j]+m),adde(ct+j+sz+1,st[i][j]+m);
			if(j)adde(st[i][j],ct+j),adde(ct+j+1,ct+j),adde(ct+sz+j,ct+sz+j+1);
			if(j<sz-1)adde(st[i][j],ct+j+sz+2);
		}
		ct+=sz*2;
		st[i].clear();
	}
	for(int i=1;i<=m;i++)
	{
		vl.insert(s[s1[i]][0]);vl.insert(s[s1[i]][1]);
		st[s[s1[i]][0]].push_back(s1[i]+m),st[s[s1[i]][1]].push_back(s1[i]+m);
		if(i==m||s[s1[i]][2]!=s[s1[i+1]][2])
		{
			for(set<int>::iterator it=vl.begin();it!=vl.end();it++)
			{
				int x=*it;
				int sz=st[x].size();
				for(int j=0;j<sz;j++)
				{
					adde(ct+j+1,st[x][j]-m),adde(ct+j+sz+1,st[x][j]-m);
					if(j)adde(st[x][j],ct+j),adde(ct+j+1,ct+j),adde(ct+sz+j,ct+sz+j+1);
					if(j<sz-1)adde(st[x][j],ct+j+sz+2);
				}
				ct+=sz*2;
				st[x].clear();
			}
			vl.clear();
		}
	}
	for(int i=1;i<=m;i++)if(s[i][3]>v)adde(i,i+m);
	for(int i=1;i<=ct;i++)if(!dfn[i])dfs1(i);
	for(int i=1;i<=m;i++)if(scc[i]==scc[i+m])return 0;
	return 1;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d%d%d",&s[i][0],&s[i][1],&s[i][2],&s[i][3]),s1[i]=i;
	sort(s1+1,s1+m+1,cmp);
	if(!check(1e9+9)){printf("No\n");return 0;}
	int lb=0,rb=1e9,as=1e9;
	while(lb<=rb)
	{
		int mid=(lb+rb)>>1;
		if(check(mid))
		as=mid,rb=mid-1;
		else lb=mid+1;
	}
	check(as);
	printf("Yes\n");
	int c1=0;
	for(int i=1;i<=m;i++)
	if(scc[i+m]>scc[i])c1++;
	printf("%d %d\n",as,c1);
	for(int i=1;i<=m;i++)if(scc[i+m]>scc[i])printf("%d ",i);
}
68 CF643F Bears and Juice
Problem

nn 只熊和若干个桶,其中有一桶是酒,每天会进行如下操作:

每只熊选择一个桶的集合,如果里面有就,那么这只熊会去睡觉并且不在回来,如果睡觉的熊数量大于 pp 那么失败

如果 ii 天之后还有熊没有睡觉且知道了哪只桶里面是酒,那么熊获胜,设 rir_iii 天能使熊有必胜策略的最大桶数,求出 r12r2...qrq(mod232)r_1\oplus 2r_2\oplus...\oplus qr_q (\bmod 2^{32})

n109,p130,q2×106n\leq 10^9,p\leq 130,q\leq 2\times 10^6

5s,256MB5s,256MB

Sol

首先 n1<pn-1<p 时可以直接令 p=n1p=n-1

dpi,jdp_{i,j} 表示还剩 ii 天和 jj 个睡觉的位置时最多可能的桶数量

那么有 dp0,i=dpi,0=1dp_{0,i}=dp_{i,0}=1

考虑 dpi,jdp_{i,j} 怎么计算

对于一个桶,记录选了它的熊集合 SS

显然 Sj|S|\leq j ,且 S=k|S|=k 的集合有 Cn(pj)kC_{n-(p-j)}^k

显然这次操作后 SS 不同的一定能够被区分开来,相同的需要接下来再去区分

如果最后酒在一个 S=k|S|=k 的桶中,那么接下来还剩下 i1i-1 天,jkj-k 个睡觉的位置

那么有 dpi,j=k=0jCn(pj)kdpi1,jkdp_{i,j}=\sum_{k=0}^jC_{n-(p-j)}^kdp_{i-1,j-k}

暴力计算复杂度 O(p2q)O(p^2q)

显然转移可以写成矩阵,考虑分块,求出矩阵的 s,2s,...,kss,2s,...,ks 次方以及 dp0,...,s1dp_{0,...,s-1} ,然后对于每个位置,相当于一个向量乘上一个矩阵然后求一个位置的值,可以快速求出

复杂度 O(p2s+p3(q/s)+pq)O(p^2s+p^3(q/s)+pq) ,取 s=pqs=\sqrt{pq}O(p2.5q0.5+pq)O(p^{2.5}q^{0.5}+pq)

Code
#include<cstdio>
using namespace std;
#define N 133
#define M 2005000
#define K 23333
#define mod 4294967295ll
#define int unsigned int
int dp[K][N],n,p,q,as,f[N][N];
struct mat{int s[N][N];mat(){for(int i=0;i<=p;i++)for(int j=0;j<=p;j++)s[i][j]=0;}}v[N],a;
mat mul(mat a,mat b){mat c;for(int i=0;i<=p;i++)for(int k=0;k<=p;k++)if(a.s[i][k])for(int j=0;j<=p;j++)c.s[i][j]=(c.s[i][j]+1ull*a.s[i][k]*b.s[k][j])&mod;return c;}
mat pw(mat a,int b){mat as;for(int i=0;i<=p;i++)as.s[i][i]=1;while(b){if(b&1)as=mul(as,a);a=mul(a,a);b>>=1;}return as;}
#define ll long long
ll exgcd(ll a,ll b,ll &x,ll &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 s){ll x,y;exgcd(s,1ull<<32,x,y);
return (x+(1ull<<32))&mod;}
int C(int i,int j)
{
	int st1=1,st2=0;
	for(int k=1;k<=j;k++)
	{
		int l=i-k+1,r=k;
		while(~l&1)l>>=1,st2++;
		while(~r&1)r>>=1,st2--;
		st1=(1ull*st1*l)&mod;
		st1=(1ull*st1*getinv(r))&mod;
	}
	st1<<=st2;
	return st1&mod;
}
signed main()
{
	scanf("%u%u%u",&n,&p,&q);
	for(int i=0;i<=p;i++)dp[0][i]=1;
	if(p>=n)p=n-1;
	for(int j=0;j<=p;j++)for(int k=0;k<=j;k++)f[j][k]=C(n-(p-j),j-k);
	for(int i=1;i<=q&&i<=16000;i++)
	for(int j=0;j<=p;j++)
	for(int k=0;k<=j;k++)
	dp[i][j]+=dp[i-1][k]*f[j][k];
	for(int j=0;j<=p;j++)for(int k=0;k<=j;k++)a.s[j][k]=f[j][k];
	a=pw(a,16000);
	for(int j=0;j<=p;j++)v[0].s[j][j]=1;
	for(int i=1;i<=130;i++)v[i]=mul(v[i-1],a);
	for(int i=1;i<=q;i++)
	{
		int as1=0;
		int st1=i%16000,st2=i/16000;
		for(int j=0;j<=p;j++)
		as1+=1ull*v[st2].s[p][j]*dp[st1][j];
		as^=1ull*i*as1;
	}
	printf("%u\n",as);
}
69 ARC091F Strange Nim
Problem

nn 堆石子,每对石子有数量 aia_ikik_i ,每次可以拿走至少1个至多当前石子数目除以 kik_i 下取整个无法操作者负,求最优策略下先手后手谁必胜

n200,ai,ki109n\leq 200,a_i,k_i\leq 10^9

2s,256MB2s,256MB

Sol

先打表

可以发现 kiaik_i|a_i 时nim值为 ai/kia_i/k_i

然后每 kk 个一行打印出来,然后发现存在一些行相同

然后搞一下可以发现不整除时有 sg(ai,ki)=sg(aiaiki1,ki)sg(a_i,k_i)=sg(a_i-\lfloor\frac{a_i}{k_i}\rfloor-1,k_i)

直接搞复杂度最坏 O(ai)O(a_i)

考虑一次减去 aimodkiaiki+1\lfloor\frac{a_i\bmod k_i}{\lfloor\frac{a_i}{k_i}\rfloor+1}\rfloor 次,这些次减去的东西一定相同

考虑复杂度,每次 aiki\lfloor\frac{a_i}{k_i}\rfloor 一定会减少,因此 kiaik_i\geq \sqrt a_i 时复杂度不超过 ai\sqrt a_i

否则,一次至少会减去 ai\sqrt a_i ,于是总复杂度 O(Tai)O(T\sqrt a_i)

Code
#include<cstdio>
using namespace std;
int n,a,b,k;
int solve(int a,int b){if(a%b==0)return a/b;int tp=(a%b)/(a/b+1)-1;if(tp<0)tp=0;a-=(a/b+1)*tp;return solve(a/b*(b-1)+a%b-1,b);}
int main(){scanf("%d",&n);while(n--)scanf("%d%d",&a,&b),k^=solve(a,b);printf("%s\n",k?"Takahashi":"Aoki");}
70 CF575I Robots protection
Problem

一个 n×nn\times n 的二维平面,两种操作:

给一个等腰直角三角形加一个值

求一个点的值

n5000,q105n\leq 5000,q\leq 10^5

1.5s,1024MB1.5s,1024MB

Sol

对于直角在左下的情况,满足的条件一定形如 xa,yb,x+ycx\geq a,y\geq b,x+y\leq c

显然可以看成 axcb,yb,x+yca\leq x\leq c-b,y\geq b,x+y\leq c

考虑容斥一个限制 ,相当于 axcb,x+yca\leq x\leq c-b,x+y\leq c 的部分减去 axcb,y<b,x+yca\leq x\leq c-b,y<b,x+y\leq c 的部分

第二部分的第三个限制显然没用,所以第二部分是 axcb,y<ba\leq x\leq c-b,y<b

那么可以看成两个二维数点,可以分开二维树状数组解决

另外三个方向同理

复杂度 O(n2+qlog2n)O(n^2+q\log^2n)

Code
#include<cstdio>
using namespace std;
#define N 5050
int tr1[N][N],tr2[N*2][N],tr3[N*2][N],n,q,a,b,c,d,e;
void add1(int x,int y,int d){for(int i=x;i<=n;i+=i&-i)for(int j=y;j<=n;j+=j&-j)tr1[i][j]+=d;}
int query1(int x,int y){int as=0;for(int i=x;i;i-=i&-i)for(int j=y;j;j-=j&-j)as+=tr1[i][j];return as;}
void add2(int x,int y,int d){for(int i=x;i<=n*2;i+=i&-i)for(int j=y;j<=n;j+=j&-j)tr2[i][j]+=d;}
int query2(int x,int y){int as=0;for(int i=x;i;i-=i&-i)for(int j=y;j;j-=j&-j)as+=tr2[i][j];return as;}
void add3(int x,int y,int d){for(int i=x;i<=n*2;i+=i&-i)for(int j=y;j<=n;j+=j&-j)tr3[i][j]+=d;}
int query3(int x,int y){int as=0;for(int i=x;i;i-=i&-i)for(int j=y;j;j-=j&-j)as+=tr3[i][j];return as;}
void doit1(int l1,int r1,int l2,int r2){if(l1<1)l1=1;if(l2<1)l2=1;if(r2<1)return;if(r1<1)return;if(l1>r1||l2>r2)return;add1(l1,l2,1);add1(l1,r2+1,-1);add1(r1+1,l2,-1);add1(r1+1,r2+1,1);}
void doit2(int l1,int r1,int l2,int r2){if(l1<1)l1=1;if(l2<1)l2=1;if(r2<1)return;if(r1<1)return;if(l1>r1||l2>r2)return;add2(l1,l2,1);add2(l1,r2+1,-1);add2(r1+1,l2,-1);add2(r1+1,r2+1,1);}
void doit3(int l1,int r1,int l2,int r2){if(l1<1)l1=1;if(l2<1)l2=1;if(r2<1)return;if(r1<1)return;if(l1>r1||l2>r2)return;add3(l1,l2,1);add3(l1,r2+1,-1);add3(r1+1,l2,-1);add3(r1+1,r2+1,1);}
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=q;i++)
	{
		scanf("%d",&a);
		if(a==1)
		{
			scanf("%d%d%d%d",&b,&c,&d,&e);
			if(b==1)doit1(c,c+e,1,d-1),doit2(1,c+d+e,c,c+e);
			if(b==2)doit1(c,c+e,d+1,n),doit3(1,c-d+n+e,c,c+e);
			if(b==3)doit1(c-e,c,1,d-1),doit3(c-d+n-e,n*2,c-e,c);
			if(b==4)doit1(c-e,c,d+1,n),doit2(c+d-e,n*2,c-e,c);
		}
		else scanf("%d%d",&b,&c),printf("%d\n",query2(b+c,b)+query3(b-c+n,b)-query1(b,c));
	}
}
71 AGC035F Two Histograms
Problem

有一个 n×mn\times m 的棋盘,进行如下操作:

对于每一行,选定一个 xix_i ,给这一行前 xix_i 个格子加上1

对于每一列,选定一个 yiy_i ,给这一列前 yiy_i 个格子加上1

求最后可能的棋盘情况,模 998244353998244353

n,m5×105n,m\leq 5\times 10^5

2s,1024MB2s,1024MB

Sol

考虑什么情况下不同的 {xi,yi}\{x_i,y_i\} 得到的棋盘相等

如果棋盘上一个1是一行给它加的,给它染红色,如果是列加的,染蓝色

考虑两种不同的 {xi,yi}\{x_i,y_i\} 如果它们相同,一定是有一些格子的颜色不同

考虑这些格子中一个右侧和上侧都是无色的格子 (a,b)(a,b),那么对于这个格子是红色的那种方案,因为另外一种方案这个格子是蓝色,那么它上面那个格子的值一定大于0,因为它无色,所以它的值一定是2,又因为这个格子没有被染蓝色,那么一定yb=a1y_b=a-1

因为这个点右侧点无色,所以它的权值只能是0/2,但因为另外一种方案这里是蓝色,所以说右侧权值不可能是2,因此右侧是0,所以 xa=bx_a=b

因此一定有 xa=b,yb=a1x_a=b,y_b=a-1

这时可以通过改成 xa=b1,yb=ax_a=b-1,y_b=a ,改变这个格子的颜色

那么对于两种 {xi,yi}\{x_i,y_i\} 不同但棋盘相同的方案,一定可以不断地在两边找 xa=b,yb=a1x_a=b,y_b=a-1 的方案,最后让它们 {xi,yi}\{x_i,y_i\} 相同

考虑两个 {xi,yi}\{x_i,y_i\} 不同但棋盘相同,且都不存在 xa=b,yb=a1x_a=b,y_b=a-1 的方案

如果这两个的棋盘相同,考虑下标最小的一个不同的 xix_i ,设 xi=b,xi=a(b>a)x_i=b,x_i^{'}=a(b>a) ,考虑 yby_b ,因为第二个棋盘 (i,b)(i,b) 位置显然是1,所以有 yb<i,ybiy_b<i,y_b^{'}\geq i ,但前 i1i-1 行的 xix_i 都没有改变,所以 yi<i1y_i< i-1 的部分不可能改变,所以 yb=i1y_b=i-1 ,于是 xi=b,yb=i1x_i=b,y_b=i-1 ,矛盾

因此,没有两个这样的方案对应的棋盘相同

因为每一个方案都可以通过操作唯一对应一个这样的方案,所以可以得到不同的棋盘数等于这样的方案数

那么只需要求不存在 xa=b,yb=a1x_a=b,y_b=a-1{xi,yi}\{x_i,y_i\}

考虑容斥,枚举有多少个 (a,b)(a,b) 不满足限制,显然这些点没有两个 aa 相同,没有两个 bb 相同,那么答案是 i=0min(n,m)(1)iCniCmii!mninmi\sum_{i=0}^{min(n,m)} (-1)^iC_n^iC_m^ii!m^{n-i}n^{m-i}

复杂度 O(nlogmod)O(n\log mod)O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define N 500050
#define mod 998244353
int fr[N],ifr[N],n,m,as;
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<=5e5;i++)fr[i]=1ll*fr[i-1]*i%mod,ifr[i]=pw(fr[i],mod-2);
	scanf("%d%d",&n,&m);
	for(int i=0;i<=n&&i<=m;i++)as=(as+1ll*(i&1?mod-1:1)*fr[n]%mod*ifr[i]%mod*ifr[n-i]%mod*fr[m]%mod*ifr[m-i]%mod*pw(m+1,n-i)%mod*pw(n+1,m-i))%mod;
	printf("%d\n",as);
}
72 AGC028F2 Reachable Cells
Problem

给出一个 n×nn\times n 个网格,有一些格子是障碍,剩下的格子上有数字

考虑每一个满足以下条件的位置的二元组 (x,y)(x,y)

  1. 这两个位置都不是障碍
  2. 这两个位置不同
  3. xxyy 存在一条只向下和向右且不经过障碍的路径

求所有这样的二元组的 xyx*y

n1500n\leq 1500

9s,1024MB9s,1024MB

Sol

不会

73 CF504E Misha and LCP on Tree
Problem

给出一棵树,每个点上有字符,多组询问两条路径对应字符串的LCP

n3×105,m106n\leq 3\times 10^5,m\leq 10^6

8s,512MB8s,512MB

Sol

考虑树剖,变成若干段的问题

将每一条重链对应的的字符串以及反串全部拿出来建SA,然后就可以快速求出两段路径的LCP

然后每次找出两条路径的所有段,从前往后LCP即可

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

Code
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<stack>
#include<vector>
using namespace std;
#define N 1250050
#define M 2500080
int n,m,a,b,c,d,head[N],cnt,fa[N],id[N],le[N],id2[N],tp[N],ed1[N],ct,dep[N],sz[N],son[N],c1,f[N];
char s[N];
struct SA{
	int sa[M],rk[M],a[M],b[M],su[M],n,m,he[N],mn[N][21],lg[N];
	char v[M];
	void init()
	{
		m=n+255;
		for(int i=1;i<=n;i++)su[a[i]=v[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 k=1;k<=n;k<<=1)
		{
			int ct=0;
			for(int i=n;i>n-k;i--)b[++ct]=i;
			for(int i=1;i<=n;i++)if(sa[i]>k)b[++ct]=sa[i]-k;
			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];
			ct=2;
			for(int i=1;i<=n;i++)b[i]=a[i];
			a[sa[1]]=1;
			for(int i=2;i<=n;i++)a[sa[i]]=b[sa[i]]==b[sa[i-1]]&&b[sa[i]+k]==b[sa[i-1]+k]?ct-1:ct++;
		}
		for(int i=1;i<=n;i++)rk[sa[i]]=i;
		int ls=0;s[n+1]='%';
		for(int i=1;i<=n;i++)
		{
			if(ls)ls--;
			if(rk[i]==1)continue;
			while(v[i+ls]==v[sa[rk[i]-1]+ls])ls++;
			he[rk[i]]=ls;
		}
		for(int i=2;i<=n;i++)mn[i][0]=he[i];
		for(int j=1;j<=20;j++)
		for(int i=2;i+(1<<j)-1<=n;i++)
		mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1]);
		for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
	}
	int query(int l,int r)
	{
		if(l==r)return 1e9;
		if(l>r)l^=r^=l^=r;
		l++;
		int tp=lg[r-l+1];
		return min(mn[l][tp],mn[r-(1<<tp)+1][tp]);
	}
}sa;
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 dfs1(int u,int fa)
{
	sz[u]=1;dep[u]=dep[fa]+1;f[u]=fa;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa)
	{
		dfs1(ed[i].t,u);
		if(sz[son[u]]<sz[ed[i].t])son[u]=ed[i].t;
		sz[u]+=sz[ed[i].t];
	}
}
void dfs2(int u,int v,int fa)
{
	tp[u]=v;
	int fg=0;
	if(son[u])dfs2(son[u],v,u);
	else
	{
		int s1=tp[u],st=u,ct=dep[st]-dep[s1]+1;
		le[s1]=ct;id[s1]=c1+1;
		stack<char> q;
		q.push(s[u]);sa.v[++c1]=s[u];
		ed1[u]=u;
		while(st!=s1)
		{
			st=f[st];ed1[st]=u;
			q.push(s[st]);sa.v[++c1]=s[st];
		}
		sa.v[++c1]='$';
		id2[s1]=c1+1;
		while(!q.empty())sa.v[++c1]=q.top(),q.pop();
		sa.v[++c1]='$';
		return;
	}
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&ed[i].t!=son[u])
	dfs2(ed[i].t,ed[i].t,u);
}
struct sth{int l,r;};
vector<sth> tp1,tp2;
void solve1(int a,int b,vector<sth> &c)
{
	c.clear();
	stack<sth> sb;
	int fg=0;
	while(tp[a]!=tp[b])
	{
		if(dep[tp[a]]<dep[tp[b]])a^=b^=a^=b,fg^=1;
		if(!fg)c.push_back((sth){id[tp[a]]+le[tp[a]]-1-dep[a]+dep[tp[a]],id[tp[a]]+le[tp[a]]-1});
		else sb.push((sth){id2[tp[a]],id2[tp[a]]+dep[a]-dep[tp[a]]});
		a=f[tp[a]];
	}
	if(fg)a^=b^=a^=b;
	if(dep[a]>=dep[b])c.push_back((sth){id[tp[a]]+le[tp[a]]-1-dep[a]+dep[tp[a]],id[tp[a]]+le[tp[a]]-1-dep[b]+dep[tp[a]]});
	else c.push_back((sth){id2[tp[a]]+dep[a]-dep[tp[a]],id2[tp[a]]+dep[b]-dep[tp[a]]});
	while(!sb.empty())c.push_back(sb.top()),sb.pop();
}
int solve()
{
	int as=0,s1=tp1.size(),s2=tp2.size(),l1=0,l2=0;
	while(l1!=s1&&l2!=s2)
	{
		int lb1=tp1[l1].l,rb1=tp1[l1].r,lb2=tp2[l2].l,rb2=tp2[l2].r;
		int lcp=sa.query(sa.rk[lb1],sa.rk[lb2]);
		if(lcp>min(rb1-lb1+1,rb2-lb2+1))lcp=min(rb1-lb1+1,rb2-lb2+1);
		as+=lcp;
		if(lb1+lcp-1<rb1&&lb2+lcp-1<rb2)return as;
		lb1+=lcp;lb2+=lcp;
		if(lb1<=rb1)tp1[l1].l=lb1;else l1++;
		if(lb2<=rb2)tp2[l2].l=lb2;else l2++;
	}
	return as;
}
int main()
{
	scanf("%d%s",&n,s+1);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs1(1,0);dfs2(1,1,0);
	sa.n=c1;
	sa.init();
	scanf("%d",&m);
	while(m--)
	{
		scanf("%d%d%d%d",&a,&b,&c,&d);
		solve1(a,b,tp1);solve1(c,d,tp2);
		printf("%d\n",solve());
	}
}
74 CF671E Organizing a Race
Problem

nn 个城市连成一条链,每个点有 viv_i ,相邻两个点的距离为 did_i

如果举行一次 [l,r][l,r] 的比赛,选手第一天会从 ll 开始去 rr ,初始油箱是空的,你每到一个城市,可以获得 viv_i 的油,你走1单位距离需要1的油,选手中途不能转向,如果中途没油了就无法完成比赛,然后第二天以空油箱开始从 rrll

你可以进行 kk 次操作,每次选择一个位置让 vi+1v_i+1 ,求可能的最长的可以比赛的区间的长度

n105,k109n\leq 10^5,k\leq 10^9

3s,256MB3s,256MB

Sol

咕咕咕

75 ARC101E Ribbons on Tree
Problem

nn 个点,你需要将它们两两配对,然后对于每一对将两个点路径上的每条边染上色,求最后每条边都被染过色的配对方案数,模 109+710^9+7

n5000n\leq 5000

2s,1024MB2s,1024MB

Sol

考虑容斥,枚举哪些边没有被染色,那么会被分成若干个连通块,每个连通块内部可以任意连

fif_i 表示 ii 个点配对的方案数,那么 f2i=(2i1)(2i3)...31f_{2i}=(2i-1)*(2i-3)*...*3*1 ,f2i1=0f_{2i-1}=0

dpi,jdp_{i,j} 表示 ii 的子树,当前 ii 的连通块大小为 jj ,当前每一个方案的系数和

转移时考虑每个子树向上的边是否断掉即可

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

Code
#include<cstdio>
using namespace std;
#define N 5050
#define mod 1000000007
int n,sz[N],dp[N][N],f[N],head[N],cnt,a,b;
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)
{
	sz[u]=1;dp[u][1]=1;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)
	{
		dfs(ed[i].t,u);
		for(int j=sz[u];j>=1;j--)
		{
			for(int k=1;k<=sz[ed[i].t];k++)
			dp[u][j+k]=(dp[u][j+k]+1ll*dp[u][j]*dp[ed[i].t][k])%mod;
			dp[u][j]=1ll*dp[u][j]*dp[ed[i].t][0]%mod;
		}
		sz[u]+=sz[ed[i].t];
	}
	for(int i=1;i<=sz[u];i++)dp[u][0]=(dp[u][0]-1ll*dp[u][i]*f[i])%mod;
}
int main()
{
	scanf("%d",&n);
	f[0]=1;for(int i=2;i<=n;i+=2)f[i]=1ll*f[i-2]*(i-1)%mod;
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs(1,0);printf("%d\n",(mod-dp[1][0])%mod);
}
76 CF585F Digits of Number Pi
Problem

给一个长度为 ss 的数字串,再给定两个长度为 dd 的数 l,rl,r ,定义一个数是合法的当且仅当这个数存在一个长度为 d2\lfloor\frac{d}{2}\rfloor 的子串是给定串的子串,求区间内合法的数个数

s1000,d50s\leq 1000,d\leq 50

Sol

对于原串每一个长度为 d2\lfloor\frac{d}{2}\rfloor 的串都拿出来,建AC自动机

数位dp,设 dpi,j,0/1,0/1,0/1dp_{i,j,0/1,0/1,0/1} 表示考虑到第 ii 位,当前在AC自动机上第 jj 的点,当前和 l,rl,r 的关系,当前是否找到了一个合法的串

转移枚举下一个字符即可

复杂度 O(d2s)O(d^2s)

Code
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
#define mod 1000000007
#define N 50050
int ch[N][12],l1,l2,fail[N],ct=1,dp[N][52][2][2][2],is[N],fa[N];
char s[N],l[N],r[N];
void ins(int l,int r)
{
	int st=1;
	for(int i=l;i<=r;i++)
	{
		int vl=s[i]-'0';
		if(!ch[st][vl])ch[st][vl]=++ct,fa[ct]=st;
		st=ch[st][vl];
	}
	is[st]=1;
}
void init()
{
	queue<int> st;
	st.push(1);
	while(!st.empty())
	{
		int x=st.front();st.pop();
		for(int i=0;i<10;i++)
		if(ch[x][i])
		{
			int t=fail[x];
			while(t&&!ch[t][i])t=fail[t];
			if(t==0&&x!=1)t=1;
			fail[ch[x][i]]=ch[t][i];
			st.push(ch[x][i]);
		}
	}
}
int main()
{
	scanf("%s%s%s",s+1,l+1,r+1);
	l1=strlen(s+1);l2=strlen(l+1);
	for(int i=1;i<=l1-l2/2+1;i++)
	ins(i,i+l2/2-1);
	init();
	dp[1][0][1][1][0]=1;
	for(int i=0;i<l2;i++)
	for(int j=1;j<=ct;j++)
	for(int k=0;k<2;k++)
	for(int l1=0;l1<2;l1++)
	for(int s=0;s<2;s++)
	if(dp[j][i][k][l1][s])
	{
		int lb=k?l[i+1]-'0':0,rb=l1?r[i+1]-'0':9;
		for(int t=lb;t<=rb;t++)
		{
			int st=j;
			while(!ch[st][t]&&st)st=fail[st];
			if(!st)st=1;
			st=ch[st][t];
			if(!st)st=1;
			int nk=k&(t==l[i+1]-'0'),nl=l1&(t==r[i+1]-'0'),ns=s|is[st];
			dp[st][i+1][nk][nl][ns]=(dp[st][i+1][nk][nl][ns]+dp[j][i][k][l1][s])%mod;
		}
	}
	int as=0;
	for(int i=1;i<=ct;i++)
	for(int j=0;j<2;j++)
	for(int k=0;k<2;k++)
	as=(as+dp[i][l2][j][k][1])%mod;
	printf("%d\n",as);
}
77 CF708D Incorrect Flow
Problem

给出一个有源汇网络流,每条边有容量 cic_i 和流量 viv_i ,你可以修改这些值,使得这个流合法,求最小花费

n,m100,vi,ci106n,m\leq 100,v_i,c_i\leq 10^6

Sol

对于每条边,设流量增加的为 aia_i ,减少的为 bib_i ,容量增加的为 kik_i

那么有 civi+biai+ki0c_i-v_i+b_i-a_i+k_i\geq 0

uv(i,u)+a(i,u)b(i,u)uv(u,i)+a(u,i)b(u,i)=0\sum_u v_{(i,u)}+a_{(i,u)}-b_{(i,u)}-\sum_u v_{(u,i)}+a_{(u,i)}-b_{(u,i)}=0

然后要最小化 ai+bi+ki\sum a_i+b_i+k_i

然后线性规划即可

考虑每条边最终的流量

如果 viciv_i\leq c_i ,那么如果 v<viv<v_i 代价为 vivv_i-v ,如果 vivciv_i\leq v\leq c_i 代价为 vviv-v_i ,否则为 2vvici2v-v_i-c_i

那么先连一条上下界为v代价为0的边,然后连上界为v,费用为1的反向边,连上限为v-c,费用为1的正向边,再连无上限费用为2的正向边

如果 vi>civ_i>c_i ,如果 v<civ<c_i 代价为 vivv_i-v ,如果 civvic_i\leq v\leq v_i 代价为 civic_i-v_i ,否则为 2vvici2v-v_i-c_i

先连一条上界v下界c代价为0的边,然后连上界为c,费用为1的反向边,再连无上限费用为2的正向边

然后跑上下界最小可行流即可

Code
#include<cstdio>
using namespace std;
#define N 450
double s[N][N],f[N];
int n,m,a,b,c,d,su;
void pivot(int n,int m,int x,int y)
{
	double tp=s[y][x];s[y][x]=-1;
	for(int i=1;i<=m+1;i++)s[y][i]/=-tp;
	for(int i=1;i<=n;i++)
	if(i!=y)
	{
		double tp=s[i][x];s[i][x]=0;
		for(int j=1;j<=m+1;j++)s[i][j]+=tp*s[y][j];
	}
	tp=f[x];f[x]=0;
	for(int j=1;j<=m+1;j++)f[j]+=tp*s[y][j];
}
double LP(int n,int m)
{
	while(1)
	{
		int sp=0;
		for(int i=1;i<=m;i++)if(f[i]>0)sp=i;
		if(sp==0)
		return f[m+1];
		int mn=1e9,as=0;
		for(int i=1;i<=n;i++)
		{
			if(s[i][sp]>=-1e-10)continue;
			double tp=s[i][m+1]/-s[i][sp];
			if(mn>tp)mn=tp,as=i;
		}
		if(as==0)return 1e100;
		pivot(n,m,sp,as);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d%d",&a,&b,&c,&d);su+=d;
		f[i]=1;f[m+i]=f[m*2+i]=-1;
		s[a][i]++;s[a][m+i]++;s[n+a][i]--;s[n+a][m+i]--;
		s[b][i]--;s[b][m+i]--;s[n+b][i]++;s[n+b][m+i]++;
		s[n*2+i][i]=-1;s[n*2+i][m+i]=-1,s[n*2+i][m*2+i]=1;s[n*2+i][m*3+1]=c;
		s[n*2+m+i][i]=-1;s[n*2+m+i][m*3+1]=d;
	}
	for(int i=1;i<=m*3+1;i++)s[1][i]=s[n][i]=s[n+1][i]=s[n+n][i]=0;
	printf("%d\n",(int)(-LP(n*2+m*2,m*3)+su+0.1));
}
78 ARC095F Permutation Tree
Problem

对于一个排列,随意每一个 pi>1p_i>1 的位置,找到最大的满足 pj<pip_j<p_ijj ,连边 (i,j)(i,j)

给一棵树,求一个字典序最小的排列使得构造出来的树和给定树同构或者输出无解

n105n\leq 10^5

2s,256MB2s,256MB

Sol

考虑一个排列,先找到最右边的数 v1v_1 ,然后每次找到小于 viv_i 的最靠右的数为 vi+1v_{i+1} ,直到到1

显然对于每一个找出来的数,它右边的数都大于它

因此对于所有 (vi+1,vi)(v_{i+1},v_i) 的数,它们都会连到 viv_i 这个位置

因此这棵树一定包含一条链,剩下的点都连向这条链上的点

求出直径可以快速判断是否合法

考虑给一条链,设第 ii 个点除了这条链上的度数为 kik_i

那么最优解一定是先放 2,...,k1+12,...,k_1+1 ,然后放1,然后放 k1+3,...,k1+k2+2k_1+3,...,k_1+k_2+2 ,然后放 k1+2k_1+2 ,然后这样一只放完整条链

如果链的起始点 k>0k>0 ,可以将链的起始点改成这个点的一个儿子,这样 k=0,p1=1k=0,p_1=1 ,一定更优秀

同理,可以让结束点也有 k=0k=0

因此取一个直径一定最优秀

然后枚举这个链从哪边开始走,两种排列取min即可

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define N 205000
int head[N],cnt,dep[N],l,r,in[N],st[N],n,a,b,ct,st2[N],fg,as1[N],as2[N],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;in[f]++,in[t]++;}
void dfs(int u,int fa){dep[u]=dep[fa]+1;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u);}
void dfs2(int u,int t,int fa){if(!fg)st[++ct]=u;if(u==t)fg=1;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs2(ed[i].t,t,u);if(!fg)ct--;}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	dfs(1,0);int mx=0,vl;for(int i=1;i<=n;i++)if(dep[i]>mx)mx=dep[i],vl=i;
	dfs(vl,0);mx=0;int vl2=0;for(int i=1;i<=n;i++)if(dep[i]>mx)mx=dep[i],vl2=i;
	dfs2(vl,vl2,0);
	for(int i=1;i<=ct;i++)is[st[i]]=1;
	for(int i=1;i<=n;i++)if(!is[i]&&in[i]>1){printf("-1\n");return 0;}
	for(int i=1;i<=ct;i++)st2[i]=in[st[i]]-2+(in[st[i]]==1);
	int lb=0;
	for(int i=1;i<=ct;i++)
	{
		for(int j=1;j<=st2[i];j++)as1[lb+j]=lb+j+1;
		as1[lb+st2[i]+1]=lb+1;
		lb+=st2[i]+1;
	}
	lb=0;
	for(int i=ct;i>=1;i--)
	{
		for(int j=1;j<=st2[i];j++)as2[lb+j]=lb+j+1;
		as2[lb+st2[i]+1]=lb+1;
		lb+=st2[i]+1;
	}
	int fg2=0;
	for(int i=1;i<=n;i++)if(as1[i]<as2[i]){fg2=1;break;}else if(as2[i]<as1[i])break;
	if(!fg2)for(int i=1;i<=n;i++)as1[i]=as2[i];
	for(int i=1;i<=n;i++){printf("%d",as1[i]);if(i<n)printf(" ");}
}
79 CF521D Shop
Problem

nn 个数 aia_imm 种操作,每个操作为以下三种之一

  1. ax=ya_x=y
  2. ax=ax+ya_x=a_x+y
  3. ax=axya_x=a_x*y

你可以按任意顺序执行 kk 个不同的操作,你需要让最后的乘积最大,输出一种方案

n,m,k105n,m,k\leq 10^5

2s,256MB2s,256MB

Sol

显然是先赋值再加再乘

每个数的赋值只会做一次,所以这个赋值操作一定可以看成一个加操作

贪心的想,一个数加的操作一定是从大到小加入,因此可以把每一个加入都看成乘一个数

然后把所有的乘贪心从大到小拿出来

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

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 105000
int n,m,k,v[N],mx[N],r1,r2,fr[N],a,b,c,sp[N];
struct sth{int i,j;}t1[N];
vector<sth> s[N];
struct sth2{int i;long double s;}t2[N];
bool cmp(sth a,sth b){return a.j>b.j;}
bool cmp2(sth2 a,sth2 b){return a.s>b.s;}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]),mx[i]=v[i];
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&a,&b,&c);sp[i]=a;
		if(a==1){if(mx[b]<c)mx[b]=c,fr[b]=i;}
		if(a==2)s[b].push_back((sth){i,c});
		if(a==3)t1[++r1]=(sth){i,c};
	}
	for(int i=1;i<=n;i++)if(v[i]<mx[i])s[i].push_back((sth){fr[i],mx[i]-v[i]});
	for(int i=1;i<=n;i++)
	{
		sort(s[i].begin(),s[i].end(),cmp);
		int sz=s[i].size();
		long long s1=v[i];
		for(int j=0;j<sz;j++)
		t2[++r2]=(sth2){s[i][j].i,1.0*(s1+s[i][j].j)/s1},s1+=s[i][j].j;
	}
	sort(t1+1,t1+r1+1,cmp);
	sort(t2+1,t2+r2+1,cmp2);
	if(k>r1+r2)k=r1+r2;
	printf("%d\n",k);
	int l1=1,l2=1;
	for(int i=1;i<=k;i++)
	{
		if(t1[l1].j>t2[l2].s&&l1<=r1)l1++;
		else {
		if(sp[t2[l2].i]==1)printf("%d ",t2[l2].i);l2++;
		}
	}
	l1=1,l2=1;
	for(int i=1;i<=k;i++)
	{
		if(t1[l1].j>t2[l2].s&&l1<=r1)l1++;
		else {
		if(sp[t2[l2].i]==2)printf("%d ",t2[l2].i);l2++;
		}
	}
	for(int i=1;i<l1;i++)printf("%d ",t1[i].i);
}
80 AGC036F Square Constraints
Problem

求有多少个 {0,...,2n1}\{0,...,2n-1\} 的排列满足

i,n2i2+pi24n2\forall i ,n^2\leq i^2+p_i^2\leq 4n^2

答案模质数 mm

n250n\leq 250

4s,1024MB4s,1024MB

Sol

不会

81 AGC028C Min Cost Cycle
Problem

nn 个点, nnai,bia_i,b_i

对于一条有向边 (x,y)(x,y) ,边权为 min(ax,by)min(a_x,b_y)

求一个边权和最小的经过所有点的有向环,输出最小边权和

n105n\leq 10^5

2s,1024MB2s,1024MB

Sol

显然答案的下界是选 nn 个最小的数,但直接选 nn 个数可能不合法

如果有一个点的 ai,bia_i,b_i 都被选了,那么考虑将所有只选了 aa 的放在它逆时针方向,所有只选了 bb 的放在顺时针方向,剩下的交错放即可

否则,如果所有的 aa 或者所有的 bb 都被选了,那么也可行

否则,一定存在一个点,它的 a,ba,b 都被选了,那么一定不合法

于是首先处理全部选一种的方式,对于剩下的,枚举每个点,选它的 a,ba,b ,剩下选最小的即可

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

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 105000
int n,a[N],b[N],v[N*2],id1[N],id2[N];
long long su1,su2,su[N*2],as=1e18;
long long solve(int i)
{
	if(id1[i]>n&&id2[i]>n)return su[n];
	else if(id1[i]>n+1||id2[i]>n+1)return su[n+1]-(id1[i]<=n+1?v[id1[i]]:0)-(id2[i]<=n+1?v[id2[i]]:0);
	else return su[n+2]-v[id1[i]]-v[id2[i]];
}
int main()
{
	scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]),v[i*2-1]=a[i],v[i*2]=b[i],su1+=a[i],su2+=b[i];
	sort(v+1,v+n*2+1);for(int i=1;i<=n;i++)id1[i]=lower_bound(v+1,v+n*2+1,a[i])-v,id2[i]=lower_bound(v+1,v+n*2+1,b[i])-v;
	for(int i=1;i<=n*2;i++)su[i]=su[i-1]+v[i];
	as=min(su1,su2);for(int i=1;i<=n;i++)as=min(as,solve(i));
	printf("%lld\n",as);
}
82 CF585E Present for Vitalik the Philatelist
Problem

nn 个数 aa ,你需要先选出一个数,再选出一个集合,使得

集合内所有数gcd大于1

集合内所有数加上选出的那个数gcd等于1

求方案数模 109+710^9+7

n5×105,ai107n\leq 5\times 10^5,a_i\leq 10^7

5s,256MB5s,256MB

Sol

首先考虑求出 gcd=kgcd=k 的集合数

考虑容斥,求出 kgcdk|gcd 的方案数,这显然是2的(k的倍数的数个数)次方

然后暴力容斥回去就行了

然后考虑怎么算答案,枚举选出的数,那么贡献显然是gcd与这个数互质且大于1的集合数量,考虑容斥,因为每个数不超过 88 个质因子,所以暴力容斥即可

可以使用dfs实现

复杂度 O(vlogv+n28)O(v\log v+n*2^8)

Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define N 10000007
#define mod 1000000007
int ch[N],pr[N/10],ct,n,su[N],pw[N],cnt[N],a,s3[N],as,f[N];
vector<int> s;
void dfs(int l,int d,int t)
{
	if(l==s.size()){
	if(d>1)as=(as+1ll*t*su[d])%mod;return;
	}
	dfs(l+1,d*s[l],mod-t);
	dfs(l+1,d,t);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a),su[a]++,s3[a]++;
	for(int i=2;i<=1e7;i++)
	{
		if(!ch[i])pr[++ct]=i,f[i]=i;
		for(int j=1;1ll*i*pr[j]<=1e7&&j<=ct;j++)
		{
			ch[i*pr[j]]=1;f[i*pr[j]]=min(f[i],pr[j]);
			if(!i%pr[j])break;
		}
	}
	for(int i=1;i<=1e7;i++)
	for(int j=i*2;j<=1e7;j+=i)
	su[i]+=su[j];
	pw[0]=1;for(int i=1;i<=1e7;i++)pw[i]=pw[i-1]*2%mod;
	for(int i=1;i<=1e7;i++)su[i]=pw[su[i]]-1;
	for(int i=1e7;i>=1;i--)
	for(int j=i*2;j<=1e7;j+=i)
	su[i]=(su[i]-su[j]+mod)%mod;
	as=1;for(int i=1;i<=n;i++)as=as*2%mod;as--;
	as=1ll*(as-su[1]+mod)%mod*n%mod;
	for(int i=1;i<=1e7;i++)
	for(int j=i*2;j<=1e7;j+=i)
	su[i]=(su[i]+su[j])%mod;
	for(int i=1;i<=1e7;i++)
	if(s3[i])
	{
		s.clear();
		int tp=i;
		while(tp>1)
		{
			int e=f[tp];
			s.push_back(e);
			while(tp%e==0)tp/=e;
		}
		dfs(0,1,s3[i]);
	}
	printf("%d\n",as);
}
83 AGC032C Three Circuits
Problem

给一张无向连通图,求它是否能被分成三个不经过重复边的回路

n,m105n,m\leq 10^5

2s,1024MB2s,1024MB

Sol

三个回路拼起来一定是一个欧拉回路,如果原图不存在欧拉回路一定无解

如果一个点度数大于等于6,那么从这个点一定可以将欧拉回路分成大于等于三部分,因此一定合法

如果每个点度数都是2,显然不合法

如果只有一个度数为4的点,那么它连出去的两部分都是一个环,且不存在其它边,因此无解

如果一个度数为4的点分出的两部分路径中,有一部分内还有一个度数为4的点,那么可以再分开得到解

否则,考虑三个度数为4的点,如果为 A>B>C>A>B>C>AA->B->C->A->B->C->A ,可以构造 A>B>C>A,B>C>B,C>A>B>CA->B->C->A,B->C->B,C->A->B->C ,如果为 A>B>C>A>C>B>AA->B->C->A->C->B->A ,可以构造 A>B>A,B>C>B,C>A>CA->B->A,B->C->B,C->A->C

如果只有两个度数为4的点,可以发现如果两个点各连了一个环,两个点间还有一个环,那么合法,如果两个点间连了四条路径则无解

因此各种判一下即可

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define N 100500
struct edge{int t,next;}ed[N*2];
int head[N],cnt=1,in[N],n,m,ct,mx,fg,a,b,vis[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;in[f]++;in[t]++;}
void dfs(int u,int id)
{
	vis[u]=1;
	if(u==b)fg=1,vis[u]=0;
	if(u==a&&id)return;
	for(int i=head[u];i;i=ed[i].next)
	if(i!=id&&i!=(id^1)&&!vis[ed[i].t])dfs(ed[i].t,i);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d",&a,&b),adde(a,b);a=-1;
	for(int i=1;i<=n;i++)if(in[i]&1){printf("No\n");return 0;}else if(mx<in[i])mx=in[i];
	if(mx>4){printf("Yes\n");return 0;}
	for(int i=1;i<=n;i++)if(in[i]==4){ct++;if(a==-1)a=i;else b=i;}
	if(ct>2){printf("Yes\n");return 0;}
	if(ct==2)
	for(int i=head[a];i;i=ed[i].next)
	if(!vis[ed[i].t])
	{
		fg=0;
		dfs(ed[i].t,1);
		if(!fg){printf("Yes\n");return 0;}
	}
	printf("No\n");
}
84 AGC023F 01 on Tree
Problem

有一棵有根树,每个点的权值为0或1,你可以选一个排列,满足每个点的父亲比它先出现,求这个排列对应的点权序列的最小逆序对个数

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

2s,256MB2s,256MB

Sol

考虑如果当前选了一个1,那么它下面所有可选的0都一定选,因此这些一定都会一起选,可以把它们看成一段

设一段内有 s0s_0 个0, s1s_1 个1

考虑对于一个点,先合并所有儿子的段,考虑相邻两段,如果 s0/s1>s0/s1s_0/s_1>s_0^{'}/s_1^{'} 那么小的放前面一定更优

因此可以set启发式合并所有儿子的段,然后再把根这一个点加进去

考虑前两段,如果 s0/s1>s0/s1s_0/s_1>s_0^{'}/s_1^{'} ,设这两段是 a,ba,b ,那么要求先选a才能选b,如果这两段最后不相邻,如果选的是 a,c,ba,c,b ,那么如果 cc 的那个值小于 bb ,那么可以把 cc 放到前面去,如果那个值大于 bb ,可以把它往后放

因此这两段一定是相邻的,因此可以合并这两段

因此可以一直合并到第一段小于第二段即可,然后合并儿子可以直接set启发式合并

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

Code
#include<cstdio>
#include<set>
using namespace std;
#define N 200500
long long as=0;
int n,head[N],cnt,is[N],id[N],a;
struct sth{int a,b;friend bool operator <(sth a,sth b){if(1ll*a.a*b.b==1ll*a.b*b.a)return a.a<b.a;return 1ll*a.a*b.b<1ll*a.b*b.a;}};
multiset<sth> st[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)
{
	id[u]=u;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa)
	{
		dfs(ed[i].t,u);
		if(st[id[ed[i].t]].size()>st[id[u]].size())id[u]^=id[ed[i].t]^=id[u]^=id[ed[i].t];
		for(set<sth>::iterator it=st[id[ed[i].t]].begin();it!=st[id[ed[i].t]].end();it++)
		st[id[u]].insert(*it);
	}
	sth tp1=(sth){is[u],!is[u]};
	while(!st[id[u]].empty())
	{
		if(tp1<*st[id[u]].begin())break;
		sth tp2=*st[id[u]].begin();st[id[u]].erase(st[id[u]].find(tp2));
		as+=1ll*tp1.a*tp2.b;
		tp1.a+=tp2.a;tp1.b+=tp2.b;
	}
	st[id[u]].insert(tp1);
}
int main()
{
	scanf("%d",&n);
	for(int i=2;i<=n;i++)scanf("%d",&a),adde(a,i);
	for(int i=1;i<=n;i++)scanf("%d",&is[i]);
	dfs(1,0);
	int su1=0;
	while(!st[id[1]].empty())
	{
		sth tp2=*st[id[1]].begin();st[id[1]].erase(st[id[1]].find(tp2));
		as+=1ll*su1*tp2.b;su1+=tp2.a;
	}
	printf("%lld\n",as);
}
85 CF568C New Language
Problem

2626 个字符,每个字符有两种属性中的一种

mm 个限制,每个限制为如果第 ii 个字符属性为A/B,那么第 jj 个属性为 A/B

给一个长度为 nn 的字符串,求大于等于这个字符串字典序的字典序最小的合法单词,或者输出无解

n200,m1.6×105n\leq 200,m\leq 1.6\times 10^5

2s,256MB2s,256MB

Sol

考虑依次确定每一位,如果前 ii 位保持原样,那么后面一定存在一种字典序大于等于给定串的方案

一种判断方式是枚举哪一位大于或者等于,但是直接枚举可能需要 O(n2m)O(n^2m)

考虑倒着做,如果对于一位,如果增加这一位,后面的位任意,存在一种合法解,那么最优解一定是这样的位中最后的,它前面都和原串相同,这一位大于原串

对于后面的,可以贪心逐位确定每一位,每一次相当于固定一些位的属性,问是否存在一种合法方案

考虑建2-SAT ,先缩好点,相当于对于每一个点,它选不选是固定的,那么可以通过两次dfs求出每个点选不选的情况,然后就可以判合法

复杂度 O(nm)O(nm)

Code
#include<cstdio>
#include<stack>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 505
int head[N],n,m,cnt,dfn[N],low[N],scc[N],ct1,ct2,head2[N],cnt2,s[N*N][2],vl[N],le,a,b,is2[N],is3[N];
char c[5],d[5],is[32],t[N],as[N];
struct edge{int t,next;}ed[N*N],ed2[N*N];
void adde(int f,int t){ed[++cnt]=(edge){t,head[f]};head[f]=cnt;}
void adde2(int f,int t){ed2[++cnt2]=(edge){t,head2[f]};head2[f]=cnt2;}
stack<int> tp;
void dfs1(int u)
{
	dfn[u]=low[u]=++ct1;
	tp.push(u);
	for(int i=head[u];i;i=ed[i].next)
	{
		if(!dfn[ed[i].t])dfs1(ed[i].t),low[u]=min(low[u],low[ed[i].t]);
		else if(!scc[ed[i].t])low[u]=min(low[u],dfn[ed[i].t]);
	}
	if(dfn[u]==low[u])
	{
		int s=tp.top(),id=++ct2;tp.pop();
		scc[s]=id;
		while(s!=u)
		s=tp.top(),tp.pop(),scc[s]=id;
	}
}
bool check()
{
	for(int i=1;i<=ct2;i++)is2[i]=-1;
	for(int i=1;i<=n;i++)
	if(is3[i]==0){if(is2[scc[i]]==0)return 0;is2[scc[i]]=1,is2[scc[i+n]]=0;}
	else if(is3[i]==1){if(is2[scc[i+n]]==0)return 0;is2[scc[i+n]]=1,is2[scc[i]]=0;}
	for(int i=ct2;i>=1;i--)if(is2[i]==1)
	for(int j=head2[i];j;j=ed2[j].next)
	{
		if(is2[ed2[j].t]==0)return 0;
		is2[ed2[j].t]=1;
	}
	for(int i=ct2;i>=1;i--)
	for(int j=head2[i];j;j=ed2[j].next)
	if(is2[ed2[j].t]==0)
	{
		if(is2[i]==1)return 0;
		is2[i]|=is2[ed2[j].t];
	}
	for(int i=1;i<=n;i++)if(is2[scc[i]]==is2[scc[i+n]]&&is2[scc[i]]>=0)return 0;
	return 1;
}
int main()
{
	scanf("%s",is+1);le=strlen(is+1);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%s%d%s",&a,c+1,&b,d+1);
		s[i][0]=a+(c[1]=='V'?n:0);
		s[i][1]=b+(d[1]=='V'?n:0);
		adde(s[i][0],s[i][1]);
		adde(s[i][1]+(s[i][1]>n?-n:n),s[i][0]+(s[i][0]>n?-n:n));
	}
	scanf("%s",t+1);
	for(int i=1;i<=n*2;i++)if(!dfn[i])dfs1(i);
	for(int i=1;i<=n;i++)if(scc[i]==scc[i+n]){printf("-1\n");return 0;}
	for(int i=1;i<=m;i++)
	if(scc[s[i][0]]!=scc[s[i][1]])adde2(scc[s[i][0]],scc[s[i][1]]),adde2(scc[s[i][1]+(s[i][1]>n?-n:n)],scc[s[i][0]+(s[i][0]>n?-n:n)]);
	for(int i=n;i>=1;i--)
	{
		for(int j=1;j<=n;j++)is3[j]=-1;
		for(int j=1;j<i;j++)is3[j]=is[t[j]-'a'+1]=='V';
		int l1=0,l2=0;
		for(int j=t[i]-'a'+1+(i!=n);j<=le;j++)
		if(is[j]=='V')l2=1;
		else l1=1;
		if(!l1&&!l2)continue;
		else if(!l1)is3[i]=1;
		else if(!l2)is3[i]=0;
		else is3[i]=-1;
		for(int j=i+1;j<=n;j++)
		{
			int l1=0,l2=0;
			for(int k=1;k<=le;k++)
			if(is[k]=='V')l2=1;
			else l1=1;
			if(!l1&&!l2)continue;
			else if(!l1)is3[j]=1;
			else if(!l2)is3[j]=0;
			else is3[j]=-1;
		}
		if(check())
		{
			for(int j=1;j<i;j++)as[j]=t[j];
			is3[i]=0;int f1=check();
			is3[i]=1;int f2=check();
			if(is[t[i]-'a'+1]=='V')
			{
				if(f2){for(int k=le;k>t[i]-'a'+1-(i==n);k--)if(is[k]=='V')as[i]=k+'a'-1,is3[i]=1;}
				if(!f2||!as[i]) for(int k=le;k>t[i]-'a'+1-(i==n);k--)if(is[k]=='C')as[i]=k+'a'-1,is3[i]=0;
				if(!as[i]){for(int k=le;k>t[i]-'a'+1-(i==n);k--)if(is[k]=='V')as[i]=k+'a'-1,is3[i]=1;}
			}
			else
			{
				if(f1){for(int k=le;k>t[i]-'a'+1-(i==n);k--)if(is[k]=='C')as[i]=k+'a'-1,is3[i]=0;}
				if(!f1||!as[i]) for(int k=le;k>t[i]-'a'+1-(i==n);k--)if(is[k]=='V')as[i]=k+'a'-1,is3[i]=1;
				if(!as[i]){for(int k=le;k>t[i]-'a'+1-(i==n);k--)if(is[k]=='C')as[i]=k+'a'-1,is3[i]=0;}
			}
			for(int j=i+1;j<=n;j++)
			{
				is3[j]=0;int f1=check();
				is3[j]=1;int f2=check();
				if(is[1]=='V')
				{
					if(f2){for(int k=le;k>=1;k--)if(is[k]=='V')as[j]=k+'a'-1,is3[j]=1;}
					if(!f2||!as[i]) for(int k=le;k>=1;k--)if(is[k]=='C')as[j]=k+'a'-1,is3[j]=0;
					if(!as[i]){for(int k=le;k>=1;k--)if(is[k]=='V')as[j]=k+'a'-1,is3[j]=1;}
				}
				else
				{
					if(f1){for(int k=le;k>=1;k--)if(is[k]=='C')as[j]=k+'a'-1,is3[j]=0;}
					if(!f1||!as[i]) for(int k=le;k>=1;k--)if(is[k]=='V')as[j]=k+'a'-1,is3[j]=1;
					if(!as[i]){for(int k=le;k>=1;k--)if(is[k]=='C')as[j]=k+'a'-1,is3[j]=0;}
				}
			}
			printf("%s",as+1);return 0;
		}
	}
	printf("-1\n");
}
86 CF666E Forensic Examination
Problem

给定一个串 ssnn 个串 t1,...,nt_{1,...,n},每次询问 sl,...,rs_{l,...,r}[l1,r1][l_1,r_1] 的这些串中的哪个中出现次数最多以及最多的出现次数

s,q5×105,n,ti5×104|s|,q\leq 5\times 10^5,n,\sum|t_i|\leq 5\times 10^4

6s,768MB6s,768MB

Sol

不会

87 AGC025E Walking on a Tree
Problem

给一棵树和 mm 条路径,求一种给每条路径定向的方案,使得每条边被经过的方向数和最大,输出方案和最优的和

n,m2000n,m\leq 2000

2s,1024MB2s,1024MB

Sol

显然答案上界是 (u,v)min((u,v),2)\sum_{(u,v)} min((u,v)经过次数,2)

考虑构造等于上界的方案

如果有一个叶子经过次数为0,可以直接删掉

如果经过次数为1,显然这条路径无论怎么定向都是可行的,所以可以处理一下再删掉

否则,考虑连出去的两条路径 $(t,u),(u,v) $ ,钦定连成 t>u>vt->u->v 或者 v>u>tv->u->t ,可以看成有一条路径 (t,v)(t,v) ,如果路径是 t>vt->v 可以看成选 t>u,u>vt->u,u->v ,反过来同理

那么因为两条路径的公共部分会被经过两次,其余边经过次数不变,所以可以看成一个更小的问题

因此可以dfs从下到上构造,每次这种操作相当于删掉两条路径再加入一条路径,暴力枚举边即可

复杂度 O(nm)O(nm)

Code
#include<cstdio>
using namespace std;
#define N 4050
int n,m,st[N],fg,head[N],cnt,vl[N],ct,s,t,as,v[N][2],ct2,is[N],nt[N],v2[N][2];
struct edge{int t,next,id;}ed[N*2];
struct sth{int f,t,v;};bool operator <(sth a,sth b){return a.v<b.v;}
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 adde2(int f,int t,int id){ed[++cnt]=(edge){t,head[f],id*2};head[f]=cnt;ed[++cnt]=(edge){f,head[t],id*2+1};head[t]=cnt;}
void dfs(int u,int fa)
{
	if(!fg)st[++ct]=u;
	if(u==t)fg=1;
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u);
	if(!fg)ct--;
}
void dfs2(int u,int fa)
{
	for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs2(ed[i].t,u);
	int ls=0;
	for(int i=1;i<=ct2;i++)
	if(v[i][0]==u||v[i][1]==u)
	{
		if(!ls)ls=i;
		else
		{
			int l=v[ls][0],r=v[i][1];
			if(v[ls][0]==u)l=v[ls][1],is[ls]=1;
			if(v[i][1]==u)r=v[i][0],is[i]=1;
			nt[ls]=nt[i]=++ct2;
			v[ct2][0]=l;v[ct2][1]=r;
			v[ls][0]=v[ls][1]=v[i][0]=v[i][1]=0;ls=0;
			if(v[ct2][0]==v[ct2][1])v[ct2][0]=v[ct2][1]=0;
		}
	}
	if(ls)
	{
		if(v[ls][0]==u)v[ls][0]=fa;
		else v[ls][1]=fa;
		if(v[ls][0]==v[ls][1])v[ls][0]=v[ls][1]=0;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)scanf("%d%d",&s,&t),adde(s,t,i);
	for(int r=1;r<=m;r++)
	{
		scanf("%d%d",&s,&t);ct=fg=0;dfs(s,0);v[r][0]=s,v[r][1]=t;v2[r][0]=s;v2[r][1]=t;
		for(int i=1;i<ct;i++)for(int j=head[st[i]];j;j=ed[j].next)if(ed[j].t==st[i+1]&&vl[ed[j].id]<2)vl[ed[j].id]++,as++;
	}
	printf("%d\n",as);
	ct2=m;dfs2(1,0);
	for(int i=ct2;i>=1;i--)
	is[i]^=is[nt[i]];
	for(int i=1;i<=m;i++)if(is[i])printf("%d %d\n",v2[i][1],v2[i][0]);
	else printf("%d %d\n",v2[i][0],v2[i][1]);
}
88 CF568E Longest Increasing Subsequence
Problem

有一个序列,有 kk 个空位和 mm 个可选的数

你可以选一些填进去,使得序列的LIS最长,每个数最多填一次,输出任意一种方案

n105,k1000n\leq 10^5,k\leq 1000

1.5s,128MB1.5s,128MB

Sol

dpidp_i 表示以i结尾的lis长度

考虑经典的LIS做法,设 fif_i 表示长度为 ii 的LIS的最小结尾,每次插入一个数时二分找到需要替换的位置

考虑加入一个空缺,显然一个数不可能在LIS中出现多次,所以可以暴力倒序加入,这样是 O(nklogn)O(nk\log n)

显然加入的数递减,所以可以扫过去,这部分复杂度为 O(nlogn+nk)O(n\log n+nk)

然后考虑还原答案,设当前位置的 dpdpss ,先尝试找到一个能转移过来的不是空位的 dpj=s1dp_j=s-1 的位置,如果能就转移过去,不然可以贪心找到前面的一个空位,贪心放这个位置

对于剩下的空位随便放即可

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

Code
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
#define N 105000
int n,m,v[N],v2[N],dp[N],f[N],f2[N],ct,las=1e9+100;
int ins(int x)
{
	if(x>f[ct]){f[++ct]=x;return ct;}
	int t=lower_bound(f+1,f+ct+1,x)-f;
	if(f[t-1]==x)t--;
	f[t]=x;return t;
}
void doit()
{
	int lb=1;
	for(int i=1;i<=ct;i++)
	{
		while(lb<=m&&v2[lb]<=f[i-1])lb++;
		if(lb<=m&&v2[lb]<f[i])f2[i]=v2[lb];else f2[i]=f[i];
	}
	while(lb<=m&&v2[lb]<=f[ct])lb++;
	for(int i=1;i<=ct;i++)f[i]=f2[i];
	if(lb<=m)f[++ct]=v2[lb];
}
multiset<int> fu;
set<int> tp[N];
int main()
{
	scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	scanf("%d",&m);for(int i=1;i<=m;i++)scanf("%d",&v2[i]),fu.insert(v2[i]);
	sort(v2+1,v2+m+1);
	for(int i=1;i<=n;i++)if(v[i]==-1)doit();else dp[i]=ins(v[i]);
	for(int i=n;i>=1;i--)if(dp[i])tp[dp[i]].insert(i);
	int st=-1;
	if(tp[ct].size())st=*tp[ct].begin();
	else for(int i=1;i<=n;i++)if(v[i]==-1)st=i;
	for(int i=ct;i>=1;i--)
	{
		if(v[st]==-1)v[st]=*(--fu.lower_bound(las)),fu.erase(fu.find(v[st]));
		las=v[st];
		int tp2=-1;
		while(!tp[i-1].empty())
		{
			int s=*tp[i-1].begin();
			if(s<st&&v[s]<las)tp2=s;
			tp[i-1].erase(s);
		}
		if(tp2!=-1)st=tp2;
		else for(int j=st-1;j>=1;j--)if(v[j]==-1){st=j;break;}
	}
	for(int i=1;i<=n;i++)if(v[i]==-1)v[i]=*fu.begin(),fu.erase(fu.find(v[i]));
	for(int i=1;i<=n;i++)printf("%d ",v[i]);
}
89 CF704C Black Widow
Problem

给一个形如 ((x_{p_{1,1}}\oplus v_{1,1})\and ...\and (x_{p_{1,k_1}}\oplus v_{1,k_1}))\oplus ...\oplus ((x_{p_{n,1}}\oplus v_{n,1})\and ...\and (x_{p_{n,k_n}}\oplus v_{n,k_n})) 的式子,每个 xix_i 的取值都是0或1,一共有 mm 个,满足

  1. ki2k_i\leq 2
  2. 每个 xix_i 出现的次数小于2

求有多少种取值方案满足求值答案为1,答案模 109+710^9+7

n,m105n,m\leq 10^5

2s,256MB2s,256MB

Sol

对于每个括号里面的内容,把那两个变量连一条边

那么最后会剩下一堆链和环

对于一条链,设 dpi,0/1,0/1dp_{i,0/1,0/1} 表示处理了链的前 ii 个位置,当前最后一个位置是啥,当前处理的链的部分的式子的异或值是啥,当前的方案数

对于一个环,枚举环上一个点的值,然后换成链做即可

需要特判自环,重边之类的情况

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

Code
#include<cstdio>
using namespace std;
#define N 100050
#define mod 1000000007
int in[N],is[N],n,m,k,a,b,c,dp[N][2][2],st[N],ct,vis[N],as1,as2,head[N],cnt=1;
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],1<=v&&v<=2?v^3:v};head[t]=cnt;in[f]++;in[t]++;}
void dfs(int u,int fa){st[++ct]=u;vis[u]=1;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&!vis[ed[i].t])dfs(ed[i].t,u);}
int que(int x,int y){for(int i=head[x];i;i=ed[i].next)if(ed[i].t==y)return ed[i].v;return 0;}
int que2(int x,int y){for(int i=head[x];i;i=ed[i].next)if(ed[i].t==y)return i;return 0;}
int que3(int x,int y,int c){for(int i=head[x];i;i=ed[i].next)if(ed[i].t==y&&i!=c&&(i^1)!=c)return ed[i].v;return 0;}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		if(a==1)
		{
			scanf("%d",&b);
			int fg=0;
			if(b<0)fg=1,b=-b;
			if(fg)is[b]^=2;else is[b]^=1;
		}
		else
		{
			scanf("%d%d",&b,&c);
			int fg=0;
			if(b<0)fg+=2,b=-b;
			if(c<0)fg+=1,c=-c;
			adde(b,c,fg);
		}
	}
	as1=1,as2=0;
	for(int i=1;i<=m;i++)if(in[i]==1&&!vis[i])
	{
		ct=0;dfs(i,0);
		for(int j=1;j<=ct;j++)for(int k=0;k<2;k++)for(int l=0;l<2;l++)dp[j][k][l]=0;
		dp[1][(is[i]&2)>>1][1]=dp[1][is[i]&1][0]=1;
		for(int j=2;j<=ct;j++)
		{
			int st1=que(st[j-1],st[j]);
			for(int k=0;k<2;k++)
			for(int l=0;l<2;l++)
			for(int t=0;t<2;t++)
			{
				int nt=l^((is[st[j]]&(t+1))?1:0);
				if(st1^3^(k?2:0)^(t?1:0))nt^=1;
				dp[j][nt][t]=(dp[j][nt][t]+dp[j-1][l][k])%mod;
			}
		}
		int v1=(dp[ct][0][0]+dp[ct][0][1])%mod,v2=(dp[ct][1][0]+dp[ct][1][1])%mod;
		int r1=(1ll*as1*v1+1ll*as2*v2)%mod,r2=(1ll*as1*v2+1ll*as2*v1)%mod;
		as1=r1,as2=r2;
	}
	else if(in[i]==0)
	{
		vis[i]=1;
		int v1=(is[i]&1?0:1)+(is[i]&2?0:1),v2=2-v1;
		int r1=(1ll*as1*v1+1ll*as2*v2)%mod,r2=(1ll*as1*v2+1ll*as2*v1)%mod;
		as1=r1,as2=r2;
	}
	for(int i=1;i<=m;i++)
	if(!vis[i])
	{
		ct=0;dfs(i,0);
		for(int j=1;j<=ct+1;j++)for(int k=0;k<2;k++)for(int l=0;l<2;l++)dp[j][k][l]=0;
		dp[1][(is[i]&2)>>1][1]=1;
		int ls=0;
		for(int j=2;j<=ct+1;j++)
		{
			int st1=que(st[j-1],st[j==ct+1?1:j]);
			if(ct==2&&j==ct+1)st1=que3(st[j-1],st[j==ct+1?1:j],ls);
			ls=que2(st[j-1],st[j==ct+1?1:j]);
			for(int k=0;k<2;k++)
			for(int l=0;l<2;l++)
			for(int t=0;t<2;t++)
			{
				if(j==ct+1&&t==0)continue;
				int nt=l^((is[st[j==ct+1?1:j]]&(t+1))?1:0);
				if(st1^3^(k?2:0)^(t?1:0))nt^=1;
				dp[j][nt][t]=(dp[j][nt][t]+dp[j-1][l][k])%mod;
			}
		}
		int v1=(dp[ct+1][0][0]+dp[ct+1][0][1])%mod,v2=(dp[ct+1][1][0]+dp[ct+1][1][1])%mod;
		for(int j=1;j<=ct+1;j++)for(int k=0;k<2;k++)for(int l=0;l<2;l++)dp[j][k][l]=0;
		dp[1][is[i]&1][0]=1;
		for(int j=2;j<=ct+1;j++)
		{
			int st1=que(st[j-1],st[j==ct+1?1:j]);
			if(ct==2&&j==ct+1)st1=que3(st[j-1],st[j==ct+1?1:j],ls);
			ls=que2(st[j-1],st[j==ct+1?1:j]);
			for(int k=0;k<2;k++)
			for(int l=0;l<2;l++)
			for(int t=0;t<2;t++)
			{
				if(j==ct+1&&t==1)continue;
				int nt=l^((is[st[j==ct+1?1:j]]&(t+1))?1:0);
				if(st1^3^(k?2:0)^(t?1:0))nt^=1;
				dp[j][nt][t]=(dp[j][nt][t]+dp[j-1][l][k])%mod;
			}
		}
		v1=(1ll*v1+dp[ct+1][0][0]+dp[ct+1][0][1])%mod,v2=(1ll*v2+dp[ct+1][1][0]+dp[ct+1][1][1])%mod;
		int r1=(1ll*as1*v1+1ll*as2*v2)%mod,r2=(1ll*as1*v2+1ll*as2*v1)%mod;
		as1=r1,as2=r2;
	}
	printf("%d\n",as2);
}
90 ARC100F Colorful Sequence
Problem

给定一个长度为 mm 的,字符集为 [1,k][1,k] 的串,定义一个串是好的,当且仅当它存在一个长度为 kk 的子串包含所有 [1,k][1,k] 的数,求所有长度为 nn 的好的串中给定串的出现次数之和

n2.5×104,k400n\leq 2.5\times 10^4,k\leq400

2s,1024MB2s,1024MB

Sol

考虑枚举给定串出现在每个位置的方案数,相加即为答案

如果给定串是好的,那么显然这个串一定是好的,那么无论在哪个位置方案数都是 knmk^{n-m}

否则,有两种情况

1.原串每个字符只出现一次,但 m<km<k

考虑减去非法的情况,即所有不好的串中出现的次数

dpi,jdp_{i,j} 表示前 ii 个数,当前最后 jj 个数是不同的,当前的方案数

那么枚举下一个字符和哪一个相同,有 dpi,j=l=jkdpi1,l+(kj)dpi1,j1dp_{i,j}=\sum_{l=j}^kdp_{i-1,l}+(k-j)dp_{i-1,j-1}

注意到这并不要求每个字符是啥,因此可以记录所有的连续 mm 个字符不同的串的出现次数之和,再除以 m!m!

gi,jg_{i,j} 表示前 ii 个数,当前最后 jj 个数是不同的,当前前面连续 mm 个字符不同的串的出现次数之和,和dp一起转移即可

然后用总数减去即可

2.原串有字符出现多次且不是好的

继续考虑容斥,容易发现,两侧可以分开计数

还是设 dpi,jdp_{i,j} 表示前 ii 个数,当前最后 jj 个数是不同的,当前的方案数

先预处理出向左的dp和向右的dp,然后枚举位置合并算出两个都不合法的方案

复杂度 O(nk)O(nk)

Code
#include<cstdio>
using namespace std;
#define N 25050
#define K 405
#define mod 1000000007
int n,m,k,s[N],dp[N][K],su[N][K],as,l,r,s1[N][K],fr[K],ifr[K],vl[N][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 check(int l,int r){for(int i=l;i<=r;i++)for(int j=i+1;j<=r;j++)if(s[i]==s[j])return 0;return 1;}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=k;i++)scanf("%d",&s[i]);
	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=1;i<=k;i++){if(check(1,i))l=i;if(check(k-i+1,k))r=i;}
	for(int i=1;i+m-1<=k;i++)if(check(i,i+m-1)){printf("%d\n",1ll*pw(m,n-k)*(n-k+1)%mod);return 0;}
	if(l==k)
	{
		su[1][1]=dp[1][1]=m;if(l==1)vl[1][1]=s1[1][1]=m;
		for(int i=2;i<=n;i++)
		for(int j=m-1;j>0;j--)dp[i][j]=(su[i-1][j]+1ll*dp[i-1][j-1]*(m-j+1))%mod,su[i][j]=(su[i][j+1]+dp[i][j])%mod,vl[i][j]=(s1[i-1][j]+1ll*vl[i-1][j-1]*(m-j+1)+(j>=l?dp[i][j]:0))%mod,s1[i][j]=(s1[i][j+1]+vl[i][j])%mod;
		printf("%d\n",(1ll*pw(m,n-k)*(n-k+1)%mod-1ll*s1[n][1]*ifr[m]%mod*fr[m-k]%mod%mod+mod)%mod);return 0;
	}
	dp[0][l]=1;for(int j=1;j<=l;j++)su[0][j]=1;
	for(int i=1;i<=n;i++)
	for(int j=m-1;j>0;j--)dp[i][j]=(su[i-1][j]+1ll*dp[i-1][j-1]*(m-j+1))%mod,su[i][j]=(su[i][j+1]+dp[i][j])%mod;
	vl[0][r]=1;for(int j=1;j<=r;j++)s1[0][j]=1;
	for(int i=1;i<=n;i++)
	for(int j=m-1;j>0;j--)vl[i][j]=(s1[i-1][j]+1ll*vl[i-1][j-1]*(m-j+1))%mod,s1[i][j]=(s1[i][j+1]+vl[i][j])%mod;
	for(int i=1;i+k-1<=n;i++)as=(as+1ll*su[i-1][1]*s1[n-(i+k-1)][1])%mod;
	printf("%d\n",(1ll*pw(m,n-k)*(n-k+1)%mod-as+mod)%mod);
}
91 CF538H Summer Dichotomy
Problem

nn 个老师和 mm 对关系,你需要把老师分配给两组,并给每一组老师分配一些学生,使得满足如下要求:

  1. 总学生数在 [l,r][l,r] 之间
  2. ii 个老师所在的组学生数在 [li,ri][l_i,r_i] 之间
  3. 每一对的两个老师不能在同一组

输出一个方案或者无解

n,m105,l,r,li,ri109n,m\leq 10^5,l,r,l_i,r_i\leq 10^9

2s,256MB2s,256MB

Sol

如果关系形成了奇环显然无解

不然,可以对于每一个联通块,得到左侧点的限制 [l1,r1][l_1,r_1] ,右侧点的限制 [l2,r2][l_2,r_2]

那么可以得到,对于左侧组的每一个取值,右侧组可能的取值区间

显然它显然只有一个区间,那么可以线段树,每次区间修改左侧值对应的右侧值区间

然后取一个合法的值即可

因为值 10910^9 ,需要离散化+各种边界处理

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

Code
#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;
#define N 300050
int l,r,n,m,lb[N],rb[N],head[N],col[N],is[2],sb[2][2],s[N],s2[N],ct,ct2,a,b,ct3,cnt,as[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;}
map<long long,int> tp;
struct line{int l,r;friend bool operator <(line a,line b){return a.l<b.l;}}t[N],t2[N];
struct node{int l,r,l1,r1,lz,rz;}e[N*4];
void pushdown(int x){if(e[x].lz>0)e[x<<1].lz=max(e[x<<1].lz,e[x].lz),e[x<<1].rz=min(e[x<<1].rz,e[x].rz),e[x<<1].l1=max(e[x<<1].l1,e[x].lz),e[x<<1].r1=min(e[x<<1].r1,e[x].rz),
e[x<<1|1].lz=max(e[x<<1|1].lz,e[x].lz),e[x<<1|1].rz=min(e[x<<1|1].rz,e[x].rz),e[x<<1|1].l1=max(e[x<<1|1].l1,e[x].lz),e[x<<1|1].r1=min(e[x<<1|1].r1,e[x].rz),e[x].lz=-1e9,e[x].rz=1e9;}
struct sth{int l,r;};
void build(int x,int l,int r)
{
	e[x].l1=e[x].lz=-1e9;
	e[x].r1=e[x].rz=1e9;
	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,int l1,int r1)
{
	if(e[x].l==l&&e[x].r==r){e[x].lz=max(e[x].lz,l1);e[x].rz=min(e[x].rz,r1);e[x].l1=max(e[x].l1,l1);e[x].r1=min(e[x].r1,r1);return;}
	pushdown(x);
	int mid=(e[x].l+e[x].r)>>1;
	if(mid>=r)modify(x<<1,l,r,l1,r1);
	else if(mid<l)modify(x<<1|1,l,r,l1,r1);
	else modify(x<<1,l,mid,l1,r1),modify(x<<1|1,mid+1,r,l1,r1);
}
sth query(int x,int s)
{
	if(e[x].l==e[x].r)return (sth){e[x].l1,e[x].r1};
	pushdown(x);
	int mid=(e[x].l+e[x].r)>>1;
	if(mid>=s)return query(x<<1,s);
	else return query(x<<1|1,s);
}
void dfs(int x)
{
	is[col[x]]=1;
	sb[col[x]][0]=max(sb[col[x]][0],lb[x]);
	sb[col[x]][1]=min(sb[col[x]][1],rb[x]);
	for(int i=head[x];i;i=ed[i].next)
	{
		if(col[ed[i].t]==-1)col[ed[i].t]=!col[x],dfs(ed[i].t);
		else if(col[ed[i].t]==col[x]){printf("IMPOSSIBLE\n");exit(0);}
	}
}
void dfs2(int x)
{
	col[x]=-1;as[x]=1;
	for(int i=head[x];i;i=ed[i].next)
	if(col[ed[i].t]!=-1)dfs2(ed[i].t);
}
int main()
{
	scanf("%d%d%d%d",&l,&r,&n,&m);
	for(int i=1;i<=n;i++)scanf("%d%d",&lb[i],&rb[i]),s[++ct]=lb[i],s[++ct]=rb[i],s[++ct]=rb[i]+1;
	for(int i=1;i<=m;i++)scanf("%d%d",&a,&b),adde(a,b);
	if(m==0)
	{
		int mn=-1e9,mx=1e9;
		for(int i=1;i<=n;i++)mn=max(mn,lb[i]),mx=min(mx,rb[i]);
		if(mn<=mx&&mn<=r)
		{
			printf("POSSIBLE\n");
			printf("%d %d\n",mn,r-mn);
			for(int i=1;i<=n;i++)printf("1");
			return 0;
		}
	}
	for(int i=1;i<=n;i++)col[i]=-1;
	sort(s+1,s+ct+1);s[0]=-1;
	for(int i=1;i<=ct;i++)if(s[i]!=s[i-1])s2[++ct2]=s[i],tp[s[i]]=ct2;
	build(1,1,ct2);
	for(int i=1;i<=n;i++)if(col[i]==-1)
	{
		is[0]=is[1]=0;sb[0][0]=sb[1][0]=-1e9;
		sb[0][1]=sb[1][1]=1e9;
		col[i]=0;dfs(i);
		if(!is[1])t[++ct3]=(line){tp[sb[0][0]],tp[sb[0][1]]};
		else
		{
			int l1=tp[sb[0][0]],r1=tp[sb[0][1]],l2=tp[sb[1][0]],r2=tp[sb[1][1]];
			if(l1>r1||l2>r2){printf("IMPOSSIBLE\n");exit(0);}
			if(r1<l2)
			{
				if(l1>1)modify(1,1,l1-1,1e9,-1e9);
				if(l1<=r1)modify(1,l1,r1,l2,r2);
				if(tp[s2[r1]+1]<l2)modify(1,tp[s2[r1]+1],l2-1,1e9,-1e9);
				if(l2<=r2)modify(1,l2,r2,l1,r1);
				modify(1,tp[s2[r2]+1],ct2,1e9,-1e9);
			}
			else if(r2<l1)
			{
				l1^=l2^=l1^=l2;r1^=r2^=r1^=r2;
				if(l1>1)modify(1,1,l1-1,1e9,-1e9);
				if(l1<=r1)modify(1,l1,r1,l2,r2);
				if(tp[s2[r1]+1]<l2)modify(1,tp[s2[r1]+1],l2-1,1e9,-1e9);
				if(l2<=r2)modify(1,l2,r2,l1,r1);
				modify(1,tp[s2[r2]+1],ct2,1e9,-1e9);
			}
			else
			{
				if(l1>l2)l1^=l2^=l1^=l2,r1^=r2^=r1^=r2;
				if(l1>1)modify(1,1,l1-1,1e9,-1e9);
				if(l1<l2)modify(1,l1,l2-1,l2,r2);
				if(r1<=r2)
				{
					if(l2<=r1)modify(1,l2,r1,l1,r2);
					if(r1<r2)modify(1,r1+1,r2,l1,r1);
					if(r2<ct2)modify(1,r2+1,ct2,1e9,-1e9);
				}
				else
				{
					if(l2<=r2)modify(1,l2,r2,l1,r1);
					if(r2<r1)modify(1,r2+1,r1,l2,r2);
					if(r1<ct2)modify(1,r1+1,ct2,1e9,-1e9);
				}
			}
		}
	}
	if(ct3)sort(t+1,t+ct3+1);
	t2[ct3]=t[ct3];
	for(int i=ct3-1;i>=1;i--)t2[i]=(line){max(t2[i+1].l,t[i].l),min(t2[i+1].r,t[i].r)};
	int mx=tp[s2[t2[1].r]+1],st=1;
	if(!ct3)mx=ct2;
	for(int i=1;i<mx;i++)
	{
		int lb=s2[i],rb=s2[i+1]-1,lb1,rb1;
		sth t1=query(1,i);
		if(t1.l>t1.r)continue;
		if(t1.l<=-1e8)lb1=-1e9,rb1=1e9;
		else lb1=s2[t1.l],rb1=s2[t1.r];
		while(st<=ct3&&t[st].l<=i)st++;
		if(st<=ct3)lb1=max(lb1,s2[t2[st].l]),rb1=min(rb1,s2[t2[st].r]);
		lb1=max(lb1,l-rb);rb1=min(rb1,r-lb);
		if(lb1>rb1||rb1<0)continue;
		printf("POSSIBLE\n");
		int ar=lb1,al=min(rb,r-lb1);
		printf("%d %d\n",al,ar);
		for(int j=1;j<=n;j++)if(!as[j])
		{
			is[0]=is[1]=0;sb[0][0]=sb[1][0]=-1e9;
			sb[0][1]=sb[1][1]=1e9;
			dfs2(j);
			col[j]=0;dfs(j);
			if(!(al>=sb[0][0]&&al<=sb[0][1]&&ar>=sb[1][0]&&ar<=sb[1][1]))dfs2(j),col[j]=1,dfs(j);
		}
		for(int i=1;i<=n;i++)printf("%d",col[i]+1);
		return 0;
	}
	printf("IMPOSSIBLE\n");
}
92 AGC032D Rotation Sort
Problem

有一个排列,你可以:

  1. 将一个区间向左循环位移一位,代价为 aa
  2. 将一个区间向右循环位移一位,代价为 bb

求给排列排好序的最小代价

n5000n\leq 5000

2s,1024MB2s,1024MB

Sol

首先可以将操作看成

  1. 将一个数向右移动到一个位置,代价为 aa
  2. 将一个数向左移动到一个位置,代价为 bb

如果直接移动相对顺序会改变,不好处理

考虑给每个数一个实数的位置,那么每次可以看成将一个数放到一个实数位置上,让最后所有的实数位置相对递增

显然每个数最多动一次,设第 ii 个数最后的位置为 viv_i 那么如果 vi>iv_i>i 代价为a,如果 vi<iv_i<i 代价为b,相等代价为0,相当于要求 v1<v2<...<vnv_1<v_2<...<v_n

dpi,jdp_{i,j} 表示前 ii 个数,最后一个数的取值为 [j,j+1)[j,j+1) ,当前的最小代价,转移时枚举下个数放在哪,暴力转移复杂度 O(n3)O(n^3) ,可以前缀和做到 O(n2)O(n^2)

Code
#include<cstdio>
using namespace std;
#define N 5005
long long dp[N][N];
int n,a,b,p[N];
int main()
{
	scanf("%d%d%d",&n,&a,&b);
	for(int i=0;i<=n;i++)for(int j=0;j<=n;j++)dp[i][j]=1e17;
	dp[0][0]=0;
	for(int i=1;i<=n;i++)scanf("%d",&p[i]);
	for(int i=1;i<=n;i++)
	{
		long long mn=1e18;
		for(int j=0;j<=n;j++)
		{
			long long tp=mn;
			if(p[i]>j)tp+=a;
			if(p[i]<j)tp+=b;
			if(dp[i][j]>tp)dp[i][j]=tp;
			if(mn>dp[i-1][j])mn=dp[i-1][j];
			tp=mn;
			if(p[i]>j)tp+=a;else tp+=b;
			if(dp[i][j]>tp)dp[i][j]=tp;
		}
	}
	long long as=1e18;
	for(int i=0;i<=n;i++)if(as>dp[n][i])as=dp[n][i];
	printf("%lld\n",as);
}
93 AGC024D Isomorphism Freak
Problem

给一棵树,定义它的权值为以一个点为根可以得到的不重构的有根树的种类数

你有一棵 nn 个点的树,你可以加任意点,使得最后的权值最小,求最小权值和这时的最小叶子数

n100,ans<263n\leq 100,ans<2^{63}

2s,1024MB2s,1024MB

Sol

对于一棵有根树,考虑它最深的叶子的深度 dd ,显然 dd 不同的一定不同

显然对于直径 ll ,不同的 dd 个数至少是 (l+1)/2(l+1)/2

那么答案下界是 (l+1)/2(l+1)/2

考虑构造,如果直径长度为奇数,取直径中点为根,考虑每一层,如果让每一层每个点的的儿子数都一样,显然合法,那么每一层的儿子数至少要取原树中这一层的儿子数max,因此可以将每一层的max乘起来

如果直径长度为偶数,那么取这条边为中心开始确定每一层即可

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

Code
#include<cstdio>
using namespace std;
#define N 105
int as1=1e8,n,head[N],cnt,in[N],dep[N],a,b,s[N][2];
long long as2;
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;in[f]++,in[t]++;}
void dfs(int u,int fa){dep[u]=dep[fa]+1;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u);}
void solve1(int x)
{
	dfs(x,0);
	int mxdep=0;long long as=1;
	for(int i=1;i<=n;i++)if(dep[i]>mxdep)mxdep=dep[i];
	for(int i=1;i<mxdep;i++)
	{
		int mx=1;
		for(int j=1;j<=n;j++)
		if(dep[j]==i&&mx<in[j]-1)mx=in[j]-1;
		if(i==1)mx++;
		as*=mx;
	}
	if(as1>mxdep||(as1==mxdep&&as2>as))as1=mxdep,as2=as;
}
void solve2(int x,int y)
{
	dep[y]=0;dfs(x,y);dep[x]=0;dfs(y,x);dep[x]=1;
	int mxdep=0;long long as=2;
	for(int i=1;i<=n;i++)if(dep[i]>mxdep)mxdep=dep[i];
	for(int i=1;i<mxdep;i++)
	{
		int mx=1;
		for(int j=1;j<=n;j++)
		if(dep[j]==i&&mx<in[j]-1)mx=in[j]-1;
		as*=mx;
	}
	if(as1>mxdep||(as1==mxdep&&as2>as))as1=mxdep,as2=as;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)scanf("%d%d",&s[i][0],&s[i][1]),adde(s[i][0],s[i][1]);
	for(int i=1;i<=n;i++)solve1(i);
	for(int i=1;i<n;i++)solve2(s[i][0],s[i][1]);
	printf("%d %lld\n",as1,as2);
}
94 CF582E Boolean Function
Problem

给一个包含 A,B,C,D,a,b,c,d,\or,\and,(,) 的表达式,其中 A,B,C,DA,B,C,D 为01变量, a=A1a=A\oplus 1 ,其余同理

定义表达式为 {()()}\{一个变量,(表达式)运算符(表达式)\} ,给出一个表达式,其中有一些非括号字符被换成了问号

给出 mm 个限制,每个限制给出 x,y,z,wx,y,z,w 以及 A=x,B=y,C=z,D=wA=x,B=y,C=z,D=w 时原表达式的值

你需要求出,有多少种替换每一个括号的方式,使得最后得到的表达式合法且满足这 mm 个限制,模 109+710^9+7

n500,m16n\leq 500,m\leq 16

4s,256MB4s,256MB

Sol

对于一个表达式,记录它在所有 242^4 种变量取值时的值,可以得到一个二进制数

可以发现,如果将两个表达式中间用 \and 连接,可以发现得到的二进制数是原来两个表达式对应二进制数的and

如果是 \or 同理

因此可以设 dpl,r,sdp_{l,r,s} 表示考虑区间 [l,r][l,r] ,得到的二进制数为 ss 的方案数

转移时直接暴力枚举运算符fwt即可

因为合法区间数不超过 n/4n/4 ,总复杂度为 O(n24224)O(n2^42^{2^4}) ,常数较小可以通过

Code
#include<cstdio>
#include<cstring>
using namespace std;
#define N 505
#define mod 1000000007
char s[N],v2[11]="abcdABCD";
int ty[8]={255,3855,13107,21845,65280,61680,52428,43690};
int dp[N][65540],ct,n,v,b,v3[65540];
void fwt1(int *a)
{
	for(int i=2;i<=1<<16;i<<=1)
	for(int j=0;j<1<<16;j+=i)
	for(int k=j;k<j+(i>>1);k++)
	a[k+(i>>1)]=(a[k+(i>>1)]+a[k])%mod;
}
void fwt2(int *a)
{
	for(int i=2;i<=1<<16;i<<=1)
	for(int j=0;j<1<<16;j+=i)
	for(int k=j;k<j+(i>>1);k++)
	a[k]=(a[k+(i>>1)]+a[k])%mod;
}
void fwt3(int *a)
{
	for(int i=2;i<=1<<16;i<<=1)
	for(int j=0;j<1<<16;j+=i)
	for(int k=j;k<j+(i>>1);k++)
	a[k+(i>>1)]=(a[k+(i>>1)]-a[k]+mod)%mod;
}
void fwt4(int *a)
{
	for(int i=2;i<=1<<16;i<<=1)
	for(int j=0;j<1<<16;j+=i)
	for(int k=j;k<j+(i>>1);k++)
	a[k]=(mod-a[k+(i>>1)]+a[k])%mod;
}
int solve(int l,int r)
{
	if(l==r)
	{
		int s1=++ct;
		if(s[l]=='?')
		for(int j=0;j<8;j++)dp[s1][ty[j]]=1;
		else for(int j=0;j<8;j++)if(s[l]==v2[j])dp[s1][ty[j]]=1;
		return s1;
	}
	int mid=l+1,v4=1;
	for(int i=l+1;i<=r;i++)
	{
		if(s[i]=='(')v4++;
		if(s[i]==')')v4--;
		if(!v4){mid=i+1;break;}
	}
	if(s[l]!='(')mid=l+1;
	if(mid==r+1)return solve(l+1,r-1);
	int s1=++ct;
	int v1=solve(l,mid-1),v2=solve(mid+1,r);
	if(s[mid]!='|')
	{
		fwt2(dp[v1]);fwt2(dp[v2]);
		for(int i=0;i<1<<16;i++)v3[i]=1ll*dp[v1][i]*dp[v2][i]%mod;
		fwt4(v3);fwt4(dp[v1]);fwt4(dp[v2]);
		for(int i=0;i<1<<16;i++)dp[s1][i]=(dp[s1][i]+v3[i])%mod;
	}
	if(s[mid]!='&')
	{
		fwt1(dp[v1]);fwt1(dp[v2]);
		for(int i=0;i<1<<16;i++)v3[i]=1ll*dp[v1][i]*dp[v2][i]%mod;
		fwt3(v3);
		for(int i=0;i<1<<16;i++)dp[s1][i]=(dp[s1][i]+v3[i])%mod;
	}
	return s1;
}
int main()
{
	scanf("%s",s+1);
	int tp=solve(1,strlen(s+1));
	scanf("%d",&n);
	while(n--)
	{
		int tp1=0;
		for(int j=0;j<4;j++)scanf("%d",&v),tp1=tp1*2+v;scanf("%d",&b);
		for(int j=0;j<1<<16;j++)if(((j>>tp1)&1)^b)dp[tp][j]=0;
	}
	int as=0;for(int j=0;j<1<<16;j++)as=(as+dp[tp][j])%mod;
	printf("%d\n",as);
}
95 AGC039E Pairing Points
Problem

圆环上有 2n2n 个点,给出哪些点间可以连边,你需要将这些点配 nn 对两两连边,使得最后得到的图形无环且连通,求方案数

n20n\leq 20

2s,1024MB2s,1024MB

Sol

设1连到了k,考虑所有跨过这条边的边

设这些边为 (u1,v1),...(uk,vk)(u_1,v_1),...(u_k,v_k) ,那么显然此时每条边对应一个连通块

显然这时同一侧两个连通块间不能相交,因此一个连通块对应的点集是两个区间 [l1,r1],[l2,r2][l_1,r_1],[l_2,r_2] 的并 ,其中 l1r1<k<l2r2l1\leq r_1<k<l_2\leq r_2

gl1,r1,l2,r2g_{l_1,r_1,l_2,r_2} 表示这样一个连通块的方案数

考虑算 gg ,枚举中间这条边是哪两个点连的,设 fl,r,kf_{l,r,k} 表示 [l,r][l,r] 的点进行配对,其中 kk 向区间外配对,使得区间中为一个连通块的方案数

那么有 gl1,r1,l2,r2=i=l1r1j=l2r2fl1,r1,ifl2,r2,j[(i,j)]g_{l_1,r_1,l_2,r_2}=\sum_{i=l_1}^{r_1}\sum_{j=l_2}^{r_2}f_{l_1,r_1,i}f_{l_2,r_2,j}[(i,j)有边]

考虑计算 ff ,如果删掉 kk 向区间外的边,那么剩下的是若干个类似于两个区间 [l1,r1],[l2,r2][l_1,r_1],[l_2,r_2] 的并的连通块,于是枚举最后一个这样的连通块的位置,有 fl,r,k=i=lk1j=k+1rfi+1,j1,kgl,i,j,rf_{l,r,k}=\sum_{i=l}^{k-1}\sum_{j=k+1}^rf_{i+1,j-1,k}g_{l,i,j,r}

初始状态 fi,i,i=1f_{i,i,i}=1 ,枚举1连向哪,答案为 i=2nf2,n,i[(1,i)]\sum_{i=2}^nf_{2,n,i}[(1,i)有边]

复杂度 O(n6)O(n^6)

Code
#include<cstdio>
#include<cstring>
using namespace std;
#define N 45
long long dp[N][N][N],f[N][N][N][N],n;
char s[N][N];
long long calc(int a,int b,int c,int d)
{
	if(f[a][b][c][d]!=-1)return f[a][b][c][d];
	long long as=0;
	for(int i=a;i!=(b==n*2?1:b+1);i=i==n*2?1:i+1)
	for(int j=c;j!=(d==n*2?1:d+1);j=j==n*2?1:j+1)
	as+=dp[a][b][i]*dp[c][d][j]*(s[i][j]=='1');
	return f[a][b][c][d]=as;
}
int main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n*2;i++)scanf("%s",s[i]+1);
	memset(f,-1,sizeof(f));
	for(int i=1;i<=n*2;i++)dp[i][i][i]=1;
	for(int l=2;l<n*2;l++)
	for(int i=1;i<=n*2;i++)
	{
		int j=(i+l-1)%(2*n)+1;
		for(int k=i;k!=(j==n*2?1:j+1);k=k==n*2?1:k+1)
		for(int s=i==n*2?1:i+1;s!=(k==n*2?1:k+1);s=s==n*2?1:s+1)
		for(int t=j==1?n*2:j-1;t!=(k==1?n*2:k-1);t=t==1?n*2:t-1)
		dp[i][j][k]=(dp[i][j][k]+dp[s][t][k]*calc(i,s==1?n*2:s-1,t==n*2?1:t+1,j));
	}
	long long as=0;
	for(int i=2;i<=n*2;i++)as+=dp[2][n*2][i]*(s[1][i]=='1');
	printf("%lld\n",as);
}
96 AGC025F Addition and Andition
Problem

给两个二进制数 x,yx,y ,执行以下操作 kk 次:

z=x\and y ,给 x,yx,y 加上 zz

以二进制输出最后的 x,yx,y

x,y2106,k106x,y\leq 2^{10^6},k\leq 10^6

2s,1024MB2s,1024MB

Sol

不会

97 CF526F Pudding Monsters
Problem

给一个 n×nn\times n 的网格图,每行每列都正好有一个棋子,求有多少个子正方形满足这个正方形中的棋子数等于正方形的边长

n3×105n\leq 3\times 10^5

2s,256MB2s,256MB

Sol

可以看成一个排列,求连续段数量

显然析合树就完了

这相当于 maxmin=rlmax-min=r-l

使用两个单调栈维护 max,minmax,min ,再维护 rlr-l ,可以看成区间加减,求区间中0的个数

线段树即可

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

Code
#include<cstdio>
using namespace std;
#define N 300050
int n,a,b,v[N],l[N],rb,l2[N],rb2;
long long as;
struct node{int l,r,mn,ct,lz;}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].ct=e[x<<1].ct+e[x<<1|1].ct;else if(e[x<<1].mn<e[x<<1|1].mn)e[x].mn=e[x<<1].mn,e[x].ct=e[x<<1].ct;else e[x].mn=e[x<<1|1].mn,e[x].ct=e[x<<1|1].ct;}
void pushdown(int x){if(e[x].lz)e[x<<1].mn+=e[x].lz,e[x<<1].lz+=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;e[x].ct=1;
	if(l==r)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].mn+=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);
}
int query(int x,int l,int r)
{
	if(e[x].l==l&&e[x].r==r)return e[x].mn==0?e[x].ct:0;
	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);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&a,&b),v[a]=b;
	build(1,1,n);
	for(int i=1;i<=n;i++)
	{
		l[rb+1]=i;
		while(rb&&v[l[rb]]<v[i])
		{
			int t=l[rb-1]+1,t1=l[rb];
			modify(1,t,t1,v[i]-v[l[rb]]);
			rb--;
		}
		l[++rb]=i;
		l2[rb2+1]=i;
		while(rb2&&v[l2[rb2]]>v[i])
		{
			int t=l2[rb2-1]+1,t1=l2[rb2];
			modify(1,t,t1,v[l2[rb2]]-v[i]);
			rb2--;
		}
		l2[++rb2]=i;
		if(i>1)modify(1,1,i-1,-1);
		as+=query(1,1,i);
	}
	printf("%lld\n",as);
}
98 AGC037D Sorting a Grid
Problem

你有一个 n×mn\times m 的矩阵A,元素在 [1,nm][1,nm] 间,你可以进行以下操作:

将每一行任意排序,将其变成矩阵B

将B的每一列任意排序,将其变成矩阵C

将C的每一行任意排序,将其变成矩阵D

你需要使得最后矩阵 (i,j)(i,j) 位置为 (i1)m+j(i-1)*m+j ,输出一种B,C的方案

n,m100n,m\leq 100

2s,1024MB2s,1024MB

Sol

显然矩阵C的每一行的数必须在 [(i1)m+1,(i1)m+m][(i-1)*m+1,(i-1)*m+m] 间,且任意这样的矩阵C都是合法的

[(i1)m+1,(i1)m+m][(i-1)*m+1,(i-1)*m+m] 的数看成 ii ,那么相当于要求矩阵C第 ii 行的每个数都是 ii

那么相当于矩阵B的每一列都包含 [1,n][1,n] 的所有数

那么相当于在矩阵A中进行 nn 次操作,每一次在每一行中选出一个数,使得这一次选出的数包含 [1,n][1,n] 的所有数

考虑这样操作是否有解,如果还有 kk 次操作,此时每种数有 kk 个,每行有 kk 个数,对于每一个行的集合 SS ,一共有 kSk|S| 个,因此至少有 S|S| 种数,那么根据Hall定理一定可以有匹配

因此直接网络流匹配即可,复杂度 O(mn2.5)O(mn^{2.5})

Code
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
#define N 206
int head[N],cnt=1,dis[N],n,m,v[N][N],as[N][N],as2[N][N],cur[N];
multiset<int> sb[N],vl[N][N];
struct edge{int t,next,v;}ed[N*N*3];
void adde(int f,int t,int v){ed[++cnt]=(edge){t,head[f],v};head[f]=cnt;ed[++cnt]=(edge){f,head[t],0};head[t]=cnt;}
bool bfs(int s,int t)
{
	queue<int> v;
	memset(dis,-1,sizeof(dis));
	memcpy(cur,head,sizeof(cur));
	v.push(s);dis[s]=1;
	while(!v.empty())
	{
		int r=v.front();v.pop();
		for(int i=head[r];i;i=ed[i].next)
		if(dis[ed[i].t]==-1&&ed[i].v)
		{dis[ed[i].t]=dis[r]+1,v.push(ed[i].t);if(ed[i].t==t)return 1;}
	}
	return 0;
}
int dfs(int u,int t,int f)
{
	if(!f||(u==t))return f;
	int as=0,tp;
	for(int &i=cur[u];i;i=ed[i].next)
	if(ed[i].v&&dis[ed[i].t]==dis[u]+1&&(tp=dfs(ed[i].t,t,min(ed[i].v,f))))
	{
		ed[i].v-=tp,ed[i^1].v+=tp,as+=tp,f-=tp;
		if(!f)return as;
	}
	return as;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		scanf("%d",&v[i][j]);
		int v1=(v[i][j]-1)/m+1;
		sb[i].insert(v1),vl[i][v1].insert(v[i][j]);
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=n*2+2;j++)head[j]=0;
		cnt=1;
		for(int j=1;j<=n;j++)
		{
			adde(n*2+1,j,1);adde(j+n,n*2+2,1);
			for(int k=1;k<=n;k++)
			{
				multiset<int>::iterator it=sb[j].lower_bound(k);
				if(it==sb[j].end()||*it!=k)continue;
				adde(j,k+n,1);
			}
		}
		while(bfs(n*2+1,n*2+2))dfs(n*2+1,n*2+2,1e8);
		for(int j=1;j<=n;j++)
		for(int k=head[j];k;k=ed[k].next)
		if(!ed[k].v)
		{
			int l=j,r=ed[k].t-n;
			as[l][i]=as2[i][l]=*vl[l][r].begin();
			vl[l][r].erase(vl[l][r].begin());
			sb[l].erase(sb[l].find(r));
		}
	}
	for(int i=1;i<=n;i++,printf("\n"))
	for(int j=1;j<=m;j++)
	printf("%d ",as[i][j]);
	for(int i=1;i<=m;i++)sort(as2[i]+1,as2[i]+n+1);
	for(int i=1;i<=n;i++,printf("\n"))
	for(int j=1;j<=m;j++)
	printf("%d ",as2[j][i]);
}
99 AGC020E Encoding Subsets
Problem

对于一个字符串,定义它的编码方式为

字符串01分别可以被编码为01

如果字符串 AA 可以被编码为 PPBB 可以被编码为 QQ,那么 ABAB 可以被编码为 PQPQ

如果字符串 AA 可以被编码为 PP,对于 k2k≥2,字符串 AAAA....(kA)AAAA....(k个A) 可以被编码为 (Pxk)(Pxk)

对于字符串 A,BA,B ,定义 AABB 的子集当且仅当不存在一个位置, AA 的这一位为1, BB 的这一位为0

给一个长度为 nn 的字符串 SS ,求它的所有子集的编码方式和模 998244353998244353

n500n\leq 500

5s,512MB5s,512MB

Sol

首先考虑一个串的编码方式数量怎么算

fl,rf_{l,r} 表示这一段的编码方式和

转移时分两种情况,如果第一个不是括号,可以直接转移到 fl+1,rf_{l+1,r} ,如果第一个是括号,枚举 kk 和前面部分的长度 ss ,如果这个串的 [l,l+s1],[l+s,l+2s1],...,[l+(k1)s,l+ks1][l,l+s-1],[l+s,l+2s-1],...,[l+(k-1)s,l+ks-1] 部分都相同,那么有 fl,l+s1fl+ks,rf_{l,l+s-1}*f_{l+ks,r} 的方案数

对于原问题,设 dpsdp_s 为给定串的答案,转移时同样考虑两种情况,第一个不是括号的情况类似,对于第二种情况,还是枚举 kk 和前面部分的长度 ss ,对于 [l,l+s1],[l+s,l+2s1],...,[l+(k1)s,l+ks1][l,l+s-1],[l+s,l+2s-1],...,[l+(k-1)s,l+ks-1] 这些部分,如果有一个位置上有一个是0,那么显然只有所有对应位上都是0的地方有这个转移,如果所有的都是1则可以任意01,那么显然所有可以转移的是这些串的and的所有子集

那么相当于 dp_{[l,l+s-1]\and[l+s,l+2s-1]\and...\and[l+(k-1)s,l+ks-1]}*dp_{l+ks,r} 的贡献

考虑记忆化dp,可以证明这样可能的 SS 只有几万个,于是可以通过

Code
#include<cstdio>
#include<vector>
#include<map>
using namespace std;
#define mod 998244353
map<long long,int> tp;
char s[105];
vector<int> st[105];
int dp(vector<int> s);
int solve(vector<int> s)
{
	int sz=s.size(),as=0;
	if(sz==1)return 1+s[0];
	int s2=st[sz].size();
	for(int i=0;i<s2;i++)
	{
		int v=st[sz][i];
		vector<int> vl;
		for(int j=1;j<=v;j++)
		{
			int mx=1;
			for(int k=j-1;k<sz;k+=v)mx&=s[k];
			vl.push_back(mx);
		}
		as=(as+dp(vl))%mod;
	}
	return as;
}
int dp(vector<int> s)
{
	int sz=s.size();
	if(!sz)return 1;
	if(sz==1)return 1+s[0];
	long long vl=-1;
	if(sz<=50)
	{
		vl=0;
		for(int i=0;i<sz;i++)vl=vl*2+s[i];
		vl=vl*64+sz;
	}
	else vl=sz+(1ll<<60);
	if(tp[vl])return tp[vl];
	int as=0;
	for(int i=0;i<sz;i++)
	{
		vector<int> s1,s2;
		for(int j=0;j<=i;j++)s1.push_back(s[j]);
		for(int j=i+1;j<sz;j++)s2.push_back(s[j]);
		as=(as+1ll*solve(s1)*dp(s2))%mod;
	}
	if(vl!=-1)tp[vl]=as;
	return as;
}
int main()
{
	for(int i=1;i<=100;i++)for(int j=i*2;j<=100;j+=i)st[j].push_back(i);
	scanf("%s",s+1);vector<int> st1;
	for(int i=1;s[i];i++)st1.push_back(s[i]-'0');
	printf("%d\n",dp(st1));
}
100 CF521E Cycling City
Problem

给一张图,问是否存在两个点,使这两个点间存在三条除了起点和终点外不存在相同节点的路径,输出一个方案或输出无解

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

2s,256MB2s,256MB

Sol

找出一棵生成树,对于所有的非树边,看成将对应的链上进行一次覆盖

如果每条边覆盖次数小于2,那么原图一定由若干链和环组成,那么显然无解

否则,找到这两条非树边 (u1,v1)(u_1,v_1),(u2,v2)(u_2,v_2) 和在树上对应的路径的并 (x,y)(x,y)

那么构造 x>u1>v1>y,x>u2>v2>y,x>yx->u_1->v_1->y,x->u_2->v_2->y,x->y 即可

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

Code
#include<cstdio>
#include<stack>
using namespace std;
#define N 205000
struct sth{int l,r;}s[N][2],tp1,tp2;
int ct[N],n,m,head[N],fa[N],dep[N],id[N],g[N][2],is[N],cnt,f[N],fg,v[3],c1;
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;}
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
void dfs(int u,int fa)
{
	dep[u]=dep[fa]+1;f[u]=fa;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa)id[ed[i].t]=ed[i].id,dfs(ed[i].t,u);
}
void col(int l,int r)
{
	sth tp=(sth){l,r};
	while(l!=r)
	{
		if(dep[l]<dep[r])l^=r^=l^=r;
		s[id[l]][ct[id[l]]++]=tp;
		if(ct[id[l]]==2)fg=1;
		l=f[l];
	}
}
void dfs2(int u,int fa,int vl)
{
	int fg=0;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&(s[vl][0].l==s[ed[i].id][0].l&&s[vl][1].r==s[ed[i].id][1].r))dfs2(ed[i].t,u,vl),fg=1;
	if(!fg)v[++c1]=u;
}
int dis(int l,int r){int as=0;while(l!=r){if(dep[l]<dep[r])l^=r^=l^=r;l=f[l],as++;}return as;}
void solve(int i,int j)
{
	stack<int> s;
	while(i!=j)
	{
		if(dep[i]<dep[j])s.push(j),j=f[j];
		else printf(" %d",i),i=f[i];
	}
	printf(" %d",i);
	while(!s.empty())printf(" %d",s.top()),s.pop();
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++){scanf("%d%d",&g[i][0],&g[i][1]);if(finds(g[i][0])!=finds(g[i][1]))fa[finds(g[i][0])]=finds(g[i][1]),is[i]=1,adde(g[i][0],g[i][1],i);}
	for(int i=1;i<=n;i++)if(!dep[i])dfs(i,0);
	for(int i=1;i<=m;i++)
	if(!is[i])
	{
		col(g[i][0],g[i][1]);
		if(fg)
		{
			int t1,s1=-1,s2;
			for(int j=1;j<=n;j++)if(ct[j]==2)t1=j;
			for(int j=1;j<=cnt;j++)if(ed[j].id==t1)if(s1==-1)s1=ed[j].t;else s2=ed[j].t;
			for(int j=head[s1];j;j=ed[j].next)
			if(ed[j].t==s2)
			tp1=s[t1][0],tp2=s[t1][1];
			dfs2(s1,s2,t1);
			dfs2(s2,s1,t1);
			printf("YES\n");
			printf("%d",dis(v[1],v[2])+1);solve(v[1],v[2]);printf("\n");
			if(dis(g[i][0],v[1])+2+dis(g[i][1],v[2])<dis(g[i][1],v[1])+2+dis(g[i][0],v[2]))
			printf("%d",dis(g[i][0],v[1])+2+dis(g[i][1],v[2])),solve(v[1],g[i][0]),solve(g[i][1],v[2]);
			else printf("%d",dis(g[i][1],v[1])+2+dis(g[i][0],v[2])),solve(v[1],g[i][1]),solve(g[i][0],v[2]);
			printf("\n");
			g[i][0]=tp1.l,g[i][1]=tp1.r;
			if(dis(g[i][0],v[1])+2+dis(g[i][1],v[2])<dis(g[i][1],v[1])+2+dis(g[i][0],v[2]))
			printf("%d",dis(g[i][0],v[1])+2+dis(g[i][1],v[2])),solve(v[1],g[i][0]),solve(g[i][1],v[2]);
			else printf("%d",dis(g[i][1],v[1])+2+dis(g[i][0],v[2])),solve(v[1],g[i][1]),solve(g[i][0],v[2]);
			return 0;
		}
	}
	printf("NO\n");
}
101 AGC030F Permutation and Minimum
Problem

有一个长度为 2n2n 的排列,有一些位置被擦掉了

bi=min{p2i1,p2i}b_i=min\{p_{2i-1},p_{2i}\} ,求对于所有可能的排列,可能出现的 bb 的数量,模 109+710^9+7

n300n\leq 300

3s,1024MB3s,1024MB

Sol

可以看成 nn 对,每一对有三种情况

  1. 两个数都确定了,这时可以直接删掉这一对
  2. 只有一个数确定
  3. 两个数都不确定

对于第二类的,它们的位置是确定的,对于第三类是不确定的

dpi,j,kdp_{i,j,k} 表示前 ii 个数,前面还有 jj 个第二类的没有配对,还有 kk 个其余的没有配对

如果这一个数是一个第二类的数,那么它可以配一个前面的第三类,或者不配,因此有 dpi,j,k=dpi,j1,k+dpi,j,k+1dp_{i,j,k}=dp_{i,j-1,k}+dp_{i,j,k+1}

如果是第三类的数,它可以配一个第二类或第三类的数,也可以不配,因为第二类的数有位置,因此有 dpi,j,k=(j+1)dpi,j+1,k+dpi,j,k+1+dpi,j,k1dp_{i,j,k}=(j+1)dp_{i,j+1,k}+dp_{i,j,k+1}+dp_{i,j,k-1}

最后确定第三类的位置,乘上第三类对数的阶乘即可

复杂度 O(n3)O(n^3)

Code
#include<cstdio>
using namespace std;
#define N 605
#define mod 1000000007
int dp[N][N][N],c1,n,is[N],a,b;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&a,&b);
		if(a>b)a^=b^=a^=b;
		if(b==-1)c1++;
		else if(a==-1)is[n*2-b+1]=1;
		else is[n*2-a+1]=is[n*2-b+1]=2;
	}
	dp[0][0][0]=1;
	for(int i=1;i<=n*2;i++)
	for(int j=0;j<=i&&j<=n;j++)
	for(int k=0;k<=i;k++)
	if(dp[i-1][j][k])
	{
		if(is[i]==2)dp[i][j][k]=dp[i-1][j][k];
		else if(is[i]==1){dp[i][j+1][k]=(dp[i-1][j][k]+dp[i][j+1][k])%mod;if(k)dp[i][j][k-1]=(dp[i][j][k-1]+dp[i-1][j][k])%mod;}
		else
		{
			if(k)dp[i][j][k-1]=(dp[i][j][k-1]+dp[i-1][j][k])%mod;
			if(j)dp[i][j-1][k]=(dp[i][j-1][k]+1ll*dp[i-1][j][k]*j)%mod;
			dp[i][j][k+1]=(dp[i][j][k+1]+dp[i-1][j][k])%mod;
		}
	}
	int as=dp[n*2][0][0];
	for(int i=1;i<=c1;i++)as=1ll*as*i%mod;
	printf("%d\n",as);
}
102 AGC026D Histogram Coloring
Problem

有一个网格图,第 ii 列有下面 viv_i 个位置,你可以给每个位置染成红色或者蓝色,使得每一个 2×22\times 2 的满的区域中都有2个红色和两个蓝色,求方案数模 109+710^9+7

n100,vi109n\leq 100,v_i\leq 10^9

2s,1024MB2s,1024MB

Sol

dpi,jdp_{i,j} 表示到第 ii 列,当前列第一个相邻两个相同的位置出现在 j,j+1j,j+1 (或者没有),当前的方案数

考虑下一列的情况,如果 jvi+1j\geq v_{i+1} ,那么这一列这部分一定是红蓝交错,那么下一列有2种方案

如果 j<vi+1vij<v_{i+1}\leq v_i ,那么因为上一列有两个相同,那么这一列的方案唯一

如果 vi+1>viv_{i+1}>v_i 且前一列有两个相同的,那么下一列 (vi,vi+1](v_i,v_{i+1}] 部分可以任意填,[1,vi][1,v_i] 部分和上一列相反,且相同的位置和上一列相同

如果上一列没有两个相同的,那么下一列 [1,vi][1,v_i] 部分有两种方案,(vi,vi+1](v_i,v_{i+1}] 部分可以任意填,那么可以枚举在哪个位置出现相同转移

注意到 jj 在两个 viv_i 之间的dp转移是类似的,因此可以离散化

复杂度 O(n3)O(n^3)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 106
#define mod 1000000007
int dp[N][N],n,s[N],st[N],v[N],v2[N],ct,as=1;
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",&n);
	for(int i=1;i<=n;i++)scanf("%d",&s[i]),v[i]=s[i];
	sort(v+1,v+n+1);
	v[0]=1;for(int i=1;i<=n;i++)if(v[i]!=v[i-1])v2[++ct]=v[i];
	for(int i=1;i<=n;i++)for(int j=1;j<=ct;j++)if(s[i]==v2[j])st[i]=j;
	dp[0][0]=1;
	for(int i=1;i<=n;i++)
	if(s[i]==1)
	{
		int su=0;
		for(int j=0;j<=ct+1;j++)su=(su+dp[i-1][j])%mod;
		as=2ll*as*su%mod;
		dp[i][0]=1;
	}
	else if(s[i-1]<=1)
	{
		for(int j=1;j<=st[i];j++)
		dp[i][j]=pw(2,s[i]-v2[j-1]+(j>1));
		dp[i][st[i]+1]=2;
		for(int j=1;j<=st[i];j++)dp[i][j]=(dp[i][j]-dp[i][j+1]+mod)%mod;
	}
	else if(s[i]<=s[i-1])
	{
		for(int j=1;j<=st[i-1]+1;j++)
		if(j>st[i])dp[i][st[i]+1]=(dp[i][st[i]+1]+2ll*dp[i-1][j])%mod;
		else dp[i][j]=dp[i-1][j];
	}
	else
	{
		for(int j=1;j<=st[i-1];j++)dp[i][j]=1ll*dp[i-1][j]*pw(2,s[i]-s[i-1])%mod;
		for(int j=st[i-1]+1;j<=st[i];j++)dp[i][j]=2ll*dp[i-1][st[i-1]+1]*pw(2,s[i]-v2[j-1])%mod;
		dp[i][st[i]+1]=2ll*dp[i-1][st[i-1]+1]%mod;
		for(int j=st[i-1]+1;j<=st[i];j++)dp[i][j]=(dp[i][j]-dp[i][j+1]+mod)%mod;
	}
	int su=0;
	for(int j=0;j<=ct+1;j++)su=(su+dp[n][j])%mod;
	as=1ll*as*su%mod;
	printf("%d\n",as);
}
103 CF526G Spiders Evil Plan
Problem

给一棵带边权树,多组询问,每次给出一个点 xxkk ,你需要选出 kk 条路径,使得:

  1. 这些路径至少有一条经过 xx
  2. 所有路径的并形成一个连通块
  3. 路径并的边权和最大

求最大边权,强制在线

n,q105n,q\leq 10^5

1s,256MB1s,256MB

Sol

不会

104 CF698D Limak and Shooting Points
Problem

nn 个点和 mm 个位置,你可以以任意顺序在每个位置沿任意一条直线射一支箭,这支箭遇到的第一个点会被删掉,求有多少个点可以被删掉

n1000,m7n\leq 1000,m\leq 7

3s,256MB3s,256MB

Sol

枚举每个点,考虑它是否能被删掉

考虑倒着操作,首先考虑从哪个位置到那个点,那么需要在之前先删掉这条线段的点,那么记录当前已经用掉的位置和还需要删掉的点数,每次枚举用哪个位置删掉哪个点,然后加入新的线段上的点,注意线段上不能有之前选的点

预处理每个位置到每个点线段上的其它点

复杂度 O(n2k+nkk!)O(n^2k+nkk!)

Code
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
#define N 1050
#define K 8
int v[N],as,n,k,us[K],is,tp[N],inq[N],is1[N];
vector<int> q[K][N];
queue<int> s1;
struct point{long long x,y;}s[K],t[N];
point operator +(point a,point b){return (point){a.x+b.x,a.y+b.y};}
point operator -(point a,point b){return (point){a.x-b.x,a.y-b.y};}
long long cross(point x,point y){return x.x*y.y-x.y*y.x;}
long long pt(point x,point y){return x.x*y.x+x.y*y.y;}
void dfs(int ls,queue<int> s1)
{
	if(!s1.size()){is=1;return;}
	if(is)return;
	if(s1.size()>ls)return;
	if(!ls){is=1;return;}
	int s2=s1.front();s1.pop();inq[s2]=0;
	is1[s2]=1;
	for(int i=1;i<=k;i++)if(!us[i])
	{
		queue<int> s3=s1,md1;
		int sz=q[i][s2].size();
		for(int j=0;j<sz;j++)if(!inq[q[i][s2][j]]&&!is1[q[i][s2][j]])s3.push(q[i][s2][j]),md1.push(q[i][s2][j]),inq[q[i][s2][j]]=1;
		us[i]=1;tp[k-ls+1]=i;
		dfs(ls-1,s3);
		while(!md1.empty())inq[md1.front()]=0,md1.pop();
		us[i]=0;
	}
	inq[s2]=1;is1[s2]=0;
}
int main()
{
	scanf("%d%d",&k,&n);
	for(int i=1;i<=k;i++)scanf("%lld%lld",&s[i].x,&s[i].y);
	for(int i=1;i<=n;i++)scanf("%lld%lld",&t[i].x,&t[i].y);
	for(int i=1;i<=k;i++)
	for(int j=1;j<=n;j++)
	for(int k=1;k<=n;k++)
	if(j!=k)
	if(cross(t[j]-s[i],t[k]-s[i])==0&&pt(s[i]-t[j],t[k]-t[j])<0)q[i][k].push_back(j);
	for(int i=1;i<=n;i++)
	{
		while(!s1.empty())s1.pop();
		s1.push(i);inq[i]=1;
		is=0;
		dfs(k,s1);inq[i]=0;
		if(is)as++;
	}
	printf("%d\n",as);
}
105 ARC089F ColoringBalls
Problem

你有 nn 个球排成一排,你有一个长度为 mm 的红蓝序列 vv ,你第 ii 次可以选一个区间(可以为空),给这个区间染成 viv_i 颜色,一个没有染色的球不能直接染成蓝色,求最后可能出现的颜色序列数量,模 109+710^9+7

n,m70n,m\leq 70

4s,256MB4s,256MB

Sol

考虑最后的颜色序列,一定是由若干白色分开的红蓝相间的段

可以钦定每一段的两侧都是红色,但长度可以是0( r=1,b=0r=1,b=0 时不能是0 )

对于一个 rr 段红色和 r1r-1 段蓝色的段,如果 r=1r=1,那么需要一次红色

否则,依次需要1次红色,1次蓝色,以及 r2r-2 次任意颜色的段

因为段长度大于等于2倍 rr ,所以 rr 的和不超过 n/2n/2 ,所以可以爆搜 n/2n/2 的划分

那么可以贪心判断一个划分是否合法

然后考虑计算方案,先乘上不同 rr 排列的方案数,然后相当于若干段,一些段长度大于等于1,另外一些长度大于等于0,可以组合数解决

复杂度 O(partition(n/2)(n+m))O(partition(n/2)*(n+m))

Code
#include<cstdio>
using namespace std;
#define N 142
#define mod 1000000007
int n,m,st[N],c[N][N],as,fr[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 solve(int k)
{
	int v1=0,v3=k,v2=k;
	for(int i=1;i<=m;i++)
	if(s[i]=='r')
	if(v3>0)v3--;
	else v1-=v1>0;
	else if(v2>v3&&st[v2]>1)v1+=st[v2]-2,v2--;
	else v1-=v1>0;
	if(st[v2]>1||v3||v1)return 0;
	int ls=1,v5=fr[k];
	for(int i=2;i<=k;i++)if(st[i]==st[i-1])ls++;
	else v5=1ll*pw(fr[ls],mod-2)*v5%mod,ls=1;
	v5=1ll*pw(fr[ls],mod-2)*v5%mod;
	int s1=n+1,s2=1;
	for(int i=1;i<=k;i++)s1-=2*st[i]-2*(st[i]>1),s2+=2*st[i];
	if(s1<0)return 0;
	return 1ll*v5*c[s1+s2-1][s2-1]%mod;
}
void dfs(int d,int su)
{
	if(su<=35)as=(as+solve(d-1))%mod;else return;
	for(int i=st[d-1];i<=36-su;i++)st[d]=i,dfs(d+1,su+i-(i>1));
}
int main(){scanf("%d%d%s",&n,&m,s+1);for(int i=0;i<=141;i++)c[i][0]=c[i][i]=1;for(int i=2;i<=141;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<=70;i++)fr[i]=1ll*fr[i-1]*i%mod;st[0]=1;dfs(1,0);printf("%d\n",as);}
106 CF582D Number of Binominal Coefficients
Problem

给定 n,p,an,p,a ,求有多少对 0yxn0\leq y\leq x\leq n ,满足 paCxyp^a|C_x^y ,输出答案模 109+710^9+7 , pp 为质数

p,a109,n101000p,a\leq 10^9,n\leq 10^{1000}

Sol

考虑 CxyC_x^ypp 的次数

fp(x)f_p(x) 表示 x!x!pp 的次数,那么有 fp(x)=i=1xpif_p(x)=\sum_{i=1}\lfloor\frac{x}{p^i}\rfloor

考虑 Cxy=x!y!(xy)!C_x^y=\frac{x!}{y!(x-y)!} ,那么p的次数为 i=1xpixypiypi\sum_{i=1}\lfloor\frac{x}{p^i}\rfloor-\lfloor\frac{x-y}{p^i}\rfloor-\lfloor\frac{y}{p^i}\rfloor

那么可以发现,如果 xmodpiymodpix\bmod p^i \geq y\bmod p^i ,那么这里贡献为0,否则贡献为1

可以发现最后的次数不超过 logn<3500\log n<3500

dpi,j,0/1,0/1dp_{i,j,0/1,0/1} 表示当前考虑了后 ii 位,当前后面贡献的次数为 jj ,当前后面的 yy 是否小于 xx ,后面的 xx 是否小于 nn 后面的部分

转移系数相当于给定 yy 这一位与 xx 这一位的大小关系和 xx 这一位和 nn 这一位的大小关系,可以发现对于一个 xx 的取值, yy 的取值个数是关于 xx 取值的一次函数,可以等差数列求出

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

Code
#include<cstdio>
#include<cstring>
using namespace std;
#define N 3550
#define mod 1000000007
int p,a,s[N],le,dp[N][N][2][2],len;
int t[N],t2[N];
char v[N];
int main()
{
	scanf("%d%d%s",&p,&a,v+1);
	if(a>3400){printf("0\n");return 0;}
	len=strlen(v+1);
	for(int i=1;i<=len;i++)t[len-i]=v[i]-'0';
	while(1)
	{
		int fg=0;
		long long st=0;
		for(int j=len;j>=0;j--)
		{
			st=st*10+t[j];
			t2[j]=st/p;
			if(t2[j])fg=1; 
			st%=p;
		}
		s[++le]=st;
		if(!fg)break;
		for(int j=len;j>=0;j--)t[j]=t2[j];
	}
	dp[1][0][0][0]=1;
	for(int i=1;i<=le;i++)
	for(int j=0;j<=le;j++)
	for(int s1=0;s1<2;s1++)
	for(int s2=0;s2<2;s2++)
	for(int s3=0;s3<2;s3++)
	for(int s4=0;s4<2;s4++)
	if(dp[i][j][s3][s4])
	{
		int t1=s1==s3,t2=s2==s4;
		int as=0;
		if(s1&&s2)as=1ll*(p-2-s[i])*(p-1-s[i])%mod*(mod+1)/2%mod;
		else if(!s1&&!s2)as=1ll*(s[i]-1)*s[i]%mod*(mod+1)/2%mod;
		else if(s1&&!s2)as=1ll*(p-1+s[i]+1)*(p-s[i]-1)%mod*(mod+1)/2%mod*(s[i]!=p-1);
		else if(s2&&!s1)as=1ll*(p-1+p-s[i])*s[i]%mod*(mod+1)/2%mod*(s[i]!=0);
		if(t1)as=(as+(s2?p-1-s[i]:s[i]))%mod;
		if(t2)as=(as+(s1?p-1-s[i]:s[i]))%mod;
		if(t1&&t2)as++;
		dp[i+1][j+s2][s1][s2]=(dp[i+1][j+s2][s1][s2]+1ll*dp[i][j][s3][s4]*as)%mod;
	}
	int as=0;
	for(int i=a;i<=3500;i++)as=(as+dp[le+1][i][0][0])%mod;
	printf("%d\n",as);
}
107 CF634F Orchestra
Problem

给一个 n×mn\times m 的网格图,有 kk 个位置有物品,求有多少个边平行与坐标轴且边界都是整数的矩形中有至少 ll 个物品

n,m,k3000,l10n,m,k\leq 3000,l\leq 10

2s,256MB2s,256MB

Sol

考虑枚举矩形下边界,然后从下向上考虑上边界,相当于每次在一个横坐标加入一个物品,求当前有多少个区间内有至少 ll 个物品

考虑给所有数排序,一个数不合法的就是它左右的 l1l-1 个中间的,可以对于每一段搞出来左端点是它的不合法的区间数量

考虑依次加入物品的时候,维护一个链表,每次插入一个点时拿出这个点左右 ll 个点,重新考虑这些点的那个贡献即可

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

Code
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
#define N 6050
set<int> tp;
int pr[N],nt[N],sz[N],id[N],n,m,k,t,as1,ct,st1[31];
long long as;
struct pt{int x,y;friend bool operator <(pt a,pt b){return a.y<b.y;}}v[N];
void init()
{
	as1=n*(n+1)/2;
	tp.clear();tp.insert(n+1);
	for(int i=1;i<=n+k+3;i++)pr[i]=nt[i]=sz[i]=id[i]=0;
	sz[1]=n;id[n+1]=1;ct=1;
}
void ins(int s)
{
	int s1=*tp.lower_bound(s),vl=id[s1],st=++ct;
	int p=pr[vl];
	sz[st]=sz[vl]-s1+s,sz[vl]=s1-s-1;id[s]=st;tp.insert(s);
	nt[p]=st;pr[st]=p;nt[st]=vl;pr[vl]=st;
	for(int i=0;i<=20;i++)st1[i]=0;
	st1[9]=st;
	for(int i=8,nw=pr[st];i>=0&&nw;i--,nw=pr[nw])st1[i]=nw;
	for(int i=10,nw=nt[st];i<=20&&nw;i++,nw=nt[nw])st1[i]=nw;
	for(int i=0;i<=9;i++)if(i+t>9&&st1[i]&&st1[i+t])
	as1-=(sz[st1[i]]+1)*(sz[st1[i+t]]+1);
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&k,&t);
	as=1ll*n*(n+1)*m*(m+1)/4;
	for(int i=1;i<=k;i++)scanf("%d%d",&v[i].x,&v[i].y);
	sort(v+1,v+k+1);
	for(int i=1;i<=m;i++)
	{
		int las=i-1,lb=1;
		init();
		while(lb<=k&&v[lb].y<i)lb++;
		for(int j=lb;j<=k;j++)
		as-=1ll*(v[j].y-1-las)*as1,ins(v[j].x),las=v[j].y-1;
		as-=1ll*(m-las)*as1;
	}
	printf("%lld\n",as);
}
108 AGC020D Min Max Repetition
Problem

给定 a,ba,b ,求满足以下条件的字符串的第 [l,r][l,r] 字符

  1. 字符串只包含 aaAbbB
  2. 字符串中最多连续相同字符数量是所有满足上一个条件的字符串中最少的
  3. 字符串字典序是所有满足上两个条件的字符串中最小的

多组数据

T103,1a,b108,rl100T\leq 10^3,1\leq a,b\leq 10^8,r-l\leq 100

2s,512MB2s,512MB

Sol

首先容易求出最长连续相同长度 ll

考虑贪心构造,那么前面一定是 A...ABA...ABA...A

考虑到某个位置放不下去了,那么这个位置放完 AA 之后,剩下的 bk>akb-k> a*k ,此时这一位放 B 之后 b>akb>a*k ,那么后面不能有两个 A 相邻,只能 B...BAB...BAB...B

二分每一段的长度即可,复杂度 O(Tlog(A+B)+T(rl+1))O(T\log (A+B)+T(r-l+1))

Code
#include<cstdio>
using namespace std;
int T,a,b,l,r;
int solve(int l,int r){int s1=(l-1)/(r+1)+1,s2=(r-1)/(l+1)+1;if(s1<s2)s1=s2;return s1?s1:1;}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%d%d",&a,&b,&l,&r);
		int as=solve(a,b);
		int lb=0,rb=1e9,as1=0;
		while(lb<=rb)
		{
			int mid=(lb+rb)>>1,fg=1;
			if(1ll*as*mid>a||mid>b)fg=0;
			if(fg&&solve(a-as*mid,b-mid+1)>as)fg=0;
			if(fg)as1=mid,lb=mid+1;
			else rb=mid-1;
		}
		int tp=as1*(as+1);
		if(l<=tp)for(int i=l;i<=r&&i<=tp;i++)printf("%c",i%(as+1)?'A':'B');
		a-=as*as1,b-=as1;
		l-=tp,r-=tp;if(l<1)l=1;
		lb=0,rb=as,as1=0;
		while(lb<=rb)
		{
			int mid=(lb+rb)>>1,fg=1;
			if(mid>a)fg=0;
			if(fg&&solve(a-mid,b)>as)fg=0;
			if(fg)as1=mid,lb=mid+1;
			else rb=mid-1;
		}
		if(l<=as1)for(int i=l;i<=r&&i<=as1;i++)printf("A");
		a-=as1;l-=as1,r-=as1;if(l<1)l=1;
		lb=0,rb=as,as1=0;
		while(lb<=rb)
		{
			int mid=(lb+rb)>>1,fg=1;
			if(mid>b)fg=0;
			if(fg&&solve(a,b-mid)>as)fg=0;
			if(fg)as1=mid,rb=mid-1;
			else lb=mid+1;
		}
		if(l<=as1)for(int i=l;i<=r&&i<=as1;i++)printf("B");
		b-=as1;l-=as1,r-=as1;if(l<1)l=1;
		int las=b-a*as;
		for(int i=l;i<=las&&i<=r;i++)printf("B");
		r-=las;l-=las;if(l<1)l=1;
		for(int i=l;i<=r;i++)printf("%c",i%(as+1)==1?'A':'B');
		printf("\n");
	}
}
109 CF578F Mirror Box
Problem

有一个 n×mn\times m 的网格图,每个格子内有一条沿一条对角线摆放的镜子,有 kk 个位置的镜子方向没有确定,你需要求满足如下条件的方案数模质数 pp

  1. 将光线从一个位置射入,它会从一个相邻的位置射出

  2. 将光线从每个位置射入后,每个格子的四个边界都至少被经过一次

n,m,k200n,m,k\leq 200

2s,256MB2s,256MB

Sol

不会

110 CF704B Ant Man
Problem

nn 个点,对于 i<ji<j ,你从 iijj 的时间是 xjxi+di+ajx_j-x_i+d_i+a_j ,否则时间是 xixj+ci+bjx_i-x_j+c_i+b_j

求从 ss 开始, tt 结束且经过所有点一次的路径的最小长度

n5000n\leq 5000

4s,256MB4s,256MB

Sol

考虑每个点的贡献,可以发现只和这个点从左边还是右边过来,以及这个点向左边还是右边去

对于每个点,有左进左出,左进右出,右进左出,右进右出四种情况

fif_i 表示只考虑前 ii 个点部分,当前前面构成的路径段数

显然除了开头结尾都有 fi>0f_i>0

可以发现,这四种情况分别会使 fi1,+0,+0,+1f_i-1,+0,+0,+1

dpi,jdp_{i,j} 表示考虑了前 ii 个点,前面的路径段数为 jj ,当前的最小代价

枚举这个点的状态转移即可

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

Code

这里有一些枚举开头结尾的不清真操作

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 5050
long long dp[N][N],mn=1e18;
int s[N][5],n,l,r,v[N];
int main()
{
	scanf("%d%d%d",&n,&l,&r);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	for(int i=1;i<=4;i++)for(int j=1;j<=n;j++)scanf("%d",&s[j][i]);
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	for(int i=1;i<=n;i++)
	for(int j=0;j<=i*2&&j<=n;j++)
	if(i==1||j>0)
	{
		long long as=dp[i-1][j]+1ll*j*(v[i]-v[i-1]);
		if(i==l)dp[i][j+1]=min(dp[i][j+1],as+s[i][4]);
		else if(i==r)dp[i][j+1]=min(dp[i][j+1],as+s[i][2]);
		else
		{
			if(j>1)dp[i][j-2]=min(dp[i][j-2],as+s[i][1]+s[i][3]);
			dp[i][j+2]=min(dp[i][j+2],as+s[i][2]+s[i][4]);
			if(j>1)dp[i][j]=min(dp[i][j],as+min(s[i][1]+s[i][4],s[i][2]+s[i][3]));
			else if(l<r)dp[i][j]=min(dp[i][j],as+s[i][1]+s[i][4]);
			else dp[i][j]=min(dp[i][j],as+s[i][2]+s[i][3]);
		}
	}
	if(mn>dp[n][0])mn=dp[n][0];
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	for(int i=1;i<=n;i++)
	for(int j=0;j<=i*2&&j<=n;j++)
	if(i==1||j>0)
	{
		long long as=dp[i-1][j]+1ll*j*(v[i]-v[i-1]);
		if(i==l){if(j)dp[i][j-1]=min(dp[i][j-1],as+s[i][3]);}
		else if(i==r)dp[i][j+1]=min(dp[i][j+1],as+s[i][2]);
		else
		{
			if(j>1)dp[i][j-2]=min(dp[i][j-2],as+s[i][1]+s[i][3]);
			dp[i][j+2]=min(dp[i][j+2],as+s[i][2]+s[i][4]);
			if(j>1)dp[i][j]=min(dp[i][j],as+min(s[i][1]+s[i][4],s[i][2]+s[i][3]));
			else if(l<r)dp[i][j]=min(dp[i][j],as+s[i][1]+s[i][4]);
			else dp[i][j]=min(dp[i][j],as+s[i][2]+s[i][3]);
		}
	}
	if(mn>dp[n][0])mn=dp[n][0];
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	for(int i=1;i<=n;i++)
	for(int j=0;j<=i*2&&j<=n;j++)
	if(i==1||j>0)
	{
		long long as=dp[i-1][j]+1ll*j*(v[i]-v[i-1]);
		if(i==l)dp[i][j+1]=min(dp[i][j+1],as+s[i][4]);
		else if(i==r){if(j)dp[i][j-1]=min(dp[i][j-1],as+s[i][1]);}
		else
		{
			if(j>1)dp[i][j-2]=min(dp[i][j-2],as+s[i][1]+s[i][3]);
			dp[i][j+2]=min(dp[i][j+2],as+s[i][2]+s[i][4]);
			if(j>1)dp[i][j]=min(dp[i][j],as+min(s[i][1]+s[i][4],s[i][2]+s[i][3]));
			else if(l<r)dp[i][j]=min(dp[i][j],as+s[i][1]+s[i][4]);
			else dp[i][j]=min(dp[i][j],as+s[i][2]+s[i][3]);
		}
	}
	if(mn>dp[n][0])mn=dp[n][0];
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	for(int i=1;i<=n;i++)
	for(int j=0;j<=i*2&&j<=n;j++)
	if(i==1||j>0)
	{
		long long as=dp[i-1][j]+1ll*j*(v[i]-v[i-1]);
		if(i==l){if(j)dp[i][j-1]=min(dp[i][j-1],as+s[i][3]);}
		else if(i==r){if(j)dp[i][j-1]=min(dp[i][j-1],as+s[i][1]);}
		else
		{
			if(j>1)dp[i][j-2]=min(dp[i][j-2],as+s[i][1]+s[i][3]);
			dp[i][j+2]=min(dp[i][j+2],as+s[i][2]+s[i][4]);
			if(j>1)dp[i][j]=min(dp[i][j],as+min(s[i][1]+s[i][4],s[i][2]+s[i][3]));
			else if(l<r)dp[i][j]=min(dp[i][j],as+s[i][1]+s[i][4]);
			else dp[i][j]=min(dp[i][j],as+s[i][2]+s[i][3]);
		}
	}
	if(mn>dp[n][0])mn=dp[n][0];
	printf("%lld\n",mn);
}
111 ARC099F Eating Symbols Hard
Problem

有一个无限长的数组和一个字符串,对于一个字符串,你会执行以下操作:

一开始你在0,依次枚举每个字符,

  1. 如果当前是 + ,给你当前位置的数加1
  2. 如果当前是 - ,给你当前位置的数减1
  3. 如果当前是 > ,向右移动一位
  4. 如果当前是 < ,向左移动一位

求这个字符串有多少个子串,满足执行这个子串后的数组等于执行整个串后的数组,一个子串出现多次算多次

n2.5×105n\leq 2.5\times 10^5

2s,1024MB2s,1024MB

Sol

考虑执行 [1,r][1,r] 后的数组减去执行 [1,l1][1,l-1] 的数组,可以发现这等于从执行 [1,l1][1,l-1] 后的位置开始,执行 [l,r][l,r] 得到的数组

考虑无序hash,给第 ii 个位置 kik^i 的权值

g(l,r)g(l,r) 表示执行这一个区间的hash值, h(l,r)h(l,r) 表示执行后的位置

那么有 g(l,r)=(g(1,r)g(1,l1))/kh(1,l1)g(l,r)=(g(1,r)-g(1,l-1))/k^{h(1,l-1)}

从大到小枚举 ll ,依次加入所有的 g(1,r)g(1,r) ,每次算出一个 ll 合法的 g(1,r)g(1,r) ,map统计

2~6hash即可

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

Code
#include<cstdio>
#include<map>
using namespace std;
#define N 500200
#define mod1 1000000009
#define mod2 1000000007
#define ch1 131
#define ch2 233
map<long long,int> tp;
int pw1[N],pw2[N],n,su[N],vl[N],ha1[N],ha2[N],as1,as2;
long long as;
char s[N];
int pw(int a,int p,int mod){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%s",&n,s+1);
	su[0]=n;
	pw1[0]=pw2[0]=1;
	for(int i=1;i<=n*2;i++)pw1[i]=1ll*pw1[i-1]*ch1%mod1,pw2[i]=1ll*pw2[i-1]*ch2%mod2;
	for(int i=1;i<=n;i++)
	{
		su[i]=su[i-1];
		if(s[i]=='<')su[i]--;
		if(s[i]=='>')su[i]++;
		if(s[i]=='+')as1=(as1+pw1[su[i]])%mod1,as2=(as2+pw2[su[i]])%mod2;
		if(s[i]=='-')as1=(as1-pw1[su[i]]+mod1)%mod1,as2=(as2-pw2[su[i]]+mod2)%mod2;
	}
	for(int i=n;i>=1;i--)ha1[i]=(ha1[i+1]+mod1+(s[i]=='+'?1ll:(s[i]=='-'?-1ll:0))*pw1[su[i]])%mod1,ha2[i]=(ha2[i+1]+mod2+(s[i]=='+'?1ll:(s[i]=='-'?-1ll:0))*pw2[su[i]])%mod2;
	tp[0]=1;
	for(int i=n;i>=1;i--)
	{
		int nas1,nas2;
		if(su[i-1]>=su[0])nas1=1ll*as1*pw1[su[i-1]-su[0]]%mod1,nas2=1ll*as2*pw2[su[i-1]-su[0]]%mod2;
		else nas1=1ll*as1*pw(pw(ch1,mod1-2,mod1),su[0]-su[i-1],mod1)%mod1,nas2=1ll*as2*pw(pw(ch2,mod2-2,mod2),su[0]-su[i-1],mod2)%mod2;
		nas1=(ha1[i]-nas1+mod1)%mod1,nas2=(ha2[i]-nas2+mod2)%mod2;
		as+=tp[nas1*10000000000ll+nas2];
		tp[ha1[i]*10000000000ll+ha2[i]]++;
	}
	printf("%lld\n",as);
}
112 CF578E Walking!
Problem

你有一个包含 LR 的序列 ss ,你需要选一个排列 pp ,使得 spi,spi+1s_{p_i},s_{p_{i+1}} 不同,且满足 pi>pi+1p_i>p_{i+1} 的位置尽量少,求一个最小的合法排列

s105|s|\leq 10^5

1s,256MB1s,256MB

Sol

标准做法:

这相当于把尽量多的元素连向它后面与他不同的元素,每个元素最多被连一次,考虑从后向前贪心连每一个构造

然后考虑用这些路径还原整条路径,如果有一个 R...R 或者 L...L 显然可以构造,如果都是 L...R 或者 R...L 显然有解,如果后面两种都有无解,但这时显然可以将一个的开头接过去构造 L...L

因此构造的时候首先尝试连一个长度偶数的段即可

我的做法:

考虑选最左边一个合法的开头开始构造,然后当前能向右连就连,如果不能向右,考虑贪心找一个最左边的,但这时最左边的左边可能还可以匹配,如果存在这种情况,考虑删掉之前路径最后一个点,用找到的点和这个点向左匹配的点替换

感性理解这样也能最大化配对数,所以这样也是对的

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

Code
#include<cstdio>
#include<set>
#include<algorithm>
#include<cstring>
#define N 100050
using namespace std;
set<int> t[2];
char s[N];
int n,as,v[N],cl,cr;
int main()
{
	scanf("%s",s+1);n=strlen(s+1);
	for(int i=1;i<=n;i++)if(s[i]=='L')t[0].insert(i),cl++;else t[1].insert(i),cr++;
	int ls=1;
	if(cl>cr)while(s[ls]=='R')ls++;
	if(cr>cl)while(s[ls]=='L')ls++;
	v[1]=ls;
	if(s[ls]=='R')t[1].erase(ls);else t[0].erase(ls);
	for(int i=2;i<=n;i++)
	{
		int tp=s[ls]=='L',fg=0;
		set<int>::iterator it=t[tp].lower_bound(ls);
		if(it==t[tp].end())it=t[tp].begin(),as++,fg=1;
		v[i]=*it;t[tp].erase(*it);
		if(t[!tp].size()&&fg)
		{
			if(*t[!tp].begin()<v[i])
			{
				int a=*t[!tp].begin();
				t[!tp].insert(v[i-1]);
				v[i-1]=a;
				t[!tp].erase(a);
			}
		}
		ls=v[i];
	}
	printf("%d\n",as);
	for(int i=1;i<=n;i++)printf("%d ",v[i]);
}
113 AGC035E Develop
Problem

黑板上有所有 [109,109][-10^9,10^9] 的整数,你可以进行无限次操作:

选一个 [1,n][1,n] 的数 xx ,删除 xx ,插入 x2,x+kx-2,x+k 中所有不存在的数

求黑板上可能出现的数的集合的数量,模 mm

n,k150n,k\leq 150

5s,1024MB5s,1024MB

Sol

考虑如果删除 xx 会插入 yy ,连一条 (x,y)(x,y) 的有向边

将黑色看成存在,白色看成不存在

可以看成初始所有点都是黑色的,每次将一个黑色点染白,再将它的所有出边点改成黑色

考虑最后的白色点(相当于所有不在的数),如果这些数构成了一个环,显然无论如何这个环不可能全部变成白色,所以无解,否则,按照拓扑序删即可

那么相当于有一个图,需要选出一些点,使得选出的点不构成环

考虑 2k2|k 的情况,所有点按奇偶构成了两条链,然后 xxx+kx+k 连边

注意到可能的环都是链上连续 k/2+1k/2+1 个点,因此相当于链上没有连续 k/2+1k/2+1 个白点,dp即可

然后是奇数的情况,,将 iii+ki+k 放在一排,可以看成这样的图

可以发现,如果存在一条向上->向右->向上的长度大于等于 k+1k+1 的序列,就一定存在环,否则不存在环

dpi,j,kdp_{i,j,k} 表示考虑前 ii 排,当前从左边开始的这样的路径的最长长度为 jj ,当前右边对应点开始向上的最长长度为 kk ,当前的方案数

枚举每一排的情况即可,复杂度 O(n2k)O(n^2k)

Code
#include<cstdio>
using namespace std;
#define N 158
int n,s,p,dp1[N],dp2[N][N][N],pw[N];
int solve(int m)
{
	dp1[0]=1;
	for(int i=1;i<=m;i++)
	{
		if(i>s/2+1)dp1[i]=(2ll*dp1[i-1]-dp1[i-s/2-2]+p)%p;
		else if(i==s/2+1)dp1[i]=(2ll*dp1[i-1]-1+p)%p;
		else dp1[i]=2ll*dp1[i-1]%p;
	}
	return dp1[m];
}
int main()
{
	scanf("%d%d%d",&n,&s,&p);
	pw[0]=1;for(int i=1;i<=150;i++)pw[i]=pw[i-1]*2%p;
	if(~s&1)printf("%lld\n",1ll*solve(n/2)*solve(n-n/2)%p);
	else 
	if(s==1) s=4,printf("%d\n",solve(n));
	else 
	{
		int l2=n/2,l1=n-n/2;
		if(n<=s){int as=1;for(int i=1;i<=n;i++)as=as*2%p;printf("%d\n",as);return 0;}
		for(int i=0;i<=(s+1)/2;i++)dp2[0][0][i]=(s+1)/2==i?1:pw[(s+1)/2-i-1];
		for(int i=0;i<l1-(s+1)/2;i++)
		for(int j=0;j<=s+1;j++)
		for(int k=0;k<=n;k++)
		if(dp2[i][j][k])
		{
			dp2[i+1][0][0]=(dp2[i+1][0][0]+dp2[i][j][k])%p;
			dp2[i+1][0][k+1]=(dp2[i+1][0][k+1]+dp2[i][j][k])%p;
			if(j<s+1)
			{
				dp2[i+1][j==0?0:j+1][0]=(dp2[i+1][j==0?j:j+1][0]+dp2[i][j][k])%p;
				int st=j<k+1?k+2:j+1;
				if(st<=s+1)dp2[i+1][st][k+1]=(dp2[i+1][st][k+1]+dp2[i][j][k])%p;
			}
		}
		int as=0,ls=l2-(l1-(s+1)/2);
		for(int i=0;i<=s+1;i++)
		for(int j=0;j<=n;j++)
		{
			int tp=dp2[l1-(s+1)/2][i][j],s1=pw[ls];
			if(ls-(s+2-i)>=0)
			s1=(s1-pw[ls-(s+2-i)]+p)%p;
			as=(as+1ll*tp*s1)%p;
		}
		printf("%d\n",as);
	}
}
114 AGC025D Choosing Points
Problem

平面上有 4n24n^2 个坐标在 [0,2n)[0,2n) 的整点,你需要选出 n2n^2 个点,使得不存在两个点距离为 D1\sqrt D_1D2\sqrt D_2

n300n\leq 300

2s,1024MB2s,1024MB

Sol

只考虑 D1D_1 的限制

如果 D1=3mod4D_1=3\mod 4 ,显然不会有点距离为这个值

如果 D1=2mod4D_1=2\mod 4 ,不合法的一对点两维坐标差一定都是奇数,那么可以按照 xx 奇偶性分类

如果 D1=1mod4D_1=1\mod 4 ,不合法的一对两维坐标差一定是一奇一偶,那么可以按照 x+yx+y 奇偶性分类

如果 D1=0mod4D_1=0\mod 4 ,那么一定两维坐标差都是偶数,那么可以按奇偶分成4类然后看成 D1/4D_1/4 的情况

那么可以发现不能同时选的关系构成的图一定构成二分图

那么对于两个二分图分别染色,取出四个中点数最多的一个即可

复杂度 O(n3)O(n^3)

Code
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
#define N 100050
#define M 605
int n,d1,d2,head[N],cnt,col[M][M],col2[M][M],vis[M][M],ct,s1[N][2];
queue<int> tp;
void bfs(int x,int y)
{
	vis[x][y]=1;
	tp.push(x*1000+y);
	while(!tp.empty())
	{
		int s=tp.front()/1000,t=tp.front()%1000;tp.pop();
		for(int i=0;i<=ct;i++)
		for(int j=s-s1[i][0];j<=s+s1[i][0];j+=s1[i][0]*2)
		{
			for(int k=t-s1[i][1];k<=t+s1[i][1];k+=s1[i][1]*2)
			{
				if(j>=1&&j<=n&&k>=1&&k<=n&&!vis[j][k])vis[j][k]=1,col[j][k]=col[s][t]^1,tp.push(j*1000+k);
				if(!s1[i][1])break;
			}
			if(!s1[i][0])break;
		}
	}
}
int main()
{
	scanf("%d%d%d",&n,&d1,&d2);n*=2;
	ct=0;for(int i=0;i<=n;i++)if(d1>=i*i){int tp=sqrt(d1-i*i);if(i*i+tp*tp==d1)s1[++ct][0]=i,s1[ct][1]=tp;}
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!vis[i][j])bfs(i,j);
	int tp=0;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)tp+=col[i][j]?-1:1;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)col2[i][j]=col[i][j]^(tp>=0),vis[i][j]=0,col[i][j]=0;
	ct=0;for(int i=0;i<=n;i++)if(d2>=i*i){int tp=sqrt(d2-i*i);if(i*i+tp*tp==d2)s1[++ct][0]=i,s1[ct][1]=tp;}
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!vis[i][j])bfs(i,j);
	tp=0;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)tp+=col[i][j]?-1:1;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)col[i][j]=col[i][j]^(tp>=0);
	int ct=0;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(ct<n*n/4&&col[i][j]==1&&col2[i][j]==1)printf("%d %d\n",i-1,j-1),ct++;
}
115 CF516E Drazil and His Happy Friends
Problem

有两个部分,第一部分有 nn 个点,第二部分有 mm 个点,初始有 kk 个点是黑色的

在时刻 ii ,如果第一部分的点 imodni\bmod n 和第二部分的点 imodmi\bmod m 中有一个黑色,那么两个都会变成黑色

求最早在哪个时刻所有点都成为黑色

n,m109,k2×105n,m\leq 10^9,k\leq 2\times 10^5

4s,256MB4s,256MB

Sol

考虑第一部分点 ii ,在时刻 ii 它会使第二部分 imodmi\bmod m 点变黑,然后在时刻 i+mi+m ,会使第一部分 i+mmodni+m\bmod n 变黑

那么,考虑时刻 i+mi+m 变黑的点,可以发现第一部分 i+2mmodni+2m\bmod n 接下来会变黑,然后每隔 mm 时刻这个点 mm 个点之后的点会变黑

对于第一部分原先就是黑色的点 ii ,在时刻 ii 它会使第二部分 imodmi\bmod m 点变黑,随后每一个 i+kni+kn 时刻使 i+knmodmi+kn\bmod m 变黑,然后在时刻 i+mi+m ,会使第一部分 i+mmodni+m\bmod n 变黑,随后每一个 i+kmi+km 时刻使 i+kmmodni+km\bmod n 变黑

按照模 gcd(n,m)gcd(n,m) 分类,然后第一部分可以看成一个环,有一些点一开始是黑的,另外一些点在某个时刻开始是黑的,并且每 mm 时刻会使这个点向后的一个点变黑

那么对于每一类,将所有点按环上位置排序,扫一遍即可求出这个环的最小时间

如果 gcd(n,m)>kgcd(n,m)>k 直接无解

复杂度 O(klogk+klogn)O(k\log k+k\log n)

Code
#include<cstdio>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
#define N 100500
int n,m,s,t,v[N],v2[N],g;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int exgcd(int a,int b,long long &x,long long &y){if(!b){x=1,y=0;return a;}int g=exgcd(b,a%b,y,x);y-=a/b*x;return g;}
int getinv(int a,int p){long long x,y;exgcd(a,p,x,y);return (x%p+p)%p;}
struct sth{int a,t;friend bool operator <(sth a,sth b){return a.a==b.a?a.t>b.t:a.a<b.a;}};
struct que{
	vector<sth> t1,t2;
	void add1(int x,int a){if(x==1)t1.push_back((sth){(int)(1ll*a*getinv(m,n)%(n/g)),-1});else t2.push_back((sth){(int)(1ll*a*getinv(n,m)%(m/g)),-1});}
	void add2(int x,int a,int t){if(x==1)t1.push_back((sth){(int)(1ll*a*getinv(m,n)%(n/g)),t});else t2.push_back((sth){(int)(1ll*a*getinv(n,m)%(m/g)),t});}
	long long solve()
	{
		int s1=t1.size(),s2=t2.size(),las,last;
		long long as=0;
		if(!s1)return -1;
		las=-1,last=0;
		sort(t1.begin(),t1.end());
		for(int i=0;i<s1;i++)
		{
			if(las!=-1)
			{
				if(t1[i].a!=(t1[i==0?s1-1:i-1].a+1)%(n/g)&&t1[i].a!=(t1[i==0?s1-1:i-1].a)%(n/g))
				{
					long long su=1ll*(t1[i].a-las-1)*m+last;
					if(su>as)as=su;
				}
			}
			if(t1[i].t!=-1)las=t1[i].a,last=t1[i].t;
		}
		for(int i=0;i<s1;i++)
		{
			if(t1[i].a!=(t1[i==0?s1-1:i-1].a+1)%(n/g))
			{
				long long su=1ll*(t1[i].a+n/g-las-1)*m+last;
				if(su>as)as=su;
			}
			if(t1[i].t!=-1)break;
		}
		if(!s2)return -1;
		las=-1,last=0;
		sort(t2.begin(),t2.end());
		for(int i=0;i<s2;i++)
		{
			if(las!=-1)
			{
				if(t2[i].a!=(t2[i==0?s2-1:i-1].a+1)%(m/g)&&(t2[i].a!=(t2[i==0?s2-1:i-1].a)%(m/g)))
				{
					long long su=1ll*(t2[i].a-las-1)*n+last;
					if(su>as)as=su;
				}
			}
			if(t2[i].t!=-1)las=t2[i].a,last=t2[i].t;
		}
		for(int i=0;i<s2;i++)
		{
			if(t2[i].a!=(t2[i==0?s2-1:i-1].a+1)%(m/g))
			{
				long long su=1ll*(t2[i].a+(m/g)-las-1)*n+last;
				if(su>as)as=su;
			}
			if(t2[i].t!=-1)break;
		}
		return as;
	}
}tp[N];
map<int,int> tp1,tp2,tp3,tp4;
int main()
{
	scanf("%d%d",&n,&m);
	g=gcd(n,m);
	if(g>200000){printf("-1\n");return 0;}
	scanf("%d",&s);for(int i=1;i<=s;i++)scanf("%d",&v[i]),tp1[v[i]]=1;
	scanf("%d",&t);for(int i=1;i<=t;i++)scanf("%d",&v2[i]),tp2[v2[i]]=1;
	int la=0,lb=0;
	for(int i=0;i<=234567;i++)
	{
		if(tp1.size()+tp2.size()==n+m){printf("%d\n",i==0?0:i-1);return 0;}
		if(tp1.count(la)||tp2.count(lb))tp1[la]=tp2[lb]=1;
		la=(la+1)%n;lb=(lb+1)%m;
	}
	for(int i=1;i<=s;i++)if(!tp3[v[i]])
	{
		tp3[v[i]]=1;
		int s1=v[i]/g;
		tp[v[i]%g].add1(1,s1);
		tp[v[i]%g].add2(2,s1%(m/g),v[i]);
		tp[v[i]%g].add2(1,(s1+m/g)%(n/g),v[i]+m);
	}
	for(int i=1;i<=t;i++)if(!tp4[v2[i]])
	{
		tp4[v2[i]]=1;
		int s1=v2[i]/g;
		tp[v2[i]%g].add1(2,s1);
		tp[v2[i]%g].add2(1,s1%(n/g),v2[i]);
		tp[v2[i]%g].add2(2,(s1+n/g)%(m/g),v2[i]+n);
	}
	long long mn=0;
	for(int i=0;i<g;i++)
	{
		long long t1=tp[i].solve();
		if(t1==-1){printf("-1\n");return 0;}
		if(mn<t1)mn=t1;
	}
	printf("%lld\n",mn);
}
116 AGC030E Less than 3
Problem

有两个长度为 n 的01串 s,t ,保证 s,t 中没有连续三个位置相同

你每次可以修改 s 的一个位置,需要保证此时 s 中没有连续三个位置相同

求将 ss 修改成 tt 的最少操作次数

n5000n≤5000

2s,1024MB2s,1024MB

Sol

因为要求不能有三个相同,因此如果修改一个不是两侧的位置 ii ,必然有 si1si+1s_{i-1}≠s_{i+1}

考虑差分,如果 sisi+1s_i≠s_{i+1} ,就在两个元素中间画一条线

那么考虑操作中间的数

可以发现,操作一次相当于将一条线移动一个位置

...1|0 0...
...1 1|0...

然后考虑两侧的操作,相当于在两侧加入/删除一条线

0 0...
1|0...

可以看成两侧有无限多的线,这样也相当于移动线

注意到线的相对位置不会变,可以考虑枚举上面的一根线对应下面的哪一根

但是这种情况不合法:

||0|1||
||1|0||

可以考虑按奇偶性编号,这样就可以保证不会出现这种情况

假设已经确定了对应的关系,显然此时移动次数的下确界为每一对匹配的线的距离的和

考虑能不能达到下确界

将匹配按照位置关系分为向左,向右和不变,如果向左和向右相邻,那么因为上面的距离至少为1,所以下面距离至少为3,矛盾

另一种情况同理

因此,可以将匹配分成若干部分,每部分中间的方向相同,两侧均为不变的匹配

对于同一种方向的情况,易证存在达到下确界的方法

因此可以 O(n)O(n) 计算一次匹配的方案

显然左右只需要保留 n+1n+1 条线就行了

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

Code
#include<cstdio>
using namespace std;
#define N 15005
int a[N],b[N],n,s1,s2;
char s[N],t[N];
long long solve(int t)
{
	long long as=0;
	int fg=0;
	if(t<0){t=-t;fg=1;for(int i=1;i<=n;i++)a[i]^=b[i]^=a[i]^=b[i];s1^=s2^=s1^=s2;}
	int l1=1,r1=1-t;
	while(1)
	{
		int tp1=a[l1],tp2=r1<0?0:b[r1];
		if(l1>s1)tp1=n;if(r1>s2)tp2=n;
		as+=tp1<tp2?tp2-tp1:tp1-tp2;
		if(tp1==tp2&&tp1==n)break;
		l1++,r1++;
	}
	if(fg){for(int i=1;i<=n;i++)a[i]^=b[i]^=a[i]^=b[i];s1^=s2^=s1^=s2;}
	return as;
}
int main()
{
	scanf("%d%s%s",&n,s+1,t+1);
	for(int i=1;i<=n;i++)if(s[i]!=s[i+1])a[++s1]=i;
	for(int i=1;i<=n;i++)if(t[i]!=t[i+1])b[++s2]=i;
	solve(-1);
	long long mn=1e18;for(int i=s[1]==t[1]?-5000:-5001;i<=5000;i+=2){long long tp=solve(i);if(tp<mn)mn=tp;}
	printf("%lld\n",mn);
}
117 AGC027D Modulo Matrix
Problem

给定 nn ,你需要构造 n×nn\times n 的矩阵,每个元素不能超过 101510^{15},任意两个元素不同,使得存在一个正整数 mm ,使得任意相邻两个数中大数模小数等于 mm

n500n\leq 500

2s,1024MB2s,1024MB

Sol

考虑将棋盘黑白染色,在黑格子放入大数,白格子放小数

这样可以只构造 m=1m=1 的情况,一个点的权值是周围4个点的lcm+1

一个想法是直接放,但最坏可能达到 n8n^8 ,不能通过

考虑在对角线上构造,对于一条左上-右下对角线,给一个大于 nn 的质数权值,然后依次放 p,2p,3p,...,kpp,2p,3p,...,kp

注意到这样一个点周围的 kk 只有3种,质数只有两种,权值不会超过 n3(n2logn)2n^3*(n^2\log n)^2 ,可以通过

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

Code
#include<cstdio>
using namespace std;
#define N 505
int vl[N*3],n,ct,d[4][2]={-1,0,1,0,0,1,0,-1};
long long gcd(long long a,long long b){return b?gcd(b,a%b):a;}
long long as[N][N],mx;
int main()
{
	for(int i=501;i<=10000;i++)
	{
		int fg=0;
		for(int j=2;j<=100;j++)if(i%j==0)fg=1;
		if(!fg)vl[++ct]=i;
	}
	for(int i=1;i<=500;i++)as[i][i]=vl[1]*i;
	int st=2;
	for(int i=2;i<=500;i+=2){for(int j=1;i+j<=500;j++)as[i+j][j]=vl[st]*(j+i/2),as[j][i+j]=vl[st+1]*(j+i/2);st+=2;}
	for(int i=1;i<=500;i++)
	for(int j=1;j<=500;j++)
	if((i+j)&1)
	{
		long long st=1;
		for(int s=0;s<4;s++)
		{
			int vl=as[i+d[s][0]][j+d[s][1]];
			if(vl)st=st/gcd(st,vl)*vl;
		}
		as[i][j]=st+1;
		if(mx<st)
		mx=st;
	}
	scanf("%d",&n);
	for(int i=1;i<=n;i++,printf("\n"))
	for(int j=1;j<=n;j++)
	printf("%lld ",as[i][j]);
}
118 CF576E Painting Edges
Problem

有一张图,每条边有 kk 种颜色之一,有 qq 次操作,每次给一个边染新的颜色,如果染完每种颜色的边都构成二分图,那么执行这次操作并输出 YES ,否则不执行这次操作并输出 NO ,保证初始时合法

n,m,q5×105,k50n,m,q\leq 5\times 10^5,k\leq 50

6s,600MB6s,600MB

Sol

对于一条边,把它按被操作的所有时刻分开,可以得到 O(m+q)O(m+q)

对于一段,显然这条边在这一段内的颜色是相同的

对于一条边,第一段的颜色是已知的,之后每一段的颜色可以在之前的询问之后求出

二分图可以cdq分治+可持久化带权并查集维护,考虑cdq,同时维护 kk 个并查集,递归到每个询问单独的区间时,可以求出这次修改是否合法,并得到这条边接下来一段的颜色

在cdq的时候,额外维护这个区间内的操作导致的新加入的段,向上合并即可

复杂度 O((m+q)logqlogn)O((m+q)\log q\log n) ,空间复杂度 O(nk)O(nk)

Code
#include<cstdio>
#include<stack>
#include<algorithm>
#include<vector>
using namespace std;
#define N 500500
#define K 52
int fa[K][N],vl[K][N],sz[K][N],n,m,k,q,f[N][2],cl[N],las[N],as[N],a,b,nt[N];
struct modify{int a,b,c,l,r;}s1[N];
struct modify2{int a,b,l,r;};
vector<modify2> s[22][2],t[22];
struct sth{int a,b;};
stack<sth> t1[22];
void cdq(int l,int r,int v,int dep)
{
	int sz1=s[dep][v].size();
	for(int i=0;i<sz1;i++)
	{
		modify2 tp=s[dep][v][i];
		if(tp.l>l||tp.r<r)continue;
		int cl=tp.b,l1=f[tp.a][0],r1=f[tp.a][1],sb=1;
		while(fa[cl][l1]!=l1)sb^=vl[cl][l1],l1=fa[cl][l1];
		while(fa[cl][r1]!=r1)sb^=vl[cl][r1],r1=fa[cl][r1];
		if(sz[cl][l1]<sz[cl][r1])l1^=r1^=l1^=r1;
		if(l1==r1)continue;
		t1[dep].push((sth){cl,r1});
		fa[cl][r1]=l1,sz[cl][l1]+=sz[cl][r1],vl[cl][r1]=sb;
	}
	if(l==r)
	{
		int cl=s1[l].c,l1=f[s1[l].a][0],r1=f[s1[l].a][1],sb=0;
		while(fa[cl][l1]!=l1)sb^=vl[cl][l1],l1=fa[cl][l1];
		while(fa[cl][r1]!=r1)sb^=vl[cl][r1],r1=fa[cl][r1];
		if(!sb&&l1==r1)
		as[l]=0,t[dep].push_back((modify2){s1[l].a,s1[l].b,s1[l].l,s1[l].r}),s1[nt[l]].b=s1[l].b;
		else as[l]=1,t[dep].push_back((modify2){s1[l].a,s1[l].c,s1[l].l,s1[l].r});
		while(!t1[dep].empty())
		{
			sth tp=t1[dep].top();t1[dep].pop();
			sz[tp.a][fa[tp.a][tp.b]]-=sz[tp.a][tp.b];fa[tp.a][tp.b]=tp.b,vl[tp.a][tp.b]=0;
		}
		return;
	}
	int mid=(l+r)>>1;
	s[dep+1][0].clear();
	s[dep+1][1].clear();
	t[dep+1].clear();
	for(int i=0;i<sz1;i++)
	{
		modify2 tp=s[dep][v][i];
		if(tp.l<=l&&tp.r>=r)continue;
		if(tp.l<=mid)s[dep+1][0].push_back(tp);
		if(tp.r>mid)s[dep+1][1].push_back(tp);
	}
	cdq(l,mid,0,dep+1);
	int s1=t[dep+1].size();
	for(int i=0;i<s1;i++)
	{
		modify2 tp=t[dep+1][i];
		s[dep+1][1].push_back(tp);
		if(tp.r>r)t[dep].push_back(tp);
	}
	t[dep+1].clear();
	cdq(mid+1,r,1,dep+1);
	s1=t[dep+1].size();
	for(int i=0;i<s1;i++)
	{
		modify2 tp=t[dep+1][i];
		t[dep].push_back(tp);
	}
	while(!t1[dep].empty())
	{
		sth tp=t1[dep].top();t1[dep].pop();	
		sz[tp.a][fa[tp.a][tp.b]]-=sz[tp.a][tp.b];fa[tp.a][tp.b]=tp.b,vl[tp.a][tp.b]=0;
	}
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&k,&q);
	for(int i=1;i<=m;i++)scanf("%d%d",&f[i][0],&f[i][1]);
	for(int i=1;i<=k;i++)for(int j=1;j<=n;j++)fa[i][j]=j,sz[i][j]=1;
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&a,&b);
		s1[i]=(modify){a,cl[a],b,i,0};s1[las[a]].r=i-1;nt[las[a]]=i;las[a]=i;cl[a]=b;
	}
	for(int i=1;i<=m;i++)s1[las[i]].r=q;
	cdq(1,q,1,0);
	for(int i=1;i<=q;i++)printf("%s\n",as[i]?"YES":"NO");
}
119 CF704D Captain America
Problem

nn 个物品,每个物品有坐标,你需要给它们染色,染成红色费用为 rr ,蓝色费用为 bb ,有 mm 个限制,每个限制形如某一行/某一列的红蓝物品差不超过 kik_i 个,输出一个代价最小的方案或者输出无解

n,m105n,m\leq 10^5

2s,256MB2s,256MB

Sol

每个点只在一个行的限制和一个列的限制中

考虑对于每一行建一个点,每一列建一个点,对于一个物品,在对应点间连边

考虑将一个物品染成红色看成流这条边,蓝色看成不流这条边,那么对于一个行的点,它流出的流量是这一行红色的点数,对于列的点,它流入的流量是这一行红色的点数

那么可以对于每一行和每一列的限制,通过向原点/汇点连有上下界的边满足要求

然后对于 r,br,b 的大小关系,相当于求出一个合法的最大流/最小流,直接网络流即可

Code
#include<cstdio>
#include<map>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define N 105000
int n,q,r,w,s,t,ss,tt,a,b,c,v[N][2],su[N][2],ct1,ct2,head[N*2],cnt=1,cur[N*2],dis[N*2],as1[N];
map<int,int> tp1,tp2;
struct edge{int t,next,v,id;}ed[N*20];
void adde(int f,int t,int v,int id=-1){ed[++cnt]=(edge){t,head[f],v,id};head[f]=cnt;ed[++cnt]=(edge){f,head[t],0,id};head[t]=cnt;}
bool bfs(int s,int t)
{
	queue<int> tp;tp.push(s);memset(dis,-1,sizeof(dis));dis[s]=0;memcpy(cur,head,sizeof(cur));
	while(!tp.empty())
	{
		int r=tp.front();tp.pop();
		for(int i=head[r];i;i=ed[i].next)
		if(ed[i].v&&dis[ed[i].t]==-1)
		{
			dis[ed[i].t]=dis[r]+1;tp.push(ed[i].t);
			if(ed[i].t==t)return 1;
		}
	}
	return 0;
}
int dfs(int u,int t,int f)
{
	if(!f||u==t)return f;
	int as=0,tp;
	for(int &i=cur[u];i;i=ed[i].next)
	if(ed[i].v&&dis[ed[i].t]==dis[u]+1&&(tp=dfs(ed[i].t,t,min(ed[i].v,f))))
	{
		ed[i].v-=tp;ed[i^1].v+=tp;
		f-=tp,as+=tp;
		if(!f)return as;
	}
	return as;
}
int main()
{
	scanf("%d%d%d%d",&n,&q,&r,&w);
	s=n*2+1,t=n*2+2,ss=s+2,tt=t+2;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&a,&b);
		if(!tp1[a])tp1[a]=++ct1,v[ct1][0]=1e9;
		if(!tp2[b])tp2[b]=++ct2,v[ct2][1]=1e9;
		su[tp1[a]][0]++;su[tp2[b]][1]++;
		adde(tp1[a],tp2[b]+n,1,i);
	}
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		if(a==1)v[tp1[b]][0]=min(v[tp1[b]][0],c);
		else v[tp2[b]][1]=min(v[tp2[b]][1],c);
	}
	int suf=0,su2=0,as=0;
	adde(t,s,1e9);
	for(int i=1;i<=ct1;i++)
	{
		int vl=su[i][0],c1=v[i][0];
		if(c1>vl){adde(s,i,vl);continue;}
		if((vl&1)&&!c1){printf("-1\n");return 0;}
		int lb=(vl-c1+1)/2,rb=(vl+c1)/2;
		adde(s,i,rb-lb);adde(ss,i,lb);adde(s,tt,lb);suf+=lb;as+=lb;
	}
	for(int i=1;i<=ct2;i++)
	{
		int vl=su[i][1],c1=v[i][1];
		if(c1>vl){adde(i+n,t,vl);continue;}
		if((vl&1)&&!c1){printf("-1\n");return 0;}
		int lb=(vl-c1+1)/2,rb=(vl+c1)/2;
		adde(i+n,t,rb-lb);adde(i+n,tt,lb);adde(ss,t,lb);suf+=lb;
	}
	while(bfs(ss,tt))
	su2+=dfs(ss,tt,1e9);
	if(su2<suf){printf("-1\n");return 0;}
	for(int i=head[ss];i;i=ed[i].next)ed[i].v=0;
	for(int i=head[tt];i;i=ed[i].next)ed[i].v=0;
	for(int i=head[s];i;i=ed[i].next)if(ed[i].t==t||ed[i].t==tt)ed[i].v=0;else as+=ed[i^1].v;
	for(int i=head[t];i;i=ed[i].next)if(ed[i].t==s||ed[i].t==ss)ed[i].v=0;
	while(bfs(s,t))as+=dfs(s,t,1e9);
	for(int i=1;i<=ct1;i++)
	for(int j=head[i];j;j=ed[j].next)
	if(ed[j].id!=-1)as1[ed[j].id]=!ed[j].v;
	if(r>=w)
	{
		printf("%lld\n",1ll*w*as+1ll*r*(n-as));
		for(int i=1;i<=n;i++)printf("%c",as1[i]?'b':'r');
	}
	else
	{
		printf("%lld\n",1ll*r*as+1ll*w*(n-as));
		for(int i=1;i<=n;i++)printf("%c",as1[i]?'r':'b');
	}
}
120 ARC096F Sweet Alchemy
Problem

nn 种物品,第 ii 个物品制作一个需要 viv_i 时间,你有 tt 的时间,你需要制作尽量多的物品

sis_i 为你制作物品 ii 的数量,你还需要满足 i>1,sfaisisfai+k\forall i>1,s_{fa_i}\leq s_i\leq s_{fa_i}+k ,保证 fafa 构成一棵树的结构

求最多可以制作的物品数量

n50,t,vi,k109n\leq 50,t,v_i,k\leq 10^9

2s,256MB2s,256MB

Sol

考虑差分,设 di=sisfaid_i=s_i-s_{fa_i}

可以发现,如果 $ d_i$ 加一,相当于把这个子树内的都做一个

因此可以看成若干个物品,每个物品有费用 $ c_i$ ,价值 rir_i ,除了第一个物品,剩下的每一个最多做 kk 个,显然 ri<nr_i<n

考虑将所有物品按照 ri/cir_i/c_i 排序,考虑排序后的两种物品 i,ji,jri/ci>rj/cjr_i/c_i>r_j/c_j

如果 jj 选了超过 rjr_j 个, ii 还有至少 rir_i 个没选,那么可以通过增加 ii 选的数量,减少 jj 选的数量,使得费用更少

因此,可以看成存在一个 kk , kk 之前的物品最多有 nn 个没有选,后面的物品最多选 nn

因此可以每种物品先拿 nn 个出来dp,然后枚举前面拿了多少,后面贪心选即可

前面可以二进制分组优化,复杂度 O(n4logn)O(n^4\log n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 55
#define M 125555
int sz[N],su[N],dp[M],n,v,d,f[N],s[N],tp[N],ct,as;
bool cmp(int i,int j){return 1.0*su[i]/sz[i]<1.0*su[j]/sz[j];}
int solve(int s1,int r)
{
	if(s1<0)return -1e9;
	int as=0;
	for(int i=1;i<=r;i++)
	{
		int tp=s1/su[s[i]];
		if(tp>d&&sz[s[i]]!=n)tp=d;
		s1-=tp*su[s[i]],as+=tp*sz[s[i]];
		if(sz[s[i]]==n||tp<d)return as;
	}
	return as;
}
int main()
{
	scanf("%d%d%d%d",&n,&v,&d,&su[1]);sz[1]=1;
	for(int i=2;i<=n;i++)scanf("%d%d",&su[i],&f[i]),sz[i]=1;
	for(int i=n;i>=0;i--)
	{
		sz[f[i]]+=sz[i];
		if(su[f[i]]+su[i]>1e9)su[f[i]]=1e9+1;
		else su[f[i]]+=su[i];
	}
	for(int i=1;i<=n;i++)if(su[i]<=1e9)su[++ct]=su[i],sz[ct]=sz[i],s[ct]=ct;
	sort(s+1,s+ct+1,cmp);
	int fg=125000;
	for(int i=1;i<=fg;i++)dp[i]=1e9+2;
	as=solve(v,ct);
	for(int i=ct;i>=1;i--)
	{
		int mx=d;
		if(mx>50)mx=50;
		for(int t=16;t;t>>=1)
		{
			while(mx>=2*t||(mx&&t==1))
			{
				mx-=t;
				int st=t*sz[s[i]],st2=1ll*t*su[s[i]]>1e9?1e9+1:t*su[s[i]];
				if(st>fg||st2>v)continue;
				for(int j=fg-st;j>=0;j--)if(dp[j+st]>dp[j]+st2)dp[j+st]=dp[j]+st2;
			}
		}
	}
	d-=50;
	if(d<0)d=0;
	for(int i=0;i<=fg&&v>=dp[i];i++)as=max(as,i+solve(v-dp[i],ct));
	printf("%d\n",as);
}
121 CF671D Roads in Yusland
Problem

有一棵树,有 mm 种方案,第 ii 种方案会覆盖 (ui,vi)(u_i,v_i) 上的所有边,代价为 cic_i ,保证 viv_iuiu_i 的祖先,求一种选择方案的方案,使得每条边至少被一个选中的方案覆盖,且选中方案代价和最小,输出最小代价或输出无解

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

Sol

只考虑 uiu_ixx 子树内的方案,显然这些方案可能覆盖的是 xx 的子树和 xx 到根的链

dpi,jdp_{i,j} 表示考虑 uiu_iii 子树内的方案, ii 子树内的边全部被覆盖,当前到根的链覆盖到了深度为 jj 的节点,当前的最优代价

考虑枚举哪一个子树内的边向上的更多(或者是从当前点向上的最多),设为 vv ,可以用 dpv,xdp_{v,x} 加上其它子树选的向上不超过 xx 的最小代价的和更新

注意到如果不加上其它子树选的向上不超过 xx 的最小代价的和,直接加上其它子树合法的最小代价的和,这样显然是对的,因此对于一个子树内的dp(或者当前点),加的值显然是固定的
因此可以考虑动态开点线段树维护dp,直接线段树合并即可

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

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define N 300050
#define M 8000080
int head[N],cnt,dep[N],n,m,a,b,c;
struct edge{int t,next;}ed[N*2];
struct sth{int t,v;};
vector<sth> tp[N];
long long mn[M],vl[N],lz[M];
int ch[M][2],rt[N],ct;
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;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs1(ed[i].t,u);}
void ins(int x,int l,int r,int s,long long v)
{
	mn[x]=min(mn[x],v);
	if(l==r)return;
	if(lz[x])mn[ch[x][0]]+=lz[x],mn[ch[x][1]]+=lz[x],lz[ch[x][0]]+=lz[x],lz[ch[x][1]]+=lz[x],lz[x]=0;
	int mid=(l+r)>>1;
	int t=mid<s;
	if(!ch[x][t])ch[x][t]=++ct,mn[ct]=1e17+1;
	if(mid>=s)ins(ch[x][0],l,mid,s,v);
	else ins(ch[x][1],mid+1,r,s,v);
}
int merge(int x,int y)
{
	if(!x||!y)return x+y;
	if(lz[x])mn[ch[x][0]]+=lz[x],mn[ch[x][1]]+=lz[x],lz[ch[x][0]]+=lz[x],lz[ch[x][1]]+=lz[x],lz[x]=0;
	if(lz[y])mn[ch[y][0]]+=lz[y],mn[ch[y][1]]+=lz[y],lz[ch[y][0]]+=lz[y],lz[ch[y][1]]+=lz[y],lz[y]=0;
	mn[x]=min(mn[x],mn[y]);
	ch[x][0]=merge(ch[x][0],ch[y][0]);
	ch[x][1]=merge(ch[x][1],ch[y][1]);
	return x;
}
long long que(int x,int l,int r,int l1,int r1)
{
	if(!x)return 1e17+1;
	if(l==l1&&r==r1)return mn[x];
	if(lz[x])mn[ch[x][0]]+=lz[x],mn[ch[x][1]]+=lz[x],lz[ch[x][0]]+=lz[x],lz[ch[x][1]]+=lz[x],lz[x]=0;
	int mid=(l+r)>>1;
	if(mid>=r1)return que(ch[x][0],l,mid,l1,r1);
	else if(mid<l1)return que(ch[x][1],mid+1,r,l1,r1);
	else return min(que(ch[x][0],l,mid,l1,mid),que(ch[x][1],mid+1,r,mid+1,r1));
}
void dfs2(int u,int fa)
{
	rt[u]=++ct;mn[ct]=1e17+1;
	long long su=0;int fg=0;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa)
	{
		dfs2(ed[i].t,u);
		mn[rt[ed[i].t]]-=vl[ed[i].t];lz[rt[ed[i].t]]-=vl[ed[i].t];su+=vl[ed[i].t];
		rt[u]=merge(rt[ed[i].t],rt[u]);
		fg=1;
	}
	mn[rt[u]]+=su,lz[rt[u]]+=su;
	int sz=tp[u].size();
	if(!fg)for(int i=0;i<sz;i++)ins(rt[u],1,n,dep[tp[u][i].t],tp[u][i].v);
	else
	{
		for(int i=0;i<sz;i++)
		{
			long long vl=que(rt[u],1,n,1,dep[u]);
			if(vl<1e17)ins(rt[u],1,n,dep[tp[u][i].t],tp[u][i].v+vl);
		}
	}
	vl[u]=que(rt[u],1,n,1,u==1?1:dep[u]-1);
	if(vl[u]>1e16){printf("-1\n");exit(0);}
}
int main()
{
	scanf("%d%d",&n,&m);
	if(n==1){printf("0");return 0;}
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	for(int i=1;i<=m;i++)scanf("%d%d%d",&a,&b,&c),tp[a].push_back((sth){b,c});
	dfs1(1,0);dfs2(1,0);
	printf("%lld\n",vl[1]>2e15?-1:vl[1]);
}
CF547E Mike and Friends
Problem

nn 个字符串 sis_i ,多组询问求 i=lroccur(sk,si)\sum_{i=l}^r occur(s_k,s_i) ,其中 occur(s,t)occur(s,t) 表示 sstt 中出现的次数

n,si2×105,q5×105n,\sum |s_i|\leq 2\times 10^5,q\leq 5\times 10^5

3s,256MB3s,256MB

Sol

考虑对所有串建出SA,那么 occur(sk,si)occur(s_k,s_i) 即为 sis_i 的所有后缀中是 sks_k 前缀的数量,即 sis_i 的所有后缀中在某一段的数量

因此枚举 ii ,将 sis_i 所有后缀在SA上的位置+1,使用主席树/离线线段树,然后询问直接查即可

复杂度 $O((\sum |s_i|+n+q)\log (n+\sum |s_i|))

Code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 800080
int n,m,lb[N],rb[N],a,b,c,l1,tp[N];
char s[N];
struct SA{
	int sa[N],rk[N],a[N],b[N],su[N],n,m,he[N],mn[N][20],lg[N];
	char v[N];
	void init()
	{
		m=n+255;
		for(int i=1;i<=n;i++)su[a[i]=v[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 k=1;k<=n;k<<=1)
		{
			int ct=0;
			for(int i=n;i>n-k;i--)b[++ct]=i;
			for(int i=1;i<=n;i++)if(sa[i]>k)b[++ct]=sa[i]-k;
			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];
			ct=2;
			for(int i=1;i<=n;i++)b[i]=a[i];
			a[sa[1]]=1;
			for(int i=2;i<=n;i++)a[sa[i]]=b[sa[i]]==b[sa[i-1]]&&b[sa[i]+k]==b[sa[i-1]+k]?ct-1:ct++;
		}
		for(int i=1;i<=n;i++)rk[sa[i]]=i;
		int ls=0;s[n+1]='%';
		for(int i=1;i<=n;i++)
		{
			if(ls)ls--;
			if(rk[i]==1)continue;
			while(v[i+ls]==v[sa[rk[i]-1]+ls])ls++;
			he[rk[i]]=ls;
		}
		for(int i=2;i<=n;i++)mn[i][0]=he[i];
		for(int j=1;j<=19;j++)
		for(int i=2;i+(1<<j)-1<=n;i++)
		mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1]);
		for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
	}
	int query(int l,int r)
	{
		if(l==r)return 1e9;
		if(l>r)l^=r^=l^=r;
		l++;
		int tp=lg[r-l+1];
		return min(mn[l][tp],mn[r-(1<<tp)+1][tp]);
	}
}sa;
struct pretree{
	int ch[N*8][2],su[N*8],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,l1);}
	int adds(int x,int l,int r,int v)
	{
		int st=++ct;
		ch[st][0]=ch[x][0];ch[st][1]=ch[x][1];su[st]=su[x]+1;
		if(l==r)return st;
		int mid=(l+r)>>1;
		if(mid>=v)ch[st][0]=adds(ch[x][0],l,mid,v);
		else ch[st][1]=adds(ch[x][1],mid+1,r,v);
		return st;
	}
	void add(int v){rt[ct2+1]=adds(rt[ct2],1,l1,v);ct2++;}
	int query(int x,int l,int r,int l1,int r1)
	{
		if(l==l1&&r==r1)return su[x];
		if(!x)return 0;
		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 query(ch[x][0],l,mid,l1,mid)+query(ch[x][1],mid+1,r,mid+1,r1);
	}
}pt;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		lb[i]=l1+1;
		scanf("%s",s+1);
		int l2=strlen(s+1);
		for(int j=1;j<=l2;j++)sa.v[l1+j]=s[j];
		rb[i]=l1+l2;
		l1+=l2;sa.v[++l1]='#';
	}
	sa.n=l1;sa.init();
	pt.init();
	for(int i=1;i<=n;i++)
	{
		for(int j=lb[i];j<=rb[i];j++)
		pt.add(sa.rk[j]);
		tp[i]=pt.ct2;
	}
	while(m--)
	{
		scanf("%d%d%d",&a,&b,&c);
		int l=sa.rk[lb[c]],lb1=l,rb1=l1,as=l;
		while(lb1<=rb1)
		{
			int mid=(lb1+rb1)>>1;
			if(sa.query(l,mid)>=rb[c]-lb[c]+1)as=mid,lb1=mid+1;
			else rb1=mid-1;
		}
		lb1=1,rb1=l;
		int as2=l;
		while(lb1<=rb1)
		{
			int mid=(lb1+rb1)>>1;
			if(sa.query(mid,l)>=rb[c]-lb[c]+1)as2=mid,rb1=mid-1;
			else lb1=mid+1;
		}
		printf("%d\n",pt.query(pt.rt[tp[b]],1,l1,as2,as)-pt.query(pt.rt[tp[a-1]],1,l1,as2,as));
	}
}
123 ARC097F Monochrome Cat
Problem

有一棵树,一开始有些点是黑色,有些是白色,你可以从任意一个点开始执行以下操作:

  1. 移动到一个相邻的点,并翻转那个点的颜色,需要1s

  2. 翻转当前点的颜色,需要1s

求最早多久能使得所有点变黑

n105n\leq 10^5

2s,1024MB2s,1024MB

Sol

显然黑色的叶子没有用,可以依次删掉,使得每个叶子都是白色

枚举起点和终点,显然边的经过次数是当前边数乘2减去起点终点路径长度,考虑每个点是否需要翻转

对于非路径上的点,它被1操作翻转的次数显然是它的度数,因此可以知道它是否需要再翻转一次

对于路径上的除了终点之外的点,它会被少翻转一次

考虑树形dp, dpi,0/1/2dp_{i,0/1/2} 表示当前在点 ii ,当前子树内没有选择/选择了起点/终点,当前的最少时间,分类讨论即可

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define N 105000
int n,head[N],cnt,is[N],f[N],g[N],dp[N][2],st,as=1e9,a,b;
char s[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){is[u]=s[u]=='W';for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u),is[u]|=is[ed[i].t];}
void dfs1(int u,int fa){int is1=(fa?0:1);for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&is[ed[i].t])dfs1(ed[i].t,u),f[u]+=f[ed[i].t]+2,is1++;if((is1&1)^(s[u]=='B'))f[u]++;}
void dfs2(int u,int fa){for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa&&is[ed[i].t])g[ed[i].t]=g[u]+f[u]-f[ed[i].t],dfs2(ed[i].t,u);}
void dfs3(int u,int fa)
{
	int su=0,m1=1e9,m2=1e9,m3=1e9,is1=fa?0:1;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&is[ed[i].t])
	{
		is1++;
		dfs3(ed[i].t,u);
		su+=f[ed[i].t]+2;
		if(m1>dp[ed[i].t][0]-f[ed[i].t]-1)m1=dp[ed[i].t][0]-f[ed[i].t]-1;
		if(m2>dp[ed[i].t][1]-f[ed[i].t]-1)m3=m2,m2=dp[ed[i].t][1]-f[ed[i].t]-1;
		else if(m3>dp[ed[i].t][1]-f[ed[i].t]-1)m3=dp[ed[i].t][1]-f[ed[i].t]-1;
	}
	dp[u][0]=(m1>0?0:m1)+f[u]+((is1&1)^(s[u]=='B')?-1:1);
	dp[u][1]=m2+f[u]+((is1&1)^(s[u]=='B')?-1:1);
	if(dp[u][1]>f[u])dp[u][1]=f[u];
	if(m2>0)m3=m2,m2=0;else if(m3>0)m3=0;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&is[ed[i].t])
	{
		int s1=dp[ed[i].t][0]-f[ed[i].t]-1;
		if(dp[ed[i].t][1]-f[ed[i].t]-1==m2)s1+=m3;
		else s1+=m2;
		int as1=f[u]+g[u]+s1+((is1&1)^(s[u]=='B')?-1:1);
		if(m3==0&&((is1&1)^(s[u]=='B'))==0)as1--;
		if(as>as1)as=as1;
	}
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&is[ed[i].t])
	{
		int s1=dp[ed[i].t][1]-f[ed[i].t]-1;
		int as1=f[u]+g[u]+s1+((is1&1)^(s[u]=='B')?-1:1);
		if(as>as1)as=as1;
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)scanf("%d%d",&a,&b),adde(a,b);
	scanf("%s",s+1);
	for(int i=1;i<=n;i++)if(s[i]=='W')st=i;
	dfs(st,0);dfs1(st,0);as=f[st];dfs2(st,0);dfs3(st,0);
	if(!st)as=0;printf("%d\n",as);
}
124 CF547E Mike and Fish
Problem

nn 个点,你需要给每个点红蓝染色,使得每一行/每一列的红蓝点数量差不超过1,保证有解,输出一个方案

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

3s,256MB3s,256MB

Sol

显然可以用CF704D的做法

考虑对于每一行,将第一个点和第二个点连边,第三个点和第四个点连边,以此类推

对于每一列,进行相同的操作

这样连出来的显然是二分图,考虑进行红蓝染色,因为每一行每一列最多只有一个点没有被连边,因此相差不超过1

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define N 200050
int las[N][2],head[N],cnt,cl[N],n,a,b;
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){for(int i=head[u];i;i=ed[i].next)if(cl[ed[i].t]==-1)cl[ed[i].t]=cl[u]^1,dfs(ed[i].t);}
int main()
{
	scanf("%d",&n);for(int i=1;i<=n;i++)cl[i]=-1;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&a,&b);
		if(las[a][0])adde(las[a][0],i),las[a][0]=0;else las[a][0]=i;
		if(las[b][1])adde(las[b][1],i),las[b][1]=0;else las[b][1]=i;
	}
	for(int i=1;i<=n;i++)if(cl[i]==-1)cl[i]=0,dfs(i);
	for(int i=1;i<=n;i++)printf("%c",cl[i]?'r':'b');
}
125 AGC033D Complexity
Problem

给一个01网格,定义它的复杂度为:

如果它是全0或者全1,复杂度为0

否则,找到一种横向/竖向划分的方式,使得划分后两部分的复杂度的max最小,它的复杂度为max+1

求出给定网格的复杂度

n,m185n,m\leq 185

5s,512MB5s,512MB

Sol

显然可以 dpl1,r1,l2,r2dp_{l1,r1,l2,r2} 表示这个子矩形的复杂度,但是这样开不下

注意到答案不超过 lognm15\log nm\leq 15 ,考虑设 fl1,r1,l2,kf_{l1,r1,l2,k} 表示给定 l1,r1,l2l1,r1,l2,最大的 r2r2 使得这个矩形复杂度为 kk

同理设 gl1,l2,r2,kg_{l1,l2,r2,k} 表示给定 l1,l2,r2l1,l2,r2 ,最大的 l1l1 使得矩形复杂度为 kk

考虑枚举 kk 更新

首先考虑沿 yy 划分,枚举 l1,r1,l2l1,r1,l2 ,那么最多可以划分到 fl1,r1,l2,k1f_{l1,r1,l2,k-1} ,那么合法的最大 rr 显然是 fl1,r1,fl1,r1,l2,k1+1,k1f_{l1,r1,f_{l1,r1,l2,k-1}+1,k-1}

沿 xx 划分同理

然后可以将 f,gf,g 互相转移

复杂度 O(nm(n+m)lognm)O(nm(n+m)\log nm)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 187
int f[N][N][N],g[N][N][N],n,m,is[N],f2[N][N][N],g2[N][N][N];
char s[N][N];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	for(int i=1;i<=n;i++)
	for(int j=i;j<=n;j++)
	{
		for(int k=1;k<=m;k++){is[k]=s[i][k]=='.';for(int l=i;l<=j;l++)if(s[l][k]!=s[i][k])is[k]=-1;}
		for(int k=1;k<=m;k++)
		{
			if(is[k]==-1)f[i][j][k]=k-1;
			else if(f[i][j][k-1]>=k)f[i][j][k]=f[i][j][k-1];
			else{f[i][j][k]=k;while(f[i][j][k]<m&&is[f[i][j][k]+1]==is[k])f[i][j][k]++;}
		}
	}
	for(int i=1;i<=m;i++)
	for(int j=i;j<=m;j++)
	{
		for(int k=1;k<=n;k++){is[k]=s[k][i]=='.';for(int l=i;l<=j;l++)if(s[k][l]!=s[k][i])is[k]=-1;}
		for(int k=1;k<=n;k++)
		{
			if(is[k]==-1)g[i][j][k]=k-1;
			else if(g[i][j][k-1]>=k)g[i][j][k]=g[i][j][k-1];
			else{g[i][j][k]=k;while(g[i][j][k]<n&&is[g[i][j][k]+1]==is[k])g[i][j][k]++;}
		}
	}
	if(f[1][n][1]==m){printf("0\n");return 0;}
	for(int i=1;i<=18;i++)
	{
		for(int j=1;j<=n;j++)
		for(int k=j;k<=n;k++)
		for(int l=1;l<=m;l++)
		{
			int t=f[j][k][f[j][k][l]==m?m:f[j][k][l]+1];
			if(f2[j][k][l]<t)f2[j][k][l]=t;
			if(g2[l][t][j]<k)g2[l][t][j]=k;
		}
		for(int j=1;j<=m;j++)
		for(int k=j;k<=m;k++)
		for(int l=1;l<=n;l++)
		{
			int t=g[j][k][g[j][k][l]==n?n:g[j][k][l]+1];
			if(g2[j][k][l]<t)g2[j][k][l]=t;
			if(f2[l][t][j]<k)f2[l][t][j]=k;
		}
		for(int j=1;j<=n;j++)
		for(int k=n;k>=j;k--)
		for(int l=1;l<=m;l++)
		f2[j][k][l]=max(max(f2[j][k][l],f2[j][k+1][l]),max(f2[j-1][k][l],f2[j][k][l-1]));
		for(int j=1;j<=m;j++)
		for(int k=m;k>=j;k--)
		for(int l=1;l<=n;l++)
		g2[j][k][l]=max(max(g2[j][k][l],g2[j][k+1][l]),max(g2[j-1][k][l],g2[j][k][l-1]));
		for(int j=1;j<=n;j++)
		for(int k=j;k<=n;k++)
		for(int l=1;l<=m;l++)
		f[j][k][l]=f2[j][k][l],f2[j][k][l]=0;
		for(int j=1;j<=m;j++)
		for(int k=j;k<=m;k++)
		for(int l=1;l<=n;l++)
		g[j][k][l]=g2[j][k][l],g2[j][k][l]=0;
		if(f[1][n][1]==m){printf("%d\n",i);return 0;}
	}
}
126 AGC031F Walk on Graph
Problem

有一张有边权的图,定义一条路径 ss 的权值为 vsi2i1\sum v_{s_i}*2^{i-1} ,给定奇数 pp ,多组询问是否存在从 sstt 权值模 ppvv 的路径,路径可以不是简单路径

n,m,p50000n,m,p\leq 50000

2s,1024MB2s,1024MB

Sol

不会

127 CF575A Fibonotci
Problem

给定 s1,...,sks_1,...,s_k ,给出 mm 个位置 i,ji,jsi=js_i=j ,对于剩下的位置,有 si=siks_i=s_{i-k}

定义数列 fi=fi1si1+fi2si2f_i=f_{i-1}s_{i-1}+f_{i-2}s_{i-2} ,求 fnmodpf_n\bmod p

k,m50000,n1018,p,si109k,m\leq 50000,n\leq 10^{18},p,s_i\leq 10^9

2s,256MB2s,256MB

Sol

转移显然可以写成一个矩阵

对于非关键点间的区间,显然转移矩阵是循环的,可以使用线段树维护矩阵区间乘积得到每一段的转移矩阵乘积

然后剩下的直接搞即可

复杂度 O((m+k)logk23)O((m+k)\log k2^3)

Code
#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
#define N 50011
int mod,n,m,ct,v[N];
struct mat{int s[2][2];};
mat operator*(mat a,mat b){return (mat){(1ll*a.s[0][0]*b.s[0][0]+1ll*a.s[0][1]*b.s[1][0])%mod,(1ll*a.s[0][0]*b.s[0][1]+1ll*a.s[0][1]*b.s[1][1])%mod,(1ll*a.s[1][0]*b.s[0][0]+1ll*a.s[1][1]*b.s[1][0])%mod,(1ll*a.s[1][0]*b.s[0][1]+1ll*a.s[1][1]*b.s[1][1])%mod};}
long long k,l[N*2],a,b;
map<long long,int> tp;
struct node{int l,r;mat 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=(mat){v[l%n+1],1,v[l],0};return;}
	int mid=(e[x].l+e[x].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;
}
mat 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);
}
void mul(mat &a,mat &b)
{
	mat c;
	for(int i=0;i<=1;i++)for(int j=0;j<=1;j++)c.s[i][j]=0;
	for(int k=0;k<=1;k++)
	for(int i=0;i<=1;i++)
	if(a.s[i][k])
	for(int j=0;j<=1;j++)
	c.s[i][j]=(c.s[i][j]+1ll*a.s[i][k]*b.s[k][j])%mod;
	a=c;
}
mat pw(mat a,long long p)
{
	mat b;
	for(int i=0;i<=1;i++)for(int j=0;j<=1;j++)b.s[i][j]=(i==j);
	while(p){if(p&1)mul(b,a);mul(a,a);p>>=1;}
	return b;
}
mat query(long long l,long long r)
{
	if(l>r)return (mat){1,0,0,1};
	if((l-1)%n<=(r-1)%n&&r-l==(r-1)%n-(l-1)%n)
	return query(1,(l-1)%n+1,(r-1)%n+1);
	else
	{
		long long ln=(l-1)/n*n+n,rn=(r-1)/n*n;
		return query(1,(l-1)%n+1,n)*pw(query(1,1,n),(rn-ln)/n)*query(1,1,(r-1)%n+1);
	}
}
int main()
{
	scanf("%lld%d",&k,&mod);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	build(1,1,n);
	scanf("%d",&m);
	while(m--)
	{
		scanf("%lld%lld",&a,&b);
		l[++ct]=a+1;l[++ct]=a+2;
		tp[a]=(int)b;
	}
	if(k<2){printf("%d\n",k%mod);return 0;}
	sort(l+1,l+ct+1);
	int s1=0,s2=1;
	long long las=1;
	l[0]=1;
	for(int i=1;i<=ct;i++)if(l[i]!=l[i-1])
	{
		if(l[i]>k)
		{
			mat tp=query(las,k-1);
			printf("%d\n",(1ll*s1*tp.s[1][0]+1ll*s2*tp.s[0][0])%mod);
			return 0;
		}
		else
		{
			if(l[i]>las+1)
			{
				mat tp=query(las,l[i]-2);
				int t=(1ll*s1*tp.s[1][1]+1ll*s2*tp.s[0][1])%mod;
				s2=(1ll*s1*tp.s[1][0]+1ll*s2*tp.s[0][0])%mod;
				s1=t;
			}
			int l1=v[(l[i]-2)%n+1],l2=v[(l[i]-1)%n+1];
			if(tp.count(l[i]-2))l1=tp[l[i]-2];
			if(tp.count(l[i]-1))l2=tp[l[i]-1];
			int s=(1ll*s2*l2+1ll*l1*s1)%mod;
			s1=s2;s2=s;
		}
		las=l[i];
	}
	mat tp=query(las,k-1);
	printf("%d\n",(1ll*s1*tp.s[1][0]+1ll*s2*tp.s[0][0])%mod);
	return 0;
}
128 CF639E Bear and Paradox
Problem

nn 道题目,每题有解题时间 tit_i 和分数 viv_i ,定义 s=tis=\sum t_i ,你需要依次解决每道题,如果你在第 ii 时刻解决了一道分数为 vv 的题,你的分数是 v(1ci/s)v*(1-c*i/s)

称一个 c[0,1]c\in [0,1] 是好的,当且仅当在这个 cc 下,任意一个最优的解题方案中,viv_i 高的题目得到的分数都不低于 viv_i 低的题的分数

求最大的合法的 cc

n1.5×105,v108n\leq 1.5\times 10^5,v\leq 10^8

3.5s,256MB3.5s,256MB

Sol

考虑在做题顺序中交换相邻的两道题 i,ji,j ,容易发现 vi/tiv_i/t_i 大的题会在前面

那么可以按照 vi/tiv_i/t_i 排序,得到做题的顺序

对于 vi/tiv_i/t_i 相同的,它们内部可以任意排序,因此可以记录它们分数的最大值和最小值

考虑二分 cc ,每次先算出每道题分数的最大值和最小值,然后再按照 xx 的顺序判断

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

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 150050
struct pt{int x,y,id;}s[N],s2[N];
long long su;
int n;
double f[N][2];
bool cmp1(pt a,pt b){return 1ll*a.x*b.y>1ll*b.x*a.y;}
bool cmp2(pt a,pt b){return a.x>b.x;}
bool check(double t)
{
	int lb=1;
	long long s1=0;
	while(lb<=n)
	{
		int rb=lb;long long su1=s[lb].y;
		while(1ll*s[lb].x*s[rb+1].y==1ll*s[rb+1].x*s[lb].y&&rb<n)rb++,su1+=s[rb].y;
		for(int i=lb;i<=rb;i++)f[s[i].id][0]=s[i].x*(1-t*(s1+s[i].y)/su),f[s[i].id][1]=s[i].x*(1-t*(su1+s1)/su);
		lb=rb+1,s1+=su1;
	}
	double mn=1e9;
	lb=1;
	while(lb<=n)
	{
		int rb=lb;
		while(rb<n&&s2[rb+1].x==s2[lb].x)rb++;
		for(int i=lb;i<=rb;i++)if(f[s2[i].id][0]>mn)return 0;
		for(int i=lb;i<=rb;i++)if(mn>f[s2[i].id][1])mn=f[s2[i].id][1];
		lb=rb+1;
	}
	return 1;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&s[i].x);
	for(int i=1;i<=n;i++)scanf("%d",&s[i].y),s[i].id=i,su+=s[i].y,s2[i]=s[i];
	sort(s+1,s+n+1,cmp1);
	sort(s2+1,s2+n+1,cmp2);
	double lb=0,rb=1,as=0;
	for(int i=1;i<=60;i++)
	{
		double mid=(lb+rb)/2;
		if(check(mid))as=mid,lb=mid;
		else rb=mid;
	}
	printf("%.15lf\n",as);
}
129 AGC039F Min Product Sum
Problem

你有 n×mn\times m 的矩阵,你可以在每个格子中填入 [1,k][1,k] 的整数,定义一种填数方案的权值为

对于每个格子,计算它所在行和所在列的所有数的min,将 nmnm 个数相乘即为权值

求所有方案的权值和模 pp

n,m,k100n,m,k\leq 100

6s,1024MB6s,1024MB

Sol

如果知道了每一行/每一列的min,显然可以快速求出权值

假设当前枚举了每一行/每一列的min,考虑算答案的方案数

显然可以容斥,容斥后一些行的限制从大于等于 xx 变成了大于等于 x+1x+1

然后从大到小考虑限制,显然min小的限制不会对大的限制产生影响,因此直接每次放即可

考虑将所有行列按照容斥后变成 ii 的 -> 没有容斥的 ii -> 容斥后变成 i1i-1 的 -> 没有容斥的 i1i-1 的排序,然后可以从前往后填,最后乘上一个组合系数即可

dpi,j,kdp_{i,j,k} 表示当前考虑了大于等于 ii 的数,当前有 jj 行和 kk 列的被钦定了,当前的系数和

转移时先算容斥后变成 ii 的 ,再算没有容斥的 ii

如果每一步直接枚举有多少个行和列是这种情况,复杂度为 O(n2m2k)O(n^2m^2k)

考虑没有容斥的部分,从 (j,k)(j,k) 转移到 (j+x,k+y)(j+x,k+y) ,那么涉及到的点有 (nj)(mk)(njx)(mky)(n-j)(m-k)-(n-j-x)(m-k-y) 个,这部分的系数为 (ni+1)(nj)(mk)(njx)(mky)/x!/y!(n-i+1)^{(n-j)(m-k)-(n-j-x)(m-k-y)}/x!/y!

那么可以看成 (ni+1)x(mk)/x!(ni+1)y(njx)/y!(n-i+1)^{x(m-k)}/x!*(n-i+1)^{y(n-j-x)}/y!

可以看成从 (j,k)(j,k)(j+x,k)(j+x,k)(j+x,k+y)(j+x,k+y)

那么可以分开转移,复杂度 O(nm(n+m)k)O(nm(n+m)k)

Code
#include<cstdio>
#include<cstring>
using namespace std;
#define N 105
int dp[N][N],dp2[N][N],dp3[N][N],dp4[N][N],n,m,k,p,fr[N],ifr[N],pw[N][N],inv[N];
int doit(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()
{
	scanf("%d%d%d%d",&n,&m,&k,&p);
	for(int i=1;i<=100;i++){pw[i][0]=1;for(int j=1;j<=100;j++)pw[i][j]=1ll*pw[i][j-1]*i%p;}
	fr[0]=ifr[0]=inv[0]=1;for(int i=1;i<=100;i++)fr[i]=1ll*fr[i-1]*i%p,ifr[i]=doit(fr[i],p-2),inv[i]=doit(i,p-2);
	dp[0][0]=1;pw[0][0]=1;
	for(int i=k;i>=1;i--)
	{
		for(int j=0;j<=n;j++)
		for(int s=0;s<=m;s++)if(dp[s][j])
		{
			int s1=dp[s][j],s2=1ll*pw[k-i][n-j]*pw[i][j]%p;
			for(int t=0;t+s<=m;t++)
			dp2[j][t+s]=(dp2[j][t+s]+s1)-(dp2[j][t+s]+s1>=p?p:0),s1=p-1ll*s1*s2%p*inv[t+1]%p;
			dp[s][j]=0;
		}
		for(int s=0;s<=m;s++)
		for(int j=0;j<=n;j++)if(dp2[j][s])
		{
			int s1=dp2[j][s],s2=1ll*pw[k-i][m-s]*pw[i][s]%p;
			for(int t=0;t+j<=n;t++)
			dp3[s][j+t]=(dp3[s][j+t]+s1)-(dp3[s][j+t]+s1>=p?p:0),s1=p-1ll*s1*s2%p*inv[t+1]%p;
			dp2[j][s]=0;
		}
		for(int j=0;j<=n;j++)
		for(int s=0;s<=m;s++)if(dp3[s][j])
		{
			int s1=dp3[s][j],s2=1ll*pw[k-i+1][n-j]*pw[i][j]%p;
			for(int t=0;t+s<=m;t++)
			dp4[j][t+s]=(dp4[j][t+s]+s1)-(dp4[j][t+s]+s1>=p?p:0),s1=1ll*s1*s2%p*inv[t+1]%p;
			dp3[s][j]=0;
		}
		for(int s=0;s<=m;s++)
		for(int j=0;j<=n;j++)if(dp4[j][s])
		{
			int s1=dp4[j][s],s2=1ll*pw[k-i+1][m-s]*pw[i][s]%p;
			for(int t=0;t+j<=n;t++)
			dp[s][j+t]=(dp[s][j+t]+s1)-(dp[s][j+t]+s1>=p?p:0),s1=1ll*s1*s2%p*inv[t+1]%p;
			dp4[j][s]=0;
		}
		for(int j=0;j<n;j++)dp[m][j]=0;
		for(int j=0;j<m;j++)dp[j][n]=0;
	}
	printf("%d\n",1ll*dp[m][n]*fr[n]%p*fr[m]%p);
}
130 CF559E Gerald and Path
Problem

直线上有 nn 个路灯,第 ii 个位置为 aia_i ,照亮范围为 did_i ,每个路灯可以照亮这个位置向左 did_i 或者向右 did_i ,求被照亮的线段长度和的最大值

n100n\leq 100

4s,256MB4s,256MB

Sol

如果只考虑向右的情况,显然可以dp

考虑钦定右边向左到了哪里,设 dpi,j,k,0/1dp_{i,j,k,0/1} 表示考虑了从左向右前 ii个路灯,当前最多向右照到了 jj ,当前钦定向左照到了 kk ,当前钦定有没有被满足,当前的最优值

考虑加入一个路灯的情况,枚举向哪个方向,如果当前加入的区间是 [l,r][l,r] ,那么

如果 l<jl<j 说明满足了钦定

如果 r>kr>k 那么更新 kk 和答案

如果当前钦定被满足了并且 l>kl>k ,那么可以钦定新的 j(k,l)j\in(k,l) 作为左边界,更新答案和钦定部分

复杂度大概是 O(n4)O(n^4)

Code
#include<cstdio>
#include<algorithm>
#include<map>
#include<cstring>
using namespace std;
#define N 305
int dp[N][N][N][2],h[N],r[N],s[N],n,t[N],ct,t2[N],ct2;
map<int,int> l1;
bool cmp(int a,int b){return h[a]<h[b];}
int main()
{
	scanf("%d",&n);
	memset(dp,-0x3f,sizeof(dp));
	for(int i=1;i<=n;i++)scanf("%d%d",&h[i],&r[i]),s[i]=i,t[++ct]=h[i]-r[i],t[++ct]=h[i],t[++ct]=h[i]+r[i];
	sort(s+1,s+n+1,cmp);sort(t+1,t+ct+1);
	t[0]=-1e9;
	for(int i=1;i<=ct;i++)if(t[i]!=t[i-1])t2[++ct2]=t[i],l1[t[i]]=ct2;
	int q1=s[1],lb=l1[h[q1]-r[q1]],md=l1[h[q1]],rb=l1[h[q1]+r[q1]];
	for(int i=1;i<=ct2;i++)
	if(i<=md)
	{
		if(i<lb)dp[2][i][md][0]=t2[md]-t2[i],dp[2][i][rb][0]=t2[rb]-t2[i];
		else if(i<md)dp[2][i][md][1]=t2[md]-t2[i],dp[2][i][rb][0]=t2[rb]-t2[i];
		else dp[2][i][md][1]=t2[md]-t2[i],dp[2][i][rb][1]=t2[rb]-t2[i];
	}
	for(int i=2;i<=n;i++)
	{
		int q1=s[i],lb=l1[h[q1]-r[q1]],md=l1[h[q1]],rb=l1[h[q1]+r[q1]];
		for(int j=1;j<=md;j++)
		for(int k=j;k<=ct2;k++)
		for(int l=0;l<=1;l++)
		if(dp[i][j][k][l]>=0)
		{
			int nj=j,nk=k,nl=l,as=dp[i][j][k][l];
			if(lb<=j)nl=1;
			if(nl==0&&lb>nk)as+=t2[lb]-t2[nk];
			if(nk<md)as+=t2[md]-t2[max(lb,nk)],nk=md;
			if(nl==1)
			{
				int tp=l1[h[s[i+1]]];
				for(int s=nk+1;s<=tp;s++)
				dp[i+1][s][tp][0]=max(dp[i+1][s][tp][0],as+t2[tp]-t2[s]);
				for(int s=k;s<=lb;s++)
				dp[i+1][s][nk][0]=max(dp[i+1][s][nk][0],as+t2[lb]-t2[s]);
			}
			dp[i+1][nj][nk][nl]=max(dp[i+1][nj][nk][nl],as);
			nj=j,nk=k,nl=l,as=dp[i][j][k][l];
			if(j==md)nl=1;
			if(nl==0&&md>nk)as+=t2[md]-t2[nk];
			if(nk<rb)as+=t2[rb]-t2[max(md,nk)],nk=rb;
			if(nl==1)
			{
				int tp=l1[h[s[i+1]]];
				for(int s=nk+1;s<=tp;s++)
				dp[i+1][s][tp][0]=max(dp[i+1][s][tp][0],as+t2[tp]-t2[s]);
				for(int s=k;s<=md;s++)
				dp[i+1][s][nk][0]=max(dp[i+1][s][nk][0],as+t2[md]-t2[s]);
			}
			dp[i+1][nj][nk][nl]=max(dp[i+1][nj][nk][nl],as);
		}
	}
	int as=0;
	for(int i=1;i<=ct2;i++)
	for(int j=i;j<=ct2;j++)
	as=max(as,dp[n+1][i][j][1]);
	printf("%d\n",as);
}
131 AGC037E Reversing and Concatenating
Problem

有一个字符串 ss ,你可以执行 kk 次操作:

ssss 翻转之后的串拼在一起,并选出一个长度为 nn 的子串作为新的 ss

求可能得到的字典序最小的 ss

n5000,k109n\leq 5000,k\leq 10^9

2s,1024MB2s,1024MB

Sol

显然开头连续的字典序最小的字符越多越好,设为 aa

注意到如果一开始有 kk 个连续 aa,一次操作后可以让这些东西移到末尾,然后每一次操作都可以让这个长度翻倍,最后一次再移到开头

因此如果有大于等于 logn+1=14\log n+1=14 次操作,最后的串一定是全是 aa

不然,设第一次操作后最长的结尾的连续 aa 长度为 ll ,那么最后开头的 aa 长度为 l2l1l*2^{l-1}

这种情况下第一次以后最优操作是唯一的,因此可以枚举每一个结尾判断

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

Code
#include<cstdio>
using namespace std;
#define N 10050
int n,k,tp;
char s[N],t[N],as[N],as1[N];
void solve()
{
	int le=0,st=n;
	while(t[st]==t[n])le++,st--;
	le<<=(k-1);
	if(le>n)le=n;
	for(int i=1;i<=le;i++)as1[i]=t[n];
	for(int i=le+1,k=st;i<=n;i++,k--)as1[i]=t[k];
	for(int i=1;i<=n;i++)if(as[i]>as1[i]){for(int j=1;j<=n;j++)as[j]=as1[j];return;}
	else if(as[i]<as1[i])return;
}
int main()
{
	scanf("%d%d%s",&n,&k,s+1);
	if(k>=14){char as='z';for(int i=1;i<=n;i++)if(s[i]<as)as=s[i];for(int i=1;i<=n;i++)printf("%c",as);return 0;}
	tp=1<<k-1;
	for(int i=1;i<=n;i++)s[n*2-i+1]=s[i];
	as[1]='z'+1;
	for(int i=n+1;i<=n*2;i++)
	{
		for(int j=i-n+1;j<=i;j++)t[j-i+n]=s[j];
		solve();
	}
	printf("%s",as+1);
}
132 AGC020F Arcs on a Circle
Problem

周长为 ss 的圆上有 nn 段圆弧,每段圆弧在环上的位置随机,求环上每个位置都被至少一条圆弧覆盖的概率

n6,s50n\leq 6,s\leq 50

5s,512MB5s,512MB

Sol

钦定第一段的起始点为 00

如果每个圆弧的起始位置都是整数,可以dp,设 dpi,j,sdp_{i,j,s} 表示考虑了 [1,i][1,i] 的位置,当前向右延伸到了 jj ,当前用掉的圆弧集合为 ss ,当前的概率

然后直接转移即可

考虑实数上的情况,还需要关心实数部分的关系

注意到可以直接 O((n1)!)O((n-1)!) 枚举所有实数部分间的大小关系,然后设 dpi,j,k,sdp_{i,j,k,s} 示考虑了 [1,i][1,i] 的位置,当前向右延伸到了整数部分 jj,实数部分为 kk ,当前用掉的圆弧集合为 ss ,当前的概率

然后直接子集dp即可

复杂度 O(s2nn!3n)O(s^2nn!3^n)

Code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 7
#define M 55
long long dp[M][1<<N][M][N];
int n,le,v[N],p[N],su;
double as;
int main()
{
	scanf("%d%d",&n,&le);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);sort(v+1,v+n+1);
	for(int i=1;i<n;i++)p[i]=i;
	do{
		memset(dp,0,sizeof(dp));
		dp[0][0][v[n]][0]=1;
		for(int i=0;i<le;i++)
		for(int j=0;j<(1<<n-1);j++)
		for(int k=i;k<=le;k++)
		for(int l=0;l<n;l++)
		if(dp[i][j][k][l])
		for(int s=((1<<n-1)-1)^j;s<(1<<n-1);s=(s-1)&(((1<<n)-1)^j))
		{
			int fg=0;
			if(k>i)fg=1;
			int rs=k,rs2=l;
			for(int t=1;t<n;t++)if(s&(1<<t-1)){if(p[t]<l)fg=1;if(rs<(i+v[t]>le?le:i+v[t]))rs=(i+v[t]>le?le:i+v[t]),rs2=p[t];else if(rs==i+v[t]&&rs2<p[t])rs2=p[t];}
			if(fg)dp[i+1][j|s][rs][rs2]+=dp[i][j][k][l];
		}
		long long su=0;
		for(int i=0;i<n;i++)su+=dp[le][(1<<n-1)-1][le][i];
		double tp=su;
		for(int i=1;i<n;i++)tp/=le;
		as+=tp;
	}while(next_permutation(p+1,p+n));
	for(int i=1;i<n;i++)as/=i;
	printf("%.15lf\n",as);
}
133 CF512D Fox And Travelling
Problem

有一张图,你只有当一个点周围最多有一个未访问的点时才能访问它,每个点最多访问一次,求对于 i[1,...,n]\forall i\in[1,...,n] ,有多少种 ii 个点的访问序列,模 109+910^9+9

n100,m5000n\leq 100,m\leq 5000

3s,256MB3s,256MB

Sol

显然一个环不能访问,剩下的图有两种情况

  1. 一棵独立的树

  2. 一棵树,根存在一个不能被访问的父亲

对于情况2很好解决,设 dpi,jdp_{i,j} 表示 ii 的子树内访问 jj个点的方案数,因为不同子树间的访问顺序是独立的,所以合并子树的转移系数是 Cj+kjC_{j+k}^j ,最后再加上访问整个子树的方案数,复杂度 O(n2)O(n^2)

否则,考虑枚举最后一个被访问的点 xx ,以 xx 为根做一遍dp,同时钦定根不被删掉,根只有一个子树没有被删完即可

复杂度 O(n3)O(n^3)

Code
#include<cstdio>
#include<cstring>
using namespace std;
#define N 103
#define mod 1000000009
struct edge{int t,next;}ed[N*N];
int head[N],cnt,is[N],dp[N][N*2],dp2[N],sz[N],c[N][N],le[N],rb,ins[N],l1,cl[N],rt,dp3[N][N*2][2],a,b,ct[N],c1,n,m,vis[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 dfs1(int u,int fa)
{
	ins[u]=++rb;le[rb]=u;vis[u]=1;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa)
	{
		int t=ed[i].t;
		if(ins[t])
		for(int j=ins[t];j<=rb;j++)is[le[j]]=1;
		else
		if(!vis[t])dfs1(t,u);
	}
	ins[u]=0;rb--;
}
void dfs2(int u,int fa)
{
	le[++rb]=u;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa)
	{
		int t=ed[i].t;
		if(is[t])
		for(int j=1;j<=rb;j++)is[le[j]]=1;
		else dfs2(t,u);
	}
	rb--;
}
void dfs3(int u,int fa)
{
	cl[u]=c1;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&!cl[ed[i].t])
	{
		if(is[ed[i].t])l1=u;
		else dfs3(ed[i].t,u);
	}
}
void dfs4(int u,int fa)
{
	dp[u][0]=1;
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa&&!is[ed[i].t])
	{
		dfs4(ed[i].t,u);
		for(int j=sz[u];j>=0;j--)
		for(int k=1;k<=sz[ed[i].t];k++)
		dp[u][j+k]=(dp[u][j+k]+1ll*dp[u][j]*dp[ed[i].t][k]%mod*c[j+k][k])%mod;
		sz[u]+=sz[ed[i].t];
	}
	dp[u][sz[u]+1]=dp[u][sz[u]];
	sz[u]++;
}
void dfs5(int u,int fa)
{
	sz[u]=0;
	if(u!=rt)
	{
		dp[u][0]=1;
		for(int i=head[u];i;i=ed[i].next)
		if(ed[i].t!=fa&&!is[ed[i].t])
		{
			dfs5(ed[i].t,u);
			for(int j=sz[u];j>=0;j--)
			for(int k=1;k<=sz[ed[i].t];k++)
			dp[u][j+k]=(dp[u][j+k]+1ll*dp[u][j]*dp[ed[i].t][k]%mod*c[j+k][k])%mod;
			sz[u]+=sz[ed[i].t];
		}
		dp[u][sz[u]+1]=dp[u][sz[u]];
		sz[u]++;
	}
	else
	{
		dp3[u][0][0]=1;
		for(int i=head[u];i;i=ed[i].next)
		if(ed[i].t!=fa&&!is[ed[i].t])
		{
			dfs5(ed[i].t,u);
			for(int j=sz[u];j>=0;j--)
			{
				for(int k=1;k<=sz[ed[i].t];k++)
				{
					if(k==sz[ed[i].t])
					dp3[u][j+k][1]=(dp3[u][j+k][1]+1ll*dp3[u][j][1]*dp[ed[i].t][k]%mod*c[j+k][k])%mod,
					dp3[u][j+k][0]=(dp3[u][j+k][0]+1ll*dp3[u][j][0]*dp[ed[i].t][k]%mod*c[j+k][k])%mod;
					else dp3[u][j+k][1]=(dp3[u][j+k][1]+1ll*dp3[u][j][0]*dp[ed[i].t][k]%mod*c[j+k][k])%mod;
				}
				dp3[u][j][1]=dp3[u][j][0];
				dp3[u][j][0]=0;
			}
			sz[u]+=sz[ed[i].t];
		}
		sz[u]++;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d",&a,&b),adde(a,b);
	for(int i=1;i<=n;i++)memset(vis,0,sizeof(vis)),dfs1(i,0);
	for(int i=1;i<=n;i++)if(is[i])dfs2(i,0);
	dp2[0]=1;
	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++)if(!is[i]&&!cl[i])
	{
		c1=i;
		for(int j=0;j<=n;j++)ct[j]=0;
		l1=0;
		dfs3(i,0);
		if(l1)
		{
			dfs4(l1,0);
			for(int j=0;j<=n;j++)ct[j]=dp[l1][j];
		}
		else
		{
			for(int j=1;j<=n;j++)
			if(cl[j]==i)
			{
				rt=j;
				memset(dp,0,sizeof(dp));
				memset(dp3[j],0,sizeof(dp3[j]));
				dfs5(j,0);
				for(int k=0;k<=n;k++)ct[k+1]=(1ll*ct[k+1]+dp3[j][k][0]+dp3[j][k][1])%mod;
			}
		}
		for(int j=n;j>=0;j--)
		for(int k=1;k<=n;k++)
		dp2[j+k]=(dp2[j+k]+1ll*dp2[j]*ct[k]%mod*c[j+k][k])%mod;
	}
	for(int i=0;i<=n;i++)printf("%d\n",dp2[i]);
}
134 AGC028E High Elements
Problem

给一个序列,你需要构造一个字典序尽量小的长度为 nn 的01串,满足:

构造两个空串 x,yx,y ,依次考虑每一位,如果串的这一位为1,那么把这个元素放到 yy 结尾,否则把这个元素放到 xx 结尾,使得 xx 的前缀最大值数量和 yy 的前缀最大值数量相同

输出最小字典序串或输出无解

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

2s,1024MB2s,1024MB

Sol

不会

135 AGC022F Checkers
Problem

x=10100x=10^{100} ,数轴上有 nn 个点,第 ii 个点的位置为 xix^i ,你每次可以选择两个棋子 a,ba,b ,将 aa 的位置变成 2ba2b-a ,然后删除 bb ,求最后一个棋子可能的坐标数量,模 109+710^9+7

n50n\leq 50

2s,256MB2s,256MB

Sol

考虑每个位置对最后位置的贡献系数,可以发现一定是 (1)a2b(-1)^a*2^b

显然不超过 2n2n

考虑将操作建成树,每个点有两个儿子,边权分别为 2,12,-1 ,每个叶子的贡献为到根的边权乘积

考虑倒着做这个过程,相当于每次删一个 (1)a2b,(1)a12b1(-1)^a*2^b,(-1)^{a-1}*2^{b-1} ,加入一个 (1)a2b1(-1)^a*2^{b-1}

从大到小考虑 bb ,设 dpi,j,k,ldp_{i,j,k,l} 表示当前 b=ib=i ,当前前面有 jj个数,当前处理了大于 2b2^b 的操作后还剩 kk2b+12^{b+1} ,ll2b+1-2^{b+1} ,当前的方案数

考虑枚举 2b2^bxx 个, 2b-2^byy 个,那么会剩下 x+lkx+l-k2b2^by+kly+k-l2b-2^b

注意到如果 j>0j>0 ,此时必须 x+y>0x+y>0 ,然后可以发现可以让一个 kk 的操作和一个 ll 的操作相互抵消

fi,j,sf_{i,j,s} 表示当前 b=ib=i ,当前前面有 jj个数, kl=sk-l=s 的方案数,直接枚举 x,yx,y 转移即可

k=0k=0 的数只能有 x+y=1x+y=1 ,剩下的没有限制

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

Code
#include<cstdio>
using namespace std;
#define N 53
#define mod 1000000007
int dp[N][N*4][N],fr[N],ifr[N],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",&n);
	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);
	for(int i=n+1;i>=1;i--)dp[i][n][0]=1;
	for(int i=n;i>=0;i--)
	for(int j=0;j<=n*2;j++)
	for(int k=0;k<=n;k++)
	if(dp[i+1][j][k])
	{
		int st=(j-n)*2;
		if(st>n-k+1||st+n-k-1<0)continue;
		for(int s=0;s<=n-k;s++)
		for(int t=0;s+t<=n-k;t++)
		if(s+t)
		{
			int ns=n+st+s-t,nk=k+s+t;
			if(ns<0||ns>n*2)continue;
			if(j<n&&s<n-j)continue;
			if(j>n&&t<j-n)continue;
			if(!i&&s+t>1)continue;
			dp[i][ns][nk]=(dp[i][ns][nk]+1ll*dp[i+1][j][k]*ifr[s]%mod*ifr[t])%mod;
		}
	}
	printf("%d\n",1ll*dp[0][n+1][n]*fr[n]%mod);
}
136 CF506E Mr. Kitayuta's Gift
Problem

有一个字符串,你需要向里面插入 mm 个字符,求可以得到的回文串数量模 1000710007

n200,m109n\leq 200,m\leq 10^9

6s,768MB6s,768MB

Sol

首先考虑最后是偶数的情况

如果给定了最后的串,可以贪心匹配原串

考虑设 dpi,j,sdp_{i,j,s} 表示确定了回文串的前后 ss 位,当前原串还剩下 [l,r][l,r] 没有匹配,当前的方案数

暴力转移可以直接枚举这次的字符然后向后转移,可以矩阵做到 O(n6logm)O(n^6\log m)

注意到转移的图可以看成一个DAG,每个点上有一个自环

注意到如果 slsrs_l\neq s_r ,自环的系数为24

如果相等,那么系数为25

如果这时已经合法了,那么系数为26

也就是说自环的系数只有这三种,并且26只会出现一次

在偶数的情况下,最后的终止点是所有的 dpi+1,idp_{i+1,i}

如果走到一个系数为24的自环, jij-i 一定只会减少1

如果走到一个系数为25的自环, jij-i 一定会减少2

因此,可以通过记录走到的系数为25的自环数得到走过的三种自环数

fl,r,kf_{l,r,k} 表示当前走到 [l,r][l,r] ,当前经过了 kk 个系数为25的点的自环,当前的方案数,这部分可以dp解决

最后相当于多次询问经过了 aa 个24的自环, bb 个25的自环,1个26的自环,求走 ll 步的方案数

显然 kn/2k\leq n/2

考虑建一条 n+n/2n+n/2 个点的有向链,前 nn 个点每个点有一个24的自环,然后 n/2n/2 个点每个点有一个25的自环,然后这部分的点再各自连向一个26的自环

这样通过求这个图邻接矩阵的 ll 次幂,就可以通过两点间路径条数得到一种情况的答案

然后考虑奇数的情况

有两种情况:

  1. 以一个原串的字符作为中心,这种情况相当于 fi,if_{i,i} ,并且这种情况没有26的自环

  2. 以一个填的字符作为中心,这种情况相当于 26fi+1,i126*f_{i+1,i-1}

两种情况分别相加即可

复杂度 O(n3logm)O(n^3\log m)

Code
#include<cstdio>
#include<cstring>
#define mod 10007
using namespace std;
#define N 420
struct mat{int s[410][410];}t;
int n,m,dp[N][N][N],ct,as[N];
char s[N];
void mul(mat &a,mat &b)
{
	mat c;
	for(int i=1;i<=ct;i++)for(int j=1;j<=ct;j++)c.s[i][j]=0;
	for(int k=1;k<=ct;k++)
	for(int i=1;i<=k;i++)
	if(a.s[i][k])
	for(int j=k;j<=ct;j++)
	c.s[i][j]=(c.s[i][j]+1ll*a.s[i][k]*b.s[k][j])%mod;
	a=c;
}
void pw(mat &a,int p)
{
	mat b;
	for(int i=1;i<=ct;i++)for(int j=1;j<=ct;j++)b.s[i][j]=(i==j);
	while(p){if(p&1)mul(b,a);mul(a,a);p>>=1;}
	a=b;
}
int main()
{
	scanf("%s%d",s+1,&n);
	m=strlen(s+1);
	dp[1][m][0]=1;
	for(int i=1;i<=m;i++)
	for(int j=m;j>=i;j--)
	for(int k=0;k<=m;k++)
	{
		if(s[j]==s[i])
		{
			if(j-i<=1)as[k]=(as[k]+dp[i][j][k])%mod;
			else dp[i+1][j-1][k]=(dp[i+1][j-1][k]+dp[i][j][k])%mod;
		}
		else
		dp[i+1][j][k+1]=(dp[i+1][j][k+1]+dp[i][j][k])%mod,
		dp[i][j-1][k+1]=(dp[i][j-1][k+1]+dp[i][j][k])%mod;
	}
	if((~(n+m))&1)
	{
		int v1=m,v2=(m+1)/2;
		ct=v1+v2*2;
		for(int i=1;i<=v1;i++)t.s[i][i]=24,t.s[i][i+1]=1;
		for(int i=1;i<=v2;i++)t.s[v1+i*2-1][v1+i*2]=t.s[v1+i*2-1][v1+i*2+1]=1,t.s[v1+i*2-1][v1+i*2-1]=25,t.s[v1+i*2][v1+i*2]=26;
		pw(t,(n+m)/2);
		int as1=0;
		for(int i=0;i<v1;i++)
		as1=(as1+1ll*t.s[v1+1-i][v1+(v1-i+1)/2*2]*as[i])%mod;
		printf("%d\n",as1);
	}
	else
	{
		int v1=m,v2=(m+1)/2;
		ct=v1+v2*2;
		for(int i=1;i<=v1;i++)t.s[i][i]=24,t.s[i][i+1]=1;
		for(int i=1;i<=v2;i++)t.s[v1+i*2-1][v1+i*2]=t.s[v1+i*2-1][v1+i*2+1]=1,t.s[v1+i*2-1][v1+i*2-1]=25,t.s[v1+i*2][v1+i*2]=26;
		pw(t,(n+m)/2);
		int as1=0;
		for(int i=0;i<v1;i++)
		as1=(as1+26ll*t.s[v1+1-i][v1+(v1-i+1)/2*2]%mod*as[i])%mod;
		for(int i=1;i<=m;i++)
		{
			for(int j=0;j<=m-1;j++)
			as1=(as1+1ll*t.s[v1-j+1][v1+(v1-j+1)/2*2-1]*dp[i][i][j])%mod;
		}
		printf("%d\n",as1);
	}
}
137 CF639F Bear and Chemistry
Problem

有一个图,多次询问,每次给出边集 SS 和点集 TT ,求在图中加入这些边后,是否存在一条回路,不经过重复边且经过每个点,强制在线,每次询问相互独立

n,m,S,T3×105n,m,\sum |S|,\sum |T|\leq 3\times 10^5

6s,256MB6s,256MB

Sol

显然合法当且仅当所有点在一个边双中

首先缩边双,将图变成一个树

对于一次操作,考虑加入这些边对边双的变化

将所有涉及到的点(包括边的端点)拿出来,建虚树,然后再缩点双即可

复杂度 O(S+Tlogn)O(\sum |S|+|T|\log n)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
#define N 300050
int n,m,q,s[N][2],t[N][2],ct,r[N][2],l[N],ct2,ct3,R,a,b,c;
vector<int> v1,v2,v3;
struct t1{
	int f1[N],f2[N],is[N],fa1[N],dep[N],head[N],cnt;
	int finds1(int x){return f1[x]==x?x:f1[x]=finds1(f1[x]);}
	int finds2(int x){return f2[x]==x?x:f2[x]=finds2(f2[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 dfs(int u,int fa){dep[u]=dep[fa]+1;fa1[u]=fa;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u);}
	void un(int i,int j){while(i!=j){if(dep[i]<dep[j])i^=j^=i^=j;f2[i]=fa1[i];i=finds2(fa1[i]);}}
	void pre()
	{
		for(int i=1;i<=n;i++)f1[i]=f2[i]=i;
		for(int i=1;i<=m;i++)if(finds1(s[i][0])!=finds1(s[i][1]))adde(s[i][0],s[i][1]),f1[finds1(s[i][0])]=finds1(s[i][1]),is[i]=1;
		for(int i=1;i<=n;i++)if(!dep[i])dfs(i,0);
		for(int i=1;i<=m;i++)if(!is[i])un(s[i][0],s[i][1]);
		for(int i=1;i<=n;i++)finds2(i),finds1(i);
		for(int i=1;i<=m;i++)if(f2[s[i][0]]!=f2[s[i][1]])t[++ct][0]=f2[s[i][0]],t[ct][1]=f2[s[i][1]];
	}
}s1;
bool cmp(int i,int j){return l[i]<l[j];}
struct t2{
	int dep[N],head[N],cnt,f[N][21],st[N],rb,ct1;
	vector<int> f2[N];
	map<int,int> id;
	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;l[u]=++ct1;f[u][0]=fa;for(int i=1;i<=20;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)dfs(ed[i].t,u);}
	int LCA(int x,int y){if(dep[x]<dep[y])x^=y^=x^=y;for(int i=20;i>=0;i--)if(dep[x]-dep[y]>=(1<<i))x=f[x][i];if(x==y)return x;for(int i=20;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];return f[x][0];}
	void adde2(int a,int b){if(a==b)return;if(!id[a])id[a]=++ct2;if(!id[b])id[b]=++ct2;r[++ct3][0]=id[a];r[ct3][1]=id[b];}
	void build(vector<int> p)
	{
		sort(p.begin(),p.end(),cmp);
		rb=0;
		int s1=p.size();
		for(int i=0;i<s1;i++)
		{
			while(rb&&LCA(st[rb],p[i])!=st[rb])
			{
				int l=LCA(st[rb],p[i]);
				if(dep[l]<=dep[st[rb-1]])adde2(st[rb],st[rb-1]),rb--;
				else adde2(l,st[rb]),st[rb]=l;
			}
			st[++rb]=p[i];
		}
		for(int i=1;i<rb;i++)adde2(st[i],st[i+1]);
	}
	void solve(vector<int> r,vector<int> p)
	{
		int sz=r.size(),sz1=p.size();
		ct2=ct3=0;id.clear();
		for(int i=0;i<sz1;i++)if(!id[p[i]])id[p[i]]=++ct2,f2[s1.f1[p[i]]].push_back(p[i]);
		for(int i=0;i<sz;i++)if(!id[r[i]])id[r[i]]=++ct2,f2[s1.f1[r[i]]].push_back(r[i]);
		for(int i=0;i<sz;i++)if(f2[s1.f1[r[i]]].size())build(f2[s1.f1[r[i]]]),f2[s1.f1[r[i]]].clear();
		for(int i=0;i<sz;i+=2)adde2(r[i],r[i+1]);
		for(int i=0;i<sz1;i++)v3.push_back(id[p[i]]);
	}
	void pre()
	{
		for(int i=1;i<=ct;i++)adde(t[i][0],t[i][1]);
		for(int i=1;i<=n;i++)if(!dep[i])dfs(i,0);
	}
}s2;
struct t3{
	int f1[N],f2[N],is[N],fa1[N],dep[N],head[N],cnt;
	int finds1(int x){return f1[x]==x?x:f1[x]=finds1(f1[x]);}
	int finds2(int x){return f2[x]==x?x:f2[x]=finds2(f2[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 dfs(int u,int fa){dep[u]=dep[fa]+1;fa1[u]=fa;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs(ed[i].t,u);}
	void un(int i,int j){i=finds2(i);j=finds2(j);while(i!=j){if(dep[i]<dep[j])i^=j^=i^=j;f2[finds2(i)]=finds2(fa1[i]);i=finds2(fa1[i]);}}
	bool solve(vector<int> fu)
	{
		for(int i=1;i<=ct2;i++)f1[i]=f2[i]=i,head[i]=0,dep[i]=0;cnt=0;
		for(int i=1;i<=ct3;i++)if(finds1(r[i][0])!=finds1(r[i][1]))adde(r[i][0],r[i][1]),f1[finds1(r[i][0])]=finds1(r[i][1]),is[i]=1;
		for(int i=1;i<=ct2;i++)if(!dep[i])dfs(i,0);
		for(int i=1;i<=ct3;i++)if(!is[i])un(r[i][0],r[i][1]);
		for(int i=1;i<=ct3;i++)is[i]=0;
		for(int i=1;i<=ct2;i++)finds2(i);
		int sz1=fu.size();
		for(int i=0;i<sz1-1;i++)if(f2[fu[i]]!=f2[fu[i+1]])return 0;
		return 1;
	}
}s3;
int lsj(int x){return (x+R+n-1)%n+1;}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=m;i++)scanf("%d%d",&s[i][0],&s[i][1]);
	s1.pre();s2.pre();
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&a,&b);v1.clear();v2.clear();v3.clear();
		for(int i=1;i<=a;i++)scanf("%d",&c),c=lsj(c),v1.push_back(s1.f2[c]);
		for(int i=1;i<=b*2;i++)scanf("%d",&c),c=lsj(c),v2.push_back(s1.f2[c]);
		s2.solve(v2,v1);int as=s3.solve(v3);
		if(as)printf("YES\n"),R=(R+i)%n;
		else printf("NO\n");
	}
}
138 AGC039D Incenters
Problem

圆周上有 nn 个点,第 ii 个点的圆心角是 ai/L2πa_i/L*2\pi ,求随机选三个点得到的三角形内心坐标期望

n3000n\leq 3000

4s,1024MB4s,1024MB

Sol

EEBCDBCD 内心,那么显然有 DF=FB=BEDF=FB=BE

考虑枚举 BDBD , 计算编号中 B<C<DB<C<D 的三角形的贡献

那么此时 FF 固定,所有的 EE 都分布在圆弧上

因此,求出所有 FE\overset{\large\to}{FE} 的角度就可以计算

可以发现,只需要求出所有角度的 sin,cossin,cos 值之和,就可以算出所有向量的和

AGAG 为横轴,相当于求 EF,AGEF,AG 的夹角

相当于 BF,AGBF,AG 的夹角加上 BC\overset{\huge\frown}{BC}

那么可以看成 ACAC 的角度加上一个常数

那么直接维护所有 ACAC 角度的 sin,cossin,cos 值之和,和角公式算即可

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

Code
#include<cstdio>
#include<cmath>
using namespace std;
#define N 3050
int n,l,v[N];
double pi=acos(-1),su[N][2],as1,as2;
int main()
{
	scanf("%d%d",&n,&l);l*=2;
	for(int i=1;i<=n;i++)scanf("%d",&v[i]),v[i]*=2,su[i][0]=su[i-1][0]+cos(pi*v[i]/l),su[i][1]=su[i-1][1]+sin(pi*v[i]/l);
	for(int i=1;i<=n;i++)
	for(int j=i+2;j<=n;j++)
	{
		long long su1=1ll*v[i]+v[j];
		if(su1<l)
		{
			int st=l-(l-su1)/2;
			double c1=cos(pi*st/l+1.5*pi),c2=sin(pi*st/l+1.5*pi);
			double v1=c1*(su[j-1][0]-su[i][0])-c2*(su[j-1][1]-su[i][1]),v2=c2*(su[j-1][0]-su[i][0])+c1*(su[j-1][1]-su[i][1]);
			double st1=cos(v[i]*pi*2/l)-cos(st*pi*2/l),st2=sin(v[i]*pi*2/l)-sin(st*pi*2/l);
			double le1=sqrt(st1*st1+st2*st2);
			v1*=le1,v2*=le1;
			v1+=cos(st*pi*2/l)*(j-i-1),v2+=sin(st*pi*2/l)*(j-i-1);
			as1+=v1,as2+=v2;
		}
		else
		{
			int st=(su1-l)/2;
			double c1=cos(pi*st/l+0.5*pi),c2=sin(pi*st/l+0.5*pi);
			double v1=c1*(su[j-1][0]-su[i][0])-c2*(su[j-1][1]-su[i][1]),v2=c2*(su[j-1][0]-su[i][0])+c1*(su[j-1][1]-su[i][1]);
			double st1=cos(v[i]*pi*2/l)-cos(st*pi*2/l),st2=sin(v[i]*pi*2/l)-sin(st*pi*2/l);
			double le1=sqrt(st1*st1+st2*st2);
			v1*=le1,v2*=le1;
			v1+=cos(st*pi*2/l)*(j-i-1),v2+=sin(st*pi*2/l)*(j-i-1);
			as1+=v1,as2+=v2;
		}
	}
	double st1=1.0*n*(n-1)*(n-2)/6;
	as1/=st1,as2/=st1;
	printf("%.15lf %.15lf\n",as1,as2);
}
139 CF576D Flights for Regular Customers
Problem

有一张图,你经过一条边需要1s,不能停留,第 ii 条边只有在 did_i s以及之后才开放,求从 11nn 的最小时间或者输出无解

n,m150,d109n,m\leq 150,d\leq 10^9

4s,256MB4s,256MB

Sol

相当于在 mm 个时间段内有不同的图,求最小时间

一种方法是连一个 nn 的自环,然后枚举每一段,先二分段内是否可以到达,如果不能就处理出这段时间之后能到哪,然后处理下一段,否则就得到了最小答案

直接二分复杂度为 O(n3mlog2d)O(n^3m\log^2 d)

显然如果合法,那么一定能在 nn 步内走到,考虑从当前可以到达的点开始bfs,找到最早能到达 nn 的时刻即可

bitset优化矩乘,复杂度 O(n^3m\log d\32)

Code
#include<cstdio>
#include<bitset>
#include<algorithm>
#include<queue>
using namespace std;
#define N 160
int n,m,ct,a,b,c,dis[N];
struct sth{int a,b,c;friend bool operator <(sth a,sth b){return a.c<b.c;}}t[N];
bitset<N> as,as2;
struct mat{bitset<N> s[N];}s;
void mul(mat &a,mat &b)
{
	mat c;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	c.s[i][j]=(a.s[i]&b.s[j]).any();
	a=c;
}
mat pw(mat a,int p)
{
	mat c,ar,a1;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	ar.s[i][j]=a.s[j][i];
	a1=a;
	for(int i=1;i<=n;i++)c.s[i].reset(),c.s[i].set(i,1);
	while(p){if(p&1)mul(c,ar);mul(a1,ar);mul(ar,a);a=a1;p>>=1;}
	return c;
}
int bfs(int ti)
{
	queue<int> tp;
	for(int i=1;i<=n;i++)dis[i]=-1;
	for(int i=1;i<=n;i++)if(as[i])tp.push(i),dis[i]=0;
	while(!tp.empty())
	{
		int q=tp.front();tp.pop();
		for(int i=1;i<=n;i++)
		if(s.s[q][i]&&dis[i]==-1)
		dis[i]=dis[q]+1,tp.push(i);
	}
	if(dis[n]==-1||dis[n]>ti)return -1;
	return dis[n];
}
int main()
{
	scanf("%d%d",&n,&m);
	as.reset();as.set(1,1);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		if(c==0)s.s[a].set(b,1);
		else t[++ct]=(sth){a,b,c};
	}
	sort(t+1,t+ct+1);
	t[ct+1].c=1998244353;
	int las=0;
	int tp=bfs(t[1].c-las);
	if(tp!=-1){printf("%d\n",tp+las);return 0;}
	mat t1=pw(s,t[1].c);
	as2.reset();
	for(int j=2;j<=n;j++)
	for(int k=1;k<j;k++)
	{
		int a=t1.s[j][k],b=t1.s[k][j];
		t1.s[j].set(k,b);
		t1.s[k].set(j,a);
	}
	for(int i=1;i<=n;i++)as2[i]=(as&t1.s[i]).any();
	as=as2;
	for(int i=1;i<=ct;i++)
	{
		s.s[t[i].a][t[i].b]=1;
		las=t[i].c;
		int tp=bfs(t[i+1].c-las);
		if(tp!=-1){printf("%d\n",tp+las);return 0;}
		int tp2=t[i+1].c-t[i].c;
		mat t1=pw(s,tp2);
		as2.reset();
		for(int j=2;j<=n;j++)
		for(int k=1;k<j;k++)
		{
			int a=t1.s[j][k],b=t1.s[k][j];
			t1.s[j].set(k,b);
			t1.s[k].set(j,a);
		}
		for(int i=1;i<=n;i++)as2[i]=(as&t1.s[i]).any();
		as=as2;
	}
	printf("Impossible\n");
}
140 AGC029F Construction of a tree
Problem

你有 n1n-1 个集合 SS ,你需要在每个集合中选出两个数连一条边,使得最后能构成一棵树

输出一个方案或输出无解

n105,S2×105n\leq 10^5,\sum |S|\leq 2\times 10^5

4s,1024MB4s,1024MB

Sol

考虑钦定一个根,对于每条边,记录这条边的儿子

显然除了根以外,每个点正好被记录一次,每条边只能记录它集合内的点

将集合与除了根的点进行匹配,找不到匹配显然无解

假设当前找到了一个匹配,考虑bfs还原点,对于当前访问到的点,将包含它的集合记录的点放进队列

如果dfs中途无解,那么剩下的集合中一定有 kk 个点 kk 条边,这 kk 个集合内只可能有这 kk 个点

此时取这些点中的一个为根,那么有 nkn-k 个点,包含它们中至少一个的集合最多只有 nk1n-k-1 个,此时显然无解

因此直接dfs就可以判是否有解

复杂度 O(n\sqrt \sum |S|)

Code
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 200500
queue<int> tp;
int n,m,a,head[N],cnt=1,id[N],fa[N],cur[N],dis[N],is[N],ct1;
vector<int> st2[N];
struct edge{int t,next,v;}ed[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],0};head[t]=cnt;}
bool bfs(int s,int t)
{
	memset(dis,-1,sizeof(dis));
	memcpy(cur,head,sizeof(cur));
	dis[s]=0;
	queue<int> st;st.push(s);
	while(!st.empty())
	{
		int a=st.front();st.pop();
		for(int i=head[a];i;i=ed[i].next)
		if(ed[i].v&&dis[ed[i].t]==-1)
		{
			dis[ed[i].t]=dis[a]+1,st.push(ed[i].t);
			if(ed[i].t==t)return 1;
		}
	}
	return 0;
}
int dfs(int u,int t,int f)
{
	if(u==t||!f)return f;
	int as=0,tp;
	for(int &i=cur[u];i;i=ed[i].next)
	if(ed[i].v&&dis[ed[i].t]==dis[u]+1&&(tp=dfs(ed[i].t,t,min(ed[i].v,f))))
	{
		as+=tp,f-=tp;ed[i].v-=tp;ed[i^1].v+=tp;
		if(!f)return as;
	}
	return as;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		scanf("%d",&m);
		for(int j=1;j<=m;j++)scanf("%d",&a),st2[a].push_back(i),adde(a,i+n,1);
	}
	for(int i=1;i<n;i++)adde(2*n,i+1,1),adde(i+n,2*n+1,1);
	int as=0;while(bfs(n*2,n*2+1))
	as+=dfs(n*2,n*2+1,n);
	if(as<n-1){printf("-1\n");return 0;}
	for(int i=1;i<n;i++)
	for(int j=head[i+n];j;j=ed[j].next)if(ed[j].v)id[i]=ed[j].t;
	queue<int> st;
	st.push(1);
	while(!st.empty())
	{
		int a=st.front();st.pop();
		ct1++;
		for(int j=0;j<st2[a].size();j++)if(!fa[id[st2[a][j]]])fa[id[st2[a][j]]]=a,st.push(id[st2[a][j]]);
	}
	if(ct1<n){printf("-1\n");return 0;}
	for(int i=1;i<n;i++)printf("%d %d\n",fa[id[i]],id[i]);
}
141 AGC022E Median Replace
Problem

定义一个长度是奇数的01串是好的,当且仅当存在一种方式,每次将相邻三个字符删掉,换成它们的中位数,使得最后一个字符是1

你有一个包含 01? 的字符串,求有多少种将 ? 变成 01 的方案,使得最后的字符串是好的,模 109+710^9+7

n3×105n\leq 3\times 10^5

2s,256MB2s,256MB

Sol

考虑贪心构造

如果当前开头有两个 1 ,显然一定可以

否则考虑贪心,如果没有连续三个 0 ,那么一次只能删去一个0和一个1,那么有:

000 -> 0

001 -> 0

011 -> 1

010 -> 0

101 -> 1

1000 -> 10

1001 -> 10

(0=,1=1,2=11,3=0,4=00,5=10',6=100.7=01`)

因此直接贪心即可

复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define N 300050
#define M 10
#define mod 1000000007
int dp[N][M],t[M][2],as;
char s[N];
int main()
{
	t[1][1]=2;t[1][0]=7;
	t[2][1]=3;t[2][0]=4;
	t[3][1]=3;t[3][0]=3;
	t[4][1]=2;t[4][0]=5;
	t[5][1]=4;t[5][0]=4;
	t[6][1]=2;t[6][0]=7;
	t[7][1]=6;t[7][0]=8;
	t[8][1]=7;t[8][0]=7;
	scanf("%s",s+1);
	dp[0][1]=1;
	for(int i=1;s[i];i++)
	{
		if(s[i]!='0')for(int j=1;j<9;j++)dp[i][t[j][1]]=(dp[i][t[j][1]]+dp[i-1][j])%mod;
		if(s[i]!='1')for(int j=1;j<9;j++)dp[i][t[j][0]]=(dp[i][t[j][0]]+dp[i-1][j])%mod;
		as=(dp[i][3]+dp[i][2])%mod;
	}
	printf("%d\n",as);
}
142 CF555E Case of Computer Network
Problem

有一张图和 kk 对点,你需要给每一对点定向,使得对于每一对点,能从第一个点到达第二个点(顺序任意),求是否有解

图不一定连通

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

3s,256MB3s,256MB

Sol

首先边双连一个环就可以让边双上的点两两双向能到达

因此可以将所有的环缩起来,变成树上的问题

两个点不连通无解,否则相当于给一条链定向,考虑记录每条边是向上还是向下,分别对于两种标记树上差分即可

如果有边有两种标记无解,否则有解

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

Code
#include<cstdio>
using namespace std;
#define N 200050
int head[N],cnt,fa[N],f[N][20],n,m,q,h[N][2],ct,fa2[N],cnt2,head2[N],dep[N],fg[N][2],tp,a,b,t[N][2],is[N];
struct edge{int t,next;}ed[N*2],ed2[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 adde2(int f,int t){ed2[++cnt2]=(edge){t,head2[f]};head2[f]=cnt2;ed2[++cnt2]=(edge){f,head2[t]};head2[t]=cnt2;}
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
int finds2(int x){return fa2[x]==x?x:fa2[x]=finds2(fa2[x]);}
void dfs1(int u,int fa){f[u][0]=fa;dep[u]=dep[fa]+1;for(int i=head[u];i;i=ed[i].next)if(ed[i].t!=fa)dfs1(ed[i].t,u);}
void dfs2(int u,int fa){f[u][0]=fa;dep[u]=dep[fa]+1;for(int i=1;i<=18;i++)f[u][i]=f[f[u][i-1]][i-1];for(int i=head2[u];i;i=ed2[i].next)if(ed2[i].t!=fa)dfs2(ed2[i].t,u);}
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=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];}
void un(int x,int y){while(finds2(x)!=finds2(y)){if(dep[x]<dep[y])x^=y^=x^=y;fa2[x]=f[x][0];x=finds2(x);}}
void dfs3(int u,int fa){for(int i=head2[u];i;i=ed2[i].next)if(ed2[i].t!=fa)dfs3(ed2[i].t,u),fg[u][0]+=fg[ed2[i].t][0],fg[u][1]+=fg[ed2[i].t][1];if(fg[u][0]&&fg[u][1])tp=1;}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)fa[i]=i,fa2[i]=i;
	for(int i=1;i<=m;i++){scanf("%d%d",&a,&b);t[i][0]=a,t[i][1]=b;if(finds(a)==finds(b))h[++ct][0]=a,h[ct][1]=b;else fa[finds(a)]=finds(b),adde(a,b);}
	dfs1(1,0);for(int i=1;i<=ct;i++)un(h[i][0],h[i][1]);
	for(int i=1;i<=m;i++)if(finds2(t[i][0])!=finds2(t[i][1]))adde2(finds2(t[i][0]),finds2(t[i][1]));
	for(int i=1;i<=n;i++)dep[i]=0;
	for(int i=1;i<=n;i++)if(finds2(i)==i&&!dep[i])is[i]=1,dfs2(i,0);
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&a,&b);
		if(finds(a)!=finds(b)){printf("No\n");return 0;}
		a=finds2(a);b=finds2(b);
		int l=LCA(a,b);
		fg[a][0]++,fg[l][0]--;fg[b][1]++;fg[l][1]--;
	}
	for(int i=1;i<=n;i++)if(is[i])dfs3(i,0);
	printf("%s\n",tp?"No":"Yes");
}
143 AGC032E Module Pairing
Problem

你有 2n2n 个数,你需要将它们配对,使得每一对的 (a+b)modm(a+b)\bmod m 的最大值最小,求最小值

n105,m109n\leq 10^5,m\leq 10^9

2s,1024MB2s,1024MB

Sol

考虑二分答案 ss ,那么配对的一定是 [0,s],[m,m+s][0,s],[m,m+s]

将数排序,然后可以证明存在一种最优方案,使得存在一个 kk[1,k][1,k] 中的数从首尾开始两两配对,和在 [0,s][0,s] 间,剩下的数首尾开始两两配对,和在 [m,m+s][m,m+s]

一个证明:

显然对于第一个区间,合法的 kk 是一段前缀,所以可以二分check

对于第二个区间,合法的是一段区间,无法直接二分

注意到显然对于 kk ,第二个区间的两两和的min需要大于 mm ,max小于等于 m+sm+s,可以对于两个限制二分,或者乱搞

复杂度 O(nlognlogm)O(n\log n\log m)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,v[300002];
bool check2(int a,int b)
{
	int l=1,r=a;
	while(l<=r)
	{
		if((v[l]+v[r])%m>b)return 0;
		l++,r--;
	}
	return 1;
}
bool check(int mid)
{
	int lb=1,rb=n,as=1;
	while(lb<=rb)
	{
		int md=(lb+rb)>>1;
		if(check2(md,mid))as=md,lb=md+1;
		else rb=md-1;
	}
	if(as==n)return 1;
	if(as&1)as--;
	int mnl=0,mxr=as;
	for(int i=n;i>n/2;i--)
	{
		int lb=0,rb=i,as=i;
		while(lb<=rb)
		{
			int mid1=(lb+rb)>>1;
			if(v[mid1]+v[i]>=m)as=mid1,rb=mid1-1;
			else lb=mid1+1;
		}
		if(mnl<as-(n-i+1))mnl=as-(n-i+1);
		lb=as,rb=i;
		if(v[as]+v[i]>m+mid)return 0;
		while(lb<=rb)
		{
			int mid1=(lb+rb)>>1;
			if(v[mid1]+v[i]<=m+mid)as=mid1,lb=mid1+1;
			else rb=mid1-1;
		}
		if(mxr>as-(n-i+1))mxr=as-(n-i+1);
		if(mnl&1)mnl++;
		if(as*2+i>=n&&mnl<=i*2-n-2&&mxr>=i*2-n-2)return 1;
	}
	return 0;
}
int main()
{
	scanf("%d%d",&n,&m);n*=2;
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	sort(v+1,v+n+1);
	int lb=0,rb=m-1,as=m-1;
	while(lb<=rb)
	{
		int mid=(lb+rb)>>1;
		if(check(mid))as=mid,rb=mid-1;
		else lb=mid+1;
	}
	printf("%d\n",as);
}
144 AGC023D Go Home
Problem

一条数轴上有 nn 个地点,第 ii 个位置在 aia_i ,有 bib_i 个人的目标位置在这里

一开始所有人都在初始位于 ss 的车上,每个人目标都是尽量早到达目标位置。每个时刻,车上所有人投票决定车向哪个方向走(相等向左走),然后车会向这个方向走1单位距离,求所有人执行最优策略下,所有人都到达目标点的最早时间

n105n\leq 10^5

2s,256MB2s,256MB

Sol

如果 ss 在所有位置的左边,那么接下来显然只会向右走,在右边同理

否则,考虑第 11 个位置和第 nn 个位置

如果第 11 个位置的人数大于等于第 nn 个位置的人数,那么显然第 nn 个位置不可能比第 11 个位置先到达,并且到达第 nn 个位置的时间是到达第 11 个位置的时间加上两点间距离

因此这时第 nn 个位置的人相当于需要让车最早到达第 11 个位置,因此他们的决策和第 11 个位置的人相同,因此可以看成将第 nn 个位置合并到第 11 个位置上

小于的情况同理

最后将每一步合并前额外的距离加起来即可

显然最多合并 nn 次,复杂度 O(n)O(n)

Code
#include<cstdio>
using namespace std;
#define N 100500
long long v[N],su[N],n,s,as,l,r,tp[N],ct;
int main()
{
	scanf("%lld%lld",&n,&s);l=1,r=n;ct=n;
	for(int i=1;i<=n;i++)scanf("%lld%lld",&v[i],&su[i]);
	while(l<r)
	{
		if(s<v[l])su[l]=1e16;if(s>v[r])su[r]=1e16;
		if(su[l]>=su[r])su[l]+=su[r],tp[ct--]=v[r],r--;else su[r]+=su[l],tp[ct--]=v[l],l++;
	}
	tp[1]=v[l];tp[0]=s;
	for(int i=1;i<=n;i++)as+=tp[i]>tp[i-1]?tp[i]-tp[i-1]:tp[i-1]-tp[i];
	printf("%lld\n",as);
}
145 CF506C Mr. Kitayuta vs. Bamboos
Problem

nn 根竹子,初始高度为 aia_i,每天白天,一个人会进行 kk 次操作,每次可以让一根竹子高度减小 pp ,但竹子高度不会小于等于 00 。每天夜晚,第 ii 根竹子高度会增加 did_i ,求 mm 天后最高的竹子的长度可能的最小值

n105,m5000,k10n\leq 10^5,m\leq 5000,k\leq 10

2s,256MB2s,256MB

Sol

首先考虑二分答案

考虑倒着操作,设 fjf_{j} 表示竹子 jj 最高可能的高度

那么人的每一次操作相当于给一个 fjf_{j} 加上 pp ,每一天结束时,每个 fjf_j 会减去 djd_j

操作合法的条件是任意时刻 fj0f_j\geq 0 ,且倒着操作到开头时 fjaif_j\geq a_i

显然可以将操作留到需要的时候做

每次对最早到0的竹子进行操作,如果当前剩下的操作次数不够就不合法

最后再用剩下的操作次数让所有的 fjaif_j\geq a_i,能够做到就有解

复杂度 O((n+mk)lognlogv)O((n+mk)\log n\log v)

Sol
#include<cstdio>
#include<set>
using namespace std;
#define N 100050
struct sth{int id,ti;friend bool operator <(sth a,sth b){return a.ti==b.ti?a.id<b.id:a.ti>b.ti;}};
set<sth> tp;
int n,m,k,p,s[N],h[N],t1[N],ct1;
long long h1[N];
bool solve(long long st)
{
	tp.clear();
	for(int i=1;i<=n;i++)
	{
		h1[i]=st;t1[i]=m;
		long long s1=m-st/h[i];
		if(s1==m)return 0;
		if(s1>0)tp.insert((sth){i,(int)s1});
	}
	int ti=m,ct=0;
	while(!tp.empty())
	{
		sth sb=*tp.begin();
		if(sb.ti<=0)break;
		tp.erase(sb);
		ct+=k*(ti-sb.ti);ti=sb.ti;
		int a=sb.id;
		h1[a]-=1ll*(t1[a]-ti)*h[a];
		t1[a]=ti;
		int lct=ct;
		while(h1[a]<h[a])
		{
			if(ct==0)return 0;
			ct--,h1[a]+=p;ct1++;
		}
		int s1=ti-h1[a]/h[a];
		if(s1>0)tp.insert((sth){a,s1});
	}
	ct+=ti*k;
	for(int i=1;i<=n;i++)
	{
		h1[i]-=1ll*t1[i]*h[i];
		while(h1[i]<s[i])
		{
			if(ct==0)return 0;
			ct--;h1[i]+=p;
		}
	}
	return 1;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&k,&p);
	for(int i=1;i<=n;i++)scanf("%d%d",&s[i],&h[i]);
	long long lb=0,rb=1e13,as=1e13;
	while(lb<=rb)
	{
		long long mid=(lb+rb)>>1;
		if(solve(mid))as=mid,rb=mid-1;
		else lb=mid+1;
	}
	printf("%lld\n",as);
}
146 CF679E Bear and Bad Powers of 42
Problem

有一个序列,初始每个位置都不是42的幂,多次操作:

  1. 输出序列一个位置的值

  2. 对于一个区间,将这个区间的值变为一个给定的不是42的幂的数

  3. 对于一个区间,给区间加上一个数,如果当前区间内有一个是42的幂的数,那么一直执行操作直到区间没有是42的幂的数

n,q105,1a109n,q\leq 10^5,1\geq a\geq 10^9

2s,256MB2s,256MB

Sol

因为加的数都是正数,所以每个数只会变大

因为 4210>1016>nq1042^{10}>10^{16}>n*q*10 ,显然每个数不会超过 421042^{10}

对于3操作,在线段树上维护每个元素和下一个42的幂的距离和区间这个的min,加的时候如果线段树节点区间内距离的min小于等于加的数,就分下去处理,大于就直接返回

对于2操作,在3操作遇到一个有区间赋值的标记的节点时,可以一起操作,这样复杂度是对的

复杂度 O(nlogn10)O(n\log n*10)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 100500
#define ll long long
ll s[15]={1,42,1764,74088,3111696,130691232,5489031744ll,230539333248ll,9682651996416ll,406671383849472ll,17080198121677824ll};
int n,m,v[N],a,b,c,d;
struct segt{long long lz1,lz2,nt;int l,r;}e[N*4];
long long getnt(long long v){int lb=1,rb=10,as=-1;while(lb<=rb){int mid=(lb+rb)>>1;if(s[mid]>=v)as=mid,rb=mid-1;else lb=mid+1;}return as==-1?1e18:s[as]-v;}
void pushdown(int x)
{
	if(e[x].lz2)
	{
		e[x<<1].lz2=e[x<<1|1].lz2=e[x].lz2;
		e[x<<1].lz1=e[x<<1|1].lz1=0;
		e[x<<1].nt=e[x<<1|1].nt=e[x].nt;
		e[x].lz2=0;
	}
	if(e[x].lz1)
	{
		e[x<<1].nt-=e[x].lz1,e[x<<1|1].nt-=e[x].lz1;
		if(e[x<<1].lz2)e[x<<1].lz2+=e[x].lz1;else e[x<<1].lz1+=e[x].lz1;
		if(e[x<<1|1].lz2)e[x<<1|1].lz2+=e[x].lz1;else e[x<<1|1].lz1+=e[x].lz1;
		e[x].lz1=0;
	}
}
void pushup(int x){e[x].nt=min(e[x<<1].nt,e[x<<1|1].nt);}
void build(int x,int l,int r)
{
	e[x].l=l;e[x].r=r;
	if(l==r){e[x].lz1=v[l];e[x].nt=getnt(v[l]);return;}
	int mid=(l+r)>>1;
	build(x<<1,l,mid);build(x<<1|1,mid+1,r);
	pushup(x);
}
long long query(int x,int s)
{
	if(e[x].lz2)return e[x].lz2;
	if(e[x].l==e[x].r)return e[x].lz1;
	pushdown(x);
	int mid=(e[x].l+e[x].r)>>1;
	if(mid>=s)return query(x<<1,s);
	else return query(x<<1|1,s);
}
void modify(int x,int l,int r,int s)
{
	if(e[x].l==l&&e[x].r==r){e[x].lz1=0;e[x].lz2=s;e[x].nt=getnt(s);return;}
	int mid=(e[x].l+e[x].r)>>1;
	pushdown(x);
	if(mid>=r)modify(x<<1,l,r,s);
	else if(mid<l)modify(x<<1|1,l,r,s);
	else modify(x<<1,l,mid,s),modify(x<<1|1,mid+1,r,s);
	pushup(x);
}
void modify2(int x,int l,int r,int s)
{
	if(e[x].lz2&&(e[x].l==l&&e[x].r==r)){e[x].lz2+=s;e[x].nt=getnt(e[x].lz2);return;}
	if(e[x].nt>=s&&(e[x].l==l&&e[x].r==r)){e[x].lz1+=s;e[x].nt-=s;return;}
	if(e[x].l==e[x].r){e[x].lz1+=s;e[x].nt=getnt(e[x].lz1);return;}
	pushdown(x);
	int mid=(e[x].l+e[x].r)>>1;
	if(mid>=r)modify2(x<<1,l,r,s);
	else if(mid<l)modify2(x<<1|1,l,r,s);
	else modify2(x<<1,l,mid,s),modify2(x<<1|1,mid+1,r,s);
	pushup(x);
}
long long query2(int x,int l,int r)
{
	if(e[x].lz2)return e[x].nt;
	if(e[x].l==l&&e[x].r==r)return e[x].nt;
	pushdown(x);
	int mid=(e[x].l+e[x].r)>>1;
	if(mid>=r)return query2(x<<1,l,r);
	else if(mid<l)return query2(x<<1|1,l,r);
	else return min(query2(x<<1,l,mid),query2(x<<1|1,mid+1,r));
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&v[i]);
	build(1,1,n);
	while(m--)
	{
		scanf("%d",&a);
		if(a==1)scanf("%d",&b),printf("%lld\n",query(1,b));
		else if(a==2)scanf("%d%d%d",&b,&c,&d),modify(1,b,c,d);
		else{scanf("%d%d%d",&b,&c,&d);modify2(1,b,c,d);while(query2(1,b,c)==0)modify2(1,b,c,d);}
	}
}
147 ARC098F Donation
Problem

你有一张图,你要进行一个游戏

你一开始任意选一个点,满足 aia_i 小于等于你当前的钱数,然后你可以进行如下操作:

  1. 走到一个相邻的点 vv ,满足 ava_v 小于等于你当前的钱数

  2. 给当前点捐赠 bub_u

当每个点都被捐赠过,游戏结束,你获胜

求获胜所需要的最少钱数

n,m105n,m\leq 10^5

2s,1024MB2s,1024MB

Sol

只和点权相关,可以建出Kruskal重构树

考虑树上的操作,显然是首先在不同子树间走,然后到一个子树内不再上来

因为根的 aia_i 最大,所以在此之前钱数一定不低于 aia_i

fif_i 为在 ii 子树内走,不回到根,最少需要的钱数

gig_i 为在 ii 子树内走,回到根,最少需要的钱数

显然 gi=ai+vsubtreeofibvg_i=a_i+\sum_{v\in subtree of i} b_v

考虑枚举最后到了哪个子树,那么有 fi=minv{xsubtreeofi,xsubtreeofvbx+max{ai,bv}}f_i=min_v\{\sum_{x\in subtree of i,x\notin subtree of v}b_x+max\{a_i,b_v\}\}

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

Code
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
#define N 100500
int vis[N],fa[N],s[N],v[N],n,m,a,b,head[N],cnt,is[N];
long long su[N],dp[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 finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
priority_queue<pair<long long,int> > tp;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d%d",&s[i],&v[i]),tp.push(make_pair(-s[i]*2000000000ll+v[i],i));
	for(int i=1;i<=m;i++)scanf("%d%d",&a,&b),adde(a,b);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=n;i++)
	{
		int s1=tp.top().second;tp.pop();dp[s1]=1e17;is[s1]=1;
		for(int j=head[s1];j;j=ed[j].next)if(finds(ed[j].t)!=s1&&is[finds(ed[j].t)])vis[finds(ed[j].t)]=1,dp[s1]=min(dp[s1],max(1ll*s[s1],dp[finds(ed[j].t)]+v[s1])-su[finds(ed[j].t)]),su[s1]+=su[finds(ed[j].t)],fa[finds(ed[j].t)]=s1;
		dp[s1]+=su[s1];su[s1]+=v[s1];
		dp[s1]=min(dp[s1],max(s[s1],v[s1])+su[s1]-v[s1]);
	}
	printf("%lld\n",dp[finds(1)]);
}
148 CF516D Drazil and Morning Exercise
Problem

给一棵带正边权的树,定义一个点的权值是离它最远的叶子的距离,多组询问,每次给出 ll ,求满足连通块内最大权值减去最小权值小于 ll 的连通块的最大大小

n105,q50,vi>0n\leq 10^5,q\leq 50,v_i>0

3.5s,256MB3.5s,256MB

Sol

考虑扫描线算答案,相当于加点,删点,求最大连通块大小

显然可以LCT维护

考虑直径中点,显然以这个点为根,儿子的权值大于父亲的权值

考虑从大到小扫描线,显然每次删掉的点的儿子权值都比它大,因此每次删掉的一定是一个叶子

因此删除可以直接看成给连通块大小减1

因此只需要加入时维护并查集即可

复杂度 O(nqlogn)O(nq\log n)

Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 105000
struct edge{int t,next,v;}ed[N*2];
struct sth{int a;long long b;int t;friend bool operator <(sth a,sth b){return a.b==b.b?a.t>b.t:a.b>b.b;}}s[N*2];
int head[N],cnt,n,m,b,c,ct,fa[N],sz[N],is[N];
long long a;
int finds(int x){return fa[x]==x?x:fa[x]=finds(fa[x]);}
long long f[N],m1[N],m2[N],dp1[N],dp2[N];
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 dfs1(int u,int fa)
{
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa)
	{
		dfs1(ed[i].t,u);
		dp1[u]=max(dp1[u],dp1[ed[i].t]+ed[i].v);
		if(m1[u]<dp1[ed[i].t]+ed[i].v)m2[u]=m1[u],m1[u]=dp1[ed[i].t]+ed[i].v;
		else if(m2[u]<dp1[ed[i].t]+ed[i].v)m2[u]=dp1[ed[i].t]+ed[i].v;
	}
}
void dfs2(int u,int fa)
{
	for(int i=head[u];i;i=ed[i].next)
	if(ed[i].t!=fa)
	{
		long long st=m1[u];
		if(dp1[ed[i].t]+ed[i].v==st)st=m2[u];
		dp2[ed[i].t]=max(st,dp2[u])+ed[i].v;
		dfs2(ed[i].t,u);
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)scanf("%lld%d%d",&a,&b,&c),adde(a,b,c);
	dfs1(1,0);dfs2(1,0);
	for(int i=1;i<=n;i++)f[i]=max(dp1[i],dp2[i]);
	scanf("%d",&m);
	while(m--)
	{
		int as=1;ct=0;
		scanf("%lld",&a);
		for(int i=1;i<=n;i++)
		{
			s[++ct]=(sth){i,f[i],1};
			s[++ct]=(sth){i,f[i]-a,0};
		}
		sort(s+1,s+ct+1);
		for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1,is[i]=0;
		for(int i=1;i<=ct;i++)
		{
			int a=s[i].a,b=s[i].t;
			if(b==1)
			{
				is[a]=1;
				for(int j=head[a];j;j=ed[j].next)
				if(is[ed[j].t])
				{
					int v1=finds(a),v2=finds(ed[j].t);
					fa[v1]=v2,sz[v2]+=sz[v1],as=max(as,sz[v2]);
				}
			}
			else sz[finds(a)]--;
		}
		printf("%d\n",as);
	}
}
149 AGC032F One Third
Problem

有一个圆,每次以一个随机角度切一刀,切 nn 次,然后选出一个连续的区间,使得区间的面积和 1/31/3 尽量接近,求最接近的值和 1/31/3 差的期望,模 109+710^9+7

n106n\leq 10^6

2s,1024MB2s,1024MB

Sol

不会

150 AGC023E Inversions
Problem

给一个序列 aia_i ,求所有满足 i,pi<ai\forall i,p_i<a_i 的排列的逆序对数和,模 109+710^9+7

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

3s,256MB3s,256MB

Sol

先判掉无解

考虑枚举每一对数 (i,j)(i<j)(i,j)(i<j) ,计算逆序对的贡献

如果 ai=aja_i=a_j ,显然出现逆序对的概率是 1/21/2

否则,如果 ai<aja_i<a_j ,贡献等于 ai=aja_i=a_j 时的方案数乘 1/21/2

sis_i 表示 aj>=ia_j>=ijj 数量,那么显然合法排列数等于 sni+1i+1\prod s_{n-i+1}-i+1

那么对于 ai=aja_i=a_j 时的方案数,相当于在原来的 sis_i 上一段区间减1

维护 vi=(si(ni)1)/(si(ni)),ri=jivjv_i=(s_i-(n-i)-1)/(s_i-(n-i)),r_i=\prod_{j\leq i}v_j

那么不考虑出现0的情况,贡献为原方案数乘上 raj/rair_{a_j}/r_{a_i}

那么可以从左到右枚举 jj ,每次看成在 aia_i 位置加入 1/rai1/r_{a_i},使用树状数组即可

有0的情况对于每个位置处理出它左侧右侧的第一个0的位置即可

对于 ai>aja_i>a_j 的情况,贡献等于总方案数减去 ai=aja_i=a_j 时的方案数乘 1/21/2,维护方式同理

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

Code
#include<cstdio>
using namespace std;
#define N 300500
#define mod 1000000007
int n,p[N],su[N],v[N],su1[N],as,as1,tr[N],lb[N],tr2[N],rb[N],tr3[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 add(int x,int k){for(int i=x;i<=n;i+=i&-i)tr[i]=(tr[i]+k)%mod;}
int que(int x){int as=0;for(int i=x;i;i-=i&-i)as=(as+tr[i])%mod;return as;}
void add2(int x,int k){for(int i=x;i<=n;i+=i&-i)tr2[i]+=k;}
int que2(int x){int as=0;for(int i=x;i;i-=i&-i)as+=tr2[i];return as;}
void add3(int x,int k){for(int i=x;i<=n;i+=i&-i)tr3[i]=(tr3[i]+k)%mod;}
int que3(int x){int as=0;for(int i=x;i;i-=i&-i)as=(as+tr3[i])%mod;return as;}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&p[i]),su[p[i]]++;
	for(int i=n-1;i>=1;i--)su[i]=su[i+1]+su[i]-1;
	as=1;
	for(int i=1;i<=n;i++)as=1ll*as*su[i]%mod;
	if(!as){printf("0\n");return 0;}
	lb[0]=1;
	for(int i=1;i<=n;i++)
	{
		v[i]=1ll*(su[i]-1)*pw(su[i],mod-2)%mod;
		su1[i]=v[i];if(su1[i-1])su1[i]=1ll*su1[i]*su1[i-1]%mod;
	}
	for(int i=n;i>=1;i--)if(v[i+1])rb[i]=rb[i+1];else rb[i]=i;
	for(int i=1;i<=n;i++)if(v[i-1])lb[i]=lb[i-1];else lb[i]=i;
	for(int i=1;i<=n;i++)
	{
		int su11=que(p[i])-que(lb[p[i]]-1),su2=que2(p[i])-que2(lb[p[i]]-1);
		as1=(as1+1ll*as*su11%mod*su1[p[i]]%mod*(mod+1)/2)%mod;
		su11=que3(rb[p[i]])-que3(p[i]),su2=que2(n)-que2(p[i]);
		as1=(as1+1ll*as*su2%mod-1ll*as*su11%mod*pw(su1[p[i]],mod-2)%mod*(mod+1)/2%mod)%mod;
		add(p[i],pw(su1[p[i]]?su1[p[i]]:1,mod-2)),add2(p[i],1);add3(p[i],su1[p[i]]);
	}
	printf("%d\n",(as1+mod)%mod);
}