HIT2018秋校赛 略解

Prologue

时间:20181222-12:20:00 to 20181222-17:20:00

现场比赛(大一限定)时 L 题题目表述有误(但修正后仍无 AC)。现场无 AC 的题有 F,H,I,L(I 题现场无提交)。另有 C 题仅 3 人 AC,D 题仅 1 人 AC(均为现场,其中 C 题可能原因为题面表述不明)。

A

题目大意:给两个长度均为 N 数组 L 1.. n L_{1 .. n} L1..n G 1.. n G_{1 .. n} G1..n,正反序比对,是否相应位置元素均有 L i ≤ G i L_i ≤ G_i LiGi

水题。代码如下:

#include
#define MAX_N (100005)
int l[MAX_N];
int main()
{
	bool b,f;
	int g,n,t;
	scanf("%d",&t);
	while(t--)
	{
		b=f=true;
		scanf("%d",&n);
		for(int i=0;i<n;i++)
			scanf("%d",&l[i]);
		for(int i=0;i<n;i++)
			scanf("%d",&g),
			f&=l[i]<=g,b&=l[n-i-1]<=g;
		puts(f?b?"both":"front":b?"back":"none");
	}
	return 0;
}

B

题目大意:给长度为 n 正整数列和正整数 m,判断数列中是否存在区间和为 m 倍数。

计算出前缀和数组,判断数组中是否有对 m 模相同余数的项即可。代码如下:

#include
#define MAX_M (2005)
bool a[MAX_M];
int main()
{
	bool flag;
	int m,n,x,s;
	while(scanf("%d %d",&n,&m)==2)
	{
		for(int i=0;i<m;i++)
			a[i]=false;
		flag=false,s=0;
		while(n--)
			scanf("%d",&x),s=(s+x)%m,flag|=a[s],a[s]=true;
		puts(flag?"YES":"NO");
	}
	return 0;
}

C

题目大意:给长度为 n 正整数列 a 1.. n a_{1 .. n} a1..n,建图如下:若 g c d ( a i , a j ) > 1 gcd(a_i, a_j) > 1 gcd(ai,aj)>1,则于点 a i a_i ai 和点 a j a_j aj 间存在一条无向边;若 a i = a j a_i = a_j ai=aj,则为同一点。求连通块数和最大连通块大小。

由于每个连通块都存在公共质因子,处理出数列中每个数的所有质因子,并查集合并点集即可。代码如下:

#include
#define MAX_N (100005)
int sp[MAX_N],st[MAX_N],sz[MAX_N];
int fnd(int x)
{
	if(st[x]==x)
		return x;
	return st[x]=fnd(st[x]);
}
int main()
{
	int ai,aj,n;
	while(scanf("%d",&n)==1)
	{
		for(int i=0;i<MAX_N;i++)
			sp[i]=-1,st[i]=i,sz[i]=0;
		while(n--)
		{
			scanf("%d",&ai);
			if(sz[fnd(ai)]>0)
				continue;
			aj=ai,sz[ai]=1;
			for(int i=2;i*i<=ai&&aj>1;i++)
				if(aj%i==0)
				{
					if(sp[i]!=-1)
					{
						sp[i]=fnd(sp[i]),st[sp[i]]=ai;
						if(sp[i]!=ai)
							sz[ai]+=sz[sp[i]],sz[sp[i]]=0;
					}
					else
						sp[i]=ai;
					for(;aj%i==0;aj/=i);
				}
			if(aj>1)
				if(sp[aj]!=-1)
				{
					sp[aj]=fnd(sp[aj]),st[sp[aj]]=ai;
					if(sp[aj]!=ai)
						sz[ai]+=sz[sp[aj]],sz[sp[aj]]=0;
				}
				else
					sp[aj]=ai;
		}
		ai=aj=0;
		for(int i=0;i<MAX_N;i++)
			if(sz[i]>0)
			{
				if(aj<sz[i])
					aj=sz[i];
				ai++,sz[i]=0;
			}
		printf("%d %d\n",ai,aj);
	}
	return 0;
}

