2021.5.22 noip模拟1
Posted hzoi------fengwu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021.5.22 noip模拟1相关的知识,希望对你有一定的参考价值。
这场考试考得很烂
连暴力都没打好
只拿了25分,,,,,,,,好好总结
T1序列
A. 序列
题目描述
输入格式
输出格式
样例
数据范围与提示
这题应该是最简单的一个题了
思路倒是不难想,,考场上想出来了要nlogn做,思路大概对了一半,在处理的时候有一些问题
只得了5pts
重新看看这道题
题意:
在一个序列中找到一个最长的子序列,使它排序后是一个不完整的等比数列(公比<=1000)
可以看到公比小的不的了,所以王天泽大佬提供了一种极其暴力却暴打我nlogn的算法
不需要找最小公比,直接枚举1-1000,找到比就好了,400ms掉打你们
还是说一下我的思路(题解的)
首先一定是公比为1;直接扫一遍,找到最长的相等的子串就可以了
其次就是不为1::
我们知道任何一个数都可以质因数分解
我们设a[ ]为这个序列里的数
x = a [ i ] ; y = a [ i - 1 ];
设x>y,只有当y整除x时才有可能构成等比数列
设p=x/y
我们对p进行质因数分解
得到p1q1*p2q3*p3q3......
那么还有一个性质:::
设q为q1 q2 q3的最大公因数
那么p为p1q1/q*p2q2/q*p3q3/q....的整数倍((这个显而易见))
那么我们则mini=p1q1/q*p2q2/q*p3q3/q....即为x,y的最小公比(就是能够构成等比数列的最小的公比)::4=>2 8=>2 27=>3 216=>6
这样预处理出来所有的mini(2-n)
如果两个mini相等,那么就可以构成等比序列,当然不能有重复的
这时候就要用上map大法了,就像数组一样用只不过下标可以直接开到longlong,
每一个map都存着这个数出现的位置,更新的时候就方便多了
#include<bits/stdc++.h> using namespace std; #define re register int #define ll unsigned long long const int N=100005; int n; ll a[N]; int ans=0,res=0; ll prime[N],vis[N],cnt; ll mini[N],q[N],ch[N]; ll st[N][120]; ll ksm(ll x,int y){ ll ret=1; while(y){ if(y&1)ret=ret*x; x=x*x; y>>=1; } return ret; } ll gcd(ll x,ll y){ if(y==0)return x; return gcd(y,y%x); } void getmin(ll x,ll y,int z){ if(x==0||y==0)return ; if(x>y)swap(x,y); ll p=y%x; //cout<<z<<" "<<y<<" "<<x<<" "<<p<<"sb"<<endl; if(p!=0){ mini[z]=0; return ; } p=y/x;ch[z]=p; //cout<<p<<endl; for(re i=1;i<=cnt;i++){ q[i]=0; while(p%prime[i]==0) p/=prime[i],q[i]++; } int pd; ll g; for(re i=1;i<=cnt;i++){ if(q[i]==0)continue; if(g==0){ g=q[i]; continue; } g=gcd(g,q[i]); } for(re i=1;i<=cnt;i++){ if(q[i]==0)continue; mini[z]*=pow(prime[i],q[i]/g); } } map<ll,int> mp; int main(){ scanf("%d",&n); for(re i=2;i<=1002;i++){ if(vis[i]==0)prime[++cnt]=i; for(re j=1;j<=cnt&&i*prime[j]<=n;j++){ vis[i*prime[j]]=1; if(i%prime[j]==0)break; } } mini[1]=0; for(re i=2;i<=n;i++)mini[i]=1; for(re i=1;i<=n;i++){ scanf("%lld",&a[i]); if(a[i]==a[i-1])res++; else res=1; ans=max(ans,res); getmin(a[i-1],a[i],i); //cout<<i<<" "<<mini[i]<<endl; } int fro,beh;//前后端点 for(re i=2;i<=n;i++){ //while(mini[i-1]!=0&&ch[i]%mini[i-1]==0&&ch[i]!=0)ch[i]/=mini[i-1]; if(mini[i]==mini[i-1]&&mini[i]!=0){ if(mp[a[i]]&&mp[a[i]]>=fro)fro=mp[a[i]]+1; mp[a[i]]=i;beh=i; } else{ mp.clear(); mp[a[i-1]]=i-1; mp[a[i]]=i; fro=i-1; beh=i; } ans=max(ans,beh-fro+1); } printf("%d",ans); }
T2熟练剖分
题目描述
输入格式
输出格式
样例
数据范围与提示
这题20pts
考场上没别的心思--对着部分分就是一顿操作
考场下才想正解,说实话,这正解是真的玄乎;;;;;;;
什么树链剖分,分明就是概率期望
借着体面吓唬人
这个主要是求概率;
不妨设f [i] [j]为以i为根节点,目前遍历的儿子节点中,能让i的最大深度<=j的概率
好看出来由儿子向父亲转移
设h[j]为目前最大深度为j的概率(h[1]+h[2]+...+h[j]=f[i][j])
g[j]为目前重儿子深度<=j的概率 初值均为1(因为一开始都为0,前缀和一下都是1)
先循环选择哪一个为重儿子,再遍历每一个儿子,里面再套一层深度
分二种情况
目前儿子等于重儿子
h[i]=g[j]*(f[i][j]-f[i][j-1])+(g[j]-g[j-1])*f[i][j]-(f[i][j]-f[i][j-1])*(g[j]-g[j-1]);
//重儿子等于j,轻儿子小于等于j//////重儿子小于等于j,轻儿子等于j
重儿子不等于轻儿子
看代码吧,和上面差不多
你可能会怀疑为什么每一个f[i][j]都是中间变量,在还没运算到最后结果时
就自己调用自己,为什么是正确的
因为,每一个f[i][j]都是由前面的儿子转移的,可以当作一开始没有儿子,后来一个一个的加进来,这样就好理解了
#include<bits/stdc++.h> using namespace std; #define re register int #define ll long long const int mod=1e9+7; const int N=3005; int n,m[N]; int to[N],nxt[N],head[N],rp; int fa[N],root,siz[N]; ll inv[N]; void add_edg(int x,int y){ to[++rp]=y; nxt[rp]=head[x]; head[x]=rp; } ll f[N][N],g[N],h[N]; void dfs(int x){ siz[x]=1; int maxn=0; for(re i=head[x];i;i=nxt[i]){ dfs(to[i]); siz[x]+=siz[to[i]]; maxn=max(maxn,siz[to[i]]); } for(re i=head[x];i;i=nxt[i]){ for(re j=0;j<=n;j++)g[j]=1; int u=to[i]; for(re j=head[x];j;j=nxt[j]){ int v=to[j]; for(re k=0;k<=siz[v];k++){ ll gs=g[k],fs=f[v][k]; if(k){ gs-=g[k-1]; fs-=f[v][k-1]; } if(u==v)h[k]=(gs*f[v][k]%mod+fs*g[k]%mod-fs*gs%mod+mod)%mod; else{ fs=f[v][k-1]; if(k>1)fs-=f[v][k-2]; h[k]=(g[k]*fs%mod+gs*f[v][k-1]%mod-fs*gs%mod+mod)%mod; } } g[0]=h[0]; for(re k=1;k<=siz[v];k++) g[k]=(g[k-1]+h[k])%mod; } for(re j=siz[x];j>=1;j--)g[j]=(g[j]-g[j-1]+mod)%mod; for(re j=0;j<=siz[x];j++)f[x][j]=(f[x][j]+g[j]*inv[m[x]]%mod)%mod; } if(!head[x])f[x][0]=1; for(re i=0;i<=siz[x];i++){ f[x][i]=(f[x][i]+f[x][i-1])%mod; } } signed main(){ scanf("%d",&n); inv[1]=inv[0]=1; for(re i=2;i<=n;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod; for(re i=1;i<=n;i++){ scanf("%d",&m[i]); int son; for(re j=1;j<=m[i];j++){ scanf("%d",&son); fa[son]=i; add_edg(i,son); } } root=1; while(fa[root])root=fa[root]; //cout<<root<<endl; dfs(root); ll ans=0; for(re i=n;i>=1;i--){ ans=(ans+i*(f[root][i]-f[root][i-1]+mod)%mod)%mod; } printf("%lld",ans); }
T3建造游乐园
题目描述
输入格式
输出格式
样例
数据范围与提示
不是这个题简单是高俊垚大神讲的好(((呱叽呱叽)))
首先设f[n]为n个点时可以得到的联通欧拉图;
g[n]为n个点时可以得到的联不联通都行的;
那么最终答案就是ans=f[n]*Cn2
显然g[i]=2^Ci-12因为现在有一个i-1个点的随便一张图,那么要想让每一个点的度都为偶数
就要让第i个点连接那些奇数点这样一一对应得到了欧拉图
f[i]=g[i]-∑j(1-i-1)f[j]*g[i-j]*Ci-1j-1
这个式子怎么来的呢
减去的这一堆一定是不联通的那么肯定有很多个联通块
只需要找出两个就够了所以选出来几个数在外面
循环j(几个数在外面)
得到本式
#include<bits/stdc++.h> using namespace std; #define re register int #define ll long long const int mod=1e9+7; const int N=2005; int n; ll jc[N],inv[N],innv[N]; ll f[N],g[N]; ll ksm(ll x,ll y){ ll ret=1; while(y){ if(y&1)ret=ret*x%mod; x=x*x%mod; y>>=1; } return ret%mod; } ll C(ll x,ll y){ if(x>y)return 0; return jc[y]*inv[x]%mod*inv[y-x]%mod; } int main(){ scanf("%d",&n); jc[1]=1;jc[0]=1; inv[1]=inv[0]=1; innv[1]=innv[0]=1; for(re i=2;i<=n;i++){ jc[i]=jc[i-1]*i%mod; innv[i]=(mod-mod/i)*innv[mod%i]%mod; inv[i]=innv[i]*inv[i-1]%mod; } g[1]=1; for(re i=2;i<=n;i++){ g[i]=ksm(2,C(2,i-1)); } f[1]=1; for(re i=2;i<=n;i++){ ll tmp=0; for(re j=1;j<i;j++){ tmp=(tmp+f[j]*g[i-j]%mod*C(j-1,i-1)%mod)%mod; } f[i]=(g[i]-tmp+mod)%mod; } printf("%lld",f[n]*C(2,n)%mod); }
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define re register int 4 #define ll long long 5 const int mod=1e9+7; 6 const int N=2005; 7 int n; 8 ll jc[N],inv[N],innv[N]; 9 ll f[N],g[N]; 10 ll ksm(ll x,ll y){ 11 ll ret=1; 12 while(y){ 13 if(y&1)ret=ret*x%mod; 14 x=x*x%mod; 15 y>>=1; 16 } 17 return ret%mod; 18 } 19 ll C(ll x,ll y){ 20 if(x>y)return 0; 21 return jc[y]*inv[x]%mod*inv[y-x]%mod; 22 } 23 int main(){ 24 scanf("%d",&n); 25 jc[1]=1;jc[0]=1; 26 inv[1]=inv[0]=1; 27 innv[1]=innv[0]=1; 28 for(re i=2;i<=n;i++){ 29 jc[i]=jc[i-1]*i%mod; 30 innv[i]=(mod-mod/i)*innv[mod%i]%mod; 31 inv[i]=innv[i]*inv[i-1]%mod; 32 } 33 g[1]=1; 34 for(re i=2;i<=n;i++){ 35 g[i]=ksm(2,C(2,i-1)); 36 } 37 f[1]=1; 38 for(re i=2;i<=n;i++){ 39 ll tmp=0; 40 for(re j=1;j<i;j++){ 41 tmp=(tmp+f[j]*g[i-j]%mod*C(j-1,i-1)%mod)%mod; 42 } 43 f[i]=(g[i]-tmp+mod)%mod; 44 } 45 printf("%lld",f[n]*C(2,n)%mod)
以上是关于2021.5.22 noip模拟1的主要内容,如果未能解决你的问题,请参考以下文章