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:
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:
Solution:
序列题
我们如果看一眼题面却没有头绪不妨看一眼数据,一看到10^18我们就大概能猜到这是一道重在思维的规律题。首先此题不要误认为是1~n的正整数序列
此时我们发现根本连前缀和数组都无法使用,但在模拟小数据时可以发现以下性质:
- 如果一个数%m不为0,则可能有m−1种取值
- 如果序列中超过了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:
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 }

浙公网安备 33010602011771号