2021.9.22

T1:字符串问题

Problem:

对于某两个字符串A和B,设A的长度为p,B的长度为q,若用以下形式表示他们。

A=a1 a2 a3 ...ap

B=b1 b2 b3 ...bq

我们说A包含B,或者说B是A的子序列,当且仅当存在1<=i1 <i2 <...<iq <=p,满足a ik =bk   for 1<=k<=q

给定三个字符串X,Y,Z,求X和Y的一个公共子序列W,使得W包含Z。要求找出最长的这种序列W的长度。

Solution:

求个LCS, 只是有了限制, 多加一维表示匹配到z串的第几个, 然后用滚动数组

Code:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=509;
 4 char x[maxn],y[maxn],z[maxn];
 5 int f[2][maxn][maxn];
 6 int main(){
 7     scanf("%s%s%s",x,y,z);
 8     int xn=strlen(x);
 9     int yn=strlen(y);
10     int zn=strlen(z);
11     memset(f,0,sizeof(f));
12     int c=0,p=1;
13     for(int i=1;i<=xn;i++){
14         swap(c,p);
15         memset(f[c],0,sizeof(f[c]));
16         for(int j=1;j<=yn;j++){
17             for(int k=0;k<=zn;k++){
18                 f[c][j][k]=max(max(f[p][j][k],f[c][j-1][k]),f[c][j][k]);
19                 if(x[i-1]==y[j-1]&&(k==0||f[p][j-1][k])){
20                     if(x[i-1]==z[k]){
21                         f[c][j][k+1]=max(f[c][j][k+1],f[p][j-1][k]+1);
22                     }
23                     else{
24                         f[c][j][k]=max(f[c][j][k],f[p][j-1][k]+1);
25                     }
26                 }
27             }
28         }
29     }
30     if(f[c][yn][zn]) printf("%d\n",f[c][yn][zn]);
31     else cout<<"NO SOLUTION"<<endl;
32     return 0;
33 }

T2:天灾

Problem:

世界大陆叫做泰拉大陆,ddfy 神力无比强大,她将泰拉大陆拍成了长度为 m 的二维环形,n 个城市共 m 个信标(每个城市不止一个)分布在这个环上。ddfy 将要进行 q 次神罚,第 i 次神罚将对 Li到 Ri区间的信标造成强度为 ai打击, 每个城市的信标有一定的总耐久,当一个城市所有信标收到的打击强度总数超过耐久后城市就会嗝屁。现在你要求出对于每个城市最早在哪一次神罚时会嗝屁。
第一行两个正整数 n,m。
第二行为 m 个正整数 posi为第 i 个坐标有第 posi个城市的信标。
第三行为 n 个正整数 wi为第 i 个城市的耐久。
第四行为一个正整数 q。
之后 q 行每行三个正整数 l,r,a 为神罚的范围以及强度。

Solution:

整体二分,(但是可以分块乱搞)

看到第一时间想到的肯定是二分答案,但如果一个一个进行二分时间复杂度不允许,且这些询问不是强制在线的,我们不妨整体进行二分,我们把所有询问放在一起进行二分。我们设计一个函数solve(l,r,x,y),表示当前询问序列[x,y]的答案在当前答案[l,r]区间。有一个问题,就是为什么答案在答案区间[l,r]的所有询问会连接在一起,在询问序列的连续一段呢?这个问题放在后面,先置之不理。我们思考,怎样进行二分。

二分答案的思想是取出当前答案区间的中间值进行验证,如果比答案小,则让答案的区间的左端点为中间值加一,反之让答案的右端点为中间值。按照二分答案的思想,我们也进行中间值验证。看例题,我们思考怎么验证。

对于当前答案区间[l,r],我们让第[l,mid]次神罚全部发生,看在当前答案区间[l,r]所属的所有城市是否在第[l,mid]次神罚发生之后已经收集嗝屁,如果当前城市已经嗝屁,我们把它归为答案区间[l,mid]中,反之我们把它归为答案区间[mid+1,r]中,并且对于归为答案区间[mid+1,r]的城市我们需要进行修改,对于其嗝屁要收到的强度要减去[l,mid]场流星雨的陨石总数,此处理解一下。

