NOIP 提高组爆零祭

Day1 BC

打了一个小时的CF跑去睡觉了,就会做前三题,预感不是很好

At night

在睡觉的时候,梦到自己考试一直在码一道题目,然后直接在考试完之后直接爆炸?

啊,这。。。

出发

好紧张好紧张!!!!

考试

考试前五分钟,我对同学说不会考字符串题吧,他说不会不会,你不要奶。

然后考试一开始,打开题面一看,"string"。

《不会考字符串》

看题还是花了一会时间,第一道题不会这么简单吧,一道比较水的拓扑排序。

第二道题目发现循环节长度一定是最小循环节的倍数,然后又发现只用得到第一个循环节和第二个循环节,所以就是类似枚举因子一样的东西,这个东西\(nlogn\)预处理就行了。

但是要带个\(27\)的常数,感觉会爆炸,敢打敢AC,应该有84分的部分分。

此时还剩三个小时,看了眼第三题和第四题,感觉第四题应该是枚举哪一步会出边界,然后判断不同的区域会在\(kn+i\)步出局?感觉细节太多了,没有细想,感觉第三题暴力感十足。直到最后五分钟才发现最后一道题目暴力很好打,但是我打代码速度比较慢

然后发现第三道题其实可以暴力枚举让颜色直接到空柱上,暴力把一个柱子上的颜色搬到另外一个柱子上,大概次数的上界是\(3n^2m\),稳过40分,期望60分,但是一个小时打完,结果还是没有调出来,最后过了check(这个check我根本不知道怎么用,还是用dev-cpp的参数搞的),然后觉得可以优化,换了一个绝对不会出错的优化,结果过不了check了,绝对是代码原本就有问题,最后没调出来,换回原来的代码,遗憾离场。

赛后

听别人都是估分200+,后来发现拓扑没有判除了\(1\)-\(m\)以外的入度为\(0\)的点,这100%卡啊,那就没分了,仁慈点说不定有20,QAQ。

估分0+84+20(小数据不至于卡我吧)=104分。

同机房一个大佬看错题了,不然他也会很高,另外一个大佬发挥好了,虽然他自己说230,但是我感觉他那样骗分应该有280,而且他第二题跑的贼快???也不知道加了什么神奇的东西。

回去冲文化课啦QAQ,也许用一句话形容我这次听课最为贴切:赌徒赌徒,赌到最后,一无所有。

算了,不管了,冲冲冲!!!!!

赛赛后

看知乎说第一题不会出现\(1\)-\(m\)以外的点入度为0的情况,但是会爆long long?

( •̀ ω •́ )y多估\(50\)分,\(154\)分,进入\(154\)分神教。

成绩出来之后

Woc,90+100+50=240分,这也太好运了吧。

第三道题目的暴力竟然在不打错代码的情况下可以过?

甚至我自己都卡不掉我自己????

打错了代码还有50???

第二道题\(27Tn+nlogn\)能过!!!CCF真的换了贼NB的机子!!!

现在就是专心准备期末考了,这个考试要是炸了,就真的没了。

题解

A

其实暴力判断一下拓扑就行了。

但是需要注意,结果可能会爆longlong,我同学用double过了???

至于为什么,先看:\(60^{11}\)大于\(longlong\)的最大值。

构造方法也是非常的简单,只要搞一个4,一个3,一个5,然后就可以很快的卡到\(60\),同时搞\(11\)层,大概卡法就是如此。

B

这道题目需要你明白一个定理:一个字符串的所有循环节都是最小循环节的倍数,这个定理的证明非常的简单,但是呢,我不想在这里讲,而是未来的一篇可持久化KMP里面讲。

那么不妨从右到左枚举C的长度,转而求\(AB\)

\(S_{[a,b]}\)为字符串\(S\)\([a,b]\)区间的子串,设\(C\)的长度为\(n-i\),那么其实就是求\(S_{[1,i]}\)的循环节,设\(f[i][j]\),表示在\(S_{[1,i]}\)中,有多少个\(j(j∈[1,i])\)满足\(S_{[1,j]}\)出现奇数次的字符小于等于\(j\)

这个可以前缀和处理,这个就是\(27Tn\)复杂度的由来,瓶颈也就是在这。

那么先讲一个大概正确的转移,后面会讲如何修正错误。

