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;
}
题目大意:给你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;
}
题目大意:给你一个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^=b
using 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;
}
今天考的还行,能拿的分都拿到了