D

题目大意:给 N 个点无根树,边权为 1。M 次操作,设所有点初始均为基态,每次操作两个点 a 和 b,若为基态则变为激发态,若为激发态则变为基态。操作后,激发态点数必为偶,则存在一种方案使所有激发态点两两配对,并使所有激发态点对间距离和最小。求每次操作后最小距离和。

显然,最小点对距离和方案必然路径不相交,即所有边最多通过一次(点可能多次)。若存在一种方案存在 A-C-D-F 和 B-C-D-E 两条路径(即激发态点 ABEF,边集 C-D 通过两次),则另一种方案 A-C-B 和 E-D-F 显然更优。归纳出一般规律,对于任一种方案,若某边通过偶数次则最优方案通过 0 次,若某边通过奇数次则最优方案通过 1 次。对于一次操作,相当于点 a 和点 b 间加上一条路径(由于树性质,路径唯一)。当经过一个点的路径数为偶时,该点为基态(上例中点 C 有 A-C 一、B-C 一、C-D 二、共四为偶,故为基态),否则为激发态。每次查询所有边中经过次数为奇的边数即为最小点对距离和。

可以看出,每次操作只改变一条路径上边的奇偶性。将每次操作看成查询路径 a-b 上所有边权和,并将路径 a-b 上所有边权乘以 -1,上次查询结果加上本次查询路径边权和即为本次查询结果。这样,每次操作后,边权为 -1 的边为参与点对匹配的边,边权为 1 的边为不参与点对匹配的边。如此,问题转化成维护一棵初始边权均为 1 的树,操作类型有区间乘 -1 和区间和查询,数链剖分套线段树即可。代码如下:

