HIT2018秋校赛 略解

二〇一九年二月五日本文发布
二〇二二年三月二十九日增补了 H 题题解
二〇二三年五月二十八日增补了 F 题题解
二〇二三年一十一月四日增补了 L 题题解

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<stdio.h>
#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<stdio.h>
#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<stdio.h>
#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<stdio.h>
#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<stn[x].br) \
			stn[(x)*2].fg=-stn[(x)*2].fg, \
			stn[(x)*2+1].fg=-stn[(x)*2+1].fg; \
	}
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<algorithm>
#include<stdio.h>
#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

图论。建 d 个点 dn 条边的有向图,设顶点集 { v 0 , v 1 , . . . , v d − 1 } \{v_0, v_1, ..., v_{d - 1}\} {v0,v1,...,vd1}。对于 a i a_i ai,从顶点 v j v_j vj 到顶点 v ( j + a i )   m o d   d v_{(j + a_i) \ mod \ d} v(j+ai) mod d 建权值为 ⌊ j + a i d ⌋ − ⌊ j d ⌋ \lfloor \frac{j + a_i}{d} \rfloor - \lfloor \frac{j}{d} \rfloor dj+aidj 的有向边,其中 0 ≤ j < d 0 ≤ j < d 0j<d。则原问题转化为该图上从顶点 v 0 v_0 v0 到其自身的非零最短环,第一步特殊处理后转化为最短路问题。

注意到边权值非负,故可用 Dijkstra 最短路算法即可解决该问题。代码如下:

#include<algorithm>
#include<stdio.h>
#define MAX_D (40005)
#define MAX_N (105)
#define HeapAsn(h,i,j,k) h[j]=k,i[h[j]]=j
#define HeapIns(h,i,k,cmp) \
{ \
	int __tmp_i=i[k]; \
	if(__tmp_i==-1) \
		__tmp_i=++h[0]; \
	while(__tmp_i>1) \
		if(cmp(k,h[__tmp_i/2])) \
			HeapAsn(h,i,__tmp_i,h[__tmp_i/2]), \
			__tmp_i/=2; \
		else \
			break; \
	HeapAsn(h,i,__tmp_i,k); \
}
#define HeapExt(h,i,cmp) \
({ \
	int __tmp_i=1,__tmp_k=h[1]; \
	i[h[1]]=-1; \
	while(__tmp_i*2<h[0]) \
	{ \
		int __tmp_j=__tmp_i*2; \
		if(__tmp_j+1<h[0]&&cmp(h[__tmp_j+1],h[__tmp_j])) \
			__tmp_j++; \
		if(cmp(h[__tmp_j],h[h[0]])) \
			HeapAsn(h,i,__tmp_i,h[__tmp_j]), \
			__tmp_i=__tmp_j; \
		else \
			break; \
	} \
	HeapAsn(h,i,__tmp_i,h[h[0]--]); \
	__tmp_k; \
})
#define cmpDst(p,q) (dst[p]<dst[q])
int a[MAX_N],dst[MAX_D],hep[MAX_D],idx[MAX_D];
int main()
{
	int d,n,r,s,t;
	while(1)
	{
		scanf("%d %d",&n,&d);
		if(n==0&&d==0)
			break;
		for(int i=0;i<n;i++)
			scanf("%d",&a[i]);
		std::sort(a,a+n);
		for(int i=0;i<d;i++)
			dst[i]=idx[i]=-1;
		hep[0]=0;
		for(int i=0;i<n;i++)
			if(dst[a[i]%d]==-1)
			{
				dst[a[i]%d]=a[i]/d;
				HeapIns(hep,idx,a[i]%d,cmpDst);
			}
		while(hep[0]>0)
		{
			r=HeapExt(hep,idx,cmpDst);
			for(int i=0;i<n;i++)
			{
				s=dst[r]+(r+a[i])/d,
				t=(r+a[i])%d;
				if(dst[t]==-1||dst[t]>s)
				{
					dst[t]=s;
					HeapIns(hep,idx,t,cmpDst);
				}
			}
		}
		printf("%d\n",dst[0]);
	}
	return 0;
}

G

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

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

#include<stdio.h>
#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 点钟马步到达。不过,该想法仍存在问题。

代码如下,不正确,仅供参考:

