9.5题解

T1

最近说实话区间问题非常常见,但是考场上还是做不到灵活处理,先说几个套路吧

关于这种需要知道某个区间中的最大值的问题,常用的选择是单调栈$O(1)$维护出来序列中这个值作为最大值的最大区间,那么对于这个区间中所有跨过这个值的小区间都是以这个值作为最大值的

而对于这种区间和整除的询问常用操作是开桶记录${\%}$除数意义下的前缀和,对于前缀和相同的两个区间,他们中间的那个区间的区间和就一定满足条件

这道题同样是这种思路,还有点像上次8.23的T3那道题的思路,枚举当前区间中较小的一半,去另一半里寻找答案,那我们就只需要知道另一半里有几个和他的前缀和是相等的,由于要查询区间中的,所以选择主席树就可以了,当然其实没必要分治直接枚举每个点似乎也可以过

 1 //注意不包括长度为1的数列
 2 #include<iostream>
 3 #include<cstdio>
 4 #define ll long long
 5 #define maxn 300300
 6 #define maxa 1000100
 7 using namespace std;
 8 using namespace std;
 9 struct data
10 {
11     int lc,rc,ans;
12 }tree[maxn*30];
13 int n,k,cut;
14 ll ans;
15 int mi[20],Log[maxn];
16 int a[maxn],root[maxn];
17 ll qz[maxn];
18 int st[20][maxn],pos[20][maxn];
19 void add(int &now,int last,int l,int r,int x)
20 {
21     now=++cut;
22     if(l==r) {tree[now].ans=tree[last].ans+1;  return ;}
23     tree[now].lc=tree[last].lc;  tree[now].rc=tree[last].rc;
24     int mid=(l+r)>>1;
25     if(x<=mid) add(tree[now].lc,tree[last].lc,l,mid,x);
26     else add(tree[now].rc,tree[last].rc,mid+1,r,x);
27 }
28 int query(int L,int R,int l,int r,int x)
29 {
30     if(l==r)  return tree[R].ans-tree[L].ans;
31     int mid=(l+r)>>1;
32     if(x<=mid) return query(tree[L].lc,tree[R].lc,l,mid,x);
33     else  return query(tree[L].rc,tree[R].rc,mid+1,r,x);
34 }
35 ll solve(int l,int r)
36 {
37     if(l>=r)  return 0;
38     ll sum=0;  int i=0;
39     int len=Log[r-l+1];
40     if(st[len][l]>st[len][r-mi[len]+1])  i=pos[len][l];
41     else  i=pos[len][r-mi[len]+1];
42     if(i-l+1<r-i+1)
43     {
44         for(int j=l;j<=i;++j)  sum+=query(root[i-1],root[r],0,k,(qz[j-1]+a[i])%k);
45         sum--;
46     }
47     else
48     {
49         for(int j=i;j<=r;++j)  sum+=query(root[l-2],root[i-1],0,k,(qz[j]-a[i])%k);
50         sum--;
51     }
52     sum+=solve(l,i-1)+solve(i+1,r);
53     return sum;
54 }
55 signed main()
56 {
57 //    freopen("1.in","r",stdin);
58 //    freopen("W.out","w",stdout);
59     scanf("%d%d",&n,&k);  mi[0]=1;  Log[0]=-1;
60     for(int i=1;i<=19;++i)  mi[i]=mi[i-1]*2;
61     for(int i=1;i<=n;++i)  Log[i]=Log[i/2]+1;
62     for(int i=1;i<=n;++i)
63     {
64         scanf("%d",&a[i]);  qz[i]=qz[i-1]+1ll*a[i];
65         st[0][i]=a[i];  pos[0][i]=i;
66     }
67     add(root[0],0,0,k,0);//对于前缀和来说qz[0]=0
68     for(int i=1;i<=n;++i)  add(root[i],root[i-1],0,k,qz[i]%k);
69     for(int i=1;i<=Log[n];++i)
70     {
71         for(int j=1;j<=n;++j)
72         {
73             if(j+mi[i]-1<=n)
74             {
75                 if(st[i-1][j]>st[i-1][j+mi[i-1]])
76                     {st[i][j]=st[i-1][j];  pos[i][j]=pos[i-1][j];}
77                 else
78                     {st[i][j]=st[i-1][j+mi[i-1]];  pos[i][j]=pos[i-1][j+mi[i-1]];}
79             }
80         }
81     }
82     ans=solve(1,n);  printf("%lld\n",ans);
83     return 0;
84 }
View Code
 1 void add(int &now,int last,int l,int r,int x)
 2 {
 3     now=++cut;
 4     if(l==r) {tree[now].ans=tree[last].ans+1;  return ;}
 5     tree[now].lc=tree[last].lc;  tree[now].rc=tree[last].rc;
 6     int mid=(l+r)>>1;
 7     if(x<=mid) add(tree[now].lc,tree[last].lc,l,mid,x);
 8     else add(tree[now].rc,tree[last].rc,mid+1,r,x);
 9 }