现在解决一下上面留下的问题,我们怎么能将答案都在[x,y]区间的所有询问都放在一起呢?我们对于每一次划分,都将这些询问进行拷贝,并且修改,然后重新按左右排布,这样我们就能让这些答案在同一区间的询问在一起了。

我们分析一下时间复杂度:我们运用线段树的思想进行分析,我们一共有log2{r-l+1}层,在这个式子中的r表示答案可能到达的最大值,反之l表示的就是答案可能到达的最小值,在本题中,我们的r=n,l=1但是下方的代码最开始的传参为r=n+1这表示前n次神罚都不能满足这个询问,所以最后落在n+1的所有城市表示不能在所有n次神罚中得嗝屁,故输出−1。每一层中我们运用线段树的思想,知道遍历每一层的所有神罚,一共是线性的时间复杂度,并且每一层正正好好摊分所有m个询问,在每一层中我们每一个询问和流星雨都会运用树状数组,所以总的时间复杂度是O(nlog{n}log{m})

Code:

 1 #include<bits/stdc++.h>
 2 #define int unsigned long long
 3 using namespace std;
 4 const int maxn=400100;
 5 const int inf=1e9+5;
 6 int n,m,k,bl[maxn],ta[maxn],ar[maxn],now;
 7 int q[maxn],ql[maxn],qr[maxn],ans[maxn],st[maxn];
 8 struct node{
 9     int l,r,k;
10 }t[maxn];
11 vector<int>con[maxn];
12 inline int lowbit(int x){
13     return x&-x;
14 }
15 inline void change(int x,int k){
16     while(x<=m){
17         ar[x]+=k;
18         x+=lowbit(x);
19     }
20 }
21 inline void add(int l,int r,int k){
22     if(l<=r){
23         change(l,k);
24         change(r+1,-k);
25     }
26     else{
27         change(l,k);
28         change(m+1,-k);
29         change(1,k);
30         change(r+1,-k);
31     }
32 }
33 inline int query(int x){
34     int res=0;
35     while(x){
36         res+=ar[x];
37         x-=lowbit(x);
38     }
39     return res;
40 }
41 inline void solve(int l,int r,int L,int R){
42     if(l>r||L>R) return ;
43     if(l==r){
44         for(int i=L;i<=R;i++) ans[q[i]]=l;
45         return ;
46     }
47     int mid=(l+r)>>1;
48     while(now<mid){
49         now++;
50         add(t[now].l,t[now].r,t[now].k);
51     }
52     while(now>mid){
53         add(t[now].l,t[now].r,-t[now].k);
54         now--;
55     }
56     for(int i=L;i<=R;i++){
57         st[q[i]]=0;
58         for(int j=0;j<con[q[i]].size();j++){
59             st[q[i]]+=query(con[q[i]][j]);
60         }
61     }
62     int cnt1=0,cnt2=0;
63     for(int i=L;i<=R;i++){
64         if(st[q[i]]>=ta[q[i]]) ql[++cnt1]=q[i];
65         else qr[++cnt2]=q[i];
66     }
67     for(int i=1;i<=cnt1;i++) q[L+i-1]=ql[i];
68     for(int i=1;i<=cnt2;i++) q[L+cnt1+i-1]=qr[i];
69     solve(l,mid,L,L+cnt1-1);
70     solve(mid+1,r,L+cnt1,R);
71 }
72 signed main(){
73     cin>>n>>m;
74     for(int i=1;i<=m;i++){
75         cin>>bl[i];
76         con[bl[i]].push_back(i);
77     }
78     for(int i=1;i<=n;i++){
79         cin>>ta[i];
80         q[i]=i;
81     }
82     cin>>k;
83     for(int i=1;i<=k;i++){
84         cin>>t[i].l>>t[i].r>>t[i].k;
85     }
86     t[++k].l=1;
87     t[k].r=m;
88     t[k].k=114514191;
89     solve(1,k,1,n);
90     for(int i=1;i<=n;i++){
91         if(ans[i]==k) cout<<"I AK IOI"<<endl;
92         else cout<<ans[i]<<endl;
93     }
94     return 0;
95 }