#include
#define MAX_N (100005)
#define MAX_2LGN (0x3FFFF)
int vdg[MAX_N];
struct E
{
	int to;
	E *nxt;
}edg[MAX_N+MAX_N],*fdg[MAX_N];
struct V
{
	int dep,fah,gfa,siz,son;
}vtx[MAX_N];
struct SegTreeNode
{
	int bl,br,fg,ky;
}stn[MAX_2LGN];
void dfs0(int n)
{
	vtx[n].siz=1,vtx[n].son=0;
	for(E *i=fdg[n];i!=NULL;i=i->nxt)
		if(i->to!=vtx[n].fah)
		{
			vtx[i->to].dep=vtx[n].dep+1;
			vtx[i->to].fah=n;
			dfs0(i->to);
			vtx[n].siz+=vtx[i->to].siz;
			if(vtx[vtx[n].son].siz<vtx[i->to].siz)
				vtx[vtx[n].son].gfa=vtx[n].son,
				vtx[n].son=i->to;
			else
				vtx[i->to].gfa=i->to;
		}
}
void dfs1(int n)
{
	for(E *i=fdg[n];i!=NULL;i=i->nxt)
		if(i->to!=vtx[n].fah&&i->to!=vtx[n].son)
			dfs1(i->to);
	if(vtx[n].son!=0)
		vtx[vtx[n].son].gfa=vtx[n].gfa,
		dfs1(vtx[n].son);
	vdg[n]=++vdg[0];
}
void SegTreeBuild(int n,int l,int r)
{
	stn[n].bl=l,stn[n].br=r,stn[n].fg=1;
	if(l+1==r)
	{
		stn[n].ky=1;
		return;
	}
	int m=(l+r+1)/2;
	SegTreeBuild(n*2,l,m);
	SegTreeBuild(n*2+1,m,r);
	stn[n].ky=stn[n*2].ky+stn[n*2+1].ky;
}
#define SegTreeFlag(x) \
	if(stn[x].fg==-1) \
	{ \
		stn[x].fg=-stn[x].fg; \
		stn[x].ky=-stn[x].ky; \
		if(stn[x].bl+1
void SegTreeModify(int n,int ql,int qr)
{
	if(stn[n].bl>=ql&&stn[n].br<=qr)
	{
		stn[n].fg=-stn[n].fg;
		SegTreeFlag(n);
		return;
	}
	SegTreeFlag(n);
	if(stn[n*2].br>ql)
		SegTreeModify(n*2,ql,qr);
	else
		SegTreeFlag(n*2);
	if(stn[n*2+1].bl<qr)
		SegTreeModify(n*2+1,ql,qr);
	else
		SegTreeFlag(n*2+1);
	stn[n].ky=stn[n*2].ky+stn[n*2+1].ky;
}
int SegTreeQuery(int n,int ql,int qr)
{
	SegTreeFlag(n);
	if(stn[n].bl>=ql&&stn[n].br<=qr)
		return stn[n].ky;
	int s=0;
	if(stn[n*2].br>ql)
		s+=SegTreeQuery(n*2,ql,qr);
	else
		SegTreeFlag(n*2);
	if(stn[n*2+1].bl<qr)
		s+=SegTreeQuery(n*2+1,ql,qr);
	else
		SegTreeFlag(n*2+1);
	stn[n].ky=stn[n*2].ky+stn[n*2+1].ky;
	return s;
}
int lca(int u,int v)
{
	int s=0;
	if(vtx[u].gfa!=vtx[v].gfa)
	{
		if(vtx[vtx[u].gfa].dep<vtx[vtx[v].gfa].dep)
			u^=v^=u^=v;
		s=SegTreeQuery(1,vdg[u],vdg[vtx[u].gfa]+1)+lca(vtx[vtx[u].gfa].fah,v);
		SegTreeModify(1,vdg[u],vdg[vtx[u].gfa]+1);
	}
	else
	{
		if(vtx[u].dep<vtx[v].dep)
			u^=v^=u^=v;
		if(u!=v)
			s=SegTreeQuery(1,vdg[u],vdg[vtx[v].son]+1),
			SegTreeModify(1,vdg[u],vdg[vtx[v].son]+1);
	}
	return s;
}
int main()
{
	int a,b,m,n,t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			fdg[i]=NULL;
		for(int i=1;i<n;i++)
			scanf("%d %d",&edg[i+n].to,&edg[i].to),
			edg[i+n].nxt=fdg[edg[i].to],fdg[edg[i].to]=&edg[i+n],
			edg[i].nxt=fdg[edg[i+n].to],fdg[edg[i+n].to]=&edg[i];
		vtx[0].siz=vtx[1].fah=0,vtx[1].dep=1,dfs0(1);
		vdg[0]=0,vtx[1].gfa=1,dfs1(1);
		SegTreeBuild(1,1,n);
		scanf("%d",&m);
		n=0;
		while(m--)
			scanf("%d %d",&a,&b),
			printf("%d\n",n+=lca(a,b));
	}
	return 0;
}

E

题目大意:给 n 个区间,判断是否存在相交区间。

水题。代码如下:

#include
#include
#define MAX_N (100005)
struct POINT
{
	int x,y;
}itv[MAX_N+MAX_N];
bool cmp(POINT p,POINT q)
{
	return p.x<q.x;
}
int stk[MAX_N];
int main()
{
	bool flag;
	int n;
	while(scanf("%d",&n)==1)
	{
		for(int i=0;i<n;i++)
			scanf("%d %d",&itv[i].x,&itv[i+n].x),itv[i].y=i+1,itv[i+n].y=-i-1;
		std::sort(itv,itv+n+n,cmp);
		flag=true,stk[0]=0;
		for(int i=0;i<n+n&&flag;i++)
			if(itv[i].y>0)
				stk[++stk[0]]=itv[i].y;
			else if(-itv[i].y==stk[stk[0]])
				--stk[0];
			else
				flag=false;
		puts(flag?"NO":"YES");
	}
	return 0;
}

F

题目大意:设方程 a 1 x 1 + a 2 x 2 + . . . . + a n x n − d y = 0 a_1x_1 + a_2x_2 + .... + a_nx_n - dy = 0 a1x1+a2x2+....+anxndy=0 求最小正整数 y 使得该方程有正整数解。d ≤ 40,000,n ≤ 100,1 ≤ a i a_i ai 2 ∗ 1 0 9 2*10^9 2109

笔者能力所限,未能解决该问题。

G

题目大意:二分图最大匹配。

匈牙利算法,最大流算法均可。本题数据可能存在较大问题(过弱)。代码如下:

#include
#define MAX_K (1005)
#define MAX_N (1005)
struct E
{
	int to;
	E *nxt;
}edg[MAX_K+MAX_K],*fdg[MAX_N+MAX_N];
int m,mtc[MAX_N];
bool dfn[MAX_N];
bool dfs(int n)
{
	dfn[n-m]=true;
	for(E *i=fdg[n];i!=NULL;i=i->nxt)
		if(!dfn[mtc[i->to]-m]&&(mtc[i->to]==-1||dfs(mtc[i->to])))
		{
			mtc[i->to]=n;
			return true;
		}
	return false;
}
int main()
{
	int k,n,t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d %d %d",&n,&m,&k);
		for(int i=1;i<=m+n;i++)
			fdg[i]=NULL;
		while(k--)
			scanf("%d %d",&edg[k*2].to,&edg[k*2+1].to),edg[k*2].to+=m,
			edg[k*2].nxt=fdg[edg[k*2+1].to],fdg[edg[k*2+1].to]=&edg[k*2],
			edg[k*2+1].nxt=fdg[edg[k*2].to],fdg[edg[k*2].to]=&edg[k*2+1];
		k=0;
		for(int i=1;i<=m;i++)
			mtc[i]=-1;
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
				dfn[j]=false;
			if(dfs(i+m))
				k++;
		}
		printf("%d\n",k);
	}
	return 0;
}