对于长度为\(n-i\)\(C\),且其出现了奇数次字符的个数为\(k\),那么其贡献为,且\(t\)\(S_{[1,i]}\)的最小循环节长度:
\(\sum\limits_{j|i,t|j}f[j][k]\)

那么这个时候就有人好奇要问了,你这不是欺诈吗,这怎么看枚举因子都要\(Tnlogn\)吗,怎么就\(27\)了?

可以视作27

怎么可能这么草率。

我们发现,一个字符串重复出现两次,那么其中每个字符都只会出现一次,所以我们就需要考虑一下能否优化一下这个枚举过程。

对于\(3*t\)长度的循环节而言,我们研究一下其和\(f[t][k]\)以及\(f[2t][k]\)的关系,因为\(f[t][k]\)中的每个字符经过\(2*t\)的长度并不会影响每个字符的奇偶性质,所以\(f[3t][k]=f[t][k]*2+f[2t][k]\)

更加简单的来说:

对于\(u*t\)而言,\(f[u*t][k]=\left \lfloor \frac{u}{2} \right \rfloor*f[2t][k]+\left \lceil \frac{u}{2} \right \rceil*f[t][k]\)

于是,我们可以提前预处理\(i\)的因子向下取整和和向上取整和,分别记作\(g1,g2\),时间复杂度:\(nlogn\)

那么贡献式子简化乘\(g1[i]*f[t][k]+g2[i]*f[2t][k]\)

当然,这是不是最终的答案呢?

前面也说了,大概正确,为什么还没有绝对正确呢?

想想看\(f\)的定义,单纯决定了\(A\)而没有考虑\(B\)不能是空串。

那么这个该怎么解决?发现如果\(S_{[1,t]}\)含有的出现次数为奇数的字符数量小于\(k\),那么\(S_{[1,u*t]}(u\%2=1)\)也都满足要求,但是实际上,\(A=S_{[1,t]},B=Ø\),偶数也是类似,因此只要在预处理因子的时候处理一下即可。

只要在贡献中减去即可。

#include<cstdio>
#include<cstring>
#define  N  1100000
#define  SN  30
using  namespace  std;
typedef  long  long  LL;
int  f[N][SN],t[N]/*用来记录1到这个位置的奇数个数*/;
LL  yu1[N],yu2[N]/*分别用来记录这个约数的第一块的分量和第二个块的分量*/,yuu1[N],yuu2[N];//约数和 
int  sta[SN],now;//用来存目前奇数个字符有多少个 
inline  void  check(int  k){sta[k]++;now+=(sta[k]&1)?1:-1;}
LL  ans=0;
int  n;
char  st[N];int  kmp[N];
int  main()
{
	const  int  limit=1048580;
	for(int  i=1;i<=limit;i++)
	{
		for(int  j=i;j<=limit;j+=i)yu2[j]+=i>>1,yu1[j]+=(i+1)>>1,yuu1[j]+=(i&1),yuu2[j]+=!(i&1)/*分别表示奇数的末尾和偶数的末尾*/;
	}
	//一个nlogn的预处理 
	int  T;scanf("%d",&T);
	while(T--)
	{
		ans=0;
//		scanf("%d%d",&n,&m);
		scanf("%s",st+1);n=strlen(st+1);
		int  k=-1;kmp[0]=-1;
		memset(f,0,sizeof(f));
		memset(sta,0,sizeof(sta));now=0;
		for(int  i=1;i<=n;i++)
		{
			check(st[i]-'a');t[i]=now;
			for(int  j=now;j<=27;j++)f[i][j]++;//记录每个奇数的出现次数 
			while(k>=0  &&  st[i]!=st[k+1])k=kmp[k];
			kmp[i]=++k;
		}
		for(int  i=2;i<=n;i++)
		{
			for(int  j=0;j<=27;j++)f[i][j]+=f[i-1][j];
		}
		memset(sta,0,sizeof(sta));now=0;check(st[n]-'a');
		for(int  i=n-1;i>=2;i--)
		{
			int  k=i-kmp[i];
			if(i%k!=0)k=i;
			LL  k1=f[k][now],k2=0,j=i/k;
			if(k+k<=i/*可以容纳偶数块*/)k2=f[k+k][now]-k1;
			ans+=yu1[j]*k1+yu2[j]*k2;
			if(t[k]<=now/*这个位置也是满足要求的*/)ans-=yuu1[j];
			if(k+k<=i  &&  t[k+k]<=now)ans-=yuu2[j];
			check(st[i]-'a');
		}
		printf("%lld\n",ans);
	}
	return  0;
}

