4.5&4.7联考题解

本来想加个密码的,后来一想全HE就咱们这几个人,外省的dalao愿看也没事儿,就公开算了,省得加密码各种麻烦。

先补这两天的题解吧……如果有空的话我可能会把上次联考的题解补上= =(中午没睡觉,现在困得很,根本没法写题……

Day 1

算算算number

感觉出题人出题的时候zz了吧,费了半天搞出来一个极其麻烦还跑得慢的做法是要闹哪样啊……

算了,写一写$O(nk)$做法的推导过程吧,虽然其实非常好推……

首先定义$S_i$表示到$1~i$位置的前缀和,并且规定$S_0=0$,那么

\begin{align}Ans_i=&\sum_{j=0}^{i-1}\left(S_i-S_j\right)^k\\=&\sum_{j=0}^{i-1}\sum_{x=0}^k C_k^xS_i^x\left(-S_j\right)^{k-x}\left(二项式定理\right)\\=&\sum_{x=0}^k C_k^x S_i^x\sum_{j=0}^{i-1}\left(-S_j\right)^{k-x}\end{align}

然后就很好搞了,首先枚举$x$,然后维护$\left(-S_j\right)^{k-x}$的前缀和即可。组合数是可以直接一边算一边递推的,不需要再预处理了。

直接算是$O(nk\log k)$的,加上一些线性预处理逆元之类的技巧就可以降到$O(nk+n\log p)$了,实测极限数据只需要不到1.3s就可以出解,并且不需要利用数据随机这个性质。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=50010,p=1000000007;
 6 int qpow(int,int);
 7 int fac[maxn],fac_inv[maxn],inv[maxn];
 8 int T,n,k,s[maxn],s_k[maxn],s_inv[maxn],t[maxn],a[maxn],ans[maxn];
 9 int main(){
10     fac[0]=fac_inv[0]=1;
11     for(int i=1;i<=50001;i++)fac[i]=(long long)fac[i-1]*i%p;
12     fac_inv[50001]=qpow(fac[50001],p-2);
13     for(int i=50001-1;i;i--)fac_inv[i]=(long long)fac_inv[i+1]*(i+1)%p;
14     for(int i=1;i<=50001;i++)inv[i]=(long long)fac[i-1]*fac_inv[i]%p;
15     scanf("%d",&T);
16     while(T--){
17         memset(s,0,sizeof(s));
18         memset(ans,0,sizeof(ans));
19         scanf("%d%d",&n,&k);
20         for(int i=1;i<=n;i++){
21             scanf("%1d",&s[i]);
22             s[i]+=s[i-1];
23             s_k[i]=qpow(s[i],k);
24             s_inv[i]=qpow(s[i],p-2);
25         }
26         fill(t,t+n+1,1);
27         for(int x=0,C=1;x<=k;x++){
28             copy(t,t+n+1,a);
29             for(int i=1;i<=n;i++){
30                 a[i]=(a[i]+a[i-1])%p;
31                 ans[i]=(ans[i]+(long long)C*s_k[i]%p*a[i-1]%p)%p;
32             }
33             for(int i=0;i<=n;i++){
34                 t[i]=(long long)t[i]*((p-s[i])%p)%p;
35                 s_k[i]=(long long)s_k[i]*s_inv[i]%p;
36             }
37             C=(long long)C*(k-x)%p*inv[x+1]%p;
38         }
39         for(int i=1;i<=n;i++)printf("%d ",ans[i]);
40         printf("\n");
41     }
42     return 0;
43 }
44 int qpow(int a,int b){
45     int ans=1;
46     for(;b;b>>=1,a=(long long)a*a%p)if(b&1)ans=(long long)ans*a%p;
47     return ans;
48 }
49 /*
50 2
51 10 2
52 3672415495
53 10 2
54 9040607879
55 Answer:
56 9 117 474 634 1066 1200 2075 3043 6238 8668
57 81 81 201 201 633 633 1690 3802 6539 11435
58 */
View Code

所以你们看,这题就是个基本的二项式定理,换句话说就是比较水的高考题……

买买买buy

T(o)B(e)C(ompleted)……

树树树mst

去%了一发gzz的代码,然后发现我连Prim都不会了……我好菜啊= =

直接处理曼哈顿距离不太方便,可以坐标变换一下变成切比雪夫距离。

每次连边选的是已选集合与未选集合之间最大的一条边,边的端点有四种情况:

1.已选的最左点和未选的最右点

2.已选的最右点和未选的最左点

3.已选的最上点和未选的最下点

4.已选的最下点和未选的最上点

用set或者最大-最小堆维护所有没选的点的$x/y$坐标最大/最小值即可,复杂度$O(n\log n)$(再次吐槽一波出题人zz)。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<set>
 5 using namespace std;
 6 const int maxn=100010;
 7 multiset<pair<int,int> >mx,my;
 8 long long ans=0;
 9 int n,tx,ty,x[maxn],y[maxn],maxx,maxy,minx,miny;
10 int main(){
11     scanf("%d",&n);
12     for(int i=1;i<=n;i++){
13         scanf("%d%d",&tx,&ty);
14         x[i]=tx+ty;
15         y[i]=tx-ty;
16     }
17     maxx=minx=x[1];
18     maxy=miny=y[1];
19     for(int i=2;i<=n;i++){
20         mx.insert(make_pair(x[i],y[i]));
21         my.insert(make_pair(y[i],x[i]));
22     }
23     for(int k=1;k<n;k++){
24         int w1=maxx-mx.begin()->first,w2=mx.rbegin()->first-minx,w3=maxy-my.begin()->first,w4=my.rbegin()->first-miny,w=max(max(w1,w2),max(w3,w4));
25         if(w==w1){
26             pair<int,int>t=*mx.begin();
27             minx=min(minx,t.first);
28             maxy=max(maxy,t.second);
29             miny=min(miny,t.second);
30             mx.erase(mx.begin());
31             my.erase(my.find(make_pair(t.second,t.first)));
32         }
33         else if(w==w2){
34             pair<int,int>t=*mx.rbegin();
35             maxx=max(maxx,t.first);
36             maxy=max(maxy,t.second);
37             miny=min(miny,t.second);
38             mx.erase(mx.find(*mx.rbegin()));
39             my.erase(my.find(make_pair(t.second,t.first)));
40         }
41         else if(w==w3){
42             pair<int,int>t=*my.begin();
43             miny=min(miny,t.first);
44             maxx=max(maxx,t.second);
45             minx=min(minx,t.second);
46             my.erase(my.begin());
47             mx.erase(mx.find(make_pair(t.second,t.first)));
48         }
49         else if(w==w4){
50             pair<int,int>t=*my.rbegin();
51             maxy=max(maxy,t.first);
52             maxx=max(maxx,t.second);
53             minx=min(minx,t.second);
54             my.erase(my.find(*my.rbegin()));
55             mx.erase(mx.find(make_pair(t.second,t.first)));
56         }
57         ans+=w;
58     }
59     printf("%lld",ans);
60     return 0;
61 }
View Code

考场上想到了坐标变换和Prim,但是并没有去细想,自信自己LCT打得很熟练,码完30分大暴力之后就开始码LCT+随机化,结果费了将近一个小时,并且随机化效果还不如暴力,本来可能可以用来思考正解的宝贵时间就这么浪费了……

没办法,自己弱不能怪社会……

 

Day 2

行了行了,暴力都能写错,我是制杖……

A

注意到一次操作对逆序对产生的影响就是把所有下标$\ge x$的数与后面的数所产生的逆序对全部消掉,并且对一个位置操作后再次操作不会产生任何影响,因此可以定义每个数的贡献为它与后面的数产生的逆序对数,那么每次操作就会使答案减掉所有下标$\ge x$且权值$\le a_x$的数的贡献并把这些数的贡献移除。

如果要直接维护的话可以用树套树或者K-D树。当然离线的话要容易的多,只要求出每个数的贡献消失的时刻就行了,显然这个就是每个数前面权值$\ge a_x$的数中最早被操作的那个,鉴于都是后缀查询,树状数组维护即可。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=100010;
 6 void add(int);
 7 int query_sum(int);
 8 void modify(int,int);
 9 int query_min(int);
10 long long ans[maxn]={0};
11 int n,m,a[maxn],b[maxn],c[maxn]={0},w[maxn],x;
12 int main(){
13     scanf("%d%d",&n,&m);
14     for(int i=1;i<=n;i++)scanf("%d",&a[i]);
15     copy(a+1,a+n+1,b+1);
16     sort(b+1,b+n+1);
17     for(int i=n;i;i--){
18         a[i]=lower_bound(b+1,b+n+1,a[i])-b;
19         w[i]=query_sum(a[i]-1);
20         ans[0]+=w[i];
21         add(a[i]);
22     }
23     fill(b+1,b+n+1,(~0u)>>1);
24     for(int i=1;i<=m;i++){
25         scanf("%d",&x);
26         b[x]=min(b[x],i);
27     }
28     fill(c+1,c+n+1,(~0u)>>1);
29     for(int i=1;i<=n;i++){
30         if(b[i]<=m)modify(a[i],b[i]);
31         int t=query_min(a[i]);
32         if(t<=m)ans[t]-=w[i];
33     }
34     printf("%lld\n",ans[0]);
35     for(int i=1;i<=m;i++){
36         ans[i]+=ans[i-1];
37         printf("%lld\n",ans[i]);
38     }
39     return 0;
40 }
41 void add(int x){
42     while(x<=n){
43         c[x]++;
44         x+=x&-x;
45     }
46 }
47 int query_sum(int x){
48     int ans=0;
49     while(x){
50         ans+=c[x];
51         x&=x-1;
52     }
53     return ans;
54 }
55 void modify(int x,int d){
56     while(x){
57         c[x]=min(c[x],d);
58         x&=x-1;
59     }
60 }
61 int query_min(int x){
62     int ans=(~0u)>>1;
63     while(x<=n){
64         ans=min(ans,c[x]);
65         x+=x&-x;
66     }
67     return ans;
68 }
View Code

考场上把暴力写挂了,本来在排序的时候应该把原来的权值复制一份拿来比较,我却直接拿修改后的权值比较,居然还有40分……(这种**错误都能犯,这人没救了……

B

首先给9个格子编号,定义$f_{i,j}$表示从$i$走$n$步之后到达$j$的方案数,显然这个是可以大力跑$9$遍矩阵快速幂求出来的,不过注意到每次的转移矩阵都一样,因此只跑一遍快速幂就行了,然后用$9$个不同的初始矩阵乘一下就可以得到最终的矩阵了(当然你利用对称性的话可以把矩乘次数再减少一些,不过并不需要卡这点常数……)。

然后就很好搞了,反正总共只有$9$个格子, $O(9!)$大力枚举每个机器人最后走到了哪儿即可,复杂度$O(9!+9^4+9^3\log n)$。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int p=1000000007,dx[]={1,-1,0,0},dy[]={0,0,1,-1},
 6 id[3][3]={
 7 {0,1,2},
 8 {3,4,5},
 9 {6,7,8}
10 };
11 struct Matrix{
12     int a[9][9];
13     Matrix(int k=0){
14         memset(a,0,sizeof(a));
15         if(k)for(int i=0;i<9;i++)a[i][i]=k;
16     }
17     Matrix operator*(const Matrix &b)const{
18         Matrix c;
19         for(int i=0;i<9;i++)for(int j=0;j<9;j++)if(a[i][j]){
20             for(int k=0;k<9;k++)if(b.a[j][k])c.a[i][k]=(c.a[i][k]+(long long)a[i][j]*b.a[j][k]%p)%p;
21         }
22         return c;
23     }
24     int *operator[](int x){return a[x];}
25 }A,B;
26 Matrix qpow(Matrix,long long);
27 int f[9][9],a[9],ans=0;
28 long long n;//long long!!!
29 int main(){
30     scanf("%lld",&n);
31     for(int i=0;i<3;i++)for(int j=0;j<3;j++){
32         B[id[i][j]][id[i][j]]=1;
33         for(int k=0;k<4;k++){
34             int x=i+dx[k],y=j+dy[k];
35             if(x<0||x>=3||y<0||y>=3)continue;
36             B[id[x][y]][id[i][j]]++;
37         }
38     }
39     B=qpow(B,n);
40     for(int i=0;i<9;i++){
41         A=Matrix();
42         A[0][i]=1;
43         A=A*B;
44         for(int j=0;j<9;j++)f[j][i]=A[0][j];
45     }
46     //for(int i=0;i<9;i++)for(int j=0;j<9;j++)printf("f[%d][%d]=%d\n",i,j,f[i][j]);
47     for(int i=0;i<9;i++)a[i]=i;
48     do{
49         int tmp=1;
50         for(int i=0;i<9;i++)tmp=(long long)tmp*f[i][a[i]]%p;
51         ans=(ans+tmp)%p;
52     }while(next_permutation(a,a+9));
53     printf("%d",ans);
54     return 0;
55 }
56 Matrix qpow(Matrix a,long long b){
57     Matrix ans(1);
58     for(;b;b>>=1,a=a*a)if(b&1)ans=ans*a;
59     return ans;
60 }
View Code

C

我说这是杜教筛板子题你信么……

\begin{align}Ans=&\sum_{i=1}^n\sum_{j=1}^n(i,j)^k\\=&\sum_{d=1}^n d^k\sum_{i=1}^n\sum_{j=1}^n[(i,j)=d]\\=&\sum_{d=1}^n d^k\left(2 S_\varphi\left(\left\lfloor\frac nd\right\rfloor\right)-1\right)\\&其中S_\varphi(n)=\sum_{i=1}^n\varphi(i)\end{align}

只要能算出$d^k$的前缀和就可以分块了,鉴于$k$很小,用差分组合数(牛顿插值)或者伯努利数之类的做法随便搞一搞就可以了。

题解写的复杂度有误,杜教筛在预处理前$n^{\frac 2 3}$项的$\varphi(n)$前缀和之后是可以做到总复杂度$O(n^{\frac 2 3})$的,即使外面套了一层分块也是如此(毕竟杜教筛自带分块形式)。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int table_size=5000000,maxn=table_size+10,p=1000000007,inv_2=500000004;
 6 void phi_table(int);
 7 int S(long long);//long long!!!
 8 int s_k(long long);//long long!!!
 9 int qpow(int,int);
10 bool notp[maxn]={false},vis[maxn]={false};
11 int prime[maxn]={0},phi[maxn]={0},S_phi[maxn]={0};
12 int k,s[10],r[10],inv[10];
13 long long N;//IMPORTANT!!!Remember to use long long!!!
14 int main(){
15     scanf("%lld%d",&N,&k);
16     for(int i=1;i<=k+2;i++){
17         r[i]=qpow(i,k);
18         s[i]=(r[i]+s[i-1])%p;
19         inv[i]=qpow(i,p-2);
20     }
21     for(int j=3;j<=k+2;j++)for(int i=k+2;i>=j;i--)r[i]=(r[i]-r[i-1]+p)%p;
22     /* for(;;){
23         scanf("%lld",&N);
24         printf("%d\n",s_k(N));
25     } */
26     phi_table(min((long long)table_size,N));
27     long long i=1,last;
28     int ans=0,sk=0,tmp;
29     while(i<=N){
30         last=N/(N/i);
31         tmp=s_k(last);
32         ans=(ans+(tmp-sk+p)%p*(long long)((2*S(N/i)%p-1+p)%p)%p)%p;
33         sk=tmp;
34         i=last+1;
35     }
36     printf("%d",ans);
37     return 0;
38 }
39 void phi_table(int n){
40     phi[1]=1;
41     for(int i=2;i<=n;i++){
42         if(!notp[i]){
43             prime[++prime[0]]=i;
44             phi[i]=i-1;
45         }
46         for(int j=1;j<=prime[0]&&i*prime[j]<=n;j++){
47             notp[i*prime[j]]=true;
48             if(i%prime[j])phi[i*prime[j]]=phi[i]*(prime[j]-1);
49             else{
50                 phi[i*prime[j]]=phi[i]*prime[j];
51                 break;
52             }
53         }
54     }
55     for(int i=1;i<=n;i++)phi[i]=(phi[i]+phi[i-1])%p;
56 }
57 int S(long long n){
58     if(n<=table_size)return phi[n];
59     if(vis[N/n])return S_phi[N/n];
60     vis[N/n]=true;
61     int ans=n%p*((n+1)%p)%p*inv_2%p;
62     long long i=2,last;//long long!!!
63     while(i<=n){
64         last=n/(n/i);
65         ans=(ans-(long long)S(n/i)*((last-i+1)%p)%p+p)%p;
66         i=last+1;
67     }
68     S_phi[N/n]=ans;
69     return ans;
70 }
71 int s_k(long long n){
72     if(n<=k+2)return s[n];
73     int ans=0,C=1;
74     for(int i=0;i<=k+1;i++){
75         ans=(ans+(long long)C*r[i+1]%p)%p;
76         C=(long long)C*((n-i-1)%p)%p*inv[i+1]%p;
77     }
78     return ans;
79 }
80 int qpow(int a,int b){
81     int ans=1;
82     for(;b;b>>=1,a=(long long)a*a%p)if(b&1)ans=(long long)ans*a%p;
83     return ans;
84 }
View Code

ps:关于$ \sum_{i=1}^n\sum_{j=1}^n[(i,j)=d]=2S_\varphi\left(\left\lfloor\frac n d\right\rfloor\right)+1$的推导:

\begin{align}&\sum_{i=1}^n\sum_{j=1}^n[(i,j)=d]\\=&2\left(\sum_{i=1}^n\sum_{j=1}^i[(i,j)=d]\right)-1\\=&2\left(\sum_{d|i}^n\sum_{d|j,j\le i}\left[\left(\frac i d,\frac j d\right)=1\right]\right)-1\\=&2\left(\sum_{i=1}^{\left\lfloor\frac n d\right\rfloor}\sum_{j=1}^i[(i,j)=1]\right)-1\\=&2\left(\sum_{i=1}^{\left\lfloor\frac n d\right\rfloor}\varphi(i)\right)-1\\=&2S_\varphi\left(\left\lfloor\frac n d\right\rfloor\right)-1\\\end{align}

(好吧,感觉大多人都应该知道这个结论,真是废话……)

 

反思:

Day1在T3的LCT+随机化上浪费了好多时间(真是奇怪,为什么我的LCT有时候能不到半个小时写完不用调,有的时候又要调上大半个小时才能写出来……),导致没有时间细想T3的正解,Day2因为太久不写杜教筛和差分组合数生疏了,在推杜教筛求$S_\varphi(n)$以及求$k$次幂之和上浪费了不少时间,结果写完后两题和A的暴力之后已经没有时间去想A的正解了,两天都是把时间浪费在不应该的地方,这种考试策略的错误一定要注意。

这次联考也再次让我认识到了自己思维的薄弱之处,这些天一定要多加锻炼,不要再看到思维题就束手无策。

话说两天都挂成这样还能压线进A队是要闹哪样……

posted @ 2017-04-07 17:47  AntiLeaf  阅读(309)  评论(0编辑  收藏  举报