NOIP 模拟一 考试总结

序列

考场上信心满满的打了nlogn的做法,我以为我稳了。据考试结束1h时发现看错题目了,打成了不连续的子序列。匆匆改了n2logn的做法。考试结束后,我发现我跪了。原来到终点才会发现我做的和人家不是一道题。。。。。。。。。。题目中描述的序列不必严格等比,可以缺项的。这样的话难度就降低了呀。
观察发现AImax=1e18,也就是说假设pmin=2,那么序列的长度max为63啊,这几乎不用考虑TIME。

考虑i和i+1构成等比序列,那么可以求出序列的最小公比,可以1到1000暴搜,也可以分解质因数后求GCD,找到公比后再向后拓展,如果j和j-1之比是公比的整倍数,那么j就满足,对应的len++。注意这里要去重,不能有重复的数存在,set容器苣蒻不会使,所以就简单粗暴的离散化,then布尔数组标记,虽然每次得memset,但是时间还是可以承受的。此题细节较多,GCD的m初值没赋0导致55pts调了很久。

%%%WTZ

WTZ yyds

常数飞天代码code

#include<bits/stdc++.h>
#define f() cout<<"fuck"<<endl
#define int long long
using namespace std;
int a[100010],dp[100010],b[100010],c[100010],num,ji[100010],ans,maxn,tong[100010],su[1000001];
bool bo[100010];
int n;
inline void fr(){freopen("c.in","r",stdin);}
inline void sc(){scanf("%lld",&n);}
namespace AYX
{	inline int gcd(int a,int b)
	{	if(a<b)swap(a,b);
		while(a%b)
		{	int r=a%b;
			a=b;
			b=r;
		}
		return b;
	}	
	inline void work()
	{	for(int i=1;i<=n;++i)scanf("%lld",&a[i]),b[i]=a[i];
		sort(b+1,b+n+1);
		for(int i=1;i<=n;++i)
		{	if(c[num]==b[i])continue;
			c[++num]=b[i];
		}
		for(int i=1;i<=n;++i)
		b[i]=lower_bound(c+1,c+1+num,a[i])-c;
		int l=1;
		for(int i=1;i<=n;++i) if(a[i]==a[i-1])++l; else{ans=max(ans,l); l=1;}
		ans=max(ans,l);
		for(int j=1;j<=n-1;++j)
		{	int x=a[j],y=a[j+1];
			if(x<y)swap(x,y);
			if(x%y)continue;
			memset(bo,0,sizeof(bo));
			bo[b[j]]=1;bo[b[j+1]]=1;
			int z=x/y;
			int kk=z;
			if(z==1)continue;
			for(int i=2;i<=1000;++i)
			{	su[i]=0;
				while(z%i==0){z/=i;++su[i];}
			}
			int m=0;
			for(int i=2;i<=1000;++i){
				if(su[i]){	
					if(!m){m=su[i];continue;}
					else m=gcd(m,su[i]);				
				}
			}
			int p=1;
			for(int i=2;i<=1000;++i){	
				if(su[i]){
					for(int l=1;l<=su[i]/m;++l)
					p*=i;
				}
			}
			int r=j+1,sum=0;
			for(int i=j+2;i<=n;++i)
			{	if(bo[b[i]])break;
				int d1=a[i],d2=a[i-1];
				if(d1<d2)swap(d1,d2);
				if(d1%d2)break;
				int pp=d1/d2;
				while(pp%p==0 and p!=1)pp/=p;
				if(pp!=1)break;
				bo[b[i]]=1;
				r=i;
			}
			ans=max(ans,r-j+1);
			
		}
		printf("%lld\n",ans);
		
	}
	inline short main()
	{sc();work();return 0;}
}
signed main()
{return AYX::main();}

熟练剖分

%%%@liu_runda

考场上打了n!的暴力,特判了几个点,拿了40pts;
如果数据准确本题部分分可以拿到50的;

特判1:所有点连在一个点上,期望为1。5pts

特判2:一条直链,此时注意root不一定是端点哦,还可以在中间。那么可以分开
判出0 and 1。 10pts