当然,CCF机子快了,不代表洛谷就快了啊。

其实是可以优化的,发现前缀和部分每个位置其实对于每个\(i\)只会修改一次前缀和,同时查询前缀和时也只用查询\(2n\)次,那么可以把查询全部离线,然后用树状数组最后扫描一遍数组,时间复杂度就变成了:

\(O(n\log{n}+Tn\log{27})\)

应该可以通过此题,我没有去交。

C

想了一个优秀的暴力,真是lucky。

首先,考虑一个柱子上都是同一种颜色,那么实际上来讲就是把一个柱子上的同种颜色的球全部带到这个柱子上。

这样我们只要执行\(n\)次即可。

然后考虑现在我们要把 \(nowcol\) 颜色的球放到 \(id0\) 位置上,并依次考虑现有的柱子中颜色为 \(nowcol\) 的球,假设现在在考虑第 \(x\) 号柱子,最简单的思路就是把这个球放到其余的位置,并且把 \(nowcol\) 放到 \(id0\) 上,但事实上有时候是需要把除了 \(nowcol\) 球放到\(id0\)上的,这样就不能把 \(nowcol\) 的球放到 \(id0\) 上了,这个时候我们用 \(fang\) 记录一个柱子,并且记录一下这个目前柱子上有 \(cocnt\)\(nowcol\) 的球,那么这个时候只要让 \(fang\) 这个柱子空出来 \(cocnt\) 个位置即可。

让柱子空固定个位置的方法有很多,同时如果进行完这个操作之后,我们就需要处理\(id0\)上面的不是 \(nowcol\) 颜色的求了,其实只要暴力移动到\(x\)即可(不难发现不会发生任何问题),同时别忘了把原本属于\(fang\)的球移回去即可。

由于这个做法本身就是\(n^2m\)的,同时还要清空柱子、移动柱子、移回柱子、移回fang,本身带个\(4\)的常数,但是能过,真的是非常的\(luck\),甚至我自己都卡不掉我自己,我也不知道为什么。

当然,仔细想想,其实应该是比\(2\)稍稍大一点的常数,因为做法本身是\(\frac{n*(n+1)}{2}*m\)的。

