Baltic Olympiad in Informatics 2019

Flash Nautilus Valley Kitchen Necklace Olympiads
- + + +1 +1 +2

链接 题面链接 中文题面

B. Nautilus

考虑用 fi,j,t 表示点 (i,j) 是否可能在 t 时间被到达。显然可以 O(1) 转移。

注意到转移都是或操作,直接 bitset 优化。复杂度 O(n2mω)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset>
#define N 502
#define M 5010
using namespace std;
bitset<N>f[N],g[N],mp[N];
char s[M];
int main()
{
    int n,m,k;scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s+1);
        for(int j=1;j<=m;j++) if(s[j]=='.') mp[i].set(j);
    }
    for(int i=1;i<=n;i++) f[i]=mp[i];
    scanf("%s",s+1);
    for(int i=1;i<=k;i++)
    {
        for(int i=1;i<=n;i++) g[i]=f[i],f[i].reset();
        if(s[i]=='N' || s[i]=='?') for(int i=1;i<=n;i++) f[i]|=g[i+1];
        if(s[i]=='S' || s[i]=='?') for(int i=1;i<=n;i++) f[i]|=g[i-1];
        if(s[i]=='W' || s[i]=='?') for(int i=1;i<=n;i++) f[i]|=g[i]>>1;
        if(s[i]=='E' || s[i]=='?') for(int i=1;i<=n;i++) f[i]|=g[i]<<1;
        for(int i=1;i<=n;i++) f[i]&=mp[i];
    }
    int ans=0;
    for(int i=1;i<=n;i++) ans+=f[i].count();
    printf("%d\n",ans);
    return 0;
}

C. Valley

题面给了一个点 E,换句话说如果断边不在 uE 路径上直接输 escaped

如果以 E 为根,实际上断边等价于取一个子树。换句话说,如果父亲可达,那么其子树都可达。

考虑倍增,因为 dis(x,y)=depx+depy2deplca(x,y),假设 lca(x,y)y 向上 2i 的父亲中,取 min 即可。

最后查询倍增。复杂度 O((n+q)logn)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 100010
#define ll long long
#define inf 10000000000000000
using namespace std;
int nxt[N<<1],to[N<<1],w[N<<1],head[N],cnt;
void add(int u,int v,int w0){nxt[++cnt]=head[u];to[cnt]=v;w[cnt]=w0;head[u]=cnt;}
int dfn[N],fa[N],siz[N],idx;
ll dis[N];int rid[N];
void dfs1(int u,int p)
{
    dfn[u]=++idx;siz[u]=1,fa[u]=p;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];if(v==p) continue;
        dis[v]=dis[u]+w[i];rid[(i+1)/2]=v;
        dfs1(v,u);siz[u]+=siz[v];
    }
}
bool col[N];ll h[N],f[N][22],g[N][22];
void dfs2(int u)
{
    h[u]=col[u]?0:inf;
    for(int i=head[u];i;i=nxt[i]) if(to[i]!=fa[u]) dfs2(to[i]),h[u]=min(h[u],h[to[i]]+w[i]);
}
int rt,n;
void init()
{
    for(int i=1;i<=n;i++) g[i][0]=h[i]-dis[i],f[i][0]=fa[i];
    for(int d=1;1<<d<=n;d++)
        for(int i=1;i<=n;i++) g[i][d]=min(g[i][d-1],g[f[i][d-1]][d-1]),f[i][d]=f[f[i][d-1]][d-1];
}
int main()
{
    int s,q;scanf("%d%d%d%d",&n,&s,&q,&rt);
    for(int i=1,u,v,w;i<n;i++) scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w);
    dfs1(rt,0);
    for(int i=1,x;i<=s;i++) scanf("%d",&x),col[x]=true;
    dfs2(rt);init();
    while(q --> 0)
    {
        int u,d;scanf("%d%d",&d,&u);
        d=rid[d];
        if(dfn[d]>dfn[u] || dfn[d]+siz[d]<=dfn[u]){puts("escaped");continue;}
        ll res=inf,ex=dis[u];
        for(int i=20;i>=0;i--) if(dis[f[u][i]]>=dis[d]) res=min(res,g[u][i]),u=f[u][i];
        res=min(res,g[u][0]);
        if(res>=1e15){puts("oo");continue;}
        printf("%lld\n",res+ex);
    }
    return 0;
}