特判3:完全二叉树,分析性质可知期望为dep max 15pts。

还有n在20以内的20pts 50没问题的。

好了,不扯淡了,还是说正解吧。

树形dp

儿子向爹转移,可以发现跟期望无关的,求概率推逆元就好。

问题是怎么搞?废话,大力搞哈哈。可以先枚举重儿子,再枚举所有儿子,再枚举len,转移的话要分类。

  1. 如果现在是重儿子,那么对爹没贡献,f[i][j]=该儿子是j的概率×枚举过的儿子小于等于j的概率(其实就是上一个儿子枚举过后的f[i][j])+该儿子小于等于j的概率×枚举过的是j的概率-重复的该儿子是j的概率×枚举过的其他儿子有j的概率。

  2. 如果现在枚举到的是轻儿子,由于轻儿子对爹贡献是1,也就是跟爹连了一条轻边。那么该儿子的状态就要是j-1了,其他跟第一种相同。

到这里题就很明朗了,注意f数组要维护前缀和的,因为dp时要用小于等于j的概率。
还有个g[i][j],记得是本轮递推过程的前缀和,由于i不同内容变化,所以i一维隐去是无影响的,h数组可以记每个儿子产生的贡献,然后加到g数组上,g数组初始概率要设成1哦。(原因是在做乘法,加法的话设成0)最后g数组更新f数组时要还原前缀,对应往上加,记得乘上枚举重儿子是谁时产生的概率qpow(son[x],mod-2)。

对了,还有一个小优化,枚举len时max就是到size[x],不然会TLE,苣蒻搞不懂为神魔大佬们都写size[x]+1。。。。。。

好了,本题到此结束。code常数还是可以的,不是很大。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
int n,size[100010],head[100010],tot,root=1,fa[100010],f[3001][3001],g[3001],h[3001],son[100010],ans;
struct ainiysys
{int to,next;}bian[200010];
inline void fr(){freopen("c.in","r",stdin);}
inline void sc(){scanf("%lld",&n);}
namespace AYX
{	inline int qpow(int a,int b)
	{	int base=1;
		while(b)
		{	if(b&1)base=(base*a)%mod;
			a=(a*a)%mod;
			b>>=1;
		}
		return base;
	}	
	inline void add(int u,int v)
	{	bian[++tot].to=v;
		bian[tot].next=head[u];
		head[u]=tot;
	}	
	inline void dfs(int x)
	{	size[x]=1;
		for(int i=head[x];i;i=bian[i].next)
		{	int v=bian[i].to;
			dfs(v);
			size[x]+=size[v];
		}
		int p=qpow(son[x],mod-2);
		for(int i=head[x];i;i=bian[i].next)
		{	int zhongson=bian[i].to;
			for(int j=0;j<=n;++j)g[j]=1;
			for(int j=head[x];j;j=bian[j].next)
			{	int v=bian[j].to;
				for(int k=0;k<=size[v];++k)
				{	int qita=g[k],erzi=f[v][k];
					if(k)qita-=g[k-1],erzi-=f[v][k-1];
					if(v==zhongson)
					h[k]=((qita*f[v][k]+g[k]*erzi)%mod-qita*erzi+mod)%mod;
					else
					{	erzi=f[v][k-1];
						if(k-1)erzi-=f[v][k-2];
						h[k]=((qita*f[v][k-1]+g[k]*erzi)%mod-qita*erzi+mod)%mod;
					}
				}
				g[0]=h[0],h[0]=0;
				for(int k=1;k<=size[v];++k)
				g[k]=(g[k-1]+h[k])%mod;		
			}
			for(int j=size[x];j;--j)g[j]=(g[j]-g[j-1]+mod)%mod;
			for(int j=0;j<=size[x];++j)f[x][j]=(f[x][j]+g[j]*p%mod)%mod;
		}		
		if(!son[x])f[x][0]=1;
		for(int i=1;i<=size[x];++i)f[x][i]=(f[x][i]+f[x][i-1])%mod;
	}
	inline void work()
	{	for(int i=1;i<=n;++i)
		{	scanf("%lld",&son[i]);
			for(int j=1;j<=son[i];++j)
			{	int x;scanf("%lld",&x);
				add(i,x);fa[x]=i;
			} 
		}
		while(fa[root])root=fa[root];
		dfs(root);
		for(int i=1;i<=n;++i)
		{	ans=(ans+(i*((f[root][i]-f[root][i-1]+mod)%mod)))%mod;	
		}
		printf("%lld\n",ans);
	}	
	inline short main()
	{sc();work();return 0;}
}
signed main()
{return AYX::main();}

