CSP-S游记

初赛

初赛的事谁还记得啊

去考了场出赛,MAD,什么完善程序啊,直接爆炸了好不好,那两道四分题都不是很会做,其他都还行,拿到82分左右满意退场。

复赛

T1

看了一眼,眼都白了,什么乱七八糟的东西啊。

然后就在死命的乱膜,脑子都是糊的,一个半小时后过了大样例,以为胜券在握了。

T2

当时看了一眼,这不是离散化SB题,然后疯狂的打完了代码,一看到(p)(忘了是(p)还是(q))互不相等,又傻了,这不是直接爆搞?然后又改了会代码,半个小时才搞完。

T3,T4

最后两个小时看了眼题,(T4)看了一会明白了博弈策略后想到(nlogn)做法(实际上我当时根本没想全,还想复杂了,不可能实现的出来,后来膜了会题解才实现出来。),T3总觉得很简单,然来在纸上乱搞,三十多分钟后发现了新大陆,然后搞出来了,对着样例做了一遍,真的过了大样例!!!

然后就开始了疯狂码的生活,最后剩半个小时,最后一道题目连暴力都码不出来了,然后自暴自弃。

估分是(250)(盲猜第一题爆炸)。

赛后

成绩还没出,跑去你谷交了发,第一题爆炸了啊啊啊啊,还只有(20)分,然后只有(220)了,可恶的第一题!!!!!!

后来看了眼第四题的题解,思路比我清晰很多,直接处理出每一步吃掉了谁,会更方便博弈处理哪一步能走或者不能走(我一开始是想递归处理,但是这样处理非常的繁琐,我估计是实现不出来的)

然后再运用人类智慧想了想,最终想出了第四道题。很多提示就对了

第四题升黑了,或许就是我现在唯一的心里安慰吧。

回去搞课内了

题解

儒略历

暴力乱搞,没什么好说了的。

