A题
我写的是找是否存在两个1之间有0,并且要特判单一个1的情况

#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=2e5+10; const int mod=1e9+7; int main(){ ios::sync_with_stdio(false); string s; cin>>s; int i; int cnt=0; int flag=0; for(i=0;i<s.size();i++){ if(s[i]=='1'){ flag=i+1; break; } } if(!flag){ cout<<0<<endl; } else{ for(i=0;i<s.size();i++){ if(s[i]=='1') cnt++; } if(cnt==1){ cout<<0<<endl; return 0; } if(cnt==s.size()){ cout<<1<<endl; return 0; } cnt=0; flag=0; s=s+"0"; for(i=flag;i<s.size();i++){ if(s[i]=='0'&&s[i-1]=='1'){ cnt++; if(i<s.size()-1&&s[i+1]=='1'){ flag=1; } } } int tmp=0; for(i=flag;i<s.size();i++){ if(i>0&&s[i]=='1'&&s[i-1]=='0'&&s[i+1]=='0'){ flag=1; break; } } cout<<cnt-flag<<endl; } return 0; }
B题
看出规律,只有几种简单的情况,先维护一个和,之后加加减减

#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=2e5+10; const ll mod=998244353; ll a[N],b[N]; int main(){ ios::sync_with_stdio(false); ll res=0,ans=0; int i; int n; cin>>n; for(i=0;i<n;i++){ cin>>a[i]; res+=a[i]; res%=mod; } for(i=0;i<n;i++){ cin>>b[i]; ans+=b[i]; ans%=mod; } for(i=0;i<3;i++){ if(i==0){ cout<<((res-a[0]+mod)%mod)*((ans-b[0]+mod)%mod)%mod<<" "; } else if(i==1){ ll tmp1=(b[0]%mod*(res-a[1]+mod))%mod; ll tmp2=(a[0]%mod*(ans-b[1]+mod))%mod; ll tmp3=a[0]*b[0]%mod; cout<<(tmp1+tmp2-tmp3+mod)%mod<<" "; } else if(i==2){ ll tmp1=a[0]*b[1]%mod+b[0]*a[1]%mod; cout<<tmp1%mod<<" "; } } for(i=3;i<n;i++) cout<<0<<" "; return 0; }
C题
定义状态为f[i][j],i位,第一位以j开头,这样就能列出转移方程,使用矩阵快速幂优化

#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=2e5+10; const int mod=100019; struct node{ int a[10][10]; node(){ memset(a,0,sizeof a); } }S,T; node operator *(node a,node b) { node tmp; for(int i=0;i<=9;i++) { for(int j=0;j<=9;j++) { for(int k=0;k<=9;k++) { tmp.a[i][j]=((ll)tmp.a[i][j]+(ll)a.a[i][k]*b.a[k][j]%mod)%mod; } } } return tmp; } node martix_pow(node s,ll b){ int i; node tmp; for(i=0;i<10;i++){ tmp.a[i][i]=1; } while(b){ if(b&1) tmp=tmp*s; s=s*s; b>>=1; } return tmp; } int main(){ ios::sync_with_stdio(false); int i; int n; cin>>n; for(i=1;i<=9;i++){ S.a[0][i]=1; } for(i=0;i<10;i++){ for(int j=0;j<10;j++){ if(j<=i){ T.a[i][j]=1; } } } T=martix_pow(T,n-1); S=S*T; ll ans=0; for(i=1;i<=9;i++){ ans=(ans+S.a[0][i])%mod; } cout<<ans<<endl; return 0; }
D题
首先显然的贪心就是尽量选的远是更优的,因为能选就选显然不劣。
一般来说是一个个加边试探过去,看看能否成立。这里就可以用倍增优化
直到最后一段区间没法用倍增,因为会超过K,这段区间使用二分check一下。
在这样的优化下,tarjan算法的复杂度就能够满足条件了

