T1:Makik 学数学

Problem:

Makik 是一个热爱数学的孩子,对数字有着得天独厚的敏感。
在马也老师的指导下,Makik 学习了关于二进制的知识。有一天,Makik 突然想到了一个问题:告诉你一段区间内[L..R],用 N 位二进制数表示这段区间内的所有整数(可以出现前导 0),接着对于每个数进行如下变换:
对于数的最低两位,如果这两位都是 0,那么 P=1,否则 P=0。然后把这两位删去,再将 P 加到最低位的位置上。如此往复,直到只剩一位为止。
比如说对于 01101 这个二进制数,它的变换过程是 01101-->0110-->010-->00-->1。
Makik 想知道变换后的所有数中,值为 Q(Q 为 0 或 1)的有多少个?
Makik 想不出来,你能帮助他解决这个问题吗?

Solution:

一个非常非常非常非常显然的数位 DP

 [L,R] = [1,R] - [1,L-1]

所以是分别求两次小于等于某个数字的方案数

f[i,j,k) 表示从低位数起的第 i 位,按照规则计算后答案为 j  (j=0,1)

k 表示只考虑后面结尾和 lmt 后面几位 的大小关系 (k=0,1)

考虑第 i+1 位,算一下新构成的数字并判断下大小就可以了

注意到 L,R 数据范围特别大,需要用高精度,最后结果要以二进制输出,所以可以对高精度压位

Code:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=202;
 4 const int B=7;
 5 const int P=(1<<30)-1;
 6 char s[maxn];
 7 int T,n,y,i,j,k,t,a[maxn];
 8 struct Num{
 9     int x[B];
10     Num(){
11         for(int i=0;i<B;i++) x[i]=0;
12     }
13     Num operator+(Num b){
14         Num c;
15         for(int i=0;i<B;i++) c.x[i]=x[i]+b.x[i];
16         for(int i=0;i<B-1;i++){
17             if(c.x[i]>P){
18                 c.x[i+1]++;
19                 c.x[i]&=P;
20             }
21         }
22         return c;
23     }
24     Num operator-(Num b){
25         Num c;
26         for(int i=0;i<B;i++) c.x[i]=x[i]-b.x[i];
27         for(int i=0;i<B-1;i++){
28             if(c.x[i]<0){
29                 c.x[i+1]--;
30                 c.x[i]+=P+1;
31             }
32         }
33         return c;
34     }
35     void operator+=(Num b){*this=*this+b;}
36     void operator-=(Num b){*this=*this-b;}
37     void write(){
38         int i;
39         for(i=maxn-1;~i;i--) if(x[i/30]&(1<<i%30)) break;
40         if(i<0) putchar('0');
41         for(;~i;i--) putchar(x[i/30]&(1<<i%30)?'1':'0');
42     }
43 }f[maxn][2][2],tmp,ans,one;
44 Num cal(){
45     scanf("%s",s+1);
46     for(i=1;i<=n;i++){
47         a[n-i+1]=s[i]-'0';
48         f[i][0][0]=f[i][0][1]=f[i][1][0]=f[i][1][1]=Num();
49     }
50     for(t=0;t<=1;t++) f[1][t][t<a[1]]+=one;
51     for(i=1;i<n;i++){
52         for(j=0;j<=1;j++){
53             for(k=0;k<=1;k++){
54                 for(t=0;t<=1;t++){
55                     f[i+1][!j&&!t][t==a[i+1]?k:t<a[i+1]]+=f[i][j][k];
56                 }
57             }
58         }
59     }
60     return f[n][y][1];
61 }
62 int main(){
63     one.x[0]=1;
64     scanf("%d",&T);
65     while(T--){
66         scanf("%d%d",&n,&y);
67         tmp=cal();
68         ans=cal();
69         for(k=a[1],i=2;i<=n;i++) k=!k&&!a[i];
70         if(k==y) ans+=one;
71         ans-=tmp;
72         ans.write();
73         puts("");
74     }
75     return 0;
76 }

T2:Makik 的机器

Problem:

Makik 公司专门生产教科书的书皮。
Makik 想要购买一台进行生产的机器。现在有 n 台机器。所有书皮都是长方形的,每台机器能够制造的书皮的宽度和长度都有其上下界。书皮不可以旋转。
如果存在一台机器满足这样的条件:其他所有机器能够制造的书皮,它都能够制造,那样的话 Makik 就只需要买这台机器就行了。若不存在,生产工作将会遇到麻烦。
Makik 想知道,是否存在一种机器符合上述条件。你能帮帮他吗?

Solution:

模拟

