BestCoder Round #40 解题报告

  这场是第一场没有米的BC...

  大概也是想震一震那些一听说没米了就不打BC的人吧

  这次的题目质量比以往高了许多

  (然而我并没有打这一场BC

  但是今天下午到现在做的过程中真的学到了不少知识呢

 

 

 


 

 

  A题略水...

 

 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<cmath>
 5 int T,n;
 6 int main(){
 7     scanf("%d",&T);
 8     while (T--){
 9         scanf("%d",&n);
10         int tmp;
11         for (int i=1;i<=sqrt(n);i++) if (n%i==0) tmp=i;
12         printf("%d\n",2*(n/tmp+tmp));
13     }
14     return 0;
15 }

 

 


 

 

  然而第二题就不是非常简单的呢...

  开始啊..我打算数位DP搞搞,然而突然发现排列是不能有重复数字的啊...

  想到一个容易入手的状态 也就解决了这道题

  我们需要让这些排列都小于读进来的排列

  然后思考一下这些排列都有什么共性呢?

  显然一定是由一段与排列相同的数字组成的前缀构成的,而接下来的一位一定小于排列,剩下的就随意了

  这么一来好像问题就迎刃而解了

  枚举与原串相同的位数(0~n-1),以及接下来一位的数字(小于原串该位置上的数字且在前面的位置中没出现过)

  

  设当前枚举的黄色部分的位置为i,那么显然这样的字符串有(n-i)!种(即后面蓝色部分的n-i个数字的全排列)

  这些字符串对答案的贡献分几部分考虑

  1)红色部分内部的逆序对*(n-i)!,原串所有的前缀的逆序对数量可以通过树状数组预处理出

  2)红色部分与黄色+蓝色部分的逆序对数,

可以用树状数组维护黄色+蓝色部分的值(即每向后走一格就将前一个数从树状数组中删去)

然后枚举红色部分的每一个元素,统计逆序对数,最后乘上(n-i)!

  3)黄色部分与蓝色部分的逆序对数,直接与红色部分一样的答案最后-1即可(即把自己与自己减掉),

当然get的时候传参改成数值-1也是可以的

  4)蓝色部分的逆序对数,这个在(n-i)!个字符串中都是不一样的,于是我们可以考虑总数

对于一个n个数的排列,我们考虑其中的两个数,它们成为逆序对的次数为C(n,2)*(n-2)!

所以逆序对总数为C(n,2)^2*(n-2)!

  然后这道题就做完了,但是却对拍了一个下午都没找出哪里写错...

  最后写两点做题过程中积累的东西吧...

  1)C++的数组和Pas毕竟不一样...赋值啊什么的不能顶到maxn..而且居然本地不会出现RE

  2)while not eof do 在C++里是以很奇怪的方式替代的呢...

 

 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<iostream>
 4 #include<cmath>
 5 #include<cstring>
 6 #define tt 1000000007
 7 #define maxn 110
 8 #define ll long long
 9 using namespace std;
10 int n,a[maxn],tr[maxn];
11 ll w[maxn],p[maxn],b[maxn];
12 bool used[maxn];
13 int get(int x){
14     int tmp=0;
15     while (x) tmp+=tr[x],x-=x&(-x);
16     return tmp;
17 }
18 void put(int x,int y){
19     while (x<=n) tr[x]+=y,x+=x&(-x);
20 }
21 void build(){
22     w[0]=1;for(int i=1;i<maxn;i++) w[i]=(w[i-1]*i)%tt;    
23     p[1]=0;for (int i=2;i<maxn;i++) {
24         ll tmp=i*(i-1)/2;
25         p[i]=((tmp*tmp%tt)*w[i-2])%tt;
26     }
27 }
28 void solve(){
29     ll ans=0;
30     memset(tr,0,sizeof(tr));
31     for (int i=1;i<=n;i++) b[i]=(b[i-1]+get(n-a[i]+1))%tt,put(n-a[i]+1,1);
32     memset(tr,0,sizeof(tr));
33     memset(used,true,sizeof(used));
34     for (int i=1;i<=n;i++) put(i,1);
35     for (int i=1;i<=n;i++){
36         if (i!=1) put(a[i-1],-1),used[a[i-1]]=false;
37         for (int j=1;j<a[i];j++) if (used[j]){    
38             ll tmp=b[i-1];for (int k=1;k<i;k++) tmp=(tmp+get(a[k]))%tt;
39             if (j!=1) tmp=(tmp+get(j-1))%tt;
40             tmp=((tmp*w[n-i])+p[n-i])%tt;    
41             ans=(ans+tmp)%tt;
42         }
43     }
44     cout<<ans<<endl;
45 }
46 int main(){
47     build();
48     while (cin>>n){    
49         for (int i=1;i<=n;i++) scanf("%d",a+i);        
50         solve();
51     }
52     return 0;
53 }

 

 

  


 

 

 

  浓浓的数学味...于是又得去补充几个知识了呢

  首先带取模的组合数是不能直接求解的...因为一旦C(n,m)中假设n>模数p,n!中就会出现p这个因子

  然后C(n,m)就不可避地计算出了0...

  这个时候要用到Lucas定理,递归定义  