D. Kitchen

显然一个必要条件是 aibibik

在这个条件下,我们可以先让每个厨师参与尽可能多的菜,最后再单独做。这样匹配就变成:左边每个点可以匹配 ai 个,右边强制匹配 k 个,问是否合法。

由于 aibink,上面的强制匹配可以看做是最大匹配为 nk。这是 Hall 定理的一个经典结论:最大匹配成立当且仅当 max(ai,n)nk

fi,j 表示前 i 个厨师,aijmax(ai,n) 的最大值。然后枚举所有 bi 且合法中最小的即可。复杂度 O(n3)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 310
using namespace std;
int f[N*N],g[N*N];
int a[N],b[N];
int main()
{
    int n,m,k;scanf("%d%d%d",&n,&m,&k);
    int sa=0,sb=0;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),sa+=a[i];
    for(int i=1;i<=m;i++) scanf("%d",&b[i]),sb+=b[i];
    if(sa>sb){puts("Impossible");return 0;}
    for(int i=1;i<=n;i++) if(a[i]<k){puts("Impossible");return 0;}
    memset(g,0xcf,sizeof(g));
    g[0]=0;sb=0;
    for(int i=1;i<=m;i++)
    {
        memcpy(f,g,sizeof(f));
        for(int j=0;j<=sb;j++)
            g[j+b[i]]=max(g[j+b[i]],f[j]+min(b[i],n));
        sb+=b[i];
    }
    for(int i=sa;i<=sb;i++)
        if(g[i]>=n*k){printf("%d\n",i-sa);return 0;}
    puts("Impossible");
    return 0;
}

E. Necklace

首先翻转可以直接将正串和反串分别匹配,可以不考虑。

重点在于串可以循环同构,不太好处理。不妨假设一个串可以表示为 L+R,另一个串表示为 R+L。考虑枚举两个串中的分界点 p1,p2

image

直接枚举 p1,p2,Hash 判断 L,R 是否合法,复杂度 O(n3)

考虑优化。不妨将 ST 链接起来,中间放置一个特殊字符:

image

p1,p2 中间的串 S,可以发现 R 就是 S 的 border。而枚举 p1,所有 p2 的 border 可以在 O(n) 时间算出。

同样将 ST 翻转后拼接就可以得到 R。所以枚举 p1,可以在 O(n) 时间得到每个位置的 L,R 长度,最后扫一遍更新答案即可。

复杂度 O(n2)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 6010
using namespace std;
char s[N],t[N],tmp[N];
int n,m,q;
int nxt[N],f[N],g[N];
void get_nxt()
{
    // printf("get %s :",tmp+1);
    int p=0;
    for(int i=2;i<=q;i++)
    {
        while(p && tmp[p+1]!=tmp[i]) p=nxt[p];
        if(tmp[p+1]==tmp[i]) ++p;
        nxt[i]=p;
    }
    // for(int i=1;i<=q;i++) printf("%d ",nxt[i]);puts("");
}
void putl(int p)
{
    q=0;
    for(int i=p;i<=n;i++) tmp[++q]=s[i];
    tmp[++q]='$';int l=q;
    for(int i=1;i<=m;i++) tmp[++q]=t[i];
    get_nxt();
    for(int i=1;i<=m;i++) f[i]=nxt[i+l];
}
void putr(int p)
{
    q=0;
    for(int i=p;i;i--) tmp[++q]=s[i];
    tmp[++q]='$';int l=q;
    for(int i=m;i;i--) tmp[++q]=t[i];
    get_nxt();
    for(int i=1;i<=m;i++) g[m-i+1]=nxt[i+l];
}
int ans=0,sl,tl;
void solve()
{
    for(int p=1;p<=n;p++)//在 p 位置断开
    {
        putl(p);putr(p-1);
        // printf("%d\n",p);
        // for(int i=1;i<=n;i++) printf("%d ",f[i]);puts("");
        // for(int i=1;i<=n;i++) printf("%d ",g[i]);puts("");
        for(int i=1;i<=m+1;i++)
        if(f[i-1]+g[i]>ans)
        {
            ans=f[i-1]+g[i];
            sl=p-g[i];tl=i-f[i-1];
        }
    }
}
int main()
{
    scanf("%s%s",s+1,t+1);
    n=strlen(s+1),m=strlen(t+1);
    solve();
    reverse(s+1,s+n+1);sl=n-(sl+ans-1)+1;
    solve();sl=n-(sl+ans-1)+1;
    printf("%d\n%d %d\n",ans,sl-1,tl-1);
    return 0;
}

