Day1 BC

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

At night

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

啊,这。。。

出发

好紧张好紧张!!!!

考试

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

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

《不会考字符串》

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

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

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

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

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

赛后

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

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

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

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

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

赛赛后

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

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

成绩出来之后

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

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

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

打错了代码还有50???

第二道题 27 T n + n l o g n 27Tn+nlogn 27Tn+nlogn能过!!!CCF真的换了贼NB的机子!!!

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

题解

A

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

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

至于为什么,先看: 6 0 11 60^{11} 6011大于 l o n g l o n g longlong longlong的最大值。

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

B

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

那么不妨从右到左枚举C的长度,转而求 A B AB AB。

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

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

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

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

那么这个时候就有人好奇要问了,你这不是欺诈吗,这怎么看枚举因子都要 T n l o g n Tnlogn Tnlogn吗,怎么就 27 27 27了?

可以视作27

怎么可能这么草率。

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

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

更加简单的来说:

对于 u ∗ t u*t u∗t而言, f [ u ∗ t ] [ k ] = ⌊ u 2 ⌋ ∗ f [ 2 t ] [ k ] + ⌈ u 2 ⌉ ∗ f [ t ] [ k ] f[u*t][k]=\left \lfloor \frac{u}{2} \right \rfloor*f[2t][k]+\left \lceil \frac{u}{2} \right \rceil*f[t][k] f[u∗t][k]=⌊2u​⌋∗f[2t][k]+⌈2u​⌉∗f[t][k]。

于是,我们可以提前预处理 i i i的因子向下取整和和向上取整和,分别记作 g 1 , g 2 g1,g2 g1,g2,时间复杂度: n l o g n nlogn nlogn。

那么贡献式子简化乘 g 1 [ i ] ∗ f [ t ] [ k ] + g 2 [ i ] ∗ f [ 2 t ] [ k ] g1[i]*f[t][k]+g2[i]*f[2t][k] g1[i]∗f[t][k]+g2[i]∗f[2t][k]。

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

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

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

那么这个该怎么解决?发现如果 S [ 1 , t ] S_{[1,t]} S[1,t]​含有的出现次数为奇数的字符数量小于 k k k,那么 S [ 1 , u ∗ t ] ( u % 2 = 1 ) S_{[1,u*t]}(u\%2=1) S[1,u∗t]​(u%2=1)也都满足要求,但是实际上, A = S [ 1 , t ] , B = Ø A=S_{[1,t]},B=Ø 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 i i只会修改一次前缀和,同时查询前缀和时也只用查询 2 n 2n 2n次,那么可以把查询全部离线,然后用树状数组最后扫描一遍数组,时间复杂度就变成了:

O ( n log ⁡ n + T n log ⁡ 27 ) O(n\log{n}+Tn\log{27}) O(nlogn+Tnlog27)

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

C

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

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

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

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

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

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

当然,仔细想想,其实应该是比 2 2 2稍稍大一点的常数,因为做法本身是 n ∗ ( n + 1 ) 2 ∗ m \frac{n*(n+1)}{2}*m 2n∗(n+1)​∗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;
}

正确做法:

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

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

分治。

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

这个做法本身 n m l o g n nmlogn nmlogn,非常的优秀。

那么怎么做呢?

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

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

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

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

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

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

这样有个什么好处?

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

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

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

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

开头选择一种颜色作为 c o l col col,然后 c o l l coll coll就是反色。

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

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

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

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

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

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

讨论:

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

现在证明,一定能在空柱子被填满之前清空 f a n g fang fang 上面的 c o l l coll coll。

设 c o l l coll coll 的球的个数为 c n t cnt cnt ,则一定有 c n t < ⌊ m 2 ⌋ cnt<\left \lfloor \frac{m}{2} \right \rfloor cnt<⌊2m​⌋ ,且由于每个柱子顶上的球最多也就 ⌊ m 2 ⌋ \left \lfloor \frac{m}{2} \right \rfloor ⌊2m​⌋ ,所以最多 2 ∗ ⌊ m 2 ⌋ − 1 2*\left \lfloor \frac{m}{2} \right \rfloor-1 2∗⌊2m​⌋−1,不可能填满空柱子的。

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

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

D

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

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

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