建造游乐园

%%%@高老师。

欧拉回路嘛,俺不会,直接弃掉喽。样例都看不懂的。。

现在倒是会了嘛,哈哈。欧拉回路,操作是加边或砍边。其实可以先搞出回路来,再考虑增减,回路定了,任意两点之间你让它加一个或减一个就好。

所以现在问题就成了求n个点的连通欧拉回路有多少种,再乘上C(n 2)便是ans啦(如果是加边,那么能加的位置就是相邻的两个点之间的边,为n。如果是去边,由于没有重边,一个点可对应n-3个点连边,那么就是你n*(n-3)/2。在加上n就是C(n,2)了)。欧拉回路,要求每个点的度都是偶数。我们先不管这些,只看n个点,有几条边呢?显然为C(n,2)。那么我的图每条边可以连也可以不连,so总图数就是2^C(n,2)。现在再来考虑度为偶数,可是联通我不会求唉,怎么办?我可以先求所有的嘛(其实考场上sum我也不会求。。)我们可以先拿出一个点来,剩下i-1个点爱咋连咋连,因为我拿出的这一个点是万能的,想跟谁连都可以,他要是奇数我就连一下,不就成偶数了吗,偶数就不搭理他。不必担心我会成奇数,不可能滴。反正连吧连吧他就ok了。
这样的话方案数是2^C(i-1,2)。这是全体偶数,我要的是联通,那就减去不联通。

不联通怎么求,又是个问题。图要是不联通的话,最少有2个联通块,假设有i个点,我可以定住一个点在一个块里,这个块里有j个点(1=<j<=i),那么剩下的i-j个点爱咋连咋连,我管不着它,反正满足不联通了。可以发现这i-j个点的状态是我之前求过的所有的当n==i-j时的状态,显然就是递推过来的。不要忘了这j个点除了我定住的一个,其余的j-1个可不一定,所以要乘上C(i-1,j-1)。

下面在代码实现的时候C杨辉三角递推就行,数据很小。快速幂啥的就不多说了。

时间上挺快的code

#include<bits/stdc++.h>
#define int long long
#define mod 1000000007
using namespace std;
int c[2001][2001],f[2001],g[2001],n;
inline void fr(){freopen("c.in","r",stdin);}
inline void sc(){scanf("%lld",&n);}
namespace AYX
{	inline int qpow(int a,int b)
	{	int base=1;
		while(b)
		{	if(b&1)base=(base*a)%mod;
			a=(a*a)%mod;
			b>>=1;
		}
		return base;
	}
	inline void work()
	{	for(int i=0;i<=n;++i)
		{	c[i][0]=1;
			for(int j=1;j<=i;++j)
			c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
		}
		g[0]=1;f[0]=1;
		for(int i=1;i<=n;++i)
		{	f[i]=g[i]=qpow(2,(i-1)*(i-2)/2);
			for(int j=1;j<=i-1;++j)
			{	f[i]=(f[i]-(((f[j]*g[i-j])%mod)*c[i-1][j-1]%mod)+mod)%mod;
			}
		}
		printf("%lld\n",(((f[n]*n)%mod)*(n-1)%mod)*qpow(2,mod-2)%mod);
	}
	inline short main()
	{sc();work();return 0;}

}
signed main()
{return AYX::main();}

End

考试收获是很大的,虽然考的挺惨的。

posted @ 2021-10-01 19:53  -zxb-  阅读(60)  评论(0编辑  收藏  举报