Baltic Olympiad in Informatics 2017

A B C D E F
+3 + +3 + +5 +

链接 题面链接 中文题面

A. Political Development

首先至少有一个点度数 k,删去这个点后,同样至少有一个点度数 k

把与即将被删点相邻的至多 k+1 个点拿出来跑一边最大独立集。复杂度 O(nk2k)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<vector>
#define N 100010
using namespace std;
int deg[N];
set<pair<int,int>>s;
set<int>g[N];
int ans,as,f[20];
void solve(vector<int>&v)
{
    int n=v.size();
    for(int i=0;i<n;i++)
    {
        f[i]=0;
        for(int j=0;j<n;j++)
        if(i!=j && g[v[i]].count(v[j])) f[i]|=1<<j;
    }
    for(int s=1;s<1<<n;s++)
    {
        bool can=true;
        for(int i=0;i<n && can;i++) if(s>>i&1 && (f[i]&s)!=(s^(1<<i))) can=false;
        if(can) ans=max(ans,__builtin_popcount(s));
    }
}
bool vis[N];
int main()
{
    int n,k;scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++)
    {
        int x,y;scanf("%d",&x);deg[i]=x;
        while(x --> 0) scanf("%d",&y),g[i].insert(y);
    }
    for(int i=0;i<n;i++) s.emplace(deg[i],i);
    while(!s.empty())
    {
        int u=s.begin()->second;s.erase(s.begin());
        vis[u]=true;
        vector<int>p;p.push_back(u);
        for(int v:g[u]) if(!vis[v]) p.push_back(v);
        solve(p);
        for(int v:p) if(!vis[v]) s.erase({deg[v],v}),s.emplace(--deg[v],v);
    }
    printf("%d\n",ans);
    return 0;
}

B. Railway

用类似于虚树的想法,把每次要求的点按 dfn 排序,那么每个点贡献的边就是该点到该点和上一个点的 lca 部分。

直接离线后 dfs 一遍即可。

复杂度 O(nlogn)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<map>
#include<algorithm>
#define N 100010
using namespace std;
vector<int>g[N];
int dep[N],siz[N],son[N],fa[N];
map<pair<int,int>,int>mp;
void dfs(int u,int p)
{
    siz[u]=1;dep[u]=dep[p]+1;fa[u]=p;
    for(int v:g[u]) if(v!=p)
    {
        dfs(v,u);siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]) son[u]=v;
    }
}
int top[N],dfn[N],idx;
void dfs2(int u,int topp)
{
    top[u]=topp;dfn[u]=++idx;
    if(son[u]) dfs2(son[u],topp);
    for(int v:g[u]) if(v!=fa[u] && v!=son[u]) dfs2(v,v);
}
int lca(int x,int y)
{
    for(;top[x]!=top[y];x=fa[top[x]]) if(dep[top[x]]<dep[top[y]]) swap(x,y);
    return dep[x]<dep[y]?x:y;
}
int w[N],ans[N],p[N];
void dfs3(int u){for(int v:g[u]) if(v!=fa[u]) dfs3(v),w[u]+=w[v];}
int main()
{
    int n,m,k;scanf("%d%d%d",&n,&m,&k);
    for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),g[u].push_back(v),g[v].push_back(u),mp[{u,v}]=mp[{v,u}]=i;
    dfs(1,0);dfs2(1,1);
    for(int i=1;i<=m;i++)
    {
        int q;scanf("%d",&q);
        for(int i=1;i<=q;i++) scanf("%d",&p[i]);
        sort(p+1,p+q+1,[&](int x,int y){return dfn[x]<dfn[y];});
        w[p[1]]++;int l=p[1];
        for(int i=2;i<=q;i++) w[p[i]]++,w[lca(p[i],p[i-1])]--,l=lca(l,p[i]);
        w[l]--;
    }
    dfs3(1);
    for(int i=2;i<=n;i++) ans[mp[{fa[i],i}]]=w[i]>=k;
    int tt=0;
    for(int i=1;i<n;i++) tt+=ans[i];
    printf("%d\n",tt);
    for(int i=1;i<n;i++) if(ans[i]) printf("%d ",i);
    return 0;
}

C. Toll

观察到题目给的其实是一个分层图,且每层至多 k 个点,k5