1 ll lucas(int n,int m){
2     if (m==0) return 1;
3     if (n<tt&&m<tt) return c(n,m);
4     return lucas(n/tt,m/tt)*lucas(n%tt,m%tt)%tt;
5 }

 

  另一个就是连续的组合数快速求解...

  刚开始我一直在寻找同一行之间的关系..因为毕竟之前如果是整行是可以直接算的

  然而实际上列存在着一定的关系

  我们考虑这样一列上连续一段的求解(如图中红色部分)  

  根据杨辉三角的构造方法,C(n,m)=C(n-1,m)+C(n-1,m-1)

  我们设4号格子的位置为(i,a)

  最顶端红色格子的位置为(i,b)

  C(a+1,i+1)=C(a,i)+C(a,i+1) → C(a,i)=C(a+1,i+1)-C(a,i+1)

  显然...sigma(C(i,j))(a<=j<=b)=C(a+1,i+1)-C(b,i+1)

  在图中也就是橙色格子-蓝色格子

  然后对于每一列求解就可以了,但是要注意可能a,b不是真正的a,b

  因为我们现在考虑的是建立在红色部分都至少为1的基础上的,于是对于每一列都要对行标号进行处理

  另外还要注意,即使实际代表意义上不可能但取模意义下的减法可能会减出负的,也要处理

 

 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #define maxn 100010
 5 #define ll long long 
 6 int x1,y1,x2,y2,tt;
 7 ll fac[maxn],inv[maxn];
 8 int max(int a,int b){
 9     if (a>b) return a;
10     return b;
11 }
12 ll mul(ll a,ll b){
13     ll ans=1,w=a;
14     while (b){
15         if (b%2==1) ans=ans*w%tt;
16         w=w*w%tt;b=b>>1;
17     }
18     return ans;
19 }
20 void build(){
21     fac[0]=1;for (int i=1;i<maxn;i++) fac[i]=fac[i-1]*i%tt;    
22     inv[0]=1;for (int i=1;i<maxn;i++) inv[i]=mul(fac[i],tt-2);
23 }
24 ll c(int n,int m){
25     return fac[n]*inv[m]%tt*inv[n-m]%tt;
26 }
27 ll lucas(int n,int m){
28     if (m==0) return 1;
29     if (n<tt&&m<tt) return c(n,m);
30     return lucas(n/tt,m/tt)*lucas(n%tt,m%tt)%tt;
31 }
32 ll query(int n,int m){
33     if (n<m) return 0;
34     return lucas(n,m);
35 }
36 void solve(){
37     ll ans=0;    
38     for (int i=y1;i<=y2;i++) {
39         if (x2<i) continue;
40         int l=max(x1,i),r=x2;
41         if (l==i){l++;ans=(ans+1)%tt;}
42         ans=(ans+query(r+1,i+1)-query(l,i+1)+tt)%tt; 
43     }
44     printf("%lld\n",ans);
45 }
46 int main(){
47     while (scanf("%d",&x1)!=EOF){
48         scanf("%d%d%d%d",&y1,&x2,&y2,&tt);
49         build();
50         solve();
51     }
52     return 0;
53 }

 

 

 

  

 


 

posted @ 2015-05-10 20:34  mjy0724  阅读(602)  评论(0编辑  收藏  举报