T3:成神之日

Problem:

若正整数序列 A 中存在连续若干个正整数的和为 m 的倍数,则这个正整数序列 A被称为"舔 m 序列"。
给定 T 组 n 和 m,你需要知道长度为 n 的任意正整数序列 A 是否都是"舔 m 序列"。

Solution:

序列题

我们如果看一眼题面却没有头绪不妨看一眼数据,一看到10^18我们就大概能猜到这是一道重在思维的规律题。首先此题不要误认为是1~n的正整数序列

此时我们发现根本连前缀和数组都无法使用,但在模拟小数据时可以发现以下性质:

  1. 如果一个数%m不为0,则可能有m−1种取值
  2. 如果序列中超过了m-1个数,就一定有两个数%m取值相同

那么我们设前缀和为s[i]

这时候就可以把前缀和看成一个序列

如果这个序列长度超过m−1那么就必有两个数%m相等

这两个数%m相等,设为s[i],s[j],(i>j),即s[i] mod m = s[j] mod m

所以(s[i]-s[j]) mod m=0

即为j~i区间的和是题目所求的

于是问题就简单了,序列长度n>m-1,即n>=m,则N0,否则YE5

Code:

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 signed main(){
 5     int T,n,m;
 6     scanf("%lld",&T);
 7     while(T--){
 8         scanf("%lld%lld",&n,&m);
 9         if(n<m) cout<<"YE5"<<endl;
10         else cout<<"N0"<<endl;
11     }
12     return 0;
13 }

T4:最有非零数

Problem:

给出正整数 N(可能有前导 0),请求出 N!最右非零的数位的值。
第一行一个数 T 表示数据组数。下接 T 行每行一个数 N 表示一组数据。

Solution:

1:n!的尾部的"0"全部来自因子5和因子2(一对5和2产生一个0),如果把这些因子去掉,则可符合要求(2的个数明显多于5的个数)

2:设F(n)为答案所要求的数,G(n)为1,2…n中将5的倍数的数换成1后的各项乘积,G(15)=1 * 2 * 3 * 4 * 1 * 6 * 7 * 8 * 9 * 1 * 11 * 12 * 13 * 14 * 1(G(n)%10必不为0),则可推出以下两个公式

① n! = (n/5)! * 5^(n/5) * G(n)

② F(n!) = F((n/5)!) * F[5^(n/5) * G(n)] (可以递归)

3:根据②可知F[5^(n/5) * G(n)] = F[G(n) / (2^(n/5)) ],其中G(n)/(2^(n/5))可找到规律

4:枚举可找到上述规律为Mp[20] = {1,1,2,6,4,2,2,4,2,8,4,4,8,4,6,8,8,6,8,2}(20一循环)这样就可以算出答案啦

PS:

n<=19 ---- G[n] = data[n]

n>=20 ---- G[n] = data[n%20]

n过大,这里用字符串处理

Code:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int T,a[105],f[10];
 4 char s[105];
 5 int main(){
 6     f[0]=1;
 7     for(int i=1;i<=9;i++) f[i]=f[i-1]*i;
 8     scanf("%d",&T);
 9     while(T--){
10         scanf("%s",s);
11         int l=strlen(s);
12         int ans=1;
13         for(int i=0;i<l;i++) a[l-i-1]=s[i]-'0';
14         while(a[l-1]==0) l--;
15         if(!strcmp(s,"0")||!strcmp(s,"1")){
16             puts("1");
17             continue;
18         }
19         while(l){
20             int m4=0,m5=0;
21             for(int i=l-1;i>=0;i--){
22                 m5=m5*10+a[i];
23                 a[i]=m5/5;
24                 m4=(m4*10+a[i])%4;
25                 m5%=5;
26             }
27             ans=(ans<<m4+4)*f[m5]%5;
28             if(a[l-1]==0) l--;
29         }
30         if(ans&1) ans+=5;
31         printf("%d\n",ans);
32     }
33     return 0;
34 }
posted @ 2021-09-22 20:11  B_lank  阅读(104)  评论(0)    收藏  举报