直接用 fi,x,y 表示第 i 层的 xi+1 层的 y 的最短距离,这显然可以写成 max 矩阵的形式。题意等同于问区间矩阵乘积,可以直接分治解决,复杂度 O(nk3logq),常数极小。

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 100010
#define M 5
#define fi first
#define se second
using namespace std;
int n,m,k,q;
struct matr{
    int a[M][M];
    matr(){memset(a,0x3f,sizeof(a));}
    int* operator [](int x){return a[x];}
}g[N],p[N];
matr operator *(matr x,matr y)
{
    matr z;
    for(int i=0;i<k;i++)
        for(int t=0;t<k;t++)
            for(int j=0;j<k;j++) z[i][j]=min(z[i][j],x[i][t]+y[t][j]);
    return z;
}
struct node{
    int l,lx,r,rx,t;
};
int ans[N];
void solve(int l,int r,vector<node> &Q)
{
    if(l==r)
    {
        for(auto x:Q) if(x.l==x.r) ans[x.t]=g[x.l][x.lx][x.rx];
        return;
    }
    if(Q.empty()) return;
    int mid=(l+r)>>1,fl=mid,fr=mid+1;
    vector<node> ql,qr;
    for(auto x:Q) if(x.l<=mid && mid<x.r) fl=min(fl,x.l),fr=max(fr,x.r);
    p[mid]=g[mid];for(int i=mid-1;i>=fl;i--) p[i]=g[i]*p[i+1];
    p[mid+1]=g[mid+1];for(int i=mid+2;i<=fr;i++) p[i]=p[i-1]*g[i];
    for(auto x:Q) if(x.l<=mid && mid<x.r)
        ans[x.t]=(p[x.l]*p[x.r])[x.lx][x.rx];
    else if(x.r<=mid) ql.push_back(x);else if(x.l>mid) qr.push_back(x);
    solve(l,mid,ql);solve(mid+1,r,qr);
}
int main()
{
    scanf("%d%d%d%d",&k,&n,&m,&q);
    int mx=(n+k-1)/k;
    for(int i=1,u,v,w;i<=m;i++) scanf("%d%d%d",&u,&v,&w),g[u/k][u%k][v%k]=w;
    vector<node>Q;
    for(int i=0,x,y;i<q;i++) scanf("%d%d",&x,&y),Q.push_back({x/k,x%k,y/k-1,y%k,i});
    memset(ans,-1,sizeof(ans));
    solve(0,mx-1,Q);
    for(int i=0;i<q;i++) printf("%d\n",ans[i]>=1e9?-1:ans[i]);
    return 0;
}

D. Cat in a tree

一开始想了一个反悔堆贪心,后来发现完全没有必要。

首先反悔堆贪心比较显然:用小根堆记录每个子树里面选的深度,每次合并的时候判断两个堆顶是否不合法,如果不合法贪心删去较浅的那一个,容易证明是正确的。

然而事实上一个堆只会被至多弹依次。如果一个子树中 a,b 被选择了,另一个子树中 c,d 被选择了,要想某个堆弹出两次,需要 dis(a,c)<m,dis(b,d)<m,然而 dis(a,b)+dis(c,d)<dis(a,c)+dis(b,d),故 dis(a,c)+dis(b,d)>2m。所以一个堆至多被弹一次。

那么只需要记录一个点子树内最浅的位置,然后每次扫子树过程中发现不合法,就将最浅的位置删掉即可。

复杂度 O(n)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 200010
using namespace std;
int f[N],ans,m;
vector<int>g[N];
void dfs(int u,int p)
{
    f[u]=m;
    for(int v:g[u])
    {
        dfs(v,u);
        if(f[u]+f[v]+1<m) --ans,f[u]=max(f[u],f[v]+1);
        else f[u]=min(f[u],f[v]+1);
    }
    if(f[u]>=m) ++ans,f[u]=0;
}
int main()
{
    int n;scanf("%d%d",&n,&m);
    for(int i=2,u;i<=n;i++) scanf("%d",&u),g[u+1].push_back(i);
    dfs(1,0);
    printf("%d",ans);
    return 0;
}

E. Friends

很妙的题。

考虑 p+q 很小,采用暴力增广,即每次从一个点 i 开始,暴力枚举其所有出边。如果某一时刻集合出边个数加上集合内点数超过 p+q 直接无解。否则枚举每条出边是否塞进集合,总共只有 2p+q 种可能。

算出所有集合后考虑一个性质:如果 A,B 都是合法集合,那么 ABBA 中必然有一个是合法的。

考虑反证,显然点数不会不合法。假设分开集合为 A,B,公共部分为 C

如果边数不合法,假设 AB 连边为 a(粉色边),AC 连边为 b(黄色边),BC 连边为 c(蓝色边),A,C,B 向外面连边为 d,e,f

那么有 a+b+d>q,a+c+f>q,因为 A,B 要合法,有 a+c+d+eq,a+b+e+fq2a+b+c+d+2e+f2q<2a+b+c+d+f,推出 e<0,不合法。

所以只需要找出所有初始集合,然后调整即可。