#include<cstdio>
#include<cstring>
#define  N  60
#define  M  410
using  namespace  std;
inline  void  getz(int  &x)
{
	x=0;char  c=getchar();
	while(c>'9'  ||  c<'0')c=getchar();
	while(c<='9'  &&  c>='0')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
int  n,m;
struct  node
{
	int  x,y;
}sta[1100000];int  top;
int  a[N][M];//第0位存这个位置还剩几个数字
inline  bool  pd1(int  x){return  a[x][0]>=0;}
inline  bool  pd2(int  x){return  a[x][0]<=m;}
inline  void  move(int  x,int  y)
{
	++top;sta[top].x=x;sta[top].y=y;
	a[y][a[y][0]+1]=a[x][a[x][0]];
	a[x][0]--;a[y][0]++;
}
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
bool  v1[N];//检验这个柱子是不是已经塞满了的柱子 
bool  v2[N];//检验这个颜色是不是已经被选用的颜色 
int  st0[N],to0;//用来储存那些没有满的柱子的位置 
int  depcnt[N],depsum[N];
int  id0/*0个东西的柱子在的位置*/,nowcol,nowcnt/*现在在id0这个柱子上已经有多少个这种颜色*/;
int  bk,temp;//现在id0放了多少SB东西 
void  solve3(int  x)//使第x个柱子是空的 
{
	for(int  i=1;i<=n;i++)
	{
		if(!v1[i]  &&  i!=x)
		{
			while(a[i][0]<m)move(x,i);
		}
	}
}
void  solve4(int  x,int  k,int  goal)//使得x这个柱子上面空的地方不超过k,当然,还要注意nowcnt 
{
	if(a[x][0]>k/*多了,需要放到一个地方,当然,需要注意的是,可以放到goal,此时就是往回放*/)
	{
		for(int  i=1;i<=n  &&  a[x][0]>k;i++)
		{
			if(i!=x  &&  i!=id0  &&  !v1[i]  &&  a[i][0]<m)
			{
				while(a[x][0]>k  &&  a[i][0]<m)
				{
					if(a[x][a[x][0]]==nowcol)move(x,id0),nowcnt++;
					else  move(x,i);
				}
			}
		}
		if(a[x][0]>k)
		{
			while(a[x][0]>k)move(x,id0),bk++,temp++;
		}
	}
	else  if(a[x][0]<k)
	{
		while(a[x][0]<k  &&  a[goal][0])//这个位置还有!!! 
		{
			if(a[goal][a[goal][0]]==nowcol)move(goal,id0),nowcnt++,k++/*因为这样子放k会相应的变大 */;
			else  move(goal,x);
		}
		if(!a[goal][0])return  ;//直接就空了我玩你妈 
	}
}
inline  int  calc(int  x){return  m-a[x][0];}
void  solve2(int  x)
{
	int  mindep=0,cocnt=0;
	for(int  i=a[x][0];i>=1;i--)
	{
		if(a[x][i]==nowcol)mindep=i,cocnt++;
	}
	if(!cocnt)return  ;
	int  fang=id0;
	if((m-calc(x)-calc(id0))<(a[x][0]-mindep+1-cocnt)/*承载不了*/)
	{
		for(int  i=1;i<=n;i++)
		{
			if(!v1[i]  &&  i!=id0  &&  i!=x)
			{
				fang=i;
				break;
			}
		}
		solve4(fang,m-cocnt,x);//给我折腾出这么多的位置 
		if(a[x][0]<mindep/*我玩你妈*/)return  ;
	}
	to0=0;
	for(int  i=1;i<=n;i++)
	{
		if(a[i][0]==m)continue;
		if(!v1[i]  &&  i!=x  &&  i!=fang  &&  a[i][0]<m  &&  i!=id0)st0[++to0]=i;
	}
	if(a[id0][0]<m)st0[++to0]=id0;
	int  tonow=1;
	while(a[x][0]>=mindep)
	{
		if(a[x][a[x][0]]==nowcol)move(x,fang),nowcnt+=(fang==id0);
		else
		{
			move(x,st0[tonow]);
			if(st0[tonow]==id0)bk++;
			if(a[st0[tonow]][0]==m)tonow++;
		}
	}
	while(bk)move(id0,x),bk--;
	if(fang!=id0)
	{
		while(a[fang][0]  &&  a[fang][a[fang][0]]==nowcol)move(fang,id0),nowcnt++;
	}
	while(temp)
	{
		if(a[x][a[x][0]]==nowcol)move(x,id0),nowcnt++;
		else  move(x,fang);
		temp--;
	}
}
int  tot=0;
void  solve1()
{
	tot++;
	memset(depsum,0,sizeof(depsum));
	for(int  i=1;i<=n;i++)
	{
		if(v1[i])continue;
		if(!a[i][0])
		{
			id0=i;
			continue;//找到存放这个新的颜色的地方 
		}
		for(int  j=1;j<n;j++)depcnt[j]=n+1;
		for(int  j=a[i][0];j>=1;j--)depcnt[a[i][j]]=j;
		for(int  j=1;j<n;j++)depsum[j]+=n+1-depcnt[j];
	}
	int  num=999999999;nowcol=0;nowcnt=0;
	for(int  i=1;i<n;i++)
	{
		if(!v2[i]  &&  depsum[i]<num)num=depsum[i],nowcol=i;
	}
	for(int  i=1;i<=n;i++)//对每个柱子进行检查 
	{
		if(!v1[i]  &&  i!=id0)
		{
			solve2(i);//对这个柱子进行清除
			if(nowcnt==m)//已经满了
			{
				v1[id0]=1;v2[nowcol]=1;
				solve3(i);//对这个柱子进行清除 
				return  ;
			}
		}
	}
}
int  main()
{
//	freopen("ball.in","r",stdin);
//	freopen("ball.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int  i=1;i<=n;i++)
	{
		a[i][0]=m;
		for(int  j=1;j<=m;j++)scanf("%d",&a[i][j]);
	}
	n++;
	for(int  i=1;i<n;i++)solve1();
	printf("%d\n",top);
	for(int  i=1;i<=top;i++)
	{
		printf("%d %d\n",sta[i].x,sta[i].y);
	 }
	return  0;
}

正确做法:

怎么可能是这么屮的做法。

后面看了题解,妙啊!!!

分治。

把一般的颜色归为白色,一半的颜色归为黑色,那么就只需要把柱子全部分成只有黑色的柱子和只有白色的柱子即可。

这个做法本身\(nmlogn\),非常的优秀。

那么怎么做呢?

考虑\(n+1\)个柱子,那么如何处理呢?

注:该做法没有代码验证和图片,如不需要者可以跳过。

首先考虑如何把一个柱子变成上面和下面颜色不同的柱子,即要么上白下黑或者上黑下白。

首先选择两个满柱子,然后分别移走\(\left \lfloor \frac{m}{2} \right \rfloor\)个球到空柱子上,同时随便选择一个柱子命名为\(fang\),如果\(m\)不能被\(2\),那么就选择一个柱子多放一个,同时这个柱子命名为\(fang\)

对于除了这两个柱子外的\(n-1\)个柱子,看看其黑球多还是白球多,假设是黑球多,就把黑球优先移动到\(fang\),白球移动到\(fang\)外的另外一个柱子。

这样,\(fang\)这个柱子上面(不考虑\(fang\)原本有的,只考虑放的,下同)就全是黑色了,但是另外一个柱子既有黑也有白,先把\(fang\)上面的黑色移动回柱子上,对于另外一个柱子,顶上是黑色则移动回柱子上,白色则移动到\(fang\)上,直到放到这个柱子上的球全部被挪回去,最后再把\(fang\)上面的白球移回去。

这样有个什么好处?

柱子上面的同色球一定小于等于下面的同色球数量。

但是这两个非满柱子呢?考虑合起来后拆开一个合并好的柱子做类似的操作,然后等会合起来

考虑现在的 \(n\) 个柱子,如果有已经全是同一种颜色的柱子则不参与考虑。

先做出定义:
满单色柱子:全部都是一种颜色的满柱子(一般在形成之后排出考虑)。
分层(满)柱子:即上下分层的(满)柱子。
单色柱子:即全都是一种颜色的柱子,下文尤指因为分层柱子上方颜色的移动而形成的单色柱子。
压缩某个颜色:即把所有这个颜色的单色柱子尽量整合成满单色柱子,可能会剩下一个单色柱子(没满)。

开头选择一种颜色作为\(col\),然后\(coll\)就是反色。

  1. 如果都是分层满柱子,则选择一个柱子,以其上面的颜色作为\(col\),并且定义空柱子为\(fang\)
  2. 如果一开始没有分层柱子顶部为\(col\)颜色,结束讨论,依次考虑顶部为\(col\)颜色的分层柱子(优先考虑没有满的分层柱子),将顶部的球移动到\(fang\)柱子上,直到\(fang\)柱子被填满或者没有柱子上面是\(col\),跳到第3步。
  3. 压缩\(coll\),然后可能会形成没满的颜色为\(coll\)的单色柱子,则选择这个单色柱子为\(fang\)\(col=coll\),重新开始第二步,如果没有形成,则\(fang\)为空柱子,跳到第\(2\)步。

首先证明一个事情:
不可能出现没有非空柱子、非满单色柱子却有两个非满分层柱子。

因为过程中都是优先移动非满分层柱子,所以不可能出现顶部同色的两个非满分层柱子,考虑不同颜色,由构造方法可知:顶部同种颜色球的个数\(≤\left \lfloor \frac{m}{2} \right \rfloor\),所以 顶部同种颜色球的个数-1\(<\left \lfloor \frac{m}{2} \right \rfloor\),所以 \(2*\)顶部同种颜色球的个数\(<m\),则不可能出现这种情况,因为空出来的位置个数必须\(=m\)

接下来还有较为关键的一次讨论。

会跳出来只可能出现两种情况:

  1. 没有分层柱子,则绝对合并好了,结束讨论
  2. 有顶部同种颜色的分层柱子,记顶部颜色为\(col\),同时可能还有颜色为\(coll\)的单色柱子,开始执行下述讨论。

讨论:

  1. 如果没有分层柱子了,跳出讨论,如果没有这种\(coll\)的单色柱子,照样暴力移动顶部为\(col\)的球到空柱子上并且压缩\(coll\)
  2. 如果有,则考虑目前非满分层柱子,记为\(fang\),然后把\(coll\)的球全部放到\(fang\)上,然后依次考虑其余的分层柱子,在一个分层柱子移动完上面的\(col\)色球到空柱子之后,把\(fang\)上面的 \(coll\) 的球放到这个柱子上面,如果 \(fang\) 上面已经没有 \(coll\) 了,则优先考虑 \(fang\) 然后考虑其余分层柱子,填满空柱子,并且在填满之后压缩\(coll\),重新执行讨论。

现在证明,一定能在空柱子被填满之前清空 \(fang\) 上面的\(coll\)

\(coll\) 的球的个数为 \(cnt\) ,则一定有 \(cnt<\left \lfloor \frac{m}{2} \right \rfloor\) ,且由于每个柱子顶上的球最多也就 \(\left \lfloor \frac{m}{2} \right \rfloor\) ,所以最多 \(2*\left \lfloor \frac{m}{2} \right \rfloor-1\),不可能填满空柱子的。

那么这个的上界是多少呢?不难发现:压缩形成满单色柱子以及填满空柱子的代价都是\(m\)次,而且每次第二次讨论的第\(2\)步也最多只会浪费 \(m\)步,则随便估计一下是\(2nm\),然后再考虑整合的复杂度,则为\(4m+2nm\),则总的是\((4n+4)m\),带上天然的\(logn\),则为 \(4nm\log{n}\),算了一下:451,508,貌似不是很多,稳过。

但是不想打了,代码过于复杂。

D

这道题目我承认是我不行。

首先最关键的一步应当是考虑拆开每一维考虑贡献。

考虑处理出\(f[x][y]\)表示第\(x\)维的坐标为\(y\)的位置会在第几次走路后出局。

那么对于一个点\((a_1,a_2,a_3...,a_k)\),就会在走第 \(min_{i=1}^{k}(f[i][a_i])\) 步的时候出局。

如果想到这个,后面的就水到渠成了。

考虑只有一维,一开始这个人位于\(0\)号位置,然后走了\(n\)步,走到了最高位置为\(maxd\),走到的最低位置为\(mind\),最终位于的\(end\)位置,那么记周期值\(T=|end|\)为了方便下面的讨论,如果\(end<0\),则把所有的走的步数乘上\(-1\),同时交换\(maxd,mind\)

那么走了\(n\)步后有多少的位置不会走出界呢?显然是 \(w_{i}-maxd+mind(mind<0)\) ,那么,再多走\(n\)步,这个值便会减少\(T\)个,\(maxd\)会加上\(T\)(负数的情况就是不同走完\(n\)步,就能让所有位置出界)。

那么,现在证明,对于\(maxd<u≤T+maxd,g[u]=g[u-T]+n\)\(g[x]\) 就是 \(0\) 第一次走到 \(x\) 花的步数)。