那么对于一个点 ( a 1 , a 2 , a 3 . . . , a k ) (a_1,a_2,a_3...,a_k) (a1​,a2​,a3​...,ak​),就会在走第 m i n i = 1 k ( f [ i ] [ a i ] ) min_{i=1}^{k}(f[i][a_i]) mini=1k​(f[i][ai​]) 步的时候出局。

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

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

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

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

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

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

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

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

那么就为: ∑ j = 0 C ( i ) − T i ∗ j ≥ 0 ( 1 ≤ i ≤ k ) ( C ( 2 ) − T 2 ∗ j ) ∗ ( f [ 1 ] [ a 1 ] + n ∗ j ) \sum\limits_{j=0}^{C(i)-T_{i}*j≥0(1≤i≤k)}(C(2)-T_{2}*j)*(f[1][a_{1}]+n*j) j=0∑C(i)−Ti​∗j≥0(1≤i≤k)​(C(2)−T2​∗j)∗(f[1][a1​]+n∗j)。

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

那么就为 ∑ j = 0 C ( i ) − T i ∗ j ≥ 0 ( 1 ≤ i ≤ k ) ( ∏ t = 1 t = k ( C ( t ) − T t ∗ j ) ∗ [ t ≠ s t 0 ] ) ∗ ( f [ s t 0 ] [ a s t 0 ] + n ∗ j ) \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) j=0∑C(i)−Ti​∗j≥0(1≤i≤k)​(t=1∏t=k​(C(t)−Tt​∗j)∗[t​=st0])∗(f[st0][ast0​]+n∗j)。

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

设 l i m i t limit limit为最大的满足 C ( i ) − T i ∗ j ≥ 0 ( 1 ≤ i ≤ k ) C(i)-T_{i}*j≥0(1≤i≤k) C(i)−Ti​∗j≥0(1≤i≤k) 的 j j j值,那么式子化简为:

∑ j = 0 l i m i t ( ∏ t = 1 t = k ( C ( t ) − T t ∗ j ) ∗ [ t ≠ s t 0 ] ) ∗ ( f [ s t 0 ] [ a s t 0 ] + n ∗ 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) j=0∑limit​(t=1∏t=k​(C(t)−Tt​∗j)∗[t​=st0])∗(f[st0][ast0​]+n∗j)

试着暂时把 C ( s t 0 ) C(st0) C(st0)替换成 f [ s t 0 ] [ a s t 0 ] f[st0][a_{st0}] f[st0][ast0​], T s t 0 T_{st0} Tst0​换成 − n -n −n,那么式子又化简成:

∑ j = 0 l i m i t ∏ t = 1 t = k ( C ( t ) − T t ∗ j ) \sum\limits_{j=0}^{limit}\prod\limits_{t=1}^{t=k}(C(t)-T_{t}*j) j=0∑limit​t=1∏t=k​(C(t)−Tt​∗j)

为了方便,我们再把 T k T_{k} Tk​全部取负:

∑ j = 0 l i m i t ∏ t = 1 t = k ( C ( t ) + T t ∗ j ) \sum\limits_{j=0}^{limit}\prod\limits_{t=1}^{t=k}(C(t)+T_{t}*j) j=0∑limit​t=1∏t=k​(C(t)+Tt​∗j)

这个该怎么搞呢?

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

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

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

当然, n k 2 nk^2 nk2能过的事情为什么要带个 l o g log log。

然后再设 Y ( i ) Y(i) Y(i)为 1 i + 2 i + . . . + l i m i t i 1^i+2^i+...+limit^i 1i+2i+...+limiti。(也同样可以 O ( k 2 ) O(k^2) O(k2)计算)

特殊的,认为 0 0 = 1 0^0=1 00=1。

那么式子式子就化为了:
∑ i = 0 k Y ( i ) ∗ d p [ k − i ] \sum\limits_{i=0}^{k}Y(i)*dp[k-i] i=0∑k​Y(i)∗dp[k−i]。

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

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

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

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

时间复杂度: O ( n k 2 ) O(nk^2) O(nk2)

//时间复杂度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,一旦遇到一个就直接的进行solve1for(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;
}

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

可以将时间复杂度优化到 O ( n k ) O(nk) O(nk)。

但是懒得打了。

小结

期末考RP++