10 int query(int L,int R,int l,int r,int x)
11 {
12     if(l==r)  return tree[R].ans-tree[L].ans;
13     int mid=(l+r)>>1;
14     if(x<=mid) return query(tree[L].lc,tree[R].lc,l,mid,x);
15     else  return query(tree[L].rc,tree[R].rc,mid+1,r,x);
16 }

T2

这题考场上我好像是推了好几个式子,都自己hack掉了自己,正解的式子是通过容斥的方式推出来的,总方案数是$2^{nm}$,如果要求所有位都不相同,那就是个排列就可以了,所以不合法的概率是$\frac{A_{2^n}^{m}}{2^{nm}}$,剩下的问题就是消项,由于分母的特殊性,肯定只能消去2,那问题就变成了分子里最多可以除去几个2,考虑转化

上面的式子先消去一个$2^n$之后就变成了$\frac{\prod_{i=2^n-m+1}^{2^n-1}}{2^{n(m-1)}}$,如果$m>10^6+3$,那么乘出来的乘积里一定有一项是mod的倍数,题解BB的,我不太会证,可以感性一下,由于mod里不含2这个因子,也就是是mod倍数的这一项,最小也会被消到mod,所以在${\%}mod$意义下答案肯定是0,接下来的问题是分母能被消掉几个二,很显然的一个性质$a$和$2^n-a$里能消掉的二的个数相同,保证a小于$2^n$,那式子里的连乘就可以转化为$\prod_{i=m-1}^{1}$里2的个数,也就是$(m-1)!$里2的个数,这个东西有个玄学的求法,我还是不会证,还是需要感性理解,题解说有经典的$O(logm)$的求法,并没有告诉我是什么,所以我从同桌那颓来了一个性质,$(m-1)!$里二的个数,就是m-1除以2,4,8,...,最后一个小于m的2的整次幂,感性一下,$(m-1){\div}2$就是1到m-1里有几个数包含2,以此类推,因为你每次都是在乘2,本来包含4的数里应该有2个2,但是你在2的时候已经加过了一次贡献,包含4肯定包含2,所以每次都只会多一的贡献,这样就可以得到我们需要的$(m-1)!$包含的2的个数,然后分母快速幂算答案就可以了,分子先在取模意义下算出答案,乘上逆元就可以了

 1 #include<iostream>
 2 #include<cstdio>
 3 #define ll long long
 4 #define mod 1000003
 5 using namespace std;
 6 ll n,m,jc=2,sum,a=1,b,zd;
 7 ll ksm(ll a,ll b)
 8 {
 9     ll ans=1;
10     while(b)
11     {
12         if(b&1)  ans=(ans*a)%mod;
13         b>>=1;  a=(a*a)%mod;
14     }
15     return ans;
16 }
17 int main()
18 {
19     scanf("%lld%lld",&n,&m);
20     while(1)
21     {
22         if(jc>m-1)  break;
23         sum+=(m-1)/jc;  jc*=2;
24     }
25     zd=ksm(2,n);
26     if(m>=mod)
27     {
28         n%=mod-1;  m%=mod-1;
29         ll mi=(n*m-n)%(mod-1);  mi=(mi-sum%(mod-1)+mod-1)%(mod-1);
30         a=0;  b=ksm(2,mi);
31     }
32     else
33     {
34         for(int i=zd-m+1;i<=zd-1;++i)  a=(a*i)%mod;
35         ll da=ksm(2,sum);
36         a%=mod;  a=a*ksm(da,mod-2)%mod;
37         n%=mod-1;  m%=mod-1;
38         ll mi=(n*m-n)%(mod-1);  mi=(mi-sum%(mod-1)+mod-1)%(mod-1);
39         b=ksm(2,mi);
40     }
41     ll ls=b-a;
42     while(ls<0)  ls+=mod;
43     printf("%lld %lld\n",ls,b);
44     return 0;
45 }
View Code

T3

好像是先DP再递推的思路,没过,所以咕了

posted @ 2019-09-20 17:42  hzoi_X&R  阅读(177)  评论(0编辑  收藏  举报