考虑 \(0\)\(u-T\) 号位置花了 \(t\) 的步数,但是又花了 \(n-t\) 的步数走到了\(T\),同时又花了 \(t\) 的步数走到了\(u\),那么 \(g[u-T]=t,g[t]=t+n-t+t=n+t\),一目了然。

那么就简单多了,这个是具有周期性的。

扩展到\(2\)维也是如此,对于第一维坐标为\(x\)的点,其会做多少的贡献呢?重新定义\(a\)数组之间的小于号为\(f[i][a_{i}]<f[j][a_{j}]\)或者\(f[i][a_{i}]<f[j][a_{j}],i<j\),设有 \(k\) 个点对满足 \(a_{1}<a_{2}\),那么贡献就为\(k*f[1][x]\)

对于\(a_{1}\),设\(a_{2}\)为满足\(a_{1}<a_{2}\)\(f[2][a_2]\)最小的坐标,那么如果\(f[2][a_{2}]>n\)\(a_{1}>n\),设\(C(i)\)表示有多少个\(j\)满足\(f[i][j]≥f[i][a_{i}]\)

那么就为:\(\sum\limits_{j=0}^{C(i)-T_{i}*j≥0(1≤i≤k)}(C(2)-T_{2}*j)*(f[1][a_{1}]+n*j)\)