H

题目大意:在 N * M 棋盘上,求马由 (1, 1) 到达 (a, b) 最小步数。数据范围 1 0 18 10^{18} 1018

笔者能力所限,未能解决该问题。

笔者的想法是,min(a, b) ≤ 3 时找规律,小范围数据暴力解决,大数据找规律。翻转使得终点位于 3 到 4 点半方向,当位于 3 到 4 点钟方向时,通过最多三次 1 点钟或 5 点钟马步和若干可计算次 2 点钟和 4 点钟马步到达;当位于 4 到 4 点半方向时,通过最多二次 2 点钟或 7 点钟马步和若干可计算次 4 点钟和 5 点钟马步到达。不过,该想法仍存在问题。

I

题目大意:给 n 个非负整数 x 1.. n x_{1 .. n} x1..n,将其划为三部分,设三部分数个数分别为 n 1 n_1 n1 n 2 n_2 n2 n 3 n_3 n3,每部分数中最大值分别为 h 1 h_1 h1 h 2 h_2 h2 h 3 h_3 h3,求 ( n 1 + n 3 ) ∗ m a x ( h 1 , h 3 ) + n 2 ∗ h 2 (n_1 + n_3) * max(h_1, h_3) + n_2 * h_2 (n1+n3)max(h1,h3)+n2h2 的最小值。 n 1 n_1 n1 n 2 n_2 n2 n 3 n_3 n3 可以为 0。

将原数列首尾相接形成数环,则原问题等价于将数环分为两部分,数个数分别为 n 1 n_1 n1 n 2 n_2 n2,最大值分别为 h 1 h_1 h1 h 2 h_2 h2,求 n 1 ∗ h 1 + n 2 ∗ h 2 n_1 * h_1 + n_2 * h_2 n1h1+n2h2 的最小值。令 h 1 = m a x ( x 1.. n ) h_1 = max(x_{1 .. n}) h1=max(x1..n),即第一部分包含数列中最大值,可得 h 1 ≥ h 2 h_1 ≥ h_2 h1h2。考虑任一种方案,第二部分为 x L 2 x_{L2} xL2 x R 2 x_{R2} xR2(则在数环意义上,第一部分为 x R 2 + 1 x_{R2 + 1} xR2+1 x L 2 − 1 x_{L2 - 1} xL21),对于 x L 2 − 1 x_{L2 - 1} xL21 x L 2 x_{L2} xL2 之间的关系,若有 x L 2 − 1 ≤ x L 2 x_{L2 - 1} ≤ x_{L2} xL21xL2,则将 x L 2 − 1 x_{L2 - 1} xL21 划给第二部分的方案显然更优(因为如此一来, n 1 n_1 n1 减一, n 2 n_2 n2 加一, h 2 h_2 h2 不变且仍小于 h 1 h_1 h1)。于是,最优方案必有如下性质: x L 2 − 1 > x L 2 x_{L2 - 1} > x_{L2} xL21>xL2 x R 2 < x R 2 + 1 x_{R2} < x_{R2 + 1} xR2<xR2+1