复杂度 O(n2p+q+n2p)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<queue>
#define N 5100
using namespace std;
set<int>g[N];
int n,m1,m2;
bool check(const set<int>&s)
{
    if(s.size()>m1) return false;
    int tt=0;
    for(int u:s)
    {
        if(g[u].size()>m1+m2) return false;
        for(int v:g[u]) if(!s.count(v))
        {
            if(++tt>m2) return false;
        }
    }
    return true;
}
bool vis[N];
set<int>f[N],s,t,o,tmp[N];
int tt;
bool solve()
{
    if(s.size()>m1 || s.size()+t.size()+o.size()>m1+m2 || o.size()>m2) return false;
    if(t.empty())
    {
        if(check(s))
        {
            for(int u:s) vis[u]=true;
            f[++tt]=s;
            return true;
        }
        return false;
    }
    int u=*t.begin();
    tmp[u]=t;t.erase(u);
    if(!s.empty())
    {
        o.insert(u);
        if(solve()) return true;
        o.erase(u);
    }
    s.insert(u);
    for(int v:g[u]) if(!s.count(v) && !t.count(v) && !o.count(v)){t.insert(v);if(s.size()+t.size()>m1+m2) break;}
    if(solve()) return true;
    s.erase(u);
    t=tmp[u];
    return false;
}
void adjust(int x,int y)
{
    bool hv=true;
    for(int v:f[x]) if(f[y].count(v)){hv=false;break;}
    if(hv) return;
    set<int>z=f[x];
    for(int v:f[y]) if(z.count(v)) z.erase(v);
    if(check(z)){f[x]=z;return;}
    z=f[y];
    for(int v:f[x]) if(z.count(v)) z.erase(v);
    if(check(z)){f[y]=z;return;}
    throw;
}
set<set<int>>ans;
int main()
{
    scanf("%d%d%d",&n,&m1,&m2);
    for(int i=1;i<=n;i++)
    {
        int k;scanf("%d",&k);
        for(int j=1,x;j<=k;j++) scanf("%d",&x),g[i].insert(++x);
    }
    for(int i=1;i<=n;i++)
        for(int v:g[i]) if(!g[v].count(i)){puts("detention");return 0;}
    for(int i=1;i<=n;i++)
        if(!vis[i])
        {
            s=o={},t={i};
            if(!solve()){puts("detention");return 0;}
        }
    puts("home");
    for(int i=1;i<=tt;i++)
        for(int j=i+1;j<=tt;j++) adjust(i,j);
    for(int i=1;i<=tt;i++) if(!f[i].empty()) ans.insert(f[i]);
    printf("%d\n",(int)ans.size());
    for(auto &s:ans)
    {
        printf("%d ",(int)s.size());
        for(int u:s) printf("%d ",u-1);
        puts("");
    }
    return 0;
}

F. Plus Minus

签到题,可以发现要么行 +/ 交替,要么列 +/ 交替,最后减去重复部分(行列都 +/ 交替)即可。

算出每种情况可以自由选择的个数 v,那么贡献就是 2v

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define N 100010
#define mod 1000000007
using namespace std;
int x[N],y[N],op[N];char s[3];
int ksm(int a,int b=mod-2)
{
    if(b<0) return 0;
    int r=1;
    for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) r=1ll*r*a%mod;
    return r;
}
map<int,int>mp;
int main()
{
    int n,m,k;scanf("%d%d%d",&n,&m,&k);
    if(k==0){printf("%d",(ksm(2,n)+ksm(2,m)-2)%mod);return 0;}
    for(int i=1;i<=k;i++) scanf("%s%d%d",s,&x[i],&y[i]),op[i]=s[0]=='+';
    int tt=n;
    for(int i=1;i<=k;i++)
        if(!mp.count(x[i])) mp[x[i]]=(y[i]^op[i])&1,tt--;
        else if(mp[x[i]]!=((y[i]^op[i])&1)){tt=-1;break;}
    int ans=ksm(2,tt);
    tt=m;
    mp.clear();
    for(int i=1;i<=k;i++)
        if(!mp.count(y[i])) mp[y[i]]=(x[i]^op[i])&1,tt--;
        else if(mp[y[i]]!=((x[i]^op[i])&1)){tt=-1;break;}
    ans=(ans+ksm(2,tt))%mod;
    int al=(x[1]+y[1]+op[1])&1,c=1;
    for(int i=2;i<=k;i++) if(al!=((x[i]+y[i]+op[i])&1)){c=0;break;}
    ans=(ans-c+mod)%mod;
    printf("%d\n",ans);
    return 0;
}
posted @   Flying2018  阅读(40)  评论(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编程运行原理
点击右上角即可分享
微信分享提示