20171026校内训练
海棠数组啊,差分后线段树乱搞就过了
#include<iostream> #include<cstdio> using namespace std; int sum[500100],l[500100],r[500100],c[500100],a[500100]; int Abs(int x){return x>0?x:-x;} void build(int L,int R,int x) { l[x]=L;r[x]=R; if(L==R){sum[x]=c[L];return;} build(L,(L+R)/2,x<<1);build((L+R)/2+1,R,x<<1|1); sum[x]=max(sum[x<<1],sum[x<<1|1]); } void add(int x,int kk,int k) { if(l[x]==r[x]&&l[x]==kk){sum[x]+=k;return;} int mid=(l[x]+r[x])/2; if(kk<=mid)add(x<<1,kk,k); else add(x<<1|1,kk,k); sum[x]=max(sum[x<<1],sum[x<<1|1]); } int query(int x,int L,int R) { if(l[x]==L&&r[x]==R){return sum[x];} int mid=(l[x]+r[x])/2; if(R<=mid)return query(x<<1,L,R); else if(L>mid)return query(x<<1|1,L,R); else return max(query(x<<1,L,mid),query(x<<1|1,mid+1,R)); } int main() { freopen("lipschitz.in","r",stdin);freopen("lipschitz.out","w",stdout); int n;scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); c[i-1]=Abs(a[i]-a[i-1]); } build(1,n-1,1); int q;scanf("%d",&q); for(int i=1;i<=q;i++) { int type,l,r;scanf("%d%d%d",&type,&l,&r); if(type) { if(r<=l)puts("0"); else printf("%d\n",query(1,l,r-1)); } else { a[l]=r;int k1=Abs(a[l]-a[l-1]),k2=Abs(a[l+1]-a[l]); if(l!=1){add(1,l-1,k1-c[l-1]);c[l-1]=k1;} if(l!=n){add(1,l,k2-c[l]);c[l]=k2;} } } return 0; }
正解:
正难则反
定义坏的位置i满足:ai-1<ai>ai+1
f[i][j]表示前i个数有j个不好的,所以f[1][1]=1;
f[i][j] 有2种情况:
1.这个位置是坏的:
那么 它不可以填所有坏的的两边=>f[i][j]=f[i-1][j-1]*(i-(j-1)*2)
2.这个位置是好的
那么 它可以填在所有坏的的两边=>f[i][j]=f[i-1][j]*j*2
综上可得:
f[i][j]=f[i-1][j-1]*(i-(j-1)*2)+f[i-1][j]*j*2.
我的蜜汁做法:
我们用f[i-1][n-j+1]表示前i个数有j个好位置的方案数,至于为什么要这样,等等再说
先看看下表,第i行第j列表示前i+1个数有j个好位置的方案数
2
2 4
0 16 8
0 16 88 16
0 0 272 416 32
0 0 272 2880 1824 64
0 0 0 7936 24576 7680 128
0 0 0 7936 137216 185856 31616 256
0 0 0 0 353792 1841152 1304832 128512 512
0 0 0 0 353792 9061376 21253376 8728576 518656 1024
但我们发现这表并不太好看,于是我们右对齐
2
2 4
0 16 8
0 16 88 16
0 0 272 416 32
0 0 272 2880 1824 64
0 0 0 7936 24576 7680 128
0 0 0 7936 137216 185856 31616 256
0 0 0 0 353792 1841152 1304832 128512 512
0 0 0 0 353792 9061376 21253376 8728576 518656 1024
我们随便算几个数,发现
272=0*8+272*1
7936=272*8+2880*2
137216=7936*8+24576*3
2880=272*6+416*3
24576=2880*6+1824*4
185856=24576*6+7680*5
1824=416*4+32*5
7680=1824*4+64*6
31616=7680*4+128*7
我们发现了什么?f[i][j]=f[i-1][j]*(从右往左数是第几列*2)+f[i-1][j+1]*(它自己和自己上方不是0的个数)
即f[i][j]=f[i-1][j]*(n-j+1)*2+f[i-1][j+1]*(i-(n-j)-(n-j-1))(从右往左数第j列的上方有j-2个0)
这样,我们就可以O(n^2)求出这个数组,然后统计答案时,随便for一下就好了
注意,对于每个询问,当k>=n时输出0,注意上表第一行是n=2的情况,且上表没有k=0的情况
当k=0时,则答案为n!(因为你可以随便排列)
建议f数组和ans开long long,否则计算过程中有可能溢出,还要记得取膜,输出ans时要用lld
打表:
#include<cstdio> #include<cstring> using namespace std; bool used[100]; int a[100],ans[100],n; void dfs(int now){ if(now==n+1){ int tmp=0; for(int i=1;i<=n;i++)if(a[i-1]>a[i]||a[i+1]>a[i])tmp++; ans[tmp]++;return; } for(int i=1;i<=n;i++)if(!used[i])used[i]=true,a[now]=i,dfs(now+1),used[i]=false; } int main(){ freopen("xxx.out","w",stdout); for(n=2;n<=20;n++){ memset(ans,0,sizeof(ans));dfs(1); for(int k=1;k<=n-1;k++){ printf("%10d",ans[k]); }puts(""); } }
计算:
#include<iostream> #include<cstdio> using namespace std; const int mod=1e9+7; long long f[3010][3010]; int main() { freopen("permutation.in","r",stdin); freopen("permutation.out","w",stdout); int n=3000; f[1][n]=2; for(int i=2;i<=n;i++) for(int j=n-i+1;j<=n;j++) f[i][j]=(f[i-1][j]*(long long)(n-j+1)*2+f[i-1][j+1]*(long long)(i-(n-j)-(n-j-1)))%mod; int q;scanf("%d",&q); while(q--) { int n1,k1;long long ans=0;scanf("%d%d",&n1,&k1); if(k1==0){ans=1;for(int i=1;i<=n1;i++)ans=(ans*i)%mod;printf("%lld\n",ans);continue;} if(k1>=n1){puts("0");continue;} for(int i=n-n1+k1+1;i<=n;i++)ans=(ans+f[n1-1][i])%mod; printf("%lld\n",ans); } return 0; }