搜索,每次选取数环第二部分中最大数位置,将最大数连同其左边所有数或右边所有数全部划为第一部分,更新最优解,并递归搜索下去。最优解如上性质必定会被搜索到,即使存在相同数也不影响搜索顺序(若相同数比最优解 h 2 h_2 h2 大,则相同数全部划为第一部分后必存在一颗搜索子树为最优解;若相同数不比最优解 h 2 h_2 h2 大,则在搜索相同数之前就已经搜索到最优解)。处理时通过将数列最大值循环移动至数列首部使得第二部分连续,并需 ST 表实现 RMQ 功能。代码如下:

#include
#define MAX_N (10000005)
#define MAX_2N (0x1FFFFFF)
#define INF (0x7FFFFFFFFFFFFFFFll)
#define MinLL(a,b) \
({ \
	long long __tmp_a=(a),__tmp_b=(b); \
	__tmp_a<__tmp_b?__tmp_a:__tmp_b; \
})
#define MaxInx(a,b) \
({ \
	int __tmp_a=(a),__tmp_b=(b); \
	x[__tmp_a]>x[__tmp_b]?__tmp_a:__tmp_b; \
})
int m,n,st[MAX_2N],x[MAX_N],xt[MAX_N];
long long ans;
int MaxST(int f,int l,int r,int p,int m)
{
	if(l>r)
		return 0;
	int lt=p<<f,md=p<<f|1<<f-1,rt=p+1<<f;
	if(l<=lt&&r>=rt-1)
		return st[1<<m-f|p];
	if(r<md)
		return MaxST(f-1,l,r,p*2,m);
	if(l>=md)
		return MaxST(f-1,l,r,p*2+1,m);
	return MaxInx(MaxST(f-1,l,r,p*2,m),MaxST(f-1,l,r,p*2+1,m));
}
void dfs(int lt,int rt)
{
	if(lt==rt)
	{
		ans=MinLL(ans,(n-1)*(long long)x[0]+x[lt]);
		return;
	}
	if((n-rt+lt-1)*(long long)x[0]>ans)
		return;
	int md=MaxST(m,lt,rt,0,m);
	ans=MinLL(ans,(n-rt+lt-1)*(long long)x[0]+(rt-lt+1)*(long long)x[md]);
	if(lt<md)
		dfs(lt,md-1);
	if(md<rt)
		dfs(md+1,rt);
}
int main()
{
	int a,b,mod,num,s,t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d %d",&m,&n);
		xt[0]=0;
		while(m--)
		{
			scanf("%d %d %d %d %d",&num,&s,&a,&b,&mod);
			while(num--)
				xt[++xt[0]]=s,s=(s*(long long)a+b)%mod;
		}
		for(m=0,--xt[0];xt[0]>0;m++,xt[0]>>=1);
		for(int i=1;i<=n;i++)
			if(xt[xt[0]]<xt[i])
				xt[0]=i;
		for(int i=0;i<n;i++)
			x[i]=xt[(xt[0]+i-1)%n+1];
		for(int i=0;i<MAX_2N;i++)
			st[i]=0;
		for(int i=0;i<n;i++)
			st[1<<m|i]=i;
		for(int i=0;i<m;i++)
			for(int j=0;j<<i+1<n;j++)
				st[1<<m-i-1|j]=MaxInx(st[1<<m-i|j*2],st[1<<m-i|j*2+1]);
		ans=INF;
		dfs(1,n-1);
		printf("%lld\n",ans);
	}
	return 0;
}