代码肯定不好懂,毕竟不同的人有不同的想法。

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  LL;
//默认公元前-1 
LL  moday[2][16]={{0,31,28,31,30,31,30,31,31,30,31,30,31,0,0,0},{0,31,29,31,30,31,30,31,31,30,31,30,31,0,0,0}};
//LL  yeday1[4]={365,366,0,0};//儒略历
//LL  yeday2[3]={365,1461,146097};//格里高利历 
struct  node
{
	LL  yea,mo,da;
}jb1,jb2;
LL  limit=2299161;
//LL  daylimit=277;
//基本 
inline  int  ruluepd(node  x){return  x.yea%4==0;}
inline  void  rulue(node  &x,LL  &k)
{
	while(x.yea%4!=0  &&  k>=365)k-=365,x.yea++;
	if(k>=366)//进入闰年 
	{
		k-=366;x.yea++;
		LL  t=k/1461;
		k-=t*1461;
		x.yea+=t*4;
		while(k>=365+ruluepd(x))k-=365+ruluepd(x),x.yea++;
	}
	int  t=ruluepd(x);
	for(int  i=1;i<=12;i++)
	{
		if(k>=moday[t][i])k-=moday[t][i],x.mo++;
		else  break;
	}
	x.da+=k;
}
inline  int  gepd(node  x){return  x.yea%400==0  ||  (x.yea%4==0  &&  x.yea%100!=0);}
inline  void  gelai(node  &x,LL  &k)
{
	if(k<=16)
	{
		x.da+=k;return  ;
	}
	k-=17;x.mo++;x.da=1;
	
	if(k<61)
	{
		if(k>=30)x.mo++,x.da=1+(k-30);
		else  x.da=1+k;
		return  ;
	}
	k-=61;
	x.yea++;x.mo=1;x.da=1;
	
	LL  t=k/146097;
	k-=t*146097;x.yea+=t*400;
	while(!gepd(x)  &&  k>=365)
	{
		k-=365,x.yea++;
	}
	if(k>=366)
	{
		//x在闰年地带 
		while(x.yea%100!=0  &&  k>=1461)k-=1461,x.yea+=4;
		//100年为36525 
		while(k>=36524+gepd(x))k-=36524+gepd(x),x.yea+=100;//一百年没了 
		while(k>=1461-(!gepd(x)))k-=1461-(!gepd(x)),x.yea+=4;
		//最后只剩下三年
		 while(k>=365+gepd(x))k-=365+gepd(x),x.yea++;
	}
	
	int  tt=gepd(x);
	for(int  i=1;i<=12;i++)
	{
		if(k>=moday[tt][i])k-=moday[tt][i],x.mo++;
		else  break;
	}
	x.da+=k;
}
void  solve(LL  k/*过的时间*/)
{
	node  x;
	if(k>=limit)
	{
		k-=limit;
		x=jb2;
		gelai(x,k);
		printf("%lld %lld %lld
",x.da,x.mo,x.yea);
	}
	else
	{
		x=jb1;
		rulue(x,k);
		if(x.yea<=0)
		{
			x.yea--;
			printf("%lld %lld %lld BC
",x.da,x.mo,-x.yea);
		}
		else  printf("%lld %lld %lld
",x.da,x.mo,x.yea);
	}
}
int  main()
{
//	freopen("julian.in","r",stdin);
//	freopen("julian.out","w",stdout);
	jb1.yea=-4712;jb1.mo=1;jb1.da=1;
	jb2.yea=1582;jb2.mo=10;jb2.da=15;
	int  T;scanf("%d",&T);
	while(T--)
	{
		LL  x;scanf("%lld",&x);
		solve(x); 
	}
	return  0;
}

动物园

如果你发现一个位置没有任何位置或者出现在动物中,则一定可以,否则一定不行,因为(q)互不相同。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define  N  100
#define  M  1100000
#define  MM  2100000
using  namespace  std;
typedef  unsigned  long  long  LL;
template<class  T>
inline  void  getz(T  &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();
}
bool  vis[N];//这个进制是否被用了 
LL  b[M];//表示畜生的编号
int  n,m,kk,c;
LL  ans=0;
int  limit;//表示限制位数 
LL  fc[N];
int  main()
{
	getz(n);getz(m);getz(c);getz(kk);
	fc[0]=1;for(int  i=1;i<kk;i++)fc[i]=fc[i-1]*2;
	for(int  i=1;i<=n;i++)getz(b[i]);
	for(int  i=0;i<kk;i++)vis[i]=1;
	for(int  i=1;i<=m;i++)
	{
		int  x,y;getz(x);getz(y);
		vis[x]=0;
	}
	for(int  j=0;j<kk;j++)
	{
		for(int  i=1;i<=n;i++)
		{
			if(vis[j])break;
			if((b[i]&fc[j]))vis[j]=1;
		}
	}
	for(int  i=0;i<kk;i++)limit+=vis[i];
	if(kk==64  &&  limit==64)
	{
		if(!n)printf("18446744073709551616
");//对于2^64要独立判断
		else
		{
			n--;
			LL  x=0;
			for(int  i=0;i<kk;i++)x+=fc[i];
			printf("%llu
",x-(LL)n);
		}
	}
	else  printf("%llu
",((LL)1<<limit)-(LL)n);
	return  0;
}

函数调用

一道蓝题想了我40min。。。

没有递归,非常明显,DAG图。

首先,可以把询问也看成一个点,那么就是求这个点的影响。

如果用线段树,非常简单的思路就是把所有的加法全部移动到乘法的后面,由于加法不会互相影响,所以可以认为,如果一个加法后面没有乘法,其就是独立的,如果所有的操作都是独立的,那么他们怎么调换顺序都可以。

也就是把所有的加法乘上后面的乘法操作。

好,那么关键来了,我们需要维护每个操作都是独立的情况下其的贡献需要乘以几倍?

例如,已知函数:(g(1)=1) (1) (2)(g(2)=2) (3)(g(3)=) (3) (2) (1) (2)那么我们就只需要让(g(1))执行的过程中加法都必须乘(3)即可。

再例如:(g(1)=1) (1) (2)(g(2)=3) (2) (1) (1)(g(3)=2) (3)(g(4)=) (3) (3) (2) (3) (2),这个例子就比较复杂了,但是我们仔细看一下(g(4)),你会发现,让(g(2))的所有加法乘(3)和执行一次(g(2))不就相当于(g(2)*4)吗?也就是说在独立之后,每个函数需要乘的倍数是可以用加法合并的,所以只需要从根节点往下跑拓扑排序即可。

时间复杂度:(O(n+m))

#include<cstdio>
#include<cstring>
#define  N  110000
#define  M  1100000
#define  MN  1200000
using  namespace  std;
typedef  long  long  LL;
const  LL  mod=998244353;
template<class  T>
inline  void  getz(T  &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();
}
struct  node
{
	int  y,next;
}a[MN];int  len,last[N];
bool  v[N];//循环使用 
inline  void  ins(int  x,int  y){len++;a[len].y=y;a[len].next=last[x];last[x]=len;}
int  list[MN],head,tail;
int  in[N];
int  n,m,Q;
inline  void  bfs()
{
	memset(v,0,sizeof(v));
	list[head=tail=1]=m;v[m]=1;
	while(head<=tail)
	{
		int  x=list[head++];
		for(int  k=last[x];k;k=a[k].next)
		{
			int  y=a[k].y;
			in[y]++;
			if(!v[y])
			{
				v[y]=1;
				list[++tail]=y;
			}
		}
	}
}
int  qu[N];
LL  aa[N];//表示每个数字
struct  CHANGE
{
	int  x;
	LL  y,z;//如果是3的话,y后面存的是乘积,前面存长度,z存的是翻倍次数 
}ch[N];
int  xl[MN],ll[N],rr[N],top;
LL  sum[MN];
void  pre_do()//处理拓扑序 
{
	head=1;tail=1;list[1]=m;
	while(head<=tail)
	{
		int  x=list[head++];
		for(int  k=last[x];k;k=a[k].next)
		{
			int  y=a[k].y;
			in[y]--;
			if(!in[y])list[++tail]=y;
		}
	}
	for(int  i=tail;i>=1;i--)
	{
		int  x=list[i];
		if(ch[x].x==3)
		{
			LL  now=1;
			for(int  j=rr[x];j>=ll[x];j--)
			{
				int  y=xl[j];
				sum[j]=now;
				if(ch[y].x==3  ||  ch[y].x==2)now=now*ch[y].y%mod;
			}
			ch[x].y=now;
		}
	}
}
void  solve()
{
	for(int  i=1;i<=n;i++)aa[i]=(aa[i]*ch[m].y)%mod;
	ch[m].z=1;
	for(int  i=1;i<=tail;i++)
	{
		int  x=list[i];
		if(ch[x].x==3)
		{
			for(int  j=ll[x];j<=rr[x];j++)
			{
				int  y=xl[j];
				if(ch[y].x==1)aa[ch[y].y]=(aa[ch[y].y]+(ch[y].z*ch[x].z%mod*sum[j]%mod))%mod;
				else  if(ch[y].x==3)ch[y].z=(ch[y].z+(ch[x].z*sum[j]%mod))%mod;
			}
		}
	}
}
int  main()
{
	getz(n);
	for(int  i=1;i<=n;i++)getz(aa[i]);
	getz(m);
	for(int  i=1;i<=m;i++)
	{
		getz(ch[i].x);
		if(ch[i].x==1)getz(ch[i].y),getz(ch[i].z);
		else  if(ch[i].x==2)getz(ch[i].y);
		else
		{
			getz(ch[i].y);
			ll[i]=top+1;
			for(int  j=1;j<=ch[i].y;j++)
			{
				getz(xl[++top]);
				if(!v[xl[top]])
				{
					v[xl[top]]=1;
					ins(i,xl[top]);
				}
			}
			rr[i]=top;
			for(int  j=ll[i];j<=rr[i];j++)v[xl[j]]=0;
		}
	}
	getz(Q);m++;
	ll[m]=top+1;
	ch[m].x=3;
	for(int  i=1;i<=Q;i++)
	{
		getz(xl[++top]);
		if(!v[xl[top]])
		{
			v[xl[top]]=1;
			ins(m,xl[top]);
		}
	}
	rr[m]=top;
	bfs();
	pre_do();
	solve();
	for(int  i=1;i<=n;i++)printf("%lld ",aa[i]);
	printf("
");
	return  0;
}

贪吃蛇

妙啊。

先讲讲博弈策略,如果对于(x)吃掉了(y),后面(x)没有再吃蛇的机会且会被吃掉,那么(x)就不敢吃(y),那么这个该怎么运用呢?

不难发现的事情是每次吃掉的顺序都是相同的,因此我们可以考虑把吃人的顺序求出来,用(list)保存起来,然后(eat)保存这个数字什么时候被吃掉。

那么对于上面哪一条,我们该怎么求出最小的蛇数呢?其实最小的蛇数就是经过最大的轮数。

我们再(list)从后往前搜,用(now)表示吃蛇会在第(now)轮停止,所以一开始(now=n)

这样,当我们搜到了(list[i]),如果(eat[list[i]]>=now),证明其不会被吃掉,那么他敢吃,否则他不敢吃(因为如果他吃了的话,中间的蛇都是敢吃的,他就会被吃掉),现在就要停下来,然后(now=i)。(这一切的前提都是建立在蛇足够聪明,有大局观的情况下)

好,现在我们需要考虑的是如何构建出这个(list)数组。

Part1

暂时先不考虑编号问题,只考虑数值,因此下述会有许多不严谨的地方。

考虑对于数组(a),对于(a_{n}),不断的吃,吃到(a_{k})时小于(a_{n-1})了,此时得到了(a_{n}'),那么如果此时的(a_{n}'≥a_{k})的话,对于(a_{n-1})(a_{n-1}≤a_{n},a_{k+1}≥a_{k}),所以(a_{n-1}-a_{k+1}≤a_{n}'),因此我们只需要把减完之后的(a_{n-1})放在(a_{n})前面就行了,也就是有单调性。

好,什么时候取等号呢?只当(a_{n-1}=a_{n},a_{k+1}=a_{k}),开始考虑编号,这是不难发现的事情,(a_{n-1})的编号一定小于(a_{n}),所以放前面也是没有问题的,不破坏单调性。

但是,如果(a_{n}')小于(a_{k+1}),额,也就是减完之后就变成了一个最小的数字,单调性被破坏,这个时候就要考虑别的性质了。

Part2

不难发现的是:(frac{a_{n}}{2}<a_{k}),那么我们就猜了,后面的数字都是这么大的数字,那么有没有可能一个数字只要吃了最小的数字,他就会变成最小的数字呢?(忽略编号)

事实上,确实如此,这个手艹一下,会比较容易理解,设(t)数组表示每次减得到的值,只需要证明(t_{i}≤a_{k})即可。

首先对于(t_{1}=a_{n}-a_{k}),满足要求,对于(t_{2}),如果(a_{n-1}=a_{n}),那么(t_{2}=a_{k}),如果(a_{n-1}≠a_{n}),那么设(u=a_{n}-a_{n-1}),那么会得到(a_{k}-u),不妨设(a_{k}'=a_{k}-u),那么(t_{2}=a_{k}')

对于(a_{n-1}=a_{n}),如果(a_{n-2}=a_{n-1}),那么(t_{3}=t_{1}),重复如此,如果(a_{n-2}≠a_{n-1}),那么(t_{3}=a_{n-2}-a_{k}<t_{1}),同样也是重复如此,不过要把(a_{n-2})看成(a_{n})

对于(a_{n-1}<a{n}),那么得到的(a_{k}-u),不妨认为(a_{k}')就是后面的(a_{k}),同样重复如此。

不难发现,过程中(t_{i}<=a_{k}),且对于奇数(i)(t_{i}≤t_{1}),且当仅当(a_{n}=a_{n-1})时,(t_{3}=t_1),其余同理。

得证。

但是,如果考虑编号的话,在等于(a_{k})的时候,如果(a_{k}=a_{k+1}),就会造成编号的错乱。

Part3

但是我们发现,虽然编号会乱,但是每次操作的数字是不受影响的,因为我们只需要考虑数值都是(a_{k})的编号。

不妨考虑离线处理一下,对于(t(t>=0)),如果(a_{n-2t}=a_{n-2t-1})(前面都满足这个要求的情况下),那么其实就是(a_{n-2t})吃掉了一个(a_{t}),然后(a_{n-2t-1})又将其吃掉并填上去。

不妨把一开始值为(a_{k})的编号列出来,那么实际上我们就是要支持两个操作:删除最小的编号并返回,在删除之后插入一个编号。

一般遇到这种情况,只需要直接用链表。

但是链表怎么搞还是个问题,不妨把时间建一个链表,然后利用桶排序把编号排起来(因为是编号,所以只需要用桶排序就可以了),从下往上搜,对于每个点,只需要看链表中其插入的时间的点的右端点就是这个点被删掉的时间,同时删掉右端点。

但是有个问题,如果这个点插入的时间已经被删掉了,怎么处理?

但是我们发现,一个点找完右端点他就无事可干了,同时其原本右端点的右端点就是现在他的右端点。

所以只需要把删除的时间所代表的插入的点放到左端点即可。

这样,就完成了链表处理编号。

Part4

当然,如果剩下的数字都是不同的,那么就严格小于(a_{k})了,这时我们只需要从右往左跑即可。

但是还有一种情况,如果在(Part3)的时候,所有的位置都满足要求,直到(a_{n-2t}=a_{k})的话,这样可能链表就比较难处理了,这个时候因为一段的值都是(a_{k}),随便处理一下即可。

代码

时间复杂度:(O(Tn))

#include<cstdio>
#include<cstring>
#define  N  1100000
using  namespace  std;
inline  int  mymax(int  x,int  y){return  x>y?x:y;}
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
int  n,nn;
int  turn[N],list[N]/*被吃掉的顺序序列*/,now;//其最后一次吃别人之前剩下了多少数字,被?吃了。
inline  void  set_eat(int  x,int  y,int  type=0)//x吃掉了y,type等于0表示不计入list 
{
	if(type==0)list[++now]=x,turn[y]=now;//被吃掉的可怜孩子
	else  list[type]=x,turn[y]=type;
}
struct  node
{
	int  x,id;
	node(int  xx=0,int  yy=0){x=xx;id=yy;}
}a[N],b[N],sta[N];int  he1,ta1,he2,ta2;
inline  bool  operator<(node  x,node  y){return  x.x==y.x?x.id<y.id:x.x<y.x;}
inline  bool  operator>(node  x,node  y){return  x.x==y.x?x.id>y.id:x.x>y.x;}
inline  node  operator-(node  x,node  y){return  node(x.x-y.x,x.id);}

inline  node  find_small_top()
{
	if(he1>ta1  ||  (he2<=ta2  &&  sta[he2]<a[he1]))return  sta[he2];
	else  return  a[he1];
}
inline  node  find_big_top()
{
	if(he1>ta1  ||  (he2<=ta2  &&  sta[ta2]>a[ta1]))return  sta[ta2];
	else  return  a[ta1];
}
inline  void  pop_small_top()
{
	if(he1>ta1  ||  (he2<=ta2  &&  sta[he2]<a[he1]))he2++;
	else  he1++;
}
inline  void  pop_big_top()//返回是弹出了sta还是弹出了a
{
	if(he1>ta1  ||  (he2<=ta2  &&  sta[ta2]>a[ta1]))ta2--;
	else  ta1--;
}
inline  void  add_front(node  x){sta[--he2]=x;}
void  solve1()//第一部分,类似之前的一道题目,需要注意的是,这样子跑前面剩下来的数字绝对不可能是0,因为退出的先决条件是>1/2 
{
	while(now<nn)
	{
		node  x=find_big_top();
		pop_big_top();
		while(now<nn  &&  x>find_big_top())
		{
			node  y=find_small_top();
			if(y.x>x.x/2)
			{
				sta[++ta2]=x;
				return  ;
			}
			pop_small_top();
			set_eat(x.id,y.id);
			x=x-y;
		}
		if(nn==now)return  ;
		else  add_front(x);
	}
}
int  tong[N];//桶,里面装的是对应的链表编号。 
struct  LIA
{
	int  l,r,id/*承载的id*/,ti;
}lian[N];
inline  void  del(int  x){lian[lian[x].l].r=lian[x].r;lian[lian[x].r].l=lian[x].l;}//非常简单的删除 
void  solve2()
{
	int  top=0;
	while(he1<=ta1  &&  he2<=ta2)
	{
		if(a[he1]<sta[he2])a[++top]=a[he1++];
		else  a[++top]=sta[he2++];
	}
	while(he1<=ta1)a[++top]=a[he1++];
	while(he2<=ta2)a[++top]=sta[he2++];
	//类似归并排序,将其合并到一起去
	//现在就是1~top了
	//首先如果a[top]吃掉了a[1]然后a[top-1]=a[top],那么a[top-1]占掉a[top-1]的位置 
	ta2=0;//循环利用 
	memset(tong,-1,sizeof(tong));
	while(now<nn-1/*至少有三个数字没有被吃*/  &&  a[top].x!=a[1].x)
	{
		if(a[top].x==a[top-1].x)
		{
			sta[++ta2]=node(a[top-1].x,a[top].id/*因为后面就是用top的id去吃别人的*/);//新增加了一个成员
			lian[ta2].ti=++now;tong[a[top-1].id]=ta2;lian[ta2].id=a[top-1].id;//这里就要放桶了,相当于把后面不能放的在前面放好了 
			set_eat(a[top-1].id,a[top].id);
			top-=2;
		}
		else  break;
	}
	//链表时间到 
		for(int  i=1;i<=top  &&  a[i].x==a[1].x;i++)tong[a[i].id]=0;
		lian[0].l=ta2+1;lian[0].r=1;for(int  i=1;i<=ta2;i++)lian[i].l=i-1,lian[i].r=i+1;//单纯的去处理链表 
		lian[ta2+1].l=ta2;
		//为接下来的链表准备就绪 
		int  l=1;ta1=0;//循环利用,表示存活下来的那些编号 
		while(l<=n)
		{
			while(tong[l]==-1  &&  l<=n)l++;
			if(l>n)break;
			int  x=tong[l];//取出你链表的编号。 
			int  y=lian[x].r;
			if(y==ta2+1)a[++ta1]=node(a[1].x,l);//不会被删除 
			else
			{
				set_eat(sta[y].id,l,lian[y].ti);//吃掉,l消失
				lian[x].id=lian[y].id;tong[lian[x].id]=x;//载体的掉包
				del(y);//把y给删掉 
			}
			l++;
		}
	//链表结束,此时a数组被重新整合
	if(a[1].x==a[top].x)//全部都是同一个数字,此时需要两个数字两个数字向前推进 
	{
		while(now<nn)
		{
			set_eat(a[top].id,a[top-1].id);
			a[top-1]=a[top];top--;
			if(top>1)set_eat(a[top-1].id,a[top].id),top--;//你变成了0肯定要给别人吃掉啊 
		}
	}
	else//只需要记住一个变量,此变量为唯一变量。 
	{
		node  x=a[1];
		while(now<nn)
		{
			set_eat(a[top].id,x.id);
			x=a[top];
			top--;
		}
	}
}
int  main()
{
	int  T;scanf("%d",&T);
	for(int  t=1;t<=T;t++)
	{
		now=0;
		memset(turn,0,sizeof(turn));
		if(t==1)
		{
			scanf("%d",&n);
			for(int  i=1;i<=n;i++)
			{
				scanf("%d",&b[i].x);
				b[i].id=i;
			}
		}
		else
		{
			int  k;scanf("%d",&k);
			for(int  i=1;i<=k;i++)
			{
				int  x,y;scanf("%d%d",&x,&y);
				b[x].x=y;
			}
		}
		memcpy(a,b,sizeof(b));
		nn=n-1;
		he1=1;ta1=n;he2=n+1;ta2=n;
		solve1();
		if(now<nn)solve2();
		now=nn+1;//表示进行到哪个位置停下来 
		for(int  i=nn;i>=1;i--)
		{
			if(turn[list[i]]  &&  turn[list[i]]<now)now=i;//就在这个时间停下来 
		}
		printf("%d
",n-now+1);
	}
	return  0;
}
原文地址:https://www.cnblogs.com/zhangjianjunab/p/13968836.html