10月30日考试 题解(质数+最小生成树+模拟+DP优化)
T1 GCD
题目大意:设$f(x)$表示$x$除$1$以外所有因子的最大公约数。给定$l,r$,求出$\sum\limits_{i=l}^r f(i)$。$l,r\leq 10^7$。
如果一个数$x$有超过一个质因子,那么$f(x)=1$。只有当一个数为质数次幂时其$f$值为其底数。于是可以线性筛求出质数然后对于每个质数预处理它的幂即可。时间复杂度$O(n+m\log n)$。
代码:
#include<cstdio> #include<iostream> #define int long long using namespace std; const int N=10000005; bool vis[N]; int prime[2000005],tot,f[N],a,b; long long ans; inline void pre_work() { vis[0]=vis[1]=1; for (int i=2;i<=10000000;i++) { if (!vis[i]) prime[++tot]=i; for (int j=1;j<=tot&&prime[j]*i<=10000000;j++) { vis[i*prime[j]]=1; if (!(i%prime[j])) break; } } } signed main() { pre_work(); for (int i=1;i<=tot;i++) { int j=prime[i]; while(1) { f[j]=prime[i]; if (1ll*j*prime[i]>=10000000) break; j*=prime[i]; } } scanf("%lld%lld",&a,&b); for (int i=a;i<=b;i++) if (!f[i]) ans++; else ans+=f[i]; printf("%lld",ans); return 0; }
T2 小A的同学图
题目大意:给定一张$n$个点$m$条边的无向图。每条边有边权$w_i$,点有类型$c_i$。$q$次询问,每次给出$l,r$,求当$x\in[l,r]$内所有整数时,只走不超过$x$的边最多走到的点的类型个数之和。$n,m\leq 5\times 10^5,q\leq 10^5,c\leq 600$。
一开始没啥思路,想先打暴力,打着打着突然想到了正解。我的想法是对于起点$x$求出到每个点的若干路径中最大边权的最小值。一开始写bfs写挂了,发现不对;后面改用迪杰斯特拉就过了……本质上跟求最小生成树没什么区别。时间复杂度$O(n\log n+qc)$。
代码:
#include<cstdio> #include<vector> #include<queue> #include<cstring> #include<iostream> using namespace std; const int N=500005; bool vis[N]; int dis[N],b[605],c[N],n,m,Q,x,opt,mod; int head[N],cnt; long long ans,last; struct e{ int next,to,dis; }edge[N*2]; struct node{ int dis,pos; bool operator < (const node &x) const{ return x.dis<dis; } }; priority_queue<node> q; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void add(int from,int to,int dis) { edge[++cnt]=(e){head[from],to,dis}; head[from]=cnt; } inline void dijkstra() { memset(dis,0x3f,sizeof(dis)); dis[x]=0;q.push((node){dis[x],x}); while(!q.empty()) { int now=q.top().pos;q.pop(); if (vis[now]) continue; vis[now]=1; for (int i=head[now];i;i=edge[i].next) { int to=edge[i].to,d=edge[i].dis; if (dis[to]>max(dis[now],d)) { dis[to]=max(dis[now],d); if (!vis[to]) q.push((node){dis[to],to}); } } } } signed main() { n=read();m=read();Q=read();x=read();opt=read(); if (opt==1) mod=read(); for (int i=1;i<=n;i++) c[i]=read(); for (int i=1;i<=m;i++){ int u=read(),v=read(),d=read(); add(u,v,d);add(v,u,d); } dijkstra(); memset(b,0x3f,sizeof(b)); for (int i=1;i<=n;i++) b[c[i]]=min(b[c[i]],dis[i]); while(Q--) { int l=read(),r=read();ans=0; if (opt==1) l=(l^last)%mod+1,r=(r^last)%mod+1; if (l>r)swap(l,r); for (int i=1;i<=600;i++) if (b[i]<=r) ans+=min(r-l+1,r-b[i]+1); printf("%lld\n",last=ans); } return 0; }
T3 前缀
题目大意:给定字符串$s'$的循环节$s$,$|s'|$无限。给定字符串$t$,问当$s$的前缀$c$的长度至少为多少时$t$是$c$的子序列。$|t|\leq 10^{10^5}$,将会以字符+数字的形式给出,表示这个字符连续出现多少次。压缩过后的$|t|\leq 10^5$。
一开始想倍增,然而后面发现这就是个模拟……主要有三部分:
1.对于$s$的循环节求出每个字符出现了多少次。当$t$中出现$s$没有的字符可以直接判无解。
2.模拟大除法。可以参考高精。
3.记录一个变量$now$表示除了整的循环节外现在已经走到了哪里。
剩下的就是一些细节了。时间复杂度$O(|t|\log |s|)$。
代码:
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define int long long using namespace std; const int N=100005; const int mod=998244353; char ch[N]; int s[28][N],n,m,q,ans,flag,now,l,r,ret,res,v; inline int md(int x){ return (x%mod+mod)%mod; } signed main() { scanf("%s %lld",ch+1,&q),n=strlen(ch+1); for (int i=1;i<=n;i++) { for (int j=0;j<=26;j++) s[j][i]=s[j][i-1]; s[ch[i]-'a'+1][i]++;s[0][i]++; } while(q--) { scanf("%s",ch+1);m=strlen(ch+1); ans=0;now=flag=1; for (int i=1;i<=m;i=r+1) { v=(ch[i]=='*')?0:ch[i]-'a'+1; if (!s[v][n]){flag=0;break;} r=i,ret=1,res=0; if(i+1<=m&&ch[i+1]>='0'&&ch[i+1]<='9') { ret=0,l=i+1; while(r+1<=m&&ch[r+1]>='0'&&ch[r+1]<='9') ++r; int x=s[v][n]; for(int j=l;j<=r;j++) ret=ret*10+(ch[j]-'0'),res=(10*res+ret/x)%mod,ret%=x; res=res*n%mod; if(!ret) ret=x,res=md(res-n); } if(s[v][n]-s[v][now-1]<ret) res=md(res+n-now+1),ret-=s[v][n]-s[v][now-1],now=1; int x=lower_bound(s[v]+now,s[v]+n+1,s[v][now-1]+ret)-s[v]+1; res=md(res+x-now),now=x,ans=md(ans+res); } if (!flag) puts("-1"); else printf("%lld\n",ans); } return 0; }
T4 移动
题目大意:一个房间内有$n$个闸门,人一开始在$0$点,闸门分别在$1-n$,终点在$n+1$。有$m$条限制,形如$x,l,r$表示闸门$x$在$(l,r]$时间内是关闭的。人每秒可以走一步:不动,向前或向后。不能走向闭合的闸门,也不能在闸门关闭时停留在原地。每秒都是人先走后闸门再开/关。问人走到$n+1$至少需要多少秒。$n\leq 10^5,l,r\leq 10^9$。
首先可以将时间离散化,因为我们只是关心闸门是开/关的状态。然后考虑暴力转移:设$f_{i,j}$表示$i$时刻到达$j$是否可行。$f_{i,j}$一定是从$f_{k,j-1}$转移过来,我们只需要枚举$k$即可。时间复杂度$O(n^3)$,前缀和优化可以做到$O(n^2)$。
正解则考虑结点在哪些时间段是可以走的并记录下来每个时间段的编号,然后利用类似于最短路的方法实现转移。设$f_i$表示人最早几秒能到达$i$位置($i$位置指此时间段内对应的闸门编号)。转移就是考虑人往左/右走能到达哪些闸门开启的时间段。每次向优先队列里面加入node,包括$id,x,time$,表示时间段编号,闸门编号和现在的时间。优先队列以$time$为关键字进行排序,每次取出堆顶然后向左右转移,判一下合不合法。时间复杂度$O(n\log n)$。
代码:
#include<queue> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int N=200005; const int inf=0x3f3f3f3f; typedef pair<int,int> P; int id[N],f[N],n,m; vector<P> v[N],cur; struct node{ int id,x,t; node(){} node(int a,int b,int c){id=a;x=b;t=c;} }; bool operator > (node a,node b){ return a.t>b.t; } priority_queue< node,vector<node>,greater<node> > q; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void calc(node p,int x) { int r=v[p.x][p.id-id[p.x]].second; int i=lower_bound(v[x].begin(),v[x].end(),P(p.t+1,0))-v[x].begin()-1; if (v[x][i].second>=p.t+1) { if (f[id[x]+i]>p.t+1) { f[id[x]+i]=p.t+1; q.push(node(id[x]+i,x,p.t+1)); } } i++; while(i<v[x].size()&&v[x][i].first<=r+1) { if (f[id[x]+i]>v[x][i].first) { f[id[x]+i]=v[x][i].first; q.push(node(id[x]+i,x,v[x][i].first)); } i++; } } inline void solve() { memset(f,0x3f,sizeof(f)); f[0]=0; q.push(node(0,0,0)); while(!q.empty()) { node p=q.top();q.pop(); if (p.t>f[p.id]) continue; if (p.x>0) calc(p,p.x-1); if (p.x<n+1) calc(p,p.x+1); } } int main() { n=read();m=read(); for (int i=1;i<=m;i++) { int a=read(),b=read(),c=read(); v[a].push_back(P(b,c)); } v[0].push_back(P(0,inf)); v[n+1].push_back(P(0,inf)); id[1]=1; for (int i=1;i<=n;i++) { sort(v[i].begin(),v[i].end()); cur.clear(); int r=-1; for (auto p:v[i]) { if (p.first>r+1) cur.push_back(P(r+1,p.first-1)); r=max(r,p.second); } cur.push_back(P(r+1,inf)); v[i]=cur; id[i+1]=id[i]+v[i].size(); } solve(); printf("%d\n",f[id[n+1]]); return 0; }