F. Olympiads

很常见的套路。考虑一般的类似 k 短路问题有两个方向,一个是 A* 乱搞,这里考虑另一种:

  • 首先求出所有可能方案中最大的那一个。在这道题里就是依次取每个项目中没有被选的人中最大的。
  • 增广所有可能的次大值。可以发现如果指定前 k 个位置,后面要最大同样会取没有被选的人中最大的。可以枚举 k,将 k 取次大值,然后 k+1n 取最大值。
  • 保证增广唯一。这个比较繁琐,因为事实上 {1,2,3}{2,3,1} 应该是等价的。
    题解有一个比较好的实现方式,具体来说可以发现所有重复部分源于一个人 u 如果被指定替换成另一个人 v 之后,u 又在后面出现了。
    所以我们钦定一个人如果被指定替换,那么他就不应该在后续出现。那么额外存一个集合表示仍然可以出现的人即可。

最后只要依次取出优先队列中的元素,然后增广即可。

分析复杂度,一次增广会 push O(k) 个元素,处理一个元素需要 O(nk) 的时间,总增广次数 O(c),所以总复杂度 O(nk2c+cklogn)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<algorithm>
#include<queue>
#include<set>
#define N 510
#define M 2010
#define K 7
#define fi first
#define se second
using namespace std;
int n,m;
int w[N][K];pair<int,int>a[K][N];
struct node{
    int v,chs[K],las;bitset<N>vis;
    node(){v=las=0;memset(chs,0,sizeof(chs));vis.reset();}
    bool operator <(const node a)const{return v<a.v;}
    void init()
    {
        v=0;
        for(int i=1;i<=m;i++){int r=0; for(int j=1;j<=m;j++) r=max(r,w[chs[j]][i]); v+=r;}
    }
};
node get_next(const node &u,int l)
{
    node v=u;
    for(int k=l+1;k<=m;k++) v.vis[v.chs[k]]=false,v.chs[k]=0;
    for(int k=l;k<=m;k++)
        for(int i=1;i<=n;i++) if(!v.vis[a[k][i].se]){v.chs[k]=a[k][i].se;v.vis[a[k][i].se]=1;break;}
    v.las=l; v.init();return v;
}
node fir(){node u;return get_next(u,1);}
priority_queue<node>q;
int main()
{
    int k=0;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++) scanf("%d",&w[i][j]),a[j][i]={w[i][j],i};
    for(int i=1;i<=m;i++) sort(a[i]+1,a[i]+n+1,greater<pair<int,int>>());
    q.push(fir());
    while(k --> 1)
    {
        node u=q.top();q.pop();
        if(u.vis.count()==n) continue;
        for(int i=u.las;i<=m;i++) q.push(get_next(u,i));
    }
    printf("%d\n",q.top().v);
    return 0;
}
posted @   Flying2018  阅读(51)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示