NOIP 提高组爆零祭相关推荐

  1. 第一届『Citric杯』NOIP提高组模拟赛 题解

    [官方题解]第一届『Citric杯』NOIP提高组模拟赛 题解 第一题 柠檬超市 这题是本次模拟赛的送分题.做法显然. 但是注意此题有一个陷阱: 注意W和C的规模都是10^9,所以如果直接用doubl ...

  2. 2018.12.08【NOIP提高组】模拟B组总结(未完成)

    2018.12.08[NOIP提高组]模拟B组总结 diyiti 保留道路 进化序列 B diyiti Description 给定n 根直的木棍,要从中选出6 根木棍,满足:能用这6 根木棍拼出一个 ...

  3. 津津的储蓄计划 NOIp提高组2004

    这个题目当年困扰了我许久,现在来反思一下 本文为博客园ShyButHandsome的原创作品,转载请注明出处 右边有目录,方便快速浏览 题目描述 津津的零花钱一直都是自己管理.每个月的月初妈妈给津津\ ...

  4. 信息学奥赛一本通(C++版)NOIP提高组(1820-1829)

    信息学奥赛一本通(C++版)NOIP提高组目录 //1820 [题目描述] 我们可以用这样的方式来表示一个十进制数:将每个阿拉伯数字乘以一个以该数字所 处位置的(值减1)为指数,以10为底数的幂之和的 ...

  5. {小结}2016.6.11【初中部 NOIP提高组 】模拟赛C

    2016.6.11[初中部 NOIP提高组 ]模拟赛C No.1!!! 100+33.3+10+90=233.3 23333 1298. 牛棚(graze2.pas/c/cpp) 题解 1299. 洗 ...

  6. 6271. 2019.8.4【NOIP提高组A】锻造 (forging)

    6271. 2019.8.4[NOIP提高组A]锻造 (forging)  (File IO): input:forging.in output:forging.out Time Limits: 15 ...

  7. 2020.08.08【NOIP提高组】模拟:奶牛的图片 总结

    2020.08.08[NOIP提高组]模拟:奶牛的图片 总结 Description Farmer John希望给他的 N ( 1 ≤ N ≤ 100 , 000 ) N(1\leq N\leq100 ...

  8. NOIP 提高组 复赛 历年 试题

    NOIP 提高组  复赛  历年 试题 NOIP 2017 提高组 复赛  试题 https://wenku.baidu.com/view/70de9e29854769eae009581b6bd97f ...

  9. 历年NOIP提高组初赛选择解析(至2006年。未完结)

    这又是一篇没有代码的题解 这个题解不会根据一年年的来,而是根据题型来的.大家收好啊-orz 题型1--数学题 1.1集合计算 1.(NOIP2004–T1-单选)设全集 I = I= I={ a , ...

最新文章

  1. AI时代的GitHub,这个陆奇看好的方向,终于有人做了
  2. android html5播放器,android Html5播放器混音解决方案
  3. yolo v3学习笔记
  4. 调试的时候step into,step out,step over有什么区别?各有什么作用?分别在什么情况下使用?
  5. 在.NET Core中使用MongoDB明细教程(1):驱动基础及文档插入
  6. HTML常用标签+CSS基础
  7. php 增加mysql 索引,【PHP】为什么 MySQL 添加索引后就可以提高查询速度
  8. 应用程序范围的键值对
  9. CUDA学习(九十四)
  10. 如何检查PHP数组是关联数组还是顺序数组?
  11. Day6 数据清洗(2)
  12. python中替换字符串中子串的函数为_python替换字符串中的子串图文步骤
  13. 数据结构和算法(C语言版)期末速成基础不挂科补考
  14. linux uvc协议_linux uvc 深入理解(一)
  15. proteus 直流可调稳压电源
  16. composer global require “fxp/composer-asset-plugin:~1.3“ 错误 1407742e
  17. 手把手教会你视频转文字怎么操作,快来get
  18. java升序排列数组_java 数组升序排列
  19. 能提取HTML网页正文的网站,智能提取网页正文新方法
  20. CSP 201803-4 棋局评估

热门文章

  1. Android | Fragment
  2. LeetCode 398 随机数索引 Python
  3. php 修改头像案例,在laravel5.2中实现点击用户头像更改头像的方法
  4. JAVA毕业设计高校贫困生信息管理系统计算机源码+lw文档+系统+调试部署+数据库
  5. 在区块链圈,每天都是儿童节
  6. 基于Spring Boot的社区论坛开发
  7. iOS图片倒影效果的2种实现
  8. Flink学习之流处理架构
  9. linux安装nginx教程
  10. 梯度消失 梯度爆炸