Code:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int inf=1e9;
 4 int T,n;
 5 signed main(){
 6     scanf("%d",&T);
 7     while(T--){
 8         scanf("%d",&n);
 9         bool b=1,s=0;
10         int mnw=inf,mnl=inf;
11         int mxw=0,mxl=0;
12         for(int i=1;i<=n;i++){
13             int w1,w2,l1,l2;
14             scanf("%d%d%d%d",&w1,&w2,&l1,&l2);
15             if(w1<mnw) mnw=w1,b=0;
16             if(w2>mxw) mxw=w2,b=0;
17             if(l1<mnl) mnl=l1,b=0;
18             if(l2>mxl) mxl=l2,b=0;
19             if(b==0) s=0;
20             if(w1<=mnw&&w2>=mxw&&l1<=mnl&&l2>=mxl) s=1;
21             b=1;
22         }
23         if(s==1) cout<<"TAK"<<endl;
24         else cout<<"NIE"<<endl;
25     }
26     return 0;
27 }

T3:数学家 Makik

Problem:

Makik 喜欢研究数学问题,他常常在无聊的时候心算大整数乘法运算。这天,他遇到了这样一个问题:开始有一个数组,长为 l,每个数值为 0,分别为 a[1]-a[l]。有 m 次操作。
操作 1:给出 n、d、v,对所有与 n 的最大公约数为 d 的数 x,进行 a[x]+=v
操作 2:求 a[1]到 a[x]的和

Solution:

这是个莫比乌斯反演题

出题人相出个数论和数据结构的综合题,但是找不到NOIP级别的,没办法只能忍痛割爱出个莫比乌斯,话说回来,莫比乌斯要是会了,其他的应该也就会了……(吧

看这个操作 1 n d kv

相当于 a[x] += v[gcd(x,n) = d]

v[gcd(x,n) = d] = v[gcd(x/d,n/d) = 1] = v Σ(k|gcd(x/d,n/d)) μ(k) = v Σ(k|(x/d),k|(n/d)) μ(k) = Σ(k|(n/d),k*d|x) v*μ(k)

如果按照操作来说我们现在其实就是枚举了所有的 n/d 的约数 k , 然后枚举所有 kd 的倍数,对应位置加上 vμ(k)就行了。 查询 O(1)查询

但是这样修改的复杂度太大,查询的复杂度太低,不妨让修改的时候枚举约数,查询的时候也枚举约数。也即我们修改的时候,新开一个数组 f,枚举约数之后只让 kd 加上 vμ(k) 查询的时候查询 Σ(i=1~n) Σ(d|i) f(d) = Σ(d=1~n) f(d) (n/d)

后面的东西跟根号分块就行了,前面的东西始终是个区间和,单点修改区间求和,使用树状数组就可以了

时间复杂度O(q√l log l + l log l)。

Code:

 1 #include<bits/stdc++.h>
 2 #define ll long long
 3 using namespace std;
 4 const int maxn=200001;
 5 const int maxm=2480000;
 6 int T,n,m,i,j,x,y,z,p[maxn],tot,g[maxn];
 7 int nxt[maxm],v[maxm],ed,op,mu[maxn];
 8 ll b[maxn],t;
 9 bool vis[maxn];
10 inline void addedge(int x,int y){
11     v[++ed]=y;
12     nxt[ed]=g[x];
13     g[x]=ed;
14 }
15 inline void add(int x,int y){
16     for(;x<=n;x+=x&-x) b[x]+=y;
17 }
18 inline ll sum(int x){
19     ll t=0;
20     for(;x;x-=x&-x) t+=b[x];
21     return t;
22 }
23 int main(){
24     for(mu[1]=1,i=2;i<maxn;i++){
25         if(!vis[i]){
26             p[++tot]=i;
27             mu[i]=-1;
28         }
29         for(j=1;j<=tot;j++){
30             if(i*p[j]>=maxn) break;
31             vis[i*p[j]]=1;
32             if(i%p[j]) mu[i*p[j]]=-mu[i];
33             else{
34                 mu[i*p[j]]=0;
35                 break;
36             };
37         }
38     }
39     for(i=1;i<maxn;i++){
40         for(j=i;j<maxn;j+=i) addedge(j,i);
41     }
42     while(1){
43         scanf("%d%d",&n,&m);
44         if(!n) return 0;
45         for(i=1;i<=n;i++) b[i]=0;
46         printf("Case #%d:\n",++T);
47         while(m--){
48             scanf("%d%d",&op,&x);
49             if(op==1){
50                 scanf("%d%d",&y,&z);
51                 if(x%y==0){
52                     for(i=g[x/y];i;i=nxt[i]){
53                         add(v[i]*y,z*mu[v[i]]);
54                     }
55                 }
56             }
57             else{
58                 for(t=0,i=1;i<=x;i=j+1){
59                     j=x/(x/i);
60                     t+=(sum(j)-sum(i-1))*(x/i);
61                 }
62                 printf("%lld\n",t);
63             }
64         }
65     }
66 }