#include<stdio.h>
#define Read(x) \
{ \
	char __tmp_c=getchar(); \
	while(__tmp_c<'0'||__tmp_c>'9') \
		__tmp_c=getchar(); \
	for(x=0;__tmp_c>='0'&&__tmp_c<='9';__tmp_c=getchar()) \
		x=x*10+(__tmp_c-'0'); \
}
#define Write(x) \
{ \
	long long __tmp_x,__tmp_y=0; \
	for(__tmp_x=x;__tmp_x>0;__tmp_x/=10) \
		__tmp_y=__tmp_y*10+__tmp_x%10; \
	for(;__tmp_y>0;__tmp_y/=10) \
		putchar(__tmp_y%10+'0'); \
	if(x==0) \
		putchar('0'); \
	putchar('\n'); \
}
int main()
{
	int t;
	long long a,b,m,n,r;
	scanf("%d",&t);
	while(t--)
	{
		Read(n);Read(m);Read(a);Read(b); // 快速读写,原题用 cin/cout 超时
		a--,b--; // 起点 (0, 0),方便运用公式
		if(a>b) // 保证 a<=b,用对称性简化情形
			r=n,n=m,m=r,
			r=a,a=b,b=r;
		if(n==1||m==1) // n*1 或 1*m 棋盘
			if(a==0&&b==0)
				r=0;
			else
				r=-1;
		else if(n==2||m==2) // n*2 或 2*m 棋盘
			if(a*2==b%4) // 形如 (0, 4k), (1, 4k+2) 的点可到达
				r=b/2;
			else
				r=-1;
		else if(n==3&&m==3&&a==1&&b==1) // 3*3 棋盘,(1, 1) 特判,其它与通常情形相同
			r=-1;
		else if(n>=3&&m==4&&a==0&&b==3) // n*4 棋盘,(0, 3) 特判,其它与通常情形相同
			r=5;
		else if(a==0&&b==1) // 平凡棋盘,(0, 1) 特判,非公式情形
			r=3;
		else if(b==1||a==2&&b==2) // 平凡棋盘,(1, 1), (2, 2) 特判,非公式情形
			r=4;
		else if(a*2<b) // 公式情形,3 点到 4 点钟方向,分 4 种子情况
		{
			r=(b-a*2)%4;
			if(r>=2)
				r--; // 此时 r 是 1 点和 5 点和 7 点和 11 点钟马步数
			r+=b/2; // b/2 是 2 点和 4 点钟马步数
		}
		else // 公式情形,4 点到 4 点半方向,分 3 种子情况
			r=(a+b)/3+(a+b)%3; // (a+b)/3 是 4 点和 5 点钟马步数,(a+b)%3 是 2 点和 7 点钟马步数
		if(r!=-1)
			Write(r)
		else
			printf("-1\n");
	}
	return 0;
}

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<stdio.h>
#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<stdio.h>
#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<stdio.h>
#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。