实际上,我这个式子已经写的极其提示了,随随便便就可以提升到更加高维的情况,假设现在\(a\)最小的是\(st0\)

那么就为\(\sum\limits_{j=0}^{C(i)-T_{i}*j≥0(1≤i≤k)}(\prod\limits_{t=1}^{t=k}(C(t)-T_{t}*j)*[t≠st0])*(f[st0][a_{st0}]+n*j)\)

那么怎么快速计算这个式子呢?

\(limit\)为最大的满足 \(C(i)-T_{i}*j≥0(1≤i≤k)\)\(j\)值,那么式子化简为:

\(\sum\limits_{j=0}^{limit}(\prod\limits_{t=1}^{t=k}(C(t)-T_{t}*j)*[t≠st0])*(f[st0][a_{st0}]+n*j)\)

试着暂时把\(C(st0)\)替换成\(f[st0][a_{st0}]\)\(T_{st0}\)换成\(-n\),那么式子又化简成:

\(\sum\limits_{j=0}^{limit}\prod\limits_{t=1}^{t=k}(C(t)-T_{t}*j)\)

为了方便,我们再把\(T_{k}\)全部取负:

\(\sum\limits_{j=0}^{limit}\prod\limits_{t=1}^{t=k}(C(t)+T_{t}*j)\)

