洛谷9月月赛 题解(模拟+最短路+期望DP+期望DP)
可能是距离AK最近的一次,但终究是错付了QAQ
-------------------
T1 子弦
题目大意:给定一个字符串,问出现最多的非空子串的个数。
唬人题。直接统计每个字母出现的个数即可。时间复杂度$O(n)$
代码:
#include<bits/stdc++.h> using namespace std; string s; int cnt[30],ans; int main() { cin>>s; for (int i=0;i<s.length();i++) cnt[s[i]-'a']++; for (int i=0;i<26;i++) ans=max(ans,cnt[i]); cout<<ans; return 0; }
T2 雷雨
题目大意:给定一个$n*m$的方格图,每个格子内有元素$A_{i,j}$。现在给定一个起点和两个终点,问从起点到两个终点最短路并集的最小值。
设起点为$s$,终点分别为$t1,t2$。假设$t1≠t2$,那么最短路的并集上一定有一个“岔路口”。这个岔路口到起点的距离和到两个终点的距离之和一定是最小的;假设$t1=t2$,那么图上只有一条最短路,此时这个岔路口即为终点。
所以分别跑三次最短路,然后找到与起点还有两个终点距离和的最小值即可。时间复杂度$O(nm+nm\log {nm})$。
代码:
#include<cstdio> #include<iostream> #include<queue> #include<cstring> #define int long long using namespace std; const int inf=1e18; const int dx[]={0,-1,1,0,0}; const int dy[]={0,0,0,-1,1}; int n,m,a,b,c,dis[1000005][3],vis[1000005],map[1005][1005],ans=inf; struct node { int pos,dis; bool operator < (const node &x) const { return x.dis<dis; } }; priority_queue<node> q; inline int get_pos(int x,int y) { return (x-1)*m+y; } 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 bfs(int opt) { memset(vis,0,sizeof(vis)); for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) dis[get_pos(i,j)][opt]=inf; if (opt==0) dis[get_pos(1,a)][0]=map[1][a],q.push((node){get_pos(1,a),map[1][a]}); if (opt==1) dis[get_pos(n,b)][1]=map[n][b],q.push((node){get_pos(n,b),map[n][b]}); if (opt==2) dis[get_pos(n,c)][2]=map[n][c],q.push((node){get_pos(n,c),map[n][c]}); while(!q.empty()) { int now=q.top().pos;q.pop(); if (vis[now]) continue; vis[now]=1; int x=now/n+1,y=now%n; if (!y) x--,y=m; for (int i=1;i<=4;i++) { int xx=x+dx[i],yy=y+dy[i]; if (xx>=1&&xx<=n&&yy>=1&&yy<=m) { if (dis[get_pos(xx,yy)][opt]>dis[now][opt]+map[xx][yy]) { dis[get_pos(xx,yy)][opt]=dis[now][opt]+map[xx][yy]; if (!vis[get_pos(xx,yy)]) q.push((node){get_pos(xx,yy),dis[get_pos(xx,yy)][opt]}); } } } } } signed main() { n=read();m=read();a=read();b=read();c=read(); for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) map[i][j]=read(); bfs(0);bfs(1);bfs(2); for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) ans=min(ans,dis[get_pos(i,j)][0]+dis[get_pos(i,j)][1]+dis[get_pos(i,j)][2]-2*map[i][j]); printf("%lld",ans); return 0; }
T3 梦原
题目大意:给定一棵树,根节点为$1$。除根节点外每个结点等概率选择$[\max(i-k,1),i-1]$内的一个节点作为父节点。每个结点有一个权值$a_i$。现有删除操作:每次选择一个结点权值均大于$0$的连通块,操作过后连通块内所有结点权值减$1$;每次都按最优策略进行操作。问操作的期望次数。
考虑到编号为$i$的结点的父亲的编号只会比$i$小,在$[1,i]$内怎样操作都不会对后面的结点产生影响,即没有后效性,所以考虑DP。设$f_i$表示对前$i$个结点进行操作的期望次数。考虑结点$i$和它父亲$fa$之间的关系。如果$a_i>a_{fa}$,那么在某个时刻$i$会与前面的结点断开连接,这时需要操作的次数是$f_{i-1}+a_i-a_{fa}$;如果$a_i\leq a_{fa}$,那么可以直接按照之前的策略进行操作即可,$a_i$会在某个时刻先变成$0$,此时操作次数是$f_{i-1}$。所以有转移(假设此时$k$已经合法):
$f_i=k*f_{i-1}+ \frac{\sum\limits_{j=i-k}^{i-1} a_i-a_j}{k} \ (a_j<a_i)$
此时的任务变成求$j<i$且$a_j<a_i$的所有$a_j$的和。一个显然的二维偏序问题。树状数组处理即可。
时间复杂度$O(n\log n)$。
代码:
#include<iostream> #include<cstdio> #include<algorithm> #define int long long using namespace std; const int maxn=1000005; const int p=998244353; int tree1[maxn],tree2[maxn],f[maxn],n,k,a[maxn],inv[maxn]; int sum[maxn],tot[maxn]; struct node { int val,id; }b[maxn]; 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 bool cmp(node x,node y) { return x.val<y.val; } inline int lowbit(int x) { return x&(-x); } inline void add(int x,int y) { for (int i=x;i<=n;i+=lowbit(i)) tree1[i]+=y,tree2[i]++; } inline pair<int,int> query(int x) { int res1=0,res2=0; for (int i=x;i;i-=lowbit(i)) res1+=tree1[i],res2+=tree2[i]; return make_pair(res1,res2); } signed main() { n=read();k=read();inv[1]=1; for (int i=1;i<=n;i++) a[i]=read(),b[i].val=a[i],b[i].id=i; for (int i=2;i<=n;i++) inv[i]=(p-p/i)*inv[p%i]%p; sort(b+1,b+n+1,cmp); for (int i=1;i<=n;i++) { add(b[i].id,b[i].val); pair<int,int> tmp1=query(b[i].id-1); pair<int,int> tmp2=query(max(0ll,b[i].id-k-1)); sum[b[i].id]=tmp1.first-tmp2.first,tot[b[i].id]=tmp1.second-tmp2.second; } f[1]=a[1]; for (int i=2;i<=n;i++) { int cnt=min(k,i-1); f[i]=(cnt*f[i-1]%p+(tot[i]*a[i]%p-sum[i]%p+p)%p)*inv[cnt]%p; } printf("%lld",f[n]); return 0; }
T4 线形生物
题目大意:给定一条长度为$n+1$的链,初始$i$仅向$i+1$连边。现在有$m$条返祖边从$u_i$连向$v_i$,保证$1\leq v_i\leq u_i\leq n$。现在有一个人在根节点$1$。每次他会等概率走一条出边,到$n+1$时会立即停下来。问走到$n+1$的期望步数。
一开始DP设错状态了QAQ,没法处理后效性。
考虑DP。设$f_i$表示从$i$走到$i+1$的期望步数,然后就是个套路题了。设$du$表示度数,$now$表示所在结点,$to$表示走向结点。有转移:
$f_{now}=\frac{\sum\limits_{i=1}^{du} \sum\limits_{j=to}^{now} f_j}{du}+1$
将$f_{now}$提出来,得到:
$f_{now}=\frac{\sum\limits_{i=1}^{du} \sum\limits_{j=to}^{now-1} f_j+(du-1)*f_{now}}{du}+1$
移项,得到:
$f_{now}=\sum\limits_{i=1}^{du} \sum\limits_{j=to}^{now-1} f_j + du$
对于$\sum\limits_{j=to}^{now-1} f_j$,前缀和优化即可。时间复杂度$O(n+m)$。
代码:
#include<iostream> #include<cstdio> #define int long long using namespace std; const int maxn=1000005; const int p=998244353; int du[maxn],f[maxn],sum[maxn],n,m,id; int head[maxn],cnt; struct node { int next,to; }edge[maxn*2]; 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) { edge[++cnt].next=head[from]; edge[cnt].to=to; head[from]=cnt; } signed main() { id=read();n=read();m=read(); for (int i=1;i<=n;i++) du[i]=1; for (int i=1;i<=m;i++) { int x=read(),y=read(); add(x,y);du[x]++; } for (int i=1;i<=n;i++) { int tot=0; for (int j=head[i];j;j=edge[j].next) { int to=edge[j].to; tot=((tot+sum[i-1]-sum[to-1])%p+p)%p; } f[i]=(tot+du[i])%p; sum[i]=(sum[i-1]+f[i])%p; } printf("%lld",sum[n]); return 0; }