矩阵快速幂+多项式优化。令
χ m = [ f m f m − 1 f m − 2 . . . f m − k + 1 ] , A = [ a 1 a 2 . . . a k − 1 a k 1 0 . . . 0 0 0 1 . . . 0 0 . . . 0 0 . . . 1 0 ] \chi_m=\begin{bmatrix} f_m \\ f_{m-1} \\ f_{m-2} \\ ... \\ f_{m-k+1} \end{bmatrix}, A=\begin{bmatrix} a_1 & a_2 & ... & a_{k-1} & a_k \\ 1 & 0 & ... & 0 & 0 \\ 0 & 1 & ... & 0 & 0 \\ ... \\ 0 & 0 & ... & 1 & 0 \end{bmatrix} χm= fmfm1fm2...fmk+1 ,A= a110...0a2010............ak1001ak000
则有
[ f m f m − 1 . . . f m − k + 1 ] = χ n = A χ n − 1 = . . . = A n χ 0 = A n [ f 0 f − 1 . . . f 1 − k ] \begin{bmatrix} f_m \\ f_{m-1} \\ ... \\ f_{m-k+1} \end{bmatrix}=\chi_n=A\chi_{n-1}=...=A^n\chi_0 = A^n\begin{bmatrix} f_0 \\ f_{-1} \\ ... \\ f_{1-k} \end{bmatrix} fmfm1...fmk+1 =χn=Aχn1=...=Anχ0=An f0f1...f1k
用矩阵快速幂求得 A n A^n An,即是朴素的矩阵快速幂算法。下面是多项式优化的内容。求矩阵 A A A 的特征多项式
∣ x I − A ∣ = ∣ x − a 1 − a 2 . . . − a k − 1 − a k − 1 x . . . 0 0 0 − 1 . . . 0 0 . . . 0 0 . . . − 1 x ∣ = x k − a 1 x k − 1 − a 2 x k − 2 − . . . − a k x 0 = p ( x ) \left| xI-A \right| = \begin{vmatrix} x-a_1 & -a_2 & ... & -a_{k-1} & -a_k \\ -1 & x & ... & 0 & 0 \\ 0 & -1 & ... & 0 & 0 \\ ... \\ 0 & 0 & ... & -1 & x \end{vmatrix} = x^k - a_1x^{k-1} - a_2x^{k-2} - ... - a_kx^0 = p(x) xIA= xa110...0a2x10............ak1001ak00x =xka1xk1a2xk2...akx0=p(x)
由特征多项式性质 p ( A ) = p ( x ) ∣ x = A = O p(A)=p(x)|_{x=A}=O p(A)=p(x)x=A=O,得(等式右边的 O O O 为全零方阵)
A k − a 1 A k − 1 − a 2 A k − 2 − . . . − a k A 0 = O A^k - a_1A^{k-1} - a_2A^{k-2} - ... - a_kA^0 = O Aka1Ak1a2Ak2...akA0=O
这说明 A m ( ∀ m ∈ N ) A^m(\forall m\in\mathbb{N}) Am(mN) 可由 A k − 1 , A k − 2 , . . . , A 0 A^{k-1}, A^{k-2}, ..., A^0 Ak1,Ak2,...,A0 k − 1 k-1 k1 个方阵线性表示。用多项式取模运算,求得
x n = p ( x ) q ( x ) + r ( x ) r ( x ) = r k − 1 x k − 1 + r k − 2 x k − 2 + . . . + r 0 x 0 x^n=p(x)q(x)+r(x) \\ r(x)=r_{k-1}x^{k-1}+r_{k-2}x^{k-2}+...+r_0x^0 xn=p(x)q(x)+r(x)r(x)=rk1xk1+rk2xk2+...+r0x0
其中 p ( x ) p(x) p(x) 为上文的特征多项式(最高 k k k 次), r ( x ) r(x) r(x) 为不超过 k − 1 k-1 k1 次的多项式, x n x^n xn 取模后各项系数可以用快速幂求。带入 A A A,即有
A n = r ( A ) = r k − 1 A k − 1 + r k − 2 A k − 2 + . . . + r 0 A 0 A^n=r(A)=r_{k-1}A^{k-1}+r_{k-2}A^{k-2}+...+r_0A^0 An=r(A)=rk1Ak1+rk2Ak2+...+r0A0
两边同时右乘 χ 0 \chi_0 χ0,得
[ f n f n − 1 . . . f n − k + 1 ] = A n χ 0 = r k − 1 A k − 1 χ 0 + r k − 2 A k − 2 χ 0 + . . . + r 0 A 0 χ 0 = r k − 1 [ f k − 1 f k − 2 . . . f 0 ] + r k − 2 [ f k − 2 f k − 3 . . . f − 1 ] + . . . + r 0 [ f 0 f − 1 . . . f 1 − k ] \begin{bmatrix} f_n \\ f_{n-1} \\ ... \\ f_{n-k+1} \end{bmatrix}=A^n\chi_0=r_{k-1}A^{k-1}\chi_0+r_{k-2}A^{k-2}\chi_0+...+r_0A^0\chi_0=r_{k-1}\begin{bmatrix} f_{k-1} \\ f_{k-2} \\ ... \\ f_0 \end{bmatrix}+r_{k-2}\begin{bmatrix} f_{k-2} \\ f_{k-3} \\ ... \\ f_{-1} \end{bmatrix}+...+r_0\begin{bmatrix} f_0 \\ f_{-1} \\ ... \\ f_{1-k} \end{bmatrix} fnfn1...fnk+1 =Anχ0=rk1Ak1χ0+rk2Ak2χ0+...+r0A0χ0=rk1 fk1fk2...f0 +rk2 fk2fk3...f1 +...+r0 f0f1...f1k
取第一行,得
f n = r k − 1 f k − 1 + r k − 2 f k − 2 + . . . + r 0 f 0 f_n=r_{k-1}f_{k-1}+r_{k-2}f_{k-2}+...+r_0f_0 fn=rk1fk1+rk2fk2+...+r0f0
算法时间复杂度 O ( k 2 log ⁡ n ) O(k^2\log n) O(k2logn),如果模数符合 NTT 特征,时间复杂度能降到 O ( k log ⁡ k log ⁡ n ) O(k\log k\log n) O(klogklogn)