这个该怎么搞呢?

考虑把这个式子展开一下就能看到猫腻了。

这个我就不打展开过程了,说说最终结果吧:

假定一个多项式是\(C(t)x+T_{t}\),然后有\(n\)个多项式,乘起来以后,\(dp[i]\)表示\(x^i\)的系数(这个做法应该叫生成函数),可以\(k^2\)背包处理,也可以闲着没事干\(klog^2\)多项式处理。

当然,\(nk^2\)能过的事情为什么要带个\(log\)

然后再设\(Y(i)\)\(1^i+2^i+...+limit^i\)。(也同样可以\(O(k^2)\)计算)

特殊的,认为\(0^0=1\)

那么式子式子就化为了:
\(\sum\limits_{i=0}^{k}Y(i)*dp[k-i]\)

而这个,是可以\(O(k)\)计算的。

但是有没有发现条件中要求\(f[i][a_{i}]>n(1≤i≤n)\),这个是为什么?

因为还记之前证明\(g[u]=g[u-T]+n\)的定理吗?一个十分重要的要求就是,就是\(maxd<u≤T+maxd\),所以如果\(f[i][a_{i}]>n(1≤i≤n)\)\(a_{i}\)是绝对能够满足这个要求的,而且处理方法也非常简单,只要处理\(2n\),然后在后\(n\)步处理即可(因为很明显,后\(n\)步产生的\(a_{i}\)互相影响不会重叠且绝对满足要求)。

无解条件可以自己手推一下。

时间复杂度:\(O(nk^2)\)

