本来想加个密码的,后来一想全HE就咱们这几个人,外省的dalao愿看也没事儿,就公开算了,省得加密码各种麻烦。
先补这两天的题解吧……如果有空的话我可能会把上次联考的题解补上= =(中午没睡觉,现在困得很,根本没法写题……
Day 1
算算算number
感觉出题人出题的时候zz了吧,费了半天搞出来一个极其麻烦还跑得慢的做法是要闹哪样啊……
算了,写一写$O(nk)$做法的推导过程吧,虽然其实非常好推……
首先定义$S_i$表示到$1~i$位置的前缀和,并且规定$S_0=0$,那么
egin{align}Ans_i=&sum_{j=0}^{i-1}left(S_i-S_j ight)^k\=&sum_{j=0}^{i-1}sum_{x=0}^k C_k^xS_i^xleft(-S_j ight)^{k-x}left(二项式定理 ight)\=&sum_{x=0}^k C_k^x S_i^xsum_{j=0}^{i-1}left(-S_j ight)^{k-x}end{align}
然后就很好搞了,首先枚举$x$,然后维护$left(-S_j ight)^{k-x}$的前缀和即可。组合数是可以直接一边算一边递推的,不需要再预处理了。
直接算是$O(nklog k)$的,加上一些线性预处理逆元之类的技巧就可以降到$O(nk+nlog 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(" "); 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 */
所以你们看,这题就是个基本的二项式定理,换句话说就是比较水的高考题……
买买买buy
T(o)B(e)C(ompleted)……
树树树mst
去%了一发gzz的代码,然后发现我连Prim都不会了……我好菜啊= =
直接处理曼哈顿距离不太方便,可以坐标变换一下变成切比雪夫距离。
每次连边选的是已选集合与未选集合之间最大的一条边,边的端点有四种情况:
1.已选的最左点和未选的最右点
2.已选的最右点和未选的最左点
3.已选的最上点和未选的最下点
4.已选的最下点和未选的最上点
用set或者最大-最小堆维护所有没选的点的$x/y$坐标最大/最小值即可,复杂度$O(nlog 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 }
考场上想到了坐标变换和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 ",ans[0]); 35 for(int i=1;i<=m;i++){ 36 ans[i]+=ans[i-1]; 37 printf("%lld ",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 }
考场上把暴力写挂了,本来在排序的时候应该把原来的权值复制一份拿来比较,我却直接拿修改后的权值比较,居然还有40分……(这种**错误都能犯,这人没救了……
B
首先给9个格子编号,定义$f_{i,j}$表示从$i$走$n$步之后到达$j$的方案数,显然这个是可以大力跑$9$遍矩阵快速幂求出来的,不过注意到每次的转移矩阵都一样,因此只跑一遍快速幂就行了,然后用$9$个不同的初始矩阵乘一下就可以得到最终的矩阵了(当然你利用对称性的话可以把矩乘次数再减少一些,不过并不需要卡这点常数……)。
然后就很好搞了,反正总共只有$9$个格子, $O(9!)$大力枚举每个机器人最后走到了哪儿即可,复杂度$O(9!+9^4+9^3log 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 ",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 }
C
我说这是杜教筛板子题你信么……
egin{align}Ans=&sum_{i=1}^nsum_{j=1}^n(i,j)^k\=&sum_{d=1}^n d^ksum_{i=1}^nsum_{j=1}^n[(i,j)=d]\=&sum_{d=1}^n d^kleft(2 S_varphileft(leftlfloorfrac nd ight floor ight)-1 ight)\&其中S_varphi(n)=sum_{i=1}^nvarphi(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 ",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 }
ps:关于$ sum_{i=1}^nsum_{j=1}^n[(i,j)=d]=2S_varphileft(leftlfloorfrac n d ight floor ight)+1$的推导:
egin{align}&sum_{i=1}^nsum_{j=1}^n[(i,j)=d]\=&2left(sum_{i=1}^nsum_{j=1}^i[(i,j)=d] ight)-1\=&2left(sum_{d|i}^nsum_{d|j,jle i}left[left(frac i d,frac j d ight)=1 ight] ight)-1\=&2left(sum_{i=1}^{leftlfloorfrac n d ight floor}sum_{j=1}^i[(i,j)=1] ight)-1\=&2left(sum_{i=1}^{leftlfloorfrac n d ight floor}varphi(i) ight)-1\=&2S_varphileft(leftlfloorfrac n d ight floor ight)-1\end{align}
(好吧,感觉大多人都应该知道这个结论,真是废话……)
反思:
Day1在T3的LCT+随机化上浪费了好多时间(真是奇怪,为什么我的LCT有时候能不到半个小时写完不用调,有的时候又要调上大半个小时才能写出来……),导致没有时间细想T3的正解,Day2因为太久不写杜教筛和差分组合数生疏了,在推杜教筛求$S_varphi(n)$以及求$k$次幂之和上浪费了不少时间,结果写完后两题和A的暴力之后已经没有时间去想A的正解了,两天都是把时间浪费在不应该的地方,这种考试策略的错误一定要注意。
这次联考也再次让我认识到了自己思维的薄弱之处,这些天一定要多加锻炼,不要再看到思维题就束手无策。
话说两天都挂成这样还能压线进A队是要闹哪样……