多项式取模(系数反转),多项式求逆(倍增)的算法不再赘述。代码如下,只测过样例,仅供参考:

#include<stdio.h>
#define MAX_K (2050)
#define NUM_MOD (1000000007)
#define UINT_I(x) int x=1
#define UINT_MULMOD(a,b) (a*(long long)b%NUM_MOD)
#define IADD(x,y) x=(x+y)%NUM_MOD
#define POLY_I(x) POLY x={.siz=1,.coe={1}}
#define POLY_MULMOD(a,b) PolyMod(PolyMul(a,b))
#define POWER(a,n,T) \
({ \
    int __tmp_n=n; \
    T##_I(r); \
    while(__tmp_n>0) \
    { \
        if(__tmp_n&1==1) \
            r=T##_MULMOD(a,r); \
        a=T##_MULMOD(a,a); \
        __tmp_n>>=1; \
    } \
    r; \
})
struct POLY
{
    int siz,coe[MAX_K];
}a,av;
POLY PolyMul(POLY a,POLY b)
{
    POLY r={.siz=a.siz+b.siz-1,.coe={0}};
    for(int i=0;i<a.siz;i++)
        if(a.coe[i]!=0)
            for(int j=0;j<b.siz;j++)
                IADD(r.coe[i+j],UINT_MULMOD(a.coe[i],b.coe[j]));
    return r;
}
POLY PolyRev(POLY x)
{
    POLY r={.siz=x.siz,.coe={0}};
    for(int i=0;i<x.siz;i++)
        r.coe[i]=x.coe[x.siz-i-1];
    return r;
}
POLY PolySub(POLY a, POLY b)
{
    POLY r={.siz=b.siz,.coe={0}};
    if(a.siz>b.siz)
        r.siz=a.siz;
    for(int i=0;i<a.siz;i++)
        IADD(r.coe[i],a.coe[i]);
    for(int i=0;i<b.siz;i++)
        IADD(r.coe[i],NUM_MOD-b.coe[i]);
    return r;
}
POLY PolyDiv(POLY x)
{
    if(x.siz<a.siz)
        return (POLY){.siz=0,.coe={0}};
    POLY xr=PolyRev(x);
    av.siz=x.siz-a.siz+1;
    POLY r=PolyMul(xr,av);
    r.siz=av.siz,av.siz=a.siz-1;
    return PolyRev(r);
}
POLY PolyMod(POLY x)
{
    POLY q=PolyDiv(x);
    POLY r=PolySub(x,PolyMul(q,a));
    r.siz=a.siz-1;
    return r;
}
POLY PolyInv(POLY x,int p)
{
    POLY r={.siz=1,.coe={POWER(x.coe[0],NUM_MOD-2,UINT)}},t;
    for(int i=2;i<=p;i<<=1)
    {
        x.siz=i;
        t=PolyMul(x,r);
        for(int i=0;i<t.siz;i++)
            t.coe[i]=(NUM_MOD-t.coe[i])%NUM_MOD;
        IADD(t.coe[0],2);
        r=PolyMul(t,r);
        r.siz=i;
    }
    r.siz=p;
    return r;
}
int f[MAX_K];
int main()
{
    int k,n,s;
    scanf("%d%d",&n,&k);
    for(int i=k-1;i>=0;i--)
        scanf("%d",&s),
        a.coe[i]=(NUM_MOD-s)%NUM_MOD;
    for(int i=0;i<k;i++)
        scanf("%d",&s),
        f[i]=(NUM_MOD+s)%NUM_MOD;
    a.siz=k+1,a.coe[k]=1;
    POLY res={.siz=2,.coe={0,1}};
    av=PolyInv(PolyRev(a),k);
    res=POWER(res,n,POLY);
    s=0;
    for(int i=0;i<k;i++)
        IADD(s,UINT_MULMOD(res.coe[i],f[i]));
    printf("%d",s);
    return 0;
}

Epilogue

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值