【模拟赛】2019.9.15—2019.9.16

背景:

这次的模拟赛啊,,是真的自闭,自闭到家了,,(ㄒoㄒ)这次是Cydiater出的题,真的是很有水平,跟历年noip的题目难度差不多,但是很不好想的那种,然后就考了个Day1,Day2,结果,,,如果交了的话就是两百分左右,
两天的题,还是没有过一等线,,,

(由于打的太菜了,就两天都没交,但是回过头来想了想,以后还是好好交吧,不管考了多少,这样的话,至少在之后老师记录的折线图中至少就可以看到自己每次考试的进步和退步了,要是总不交或者是只有考好了才交的话最后看图的时候就很尴尬了,就几次好成绩而已,剩下的还可能被认为是没有胆量交,这,确实不是很好,而且总有这样的自卑心理,也不会有什么好结果,在博客里进行自我检讨一翻)

(而且今天老师找我单独谈话了,,,,就是说我没有交的事情,尽管第一题A掉了,我也没有胆量交,这样还是没有什么结果啊,其实模拟赛重要的是这个氛围和形式,都是在模拟真正考试的时候,这样的话就会在真正考试的时候不着急不会发生意外了,但是我总不交就第一不会有训练到自己的暴力能力,第二就没有警示自己的小错误,这点还是缺少自信啊,我还是要好好连暴力,并建立起自信!!考前的目标!!

题目:


Day1 题目:

T1:Dove 的疑惑(math)

简化题目:

给出 n n n个同余方程,其形式为: x ≡ a i ( m o d x\equiv a_{i}(mod xai(mod m i ) m_{i}) mi) 给出其中的 m i m_{i} mi,重要条件: a i < = m i a_{i}<=m_{i} ai<=mi,询问对于一组确定的 m {m} m,有多少种 a {a} a的取值方式,是无法找到对应的 x x x满足所有的同余方程。

题解:

(这个题,由于刚开始就看到了题目中给定的范围里面有 ∏ i = 1 n m i \prod_{i=1}^n m_i i=1nmi的范围,就直接以为这是个大凯的疑惑,直接对于样例进行找规律,然后就认认真真的随便找了一下,就找到了正解,,肉眼正解,这,,是真的出乎意料,这题A的,让我好意外,,,)
还是要好好写一下正解的:
(这份正解中标算的原话比较多,因为有一些还是自己证不出来的,望大佬们在下面的评论中给出证法,诚挚感谢

下面我们设 M = l c m M=lcm M=lcm { m i m_{i} mi},很显然, x ≡ a i ( m o d x\equiv a_{i}(mod xai(mod m i ) m_{i}) mi)就可以转换为: ( x + M ) ≡ a i ( m o d (x+M)\equiv a_{i}(mod (x+M)ai(mod m i ) m_{i}) mi),这两个是等效的。然而对于任意两个不同的 y , x y,x y,x来说,如果 ∣ x − y ∣ ≤ M \left|x-y\right|\leq M xyM,可以推出至少在 m o d mod mod 一个 m i m_{i} mi的时候有不同的{ a a a},(这里可以手推一下,找几个例子,按我的理解,其实就是在卡边界)对于{ m m m}来说,如果 m i m_{i} mi不相同的话,那么就恰好存在 M M M x x x存在{ a a a}而且互不相同。这样就是从 M M M上面向下推了一遍。

想到这里时,在回想一下,总情况数,就是 ∏ i = 1 n m i \prod_{i=1}^n m_i i=1nmi,这样的话,就容斥一下就好了,减去存在的情况数M。

(这里也有的大神是从excrt的式子推出来的正解,我还是太菜了,不是很会,,,)

代码:
#include<bits/stdc++.h>
#define LL long long 
using namespace std;
LL read()
{
	LL s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int n; LL ans=1,s=1;
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int main()
{
	n=read(); 
	for(int i=1,x;i<=n;i++) x=read(),ans*=x,s=s/gcd(s,x)*x;
	printf("%lld",ans-s);
	return 0;	
}

T2:捉迷藏(hide)

简化题目:

给定n 个节点的树,有点权,对于每个节点询问最大联通块的大小,使得该联通块包含该节点,同时含有偶数个点权为0点。

题解:

(这个题给的24分的偶数,直接送的分数,之后的就想着打个 n 2 n^2 n2的暴力吧,还打挂了,,,然后,,就死掉了)

正解:

由于要记录整个树的所有点答案就不能从一个点的最近0处考虑,那么现在们来考虑一下,在一个权值为0的点处,答案就可能在它的子树内部和子树的外部进行取max,也可能是他的子树内外都有,是对于总答案的贡献,这样的话,我们就开两个数组: u p [ ] up[] up[] d w [ ] dw[] dw[]。这是用来记录这个点子树之外的贡献和这个点子树的贡献。在dfs求每个子树大小的时候就可以更新好这两个数组的初值。

接下来就是标记的上下传递了,这里要从上到下,从下到上进行统计,在向上统计的时候还比较麻烦,要对于对应点的一个区间进行边界更新处理,这样保证内外子树的答案的合法性。

(具体操作看注释吧,还是比较好懂的)

代码:
#include<bits/stdc++.h>
using namespace std;
inline int read(){int r;int s=0,c;for(;!isdigit(c=getchar());s=c);for(r=c^48;isdigit(c=getchar());(r*=10)+=c^48);return s^45?r:-r;}
const int sea=1e6+7;
int n,tot,a[sea],up[sea],dw[sea],ans[sea],size[sea];
vector<int>v[sea];
template<typename T> inline bool cmax(T &a, T b) {return a < b ? a = b, 1 : 0;}
void dfs(int x,int fa)
{
	size[x]=1;
	for(int i=0;i<v[x].size();i++)
	{
		int y=v[x][i];if(y==fa) continue; 
		dfs(y,x); size[x]+=size[y];
	}
	if(a[x]==0)
	{
		for(int i=0;i<v[x].size();i++)
		{
			int y=v[x][i];if(y==fa) continue;
			cmax(dw[y],size[y]);
		}
		cmax(up[x],n-size[x]);
	}//初值统计 
}
void dfs_up(int x,int fa)
{
	for(int i=0;i<v[x].size();i++)
	{
		int y=v[x][i];if(y==fa) continue;
		dfs_up(y,x); cmax(up[x],up[y]),cmax(ans[x],up[y]);
	}//用up[y]来更新up[x]和ans[x],这整个操作是自底向上的,所以是向上传递标记 
	int p=0,q=0;
	//这里也可以理解成一个区间,p是在正向更新向上更新时的最下面的值,q是在逆向更新向上更新时的最下面的值 
	for(int i=0;i<v[x].size();i++)
	{
		int y=v[x][i];if(y==fa) continue;
		cmax(dw[y],p),cmax(p,up[y]);//用p更新y的子树内的贡献,然后用子树外的贡献去更新p,达到一种边界处理的效果 
	}
	reverse(v[x].begin(),v[x].end());//这里就是逆序了,倒序的操作,自行百度吧 
	//提醒一下这里还是用vector吧,结构体真的不好写,,
	for(int i=0;i<v[x].size();i++)
	{
		int y=v[x][i];if(y==fa) continue;
		cmax(dw[y],q),cmax(q,up[y]);
	}
}
void dfs_dw(int x,int fa)
{
	cmax(ans[x],dw[x]);
	for(int i=0;i<v[x].size();i++)
	{
		int y=v[x][i];if(y==fa) continue;
		cmax(dw[y],dw[x]);dfs_dw(y,x);
	}
}//用dw[x]来更新ans[x]和dw[y],这整个操作是自上向底的,所以是向下传递标记 
int main()
{
	n=read(); for(int i=1;i<=n;i++) a[i]=read();
	for(int i=2;i<=n;i++)
	{
		int x=read(),y=read();
		v[x].push_back(y),v[y].push_back(x);
	}
	for(int i=1;i<=n;i++) tot+=(a[i]==0);
	if(tot%2==0){for(int i=1;i<=n;i++) printf("%d\n",n);return 0;}
	dfs(1,0); dfs_up(1,0); dfs_dw(1,0);
	for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
	return 0;
} 

T3: Cicada的序列(sequence)

简化题目:

求区间连续取模的总和。

题解:

(不说了,就暴力就打挂了,,,,(ㄒoㄒ))

正解:

(表示题解上的分治啊,set啊bit啊都没有看懂,但是有一个大佬的AC代码,我听了他的思路也觉得很ok,就照着他的想法订正了,std真的,,好难懂)

这个线段树写的,是真的妙啊,真的佩服,这线段树学的,又一次觉得自己的线段树白学了,,,o(╥﹏╥)o,这个就是用线段树对于 n 2 n^2 n2的操作进行优化,优化成 l o g log log的。

(具体操作看注释吧,还是比较好懂的)

代码:
#include<bits/stdc++.h>
#define lk k<<1
#define rk k<<1|1
#define LL long long
using namespace std;
int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=1000000001;
const int sea=3e5+7;
struct hit{int l,r,w;}tr[sea*4];
int n; LL ans=0,sum=0,a[sea];
void build(int k,int l,int r)
{
	tr[k].l=l,tr[k].r=r;
	if(l==r){tr[k].w=a[l];return ;}
	int mid=(l+r)/2;
	build(lk,l,mid);build(rk,mid+1,r);
	tr[k].w=min(tr[lk].w,tr[rk].w);//这里是都要取最小,是因为要找这个点右边第一个大于他的数,这样就可以对这一段区间的值进行压缩处理
}
void alter(int k,int x)
{
	int l=tr[k].l,r=tr[k].r;
	if(l==r){tr[k].w=ocean;return ;}//全赋成正无穷
	int mid=(l+r)/2;
	if(x<=mid) alter(lk,x);
	else alter(rk,x);
	tr[k].w=min(tr[lk].w,tr[rk].w);
}
int ask(int k,int x)
{
	int l=tr[k].l,r=tr[k].r;
	if(l==r) return l;
	if(tr[k].w>x) return n+1;;//要是再左边就赋成最大值,这样就跑不到左边了
	if(tr[lk].w<=x) return ask(lk,x);//首先更新右边的
	return ask(rk,x);
}
int main()
{
	n=read(); for(int i=1;i<=n;i++) a[i]=read();
	build(1,1,n);
	for(int i=1;i<=n;i++)
	{
		alter(1,i); //先处理一下
		int j=i+1;LL last=a[i];ans+=a[i];
		while(j<=n)
		{
			int w=ask(1,last); ans+=(w-j)*last;//这一段区间里面就可以直接乘了,这也就是log算法的最大作用
			if(w<=n) last=last%a[w]; j=w;//左指针移动
		}
	}
	printf("%lld\n",ans);
//补的挂掉的暴力/(ㄒoㄒ)/~~
//	if(n<=5000)
//	{
//		for(int i=1;i<=n;++i)
//		{
//			int last=a[i]; ans+=a[i];
//			for(int j=i+1;j<=n;++j) last=last%a[j],ans+=last;
//		}
//		printf("%d\n",ans);
//	}
	
	return 0;
}

Day1 总结:

这回的失利就失利在了暴力的能力,之前是只顾着学习更多的算法了,其实都不是很扎实,暴力能力就不是很好,需要更多的洛谷橙到蓝题的刷一刷,这样提高一下自己的暴力能力。暴力能力确实很重要,要是没有暴力能力,每道题就很难准确拿分了。


Day2 题目:

T1:Dove 爱旅游(trip)

简化题目:

给你一张无向图,找到使 ∣ c n t ( x ) − c n t ( y ) ∣ \left|cnt(x)-cnt(y)\right| cnt(x)cnt(y)最大的联通子图。求最大的 ∣ c n t ( x ) − c n t ( y ) ∣ \left|cnt(x)-cnt(y)\right| cnt(x)cnt(y)

题解:

(这个题啊,巨哭~~~~没有看懂题,刚开始一直以为是直接求最大联通子图的 ∣ c n t ( x ) − c n t ( y ) ∣ \left|cnt(x)-cnt(y)\right| cnt(x)cnt(y),那不就是整棵树嘛,然后就想是不是有向图,但又发现样例推不出来,然后我就想了一个多小时,就放弃了,,然后就挂掉了)

正解:

树形DP的裸题啊,,,由于要记录绝对值,就可以把初始值赋成1和-1,这样记录子树的大小就是对于答案的贡献了,然后就两边dfs,一遍看湖泊–山川的答案,另一遍是山川–湖泊的答案,然后就取最大值就好。

代码:
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
} 
const int sea=1e6+7;
struct see{int ver,next;}e[sea<<1];
int n,ans,tot,f[sea],w[sea],a[sea],last[sea];
void add(int x,int y){e[++tot].next=last[x];e[tot].ver=y;last[x]=tot;}
void dfs(int x,int fa)
{
	f[x]=w[x];
	for(int i=last[x];i;i=e[i].next)
	{
		int y=e[i].ver;if(y==fa) continue;
		dfs(y,x); f[x]+=max(f[y],0);	
	}
	ans=max(ans,f[x]);
}
int main()
{
	freopen("trip.in","r",stdin);
	freopen("trip.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++) 
	{
		a[i]=read();
		if(a[i]==0) w[i]=-1;else w[i]=1;
	}
	for(int i=1,x,y;i<n;i++)x=read(),y=read(),add(x,y),add(y,x);
	dfs(1,0);
	for(int i=1;i<=n;i++) w[i]*=-1;
	dfs(1,0); printf("%d\n",ans);
	return 0;
}

T2:Cicada 爱烧烤(string)

简化题目:

给你一个长度,一个字符集,让你随意放字符,保证放出来的串是回文的而且任意位的前缀不为回文,求方案数。

题解:

(由于第一题没有看懂题,我就一直在肝第二题,肝了两个小时啊,,,我考场上就想到了O(n)的递推,推出来了前五种情况,但是,,,,后面好像推的不是很对,然后就死掉了,我就找了找规律第二天又从早上六点订正到了九点才订正完,真的很难想,,,)

正解:

我推了 O ( l o g ) O(log) O(log)通项公式(一个困扰了我很久的错解),但是相对于模数,我怕由于取模得原因WA掉,就又推出来了递推式,然后还是死在了取模上,我就直接看了看一位大佬的AC代码,结果发现其实就是那个递推式,只是取模没取好,,,,

首先我们应该知道:若一个非回文子串是一个待定串的前一半的话,这个待定串就一定合法

所以就只从 ( n + 1 ) / 2 (n+1)/2 (n+1)/2考虑就行了,由于正着推不是太好推,就需要逆着容斥一下,先考虑总情况数,前半段的总情况数就是 m n + 1 2 m^{\frac{n+1}{2}} m2n+1 ,然后就是每次在加上一个数的时候所加上的不合法的情况:以三个字符为例:当你确定了第一个字符的时候,(这里只求前半段,就是 n + 1 2 {\frac{n+1}{2}} 2n+1 )有 ( a , b , a ) ( a , c , a ) ( a , a , b ) ( a , a , c ) ( a , a , a ) (a,b,a)(a,c,a)(a,a,b)(a,a,c)(a,a,a) (a,b,a)(a,c,a)(a,a,b)(a,a,c)(a,a,a),这就是 2 m − 1 2m-1 2m1个的,那么m个字符就是, m ∗ ( 2 m − 1 ) = 2 ∗ m 2 + m m*(2m-1)=2*m^2+m m(2m1)=2m2+m个的,由于如果这半段不合法的话,前提就是他的前半段合法,所以每次加上的一个位置就会乘上个 m m m,再加上 f [ i + 1 4 + 1 ] f[{\frac{i+1}{4}+1}] f[4i+1+1],然后拉回视线,回到容斥的部分,就是用总情况中减去不合法的就好。这样就可以推出递推式,由于是要迭代,我在代码中处理的比较清晰。

这时候可以发现, f [ 3 ] = f [ 4 ] , f [ 5 ] = f [ 6 ] , f [ 6 ] = f [ 7 ] ⋅ ⋅ ⋅ f[3]=f[4],f[5]=f[6],f[6]=f[7]··· f[3]=f[4],f[5]=f[6],f[6]=f[7],这样其实就是在分成两半的时候的中点是在位置上或者是在位置与位置的间隔处,但是由于奇数情况是要影响前面的,所以和后面的偶数进行合并,

f [ i ] = f [ i + 1 ] ( i 为 奇 数 ) f[i]=f[i+1](i为奇数) f[i]=f[i+1](i)

(这点可能有不同的理解,奇偶数怎么理解都行,,)

千万不要试图求通项!!后果自负!!

f[1]=m
f[2]=m
f[3]=m^2-m
f[4]=m^2-m
f[5]=m3-2*m2+m
f[6]=m3-2*m2+m
f[7]=m4-2*m3+m
f[8]=m4-2*m3+m
f[9]=m5-2*m4+m
f[10]=m5-2*m4+m

……
是不是有发现什么?

还是写一下我这个“假”通项吧:
f[1]=f[2]=m,f[3]=m*(m-1) (i<=2)

f[i]=m{\frac{i+1}{2}}-2*m{\frac{i+1}{2}-1}+m (2<=i<=n)
就是这个错解,害我又花了两个小时去想正确的通项,结果发现并没有什么很好的规律,而且可能还没有代数解有的话,好像要n阶差分,再求,要是没有的话,就要上群论试试,我真的服了,,,弃疗弃疗,,,

代码:
#include<bits/stdc++.h>
#define mod 1000000007
#define LL long long
using namespace std;
inline LL read()
{
	LL s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
} 
LL ksm(LL a,LL b)
{
	LL s=1;
	while(b) 
	{
		if(b&1) s=s*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return s%mod;
}
const int sea=1e6+7;
LL n,m,f[sea];
//f[i]表示第i个位置可以形成合法的串有多少个 
//若一个非回文子串是一个待定串的前一半的话,这个串就一定合法 
int main()
{
//	freopen("string.in","r",stdin);
//	freopen("string.out","w",stdout);
	n=read(); m=read();
	f[1]=f[2]=m%mod;f[3]=f[4]=(m-1)*m%mod;
	LL sum=(2*m-1)*m%mod;
	for(int i=5;i<=n;i+=2)
	{
		f[i]=ksm(m,(i+1)/2);
		f[i]=(f[i]-sum)%mod; f[i+1]=f[i];
		sum=sum*m%mod,sum=(sum+f[i/2+2])%mod;
	}
//	for(int i=5;i<=20;i++) f[i]=(ksm(m,((i+1)/2))-2*ksm(m,((i+1)/2-1))+m)%mod,printf("%d ",f[i]);
	if(f[n]<0) f[n]+=mod;  printf("%lld\n",f[n]);
	return 0;
}

T3:Dove 的博弈(game)

简化题目:

给定 n n n 个数 a i a_{i} ai 和常数 m m m ,如果 a i + m > a i + 1 a_{i}+m>a_{i+1} ai+m>ai+1或者 a i + m > a i − 1 a_{i}+m>a_{i-1} ai+m>ai1 就意味着 a i a_{i} ai 可以把 a i a_{i} ai 或者吃掉,如果它全出吃了那就输出他的下标。

题解:

(由于T2浪费的时间太多而且为了看懂T1,我浪费了三个多小时,这个题就直接放弃了,,其实是能拿一些暴力分的,而且要是暴力打得好就正解了,,,(ㄒoㄒ))

正解:

这个题应该很好想到正解,毕竟暴搜的话就也能拿部分分,而且 O ( n ) O(n) O(n)的办法也比较好想了。
考虑一下在向外扩的时候你可以先找到现在位置上可以像左右扩到的最大情况,就是直接找出左边和右边第一个大于这个数的下标,然后再向外一步一步扩就好,这样就是O(n)的了。(可以刚开始用前缀和进行一个小操作的优化)

(提醒:这个题输出巨大,hsm的快写都炸了,,但不知道为什么printf却能完好无损的跑过,,,)

代码:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
} 
const int ocean=1e9+7; 
const int sea=8e6+7;
deque<int>dd; 
int n,m,cl[sea],cr[sea],f[sea];LL a[sea],sum[sea];
int dfs(int x)
{
	if(f[x]!=-1) return f[x];
	if(cl[x]==0&&cr[x]==n+1) return f[x]=1;
	f[x]=0; LL s=sum[cr[x]-1]-sum[cl[x]]+m;
	if(cl[x]>0&&s>=a[cl[x]]) f[x]=f[x]|dfs(cl[x]);
	if(f[x]==0&&cr[x]<=n&&s>=a[cr[x]]) f[x]=f[x]|dfs(cr[x]);
	return f[x];
} 
int main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	n=read(); m=read(); 
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
	a[0]=a[n+1]=ocean; dd.push_back(0);
	for(int i=1;i<=n;i++)
	{
		while(!dd.empty()&&a[i]>=a[dd.back()]) dd.pop_back();
		cl[i]=dd.back(); dd.push_back(i);
	}
	dd.clear(); dd.push_back(n+1);
	for(int i=n;i>=1;i--)
	{
		while(!dd.empty()&&a[i]>a[dd.back()]) dd.pop_back();
		cr[i]=dd.back(); dd.push_back(i);
	}
	memset(f,-1,sizeof(f));
	for(int i=1;i<=n;i++) if(dfs(i)) printf("%d ",i);
	return 0;
}

Day2 总结:

整体来说,我的读题能力啊,,,欲哭无泪,,,要是读好了也不至于考场上心态爆炸了,,就是心态啊,心态被T1全搞掉了,,o(╥﹏╥)o,所以要好好读题啊,,,,

总结:

这次的模拟赛,考了一个数论,一个图论,一个数据结构,一个树形DP,一个求递推式,一个初级数据结构(搜索),还是比较全面的,虽然没有达到线,但是我找到了自己的一些不足,一是暴力能力,二是读题能力及心态,继续加油,才第一次的模拟赛,还有二十几次呢,,,,

Continue……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值