模拟赛day2 2014 10 26

来源:互联网 发布:哪个软件可以听歌识曲 编辑:程序博客网 时间:2024/06/18 18:29

T1:

题目大意:给你一颗二叉树,再给你每个点的权值a[i],让你改变最少的点的权值,是的满足任意的p,a[lch]<a[p],a[rch]>a[p],lch代表p的左儿子及其子树,rch代表p的右儿子及其子树,权值必须时刻为整数(可以为负数或者0)  n<=100000


这题是原题,但是我当时没做,还好考的时候想出来了

首先看到一棵树很烦躁,所以就把树按中序遍历搞出来,就转化成求把一个序列变成严格上升最小需要改多少个数

设f[i]代表第i个数不改,满足性质最少需要多少步,f[i]=i-1   min{f[k]-k}    a[i]-a[k]>=i-k  

a[i]-a[k]>=i-k    就等价于 a[i]-i>=a[k]-k

这个我当时是这样做的,先按a[i]-i排序,再在树状数组中的i位置插入f[i]-i,求最小值就OK,不过这样比较慢

其实这就是一个最长上升子序列,先按a[i]-i>=a[k]-k排序就好了  时间复杂度O(nlog2(n))

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;const int inf=2147483647,maxn=100011;int c[maxn][3],n,a[maxn];void init(){scanf("%d",&n);for (int i=1;i<=n;++i) scanf("%d",a+i);int a,b;for (int i=2;i<=n;++i){scanf("%d%d",&a,&b);c[a][b]=i;}}struct Tbit{int t[maxn];void clear(){memset(t,63,sizeof(t));}void ins(int x,int m){for (;x<=n;x+=x&(-x)) t[x]=min(t[x],m);}int Query(int x,int ans=inf){for (;x;x-=x&(-x)) ans=min(ans,t[x]);return ans;}}bit;int A[maxn],stack_x[maxn],stack_p[maxn];void Gets(){int cnt=0,top=0;stack_x[++top]=1;stack_p[top]=0;while (top){int x=stack_x[top],p=stack_p[top];if (!x || p>2){--top;++stack_p[top];continue;}if (p==0){stack_x[++top]=c[x][0];stack_p[top]=0;continue;}if (p==1){++cnt;A[cnt]=a[x]-cnt;++stack_p[top];continue;}if (p==2){stack_x[++top]=c[x][1];stack_p[top]=0;continue;}}}inline bool cmp(int *a,int *b){if (*a==*b) return a-A<b-A;return *a<*b;}int *p[maxn],f[maxn];void work(){Gets();A[0]=-inf;A[++n]=inf;for (int i=1;i<=n;++i) p[i]=A+i;sort(p+1,p+n+1,cmp);for (int i=1;i<=n;++i){int x=p[i]-A;f[x]=bit.Query(x)+x-1;bit.ins(x,f[x]-x);}//for (int i=1;i<=n;++i) cout<<i<<' '<<f[i]<<endl;printf("%d\n",f[n]);}int main(){init();work();return 0;}



T2:

题目大意:给你n个数a[i],现在要找到一对(l,r) 使得存在一个k  l<=k<=r  对于任意的a[i]%a[k]==0 l<=i<=r   n<=500000 a[i]<2^31

这题可以预处理出来一个ST表,然后就可以了,复杂度是O(nlog2(n)log2(a)) 的,但是求GCD是远小于log2(a)的,所以还是可以过的

#include<cmath>#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;const int maxn=500011,maxq=25;inline int read(){char ch=getchar();int x=0;while (!isdigit(ch)) ch=getchar();for (;isdigit(ch);ch=getchar()) x=x*10+ch-'0';return x;}int n,a[maxn];void init(){n=read();for (int i=1;i<=n;++i) a[i]=read();}inline int gcd(int m,int n){int t;while (n!=0){t=m%n;m=n;n=t;}return m;}int p[maxq],f[maxq][maxn][2],quit;void prepare(){quit=(int)log2(n);for (int i=0;i<=quit;++i) p[i]=1<<i;for (int i=1;i<=n;++i) f[0][i][0]=f[0][i][1]=a[i];for (int i=1;i<=quit;++i) for (int j=n;j>=1;--j){if (j-p[i]+1<1) break;f[i][j][0]=gcd(f[i-1][j][0],f[i-1][j-p[i-1]][0]);}for (int i=1;i<=quit;++i) for (int j=1;j<=n;++j){if (j+p[i]-1>n) break;f[i][j][1]=gcd(f[i-1][j][1],f[i-1][j+p[i-1]][1]);}}int getl(int x){int tmp=x-1;for (int i=quit;i>=0;--i) if (tmp-p[i]+1>=1 && f[i][tmp][0]%a[x]==0) tmp=tmp-p[i];return tmp+1;}int getr(int x){int tmp=x+1;for (int i=quit;i>=0;--i) if (tmp+p[i]-1<=n && f[i][tmp][1]%a[x]==0) tmp=tmp+p[i];return tmp-1;}int l[maxn],r[maxn];bool vis[maxn];void work(){prepare();for (int i=1;i<=n;++i) l[i]=getl(i),r[i]=getr(i);int ans=0,sum=0;for (int i=1;i<=n;++i) if (r[i]-l[i]>ans) ans=r[i]-l[i];for (int i=1;i<=n;++i) if (r[i]-l[i]==ans) vis[l[i]]=1;for (int i=1;i<=n;++i) if (vis[i]) ++sum;printf("%d %d\n",sum,ans);for (int i=1;i<=n;++i) if (vis[i]) printf("%d ",i);}int main(){init();work();return 0;}
这题还可以用各种数据结构维护,都可以在给定的时间内通过

不过这题还可以用一个神奇的O(n*a(n))的方法做,a(n) 是并查集常数,怒虐std 10倍(zzb大神发明的)

具体来说:我们先枚举k,再看其左右分别能扩展多远就行了

我们只考虑左边,如果对于一个j,有j<i且对于任意 i 有a[i]%a[k]==0   j<=i<=k 就可以把j到k合并到一个点,因为后面的点可以扩展到k,就一定可以扩展到j

合并的话,就只要在并查集上连跳边就行了

#include<cmath>#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;const int maxn=500011,maxq=25;inline int read(){char ch=getchar();int x=0;while (!isdigit(ch)) ch=getchar();for (;isdigit(ch);ch=getchar()) x=x*10+ch-'0';return x;}int n,a[maxn],fa[maxn];int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}void init(){n=read();for (int i=1;i<=n;++i) a[i]=read();}int l[maxn],r[maxn];bool vis[maxn];void work(){for (int i=1;i<=n;++i) fa[i]=i;for (int i=1,j,last;i<=n;++i){for (j=i-1,last=i;j>0;j=find(j)-1)if (a[j]%a[i]==0) fa[last]=j,last=j; else break;l[i]=j+1;}//for (int i=1;i<=n;++i) cout<<i<<' '<<l[i]<<endl;//for (;;);for (int i=1;i<=n;++i) fa[i]=i;for (int i=n,j,last;i>=1;--i){for (j=i+1,last=i;j<=n;j=find(j)+1)if (a[j]%a[i]==0) fa[last]=j,last=j; else break;r[i]=j-1;}int ans=0,sum=0;for (int i=1;i<=n;++i) if (r[i]-l[i]>ans) ans=r[i]-l[i];for (int i=1;i<=n;++i) if (r[i]-l[i]==ans) vis[l[i]]=1;for (int i=1;i<=n;++i) if (vis[i]) ++sum;printf("%d %d\n",sum,ans);for (int i=1;i<=n;++i) if (vis[i]) printf("%d ",i);}int main(){init();work();return 0;}



T3:

题目大意:给你一个1到n的排列P,求有多少个Q(1到n-1的排列) 使得 swap(P[Q[1]],P[Q[1]+1])  swap(P[Q[2]],P[Q[2]+1]) .... swap(P[Q[n-1]],P[Q[n-1]+1])  后,P满足P[i]==i,答案mod 1000000007  n<=50


这题完全没思路,就打了个O(n!) 的暴力拿了30分

正解是这样的

因为换的是排列,所以(i,i+1) 只能被换一次,要保证换完后所有在i左边的小于i,在i右边的大于i

所以可以记忆化搜索,  f[l][r]代表l到r有多少种方案可以使得l到r从小到大排好

枚举一个k,先把P[k],P[k+1]换一下,如果check(l,k) && check(k+1,r) 就可以用k来更新f[l][r]      check(a,b) 代表P[a]到P[b]中的数都在a到b范围内

具体来说f[l][r]+=f[l][k]*f[k+1][r]*C(r-l-1,k-l) 这个是为什么,自己仔细想想就知道了

#include<cstdio>#include<cstring>#define swap(a,b) a^=b^=a^=busing namespace std;const int maxn=53,mod=1000000007;int a[maxn],n,f[maxn][maxn],c[maxn][maxn];void init(){scanf("%d",&n);for (int i=1;i<=n;++i) scanf("%d",a+i),++a[i];}bool check(int l,int r){for (int i=l;i<=r;++i) if (a[i]<l || a[i]>r) return 0;return 1;}int dfs(int l,int r){if (l==r) return 1;if (f[l][r]) return f[l][r];for (int k=l;k<r;++k){swap(a[k],a[k+1]);if (check(l,k) && check(k+1,r)) f[l][r]=(f[l][r]+1LL*dfs(l,k)*dfs(k+1,r)%mod*c[r-l-1][k-l]%mod)%mod;swap(a[k],a[k+1]);}return f[l][r];}void prepare(){c[0][0]=1;for (int i=1;i<=n;++i) for (int j=0;j<=i;++j) c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;}void work(){prepare();dfs(1,n);printf("%d\n",f[1][n]);}int main(){init();work();return 0;}


今天考的还行,能拿的分都拿到了

0 0
原创粉丝点击