#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=2e6+10; const int mod=1e9+7; int n,m; ll k,sum; int x[N],y[N]; vector<int> g[N]; int dfn[N],low[N]; int ins[N],idx; stack<int> q; void tarjan(int u){ dfn[u]=low[u]=++idx; int i; q.push(u),ins[u]=1; for(i=0;i<g[u].size();i++){ int j=g[u][i]; if(!dfn[j]){ tarjan(j); low[u]=min(low[u],low[j]); } else if(ins[j]){ low[u]=min(low[u],dfn[j]); } } if(dfn[u]==low[u]){ ll tmp=0; while(1){ int t=q.top(); q.pop(); ins[t]=0; tmp++; if(t==u) break; } sum+=(tmp*tmp); } } ll check(int l,int r){ int i,j; r=min(r,m); vector<int> num; for(i=l;i<=r;i++){ g[x[i]].push_back(y[i]); num.push_back(x[i]); dfn[x[i]]=dfn[y[i]]=0; } sum=n; for(i=0;i<num.size();i++){ if(!dfn[num[i]]){ idx=0; tarjan(num[i]); sum-=idx; } } for(i=l;i<=r;i++){ g[x[i]].clear(); } return sum; } int main(){ ios::sync_with_stdio(false); cin>>n>>m>>k; int i; for(i=1;i<=m;i++){ cin>>x[i]>>y[i]; } int now=1; int ans=0; for(now=1;now<=m;now++){ int i=0; while(check(now,now+(1<<i)-1)<=k){ if(now+(1<<i)-1>m) break; i++; } int l=now+(1<<(i-1))-1,r=min(m,now+(1<<i)-1); while(l<r){ int mid=l+r+1>>1; if(check(now,mid)<=k){ l=mid; } else{ r=mid-1; } } now=l; ans++; } cout<<ans<<endl; return 0; }
E题
首先看看官方题解:
-----------------------------------------------
考虑先把所有询问离线,然后从小到大枚举询问右端点,用某种数据结构维护每个左端点的答案。
考虑对于一个点,只保留最后一次覆盖它的线段,并把这个点的权值记为线段的标号。
考虑把相邻的权值相同的点合并成同一段来维护,段的权值就是这些点的权值。那么新加入一条线段,就相当于合并(或者说删除)它覆盖到的旧的段,把这些段的权值变成当前枚举到的右端点。
当然可能有一些段会被当前线段从中截断,这种段至多有两个,可以先把它们分解成两段。于是我们可以假定新加入的线段覆盖到的每个段都是完整的。
对应在统计答案的数据结构上,如果合并了一个权值是 xx 的段 AA,相当于数据结构上 [x+1,r][x+1,r] 这个区间的答案整体加上段 AA 的长度,其中 rr 是当前的右端点。
由于询问是单点询问,因此可以用树状数组来维护差分数组实现区间修改(或者直接使用线段树)。
考虑如何维护出覆盖了哪些线段。可以使用 set 来维护每两个连续段之间的分界点,每次暴力合并连续段即可。根据均摊理论,总复杂度就是O(nlogn)。
--------------------------------------------------
解释就是因为我们从小枚举右端点,因此树状数组就能够维护的是i到当前r的最大覆盖范围。
我们用set维护所有线段,并且每条线段都用最大的位置来取代他,这样就能够在树状数组上就可以刚好维护而不会重复维护。
具体注释可以看代码。

#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pll; const int N=2e5+10; const int mod=1e9+7; ll tr[N]; ll ans[N]; int lowbit(int x){ return x&-x; } void add(int x,int c){ int i; for(i=x;i<N;i+=lowbit(i)){ tr[i]+=c; } } ll sum(int x){ int i; ll res=0; for(i=x;i;i-=lowbit(i)){ res+=tr[i]; } return res; } set<pll> s; int L[N],R[N]; vector<pll> num[N]; int main(){ ios::sync_with_stdio(false); int i; int n,q; cin>>n>>q; for(i=1;i<=n;i++){ cin>>L[i]>>R[i]; } for(i=1;i<=q;i++){ int a,b; cin>>a>>b; num[b].push_back({a,i}); } s.insert({1e9+10,0});//m1存贮的是线段的两两分界点 for(i=1;i<=n;i++){ int l=L[i],r=R[i]; int pos=-1,d=l-1; auto it=s.lower_bound({l-1,0});//找到大于l-1的第一个点 while(1){ int x=it->first,id=it->second;//找到位置和id int tmp=min(x,r)-d;//计算长度 add(id+1,tmp);//因为到id的位置这段都已经覆盖过了,所以用了i后,id+1到i需要加上这段长度 add(i+1,-tmp); if(pos==-1){ pos=id;//记录分界点 } if(x<=r){ auto it1=it; it++; d=x; s.erase(it1); } else{ break; } } if(d>1){ s.insert({l-1,pos});//l-1的还是由碰到的第一个所控制的 } s.insert({r,i});//到l-r是由i控制的 for(int j=0;j<num[i].size();j++){ ans[num[i][j].second]=sum(num[i][j].first); } } for(i=1;i<=q;i++){ cout<<ans[i]<<endl; } return 0; }