J

题目大意:求 N! 的因子中有多少数恰有 75 个因子。

一个数有奇数个因子当且仅当它为完全平方数。 75 = 3 ∗ 5 ∗ 5 = 5 ∗ 15 = 3 ∗ 25 = 75 75 = 3 * 5 * 5 = 5 * 15 = 3 * 25 = 75 75=355=515=325=75,即有 75 个因子的数 X = p 1 2 ∗ p 2 4 ∗ p 3 4 X = p_1^2 * p_2^4 * p_3^4 X=p12p24p34 X = p 1 4 ∗ p 2 14 X = p_1^4 * p_2^{14} X=p14p214 X = p 1 2 ∗ p 2 24 X = p_1^2 * p_2^{24} X=p12p224 X = p 1 74 X = p_1^{74} X=p174 p 1 , p 2 , p 3 p_1,p_2,p_3 p1,p2,p3 均为质数且互不相同)。

3 / 2 + 3 / 4 = 1 + 0 = 1
4 / 2 + 4 / 4 = 2 + 1 = 3
5 / 2 + 5 / 4 = 2 + 1 = 3
6 / 2 + 6 / 4 = 3 + 1 = 4
15 / 2 + 15 / 4 + 15 / 8 + 15 / 16 = 7 + 3 + 1 + 0 = 11
16 / 2 + 16 / 4 + 16 / 8 + 16 / 16 = 8 + 4 + 2 + 1 = 15
27 / 2 + 27 / 4 + 27 / 8 + 27 / 16 = 13 + 6 + 3 + 1 = 23
28 / 2 + 28 / 4 + 28 / 8 + 28 / 16 = 14 + 7 + 3 + 1 = 25
77 / 2 + 77 / 4 + 77 / 8 + 77 / 16 + 77 / 32 + 77 / 64 = 38 + 19 + 9 + 4 + 2 + 1 = 73
78 / 2 + 78 / 4 + 78 / 8 + 78 / 16 + 78 / 32 + 78 / 64 = 39 + 19 + 9 + 4 + 2 + 1 = 74

4! 可提供 2 个质因子 2,6! 可提供 4 个质因子 2,16! 可提供 14 个质因子 2,28! 可提供 24 个质因子 2,78! 可提供 74 个质因子 2。

5 / 3 = 1
6 / 3 = 2
8 / 3 + 8 / 9 = 2 + 0 = 2
9 / 3 + 9 / 9 = 3 + 1 = 4
29 / 3 + 29 / 9 + 29 / 27 = 9 + 3 + 1 = 13
30 / 3 + 30 / 9 + 30 / 27 = 10 + 3 + 1 = 14
53 / 3 + 53 / 9 + 53 / 27 = 17 + 5 + 1 = 23
54 / 3 + 54 / 9 + 54 / 27 = 18 + 6 + 2 = 26

6! 可提供 2 个质因子 3,9! 可提供 4 个质因子 3,30! 可提供 14 个质因子 3,54! 可提供 24 个质因子 3。

59 / 5 + 59 / 25 = 11 + 2 = 13
60 / 5 + 60 / 25 = 12 + 2 = 14
99 / 5 + 99 / 25 = 19 + 3 = 22
100 / 5 + 100 / 25 = 20 + 4 = 24

60! 可提供 14 个质因子 5,100! 可提供 24 个质因子 5。

90 / 7 + 90 / 49 = 12 + 1 = 13
91 / 7 + 91 / 49 = 13 + 1 = 14

91! 可提供 14 个质因子 7。

当 p > 4 时,(2p)! 可提供 2 个质因子 p,(4p)! 可提供 4 个质因子 p。

打表即可。代码如下:

#include
#define MAX_N (105)
bool prime(int x)
{
	if(x<2)
		return false;
	for(int i=2;i*i<=x;i++)
		if(x%i==0)
			return false;
	return true;
}
int ans[MAX_N];
int main()
{
	int n,s02,s04,s14,s24;
	ans[0]=s02=s04=s14=s24=0;
	for(int i=1;i<=100;i++)
	{
		ans[i]=ans[i-1];
		if(i%2==0&&prime(i/2))
			ans[i]+=s04*(s04-1)/2+s24,s02++;
		if(i==6||i==9||i!=8&&i!=12&&i%4==0&&prime(i/4))
			ans[i]+=(s02-2)*s04+s14,s04++;
		if(i==16||i==30||i==60||i==91)
			ans[i]+=s04-1,s14++;
		if(i==28||i==54||i==100)
			ans[i]+=s02-1,s24++;
		if(i==78)
			ans[i]++;
	}
	while(scanf("%d",&n)==1)
		printf("%d\n",ans[n]);
	return 0;
}

K

题目大意:给 N 个点无根树,边权为 1,求路径长为奇的路径数。

以点 1 为根建树,设 S i , 0 S_{i, 0} Si,0 为以 i 为根子树中距根长度为偶数的点数(包括根), S i , 1 S_{i, 1} Si,1 为以 i 为根子树中距根长度为奇数的点数。考虑点对 (s, t),设其最近公共祖先为 lca(s, t),则路径 s - lca(s, t) - t 和路径 s - lca(s, t) - 1 - lca(s, t) - t 长度奇偶性相同,树上任一路径均可看作经过根的长度奇偶性相同的等价路径,由奇数 = 偶数 + 奇数可知,则 S 1 , 0 ∗ S 1 , 1 S_{1, 0} * S_{1, 1} S1,0S1,1 即为所求。代码如下:

#include
#define MAX_N (100005)
int s[MAX_N][2];
struct E
{
	int to;
	E *nxt;
}edg[MAX_N+MAX_N],*vtx[MAX_N];
void dfs(int x)
{
	s[x][0]=1,s[x][1]=0;
	for(E *i=vtx[x];i!=NULL;i=i->nxt)
		if(s[i->to][0]==0)
			dfs(i->to),
			s[x][0]+=s[i->to][1],
			s[x][1]+=s[i->to][0];
}
int main()
{
	int t,n;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			vtx[i]=NULL,s[i][0]=0;
		for(int i=1;i<n;i++)
			scanf("%d %d",&edg[i*2].to,&edg[i*2-1].to),
			edg[i*2].nxt=vtx[edg[i*2-1].to],vtx[edg[i*2-1].to]=&edg[i*2],
			edg[i*2-1].nxt=vtx[edg[i*2].to],vtx[edg[i*2].to]=&edg[i*2-1];
		dfs(1);
		printf("%lld\n",s[1][0]*(long long)s[1][1]);
	}
	return 0;
}

L

题目大意:给 n ( ≤ 1 0 9 ) , k ( ≤ 1000 ) , a 1.. k , f 0.. k − 1 n(≤ 10^9), k(≤ 1000), a_{1 .. k}, f_{0 .. k - 1} n(109),k(1000),a1..k,f0..k1,已知 f n = ∑ j = 1 k a j ∗ f n − j f_n = \sum_{j=1}^k a_j*f_{n-j} fn=j=1kajfnj,求 f n f_n fn mod 1e9+7。

笔者能力所限,未能解决该问题。

Epilogue

笔者有幸参与此次比赛,无奈才疏学浅,仅 AC 二题(B 题和 K 题)。L 题开始想借矩阵快速幂之力,但矩阵乘法时间复杂度 k 3 k^3 k3 直接 TLE。J 题考场中程序已然写成,不幸代码中一个常数算错而 WA。E 题代码 WA 而检查无果,考后发现一条调试输出语句忘删。HF 题均非力所能及,直接放弃。ACG 题未加细看便直接放弃,但事实上均可做。D 题现场便想出数剖套线段树的做法,但数剖代码过长线段树长时未写而放弃。最后编写 I 题,直到考试结束都笔者都未能将 I 题编完,不过现在看来当时的贪心思路偏差较大,就算编完也只能以 WA 收尾。从此次比赛看来,HIT 藏龙卧虎,算法竞赛之路任重而道远。

你可能感兴趣的:(题解)