//时间复杂度O(nk^2)的做法 
#include<cstdio>
#include<cstring>
#define  N  510000
#define  K  30
using  namespace  std;
typedef  long  long  LL;
const  LL  mod=1e9+7;
template<class  T>
inline  void  getz(T  x,T  y){x^=y^=x^=y;}
template<class  T>
inline  T  zabs(T  x,T  y){return  x<0?-x:x;}
template<class  T>
inline  LL  ksm(LL  x,LL  y) 
{
	LL  ans=1;
	while(y)
	{
		if(y&1)ans=(ans*x)%mod;
		x=(x*x)%mod;y>>=1;
	}
	return  ans;
}
template<class  T>
inline  T  mymin(T  x,T  y){return  x<y?x:y;}
template<class  T>
inline  T  mymax(T  x,T  y){return  x>y?x:y;}
LL  f[N];//用来求1-limit的第i次幂之和的。 
LL  fc[K],nfc[K],ni[K]/*用来处理逆元的,这三个东西处理到30就完全够用了*/;
template<class  T>
inline  LL  findC(T  x,T  y){return  fc[y]*nfc[x]%mod*nfc[y-x];}//从y个当中选x个 
template<class  T>
inline  T  zabs(T  x){return  x<0?-x:x;}
template<class  T>
inline  T  findfu(T  x){return  (zabs(x)&1)?-1:1;}
template<class  T>
inline  void  findmi(LL  x,T  k)
{
	LL  now=f[0]=x%mod;
	for(int  i=1;i<=k;i++)
	{
		now=(now*x)%mod;
		f[i]=now;
		for(int  j=0;j<i;j++)
		{
			f[i]+=findfu(i+1-j)*findC(j,i+1)%mod*f[j]%mod;
			f[i]=(f[i]+mod)%mod;
		}
		f[i]=f[i]*ni[i+1]%mod;
	}
}//用来处理自然数幂的
struct  move
{
	int  x,y;
}mo[N];int  n,k,w[N]/*表示限制*/;
LL  dp[K],z[K],y[K]/*要么选择左边,要么选择右边*/;
inline  void  beibao()//一个k^2的背包,当然,其实这一部分可回溯背包也可以处理出来 
{
	memset(dp,0,sizeof(dp));dp[0]=1;
	for(int  i=1;i<=k;i++)
	{
		for(int  j=k;j>=1;j--)dp[j]=dp[j-1]*z[i]+dp[j]*y[i],dp[j]%=mod;
		dp[0]=dp[0]*y[i]%mod;
	}
}
int  line[K];//从0开始的异世界 
int  zhou[K],maxd[K],mind[K];
inline  int  find_cha(int  x){return  w[x]-maxd[x]+mind[x];}//表示这一维还剩下多少数字 
bool  pre_do()
{
	for(int  i=1;i<=n;i++)line[mo[i].x]+=mo[i].y,maxd[mo[i].x]=mymax(maxd[mo[i].x],line[mo[i].x]),mind[mo[i].x]=mymin(mind[mo[i].x],line[mo[i].x]);
	for(int  i=1;i<=k;i++)
	{
		if(find_cha(i)<=0  ||  line[i])return  1;
	}
	return  0;
}
LL  ans;
inline  bool  add_node(int  x,LL  val)//在第x个位置多了一个出界的点,返回值是是否接下来还能有人走出去
{
	LL  sum=1;
	for(int  i=1;i<=k;i++)
	{
		if(i==x)continue;
		sum=sum*find_cha(i)%mod;
	}
	ans=(ans+sum*val%mod)%mod;
	return  find_cha(x)>0;
}
inline  bool  solve1(int  x,LL  val)
{
	int  limit=999999999;//自然数最多到多少,这个自然数的大小取决于每次周期缩小能到多少 
	for(int  i=1;i<=k;i++)
	{
		if(zhou[i])limit=mymin((find_cha(i)-(i!=x))/zhou[i],limit);
	}
	findmi(limit,k);//直接自然数幂 
	f[0]++;
	for(int  i=1;i<=k;i++)
	{
		if(i!=x)z[i]=find_cha(i),y[i]=-zhou[i];//表示周期 
		else  z[i]=val,y[i]=n/*每多加一个周期就要加n*/;
	}
	beibao();
	LL  sum=0;
	for(int  i=0;i<=k;i++)sum+=dp[i]*f[k-i]%mod;
	ans=(ans+sum%mod+mod)%mod;
	return  find_cha(x)>0;
}
inline  bool  solve()//直接解决问题!!! 
{
	if(pre_do()==0)return  0;
	for(int  i=1;i<=k;i++)zhou[i]=zabs(line[i]),line[i]=maxd[i]=mind[i]=0; 
	//先处理前n步,这几步处理起来比较方便 
	for(int  i=1;i<=n;i++)
	{
		int  x=mo[i].x;line[x]+=mo[i].y;
		bool  bk=0;
		if(line[x]<mind[x])mind[x]=line[x],bk=1;
		else  if(line[x]>maxd[x])maxd[x]=line[x],bk=1;
		if(bk==1)
		{
			if(!add_node(x,i))return  1;
		}
	}
	//开始处理接下来2n步,这n步非常的NB,一旦遇到一个就直接的进行solve1
	for(int  i=1;i<=n;i++)
	{
		int  x=mo[i].x;line[x]+=mo[i].y;
		bool  bk=0;
		if(line[x]<mind[x])mind[x]=line[x],bk=1;
		else  if(line[x]>maxd[x])maxd[x]=line[x],bk=1;
		if(bk==1)
		{
			if(!solve1(x,i+n))return  1;
		}
	}
	return  1;
}
int  main()
{
	scanf("%d%d",&n,&k);
	for(int  i=1;i<=k;i++)scanf("%d",&w[i]);
	for(int  i=1;i<=n;i++)scanf("%d%d",&mo[i].x,&mo[i].y);
	fc[0]=nfc[0]=fc[1]=ni[1]=1;for(LL  i=2;i<=30;i++)ni[i]=ni[mod%i]*(mod-mod/i)%mod,fc[i]=fc[i-1]*i%mod;
	for(int  i=1;i<=30;i++)nfc[i]=nfc[i-1]*ni[i]%mod;
	if(solve()==0)printf("-1\n");
	else  printf("%lld\n",ans);
	return  0;
}

但事实上可以观察到\(limit\)全局只会变一次,背包也变得不多,可以用可回溯背包处理。

可以将时间复杂度优化到\(O(nk)\)

但是懒得打了。

小结

期末考RP++

posted @ 2020-12-06 16:02  敌敌畏58  阅读(170)  评论(0编辑  收藏  举报