Baltic Olympiad in Informatics 2017
A | B | C | D | E | F |
---|---|---|---|---|---|
链接 题面链接 中文题面
A. Political Development
首先至少有一个点度数 ,删去这个点后,同样至少有一个点度数 。
把与即将被删点相邻的至多 个点拿出来跑一边最大独立集。复杂度 。
代码
#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 一遍即可。
复杂度 。
代码
#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
观察到题目给的其实是一个分层图,且每层至多 个点,。
直接用 表示第 层的 到 层的 的最短距离,这显然可以写成 矩阵的形式。题意等同于问区间矩阵乘积,可以直接分治解决,复杂度 ,常数极小。
代码
#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
一开始想了一个反悔堆贪心,后来发现完全没有必要。
首先反悔堆贪心比较显然:用小根堆记录每个子树里面选的深度,每次合并的时候判断两个堆顶是否不合法,如果不合法贪心删去较浅的那一个,容易证明是正确的。
然而事实上一个堆只会被至多弹依次。如果一个子树中 被选择了,另一个子树中 被选择了,要想某个堆弹出两次,需要 ,然而 ,故 。所以一个堆至多被弹一次。
那么只需要记录一个点子树内最浅的位置,然后每次扫子树过程中发现不合法,就将最浅的位置删掉即可。
复杂度 。
代码
#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
很妙的题。
考虑 很小,采用暴力增广,即每次从一个点 开始,暴力枚举其所有出边。如果某一时刻集合出边个数加上集合内点数超过 直接无解。否则枚举每条出边是否塞进集合,总共只有 种可能。
算出所有集合后考虑一个性质:如果 都是合法集合,那么 和 中必然有一个是合法的。
考虑反证,显然点数不会不合法。假设分开集合为 ,公共部分为 。
如果边数不合法,假设 向 连边为 (粉色边), 向 连边为 (黄色边), 向 连边为 (蓝色边), 向外面连边为 。
那么有 ,因为 要合法,有 ,,推出 ,不合法。
所以只需要找出所有初始集合,然后调整即可。
复杂度 。
代码
#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
签到题,可以发现要么行 交替,要么列 交替,最后减去重复部分(行列都 交替)即可。
算出每种情况可以自由选择的个数 ,那么贡献就是 。
代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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编程运行原理