洛谷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;
}
posted @ 2020-09-19 19:55  我亦如此向往  阅读(392)  评论(1编辑  收藏  举报