Baltic Olympiad in Informatics 2018

polygon dna worm alternating genetics paths
\(\color{green}{\texttt{+}}\) \(\color{green}{\texttt{+}}\) \(\color{red}{\texttt{+}}\) \(\color{red}{\texttt{-}}\) \(\color{green}{\texttt{+}}\) \(\color{green}{\texttt{+}}\)

链接 题面链接 中文题面

A. Love Polygon

注意到一个点至少有一条出边,而总边数是 \(n\),所以这是一棵基环内向树。

除去原本的二元环,实际上答案就是 \(n-匹配数\)

找出每一个块,非环部分先贪心匹配。如果环上有点已经被匹配了就从该点开始,否则任取一个点开始贪心匹配,容易证明是正确的。

复杂度 \(O(n)\)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#define N 100010
using namespace std;
map<string,int>mp;
int fa[N];
bool vis[N];int cir[N],T;
vector<int>g[N];
int ans;bool col[N];
void dfs(int u){vis[u]=true;for(int v:g[u]) if(cir[v]!=T){dfs(v);if(!col[u] && !col[v]) col[u]=col[v]=true,++ans;}}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;cin>>n;if(n&1){puts("-1");return 0;}
    for(int i=1,tt=0;i<=n;i++)
    {
        string s1,s2;cin>>s1>>s2;
        if(!mp.count(s1)) mp[s1]=++tt;
        if(!mp.count(s2)) mp[s2]=++tt;
        int u=mp[s1],v=mp[s2];
        if(fa[u]){puts("-1");return 0;}
        fa[u]=v;g[v].push_back(u);
    }
    for(int i=1;i<=n;i++) if(!fa[i]){puts("-1");return 0;}
    for(int i=1;i<=n;i++) if(!vis[i])
    {
        int u=i;
        ++T;for(;cir[u]!=T;u=fa[u]) cir[u]=T;
        vector<int>c;
        ++T;for(;cir[u]!=T;u=fa[u]) cir[u]=T,c.push_back(u);
        if(c.size()==2){col[c[0]]=col[c[1]]=true,ans+=2;}
        for(int v:c) dfs(v);
        if(c.size()<=2) continue;
        int w=u;
        for(int v:c) if(col[v]){w=v;continue;}
        for(int v=fa[w];v!=w;v=fa[v]) if(!col[v] && !col[fa[v]]) col[v]=col[fa[v]]=true,++ans;
    }
    cout<<n-ans;
    return 0;
}

B. Martian DNA

签到题,直接双指针扫过去即可。

复杂度 \(O(n)\)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 200010
using namespace std;
int res,a[N],d[N],c[N];
void add(int x){if((++c[x])==d[x]) ++res;}
void del(int x){if((c[x]--)==d[x]) --res;}
int main()
{
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1,x,y;i<=k;i++) scanf("%d%d",&x,&y),d[x]=y;
    int ans=n+1;res=m-k;
    for(int i=1,j=1;i<=n;i++)
    {
        while(j<=n && res!=m) add(a[j++]);
        if(res!=m) break;
        ans=min(ans,j-i);
        del(a[i]);
    }
    if(ans>n) puts("impossible");
    else printf("%d\n",ans);
    return 0;
}

C. Worm Worries

这道题本质上有三个子任务。

子任务 1 (subtask 1,2)

子任务 1 约束\(M = K = 1, N = 10^6, Q = 35\)

显然有一个查询次数 \(2\log_2 N\) 的做法:每次查询中点与其右边的点,舍去较小权值对应的一半序列。容易证明,较大权值的部分一定存在可行的峰值。

毛估估一下要 \(40\) 次,恰好过不去。

考虑优化:上述做法每次要用 2 个询问,有点浪费。

事实上如果询问点在 \(x,y\),有 \(a_x<a_y\),那么 \([1,x]\) 部分同样可以舍掉。

事实上取 \(x\) 为黄金分割点(大约为 \(0.38\)),每次将 \(y\) 取另一黄金分割点,每次可以舍去 \(\phi\) 的部分,询问次数为 \(\log_{\phi}n\),算一下大概 \(30\) 次,可以通过。

子任务 2 (subtask 3,4)

子任务 2 约束\(K = 1, N=M=1000,Q=3500\)

考虑每次取较长边的一半(不妨假设为横坐标),分成两段,找到其中的最大值,扩展最大值周围的格子。

容易证明,当前极值在右边,如果最终极值在左边,一定会有当前极值引出的一条路径穿过中间的查询点,那么一定会被更新最大值。所以如果已知的最大值(包括之前询问得到的)在右边,那么递归处理右边的格子,否则递归左边格子一定合法。

\[T(n)=T(\frac n2)+1.5n \]

毛估估一下可以通过。

子任务 3 (subtask 5,6)

subtask 5 可以用类似子任务 2 的方法处理。

子任务 3 约束\(N = M = K = 500, Q = 1.5\times 10^5\)

考虑一种很自然的想法:每次随机 \(\frac Q2\) 个点,取其中最大的点,贪心向其四周查找最大值并转移。

分析一下正确概率:

不妨考虑最坏情况,一次转移认为要 \(6\) 次查询,那么要能在 \(\frac Q2\) 步内到达极大值,初始点一定要在前 \(\frac Q{12}\) 大的点中。成功概率为:

\[1-(1-\frac{Q}{12NMK})^{\frac Q2} \]

用 python 算一下大约是 \(97.6\%\)

事实上上述式子根本跑不满,如果认为一次转移期望 \(3\) 次查询,就有 \(99.94\%\) 的正确率。事实上正确率比这个更高。

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<random>
#include<chrono>
#include<cmath>
using namespace std;
int qry(int x,int y,int z){printf("? %d %d %d\n",x,y,z);fflush(stdout);int r;scanf("%d",&r);return r;}
int n,m,k,Q;
mt19937 Rand(std::chrono::system_clock::now().time_since_epoch().count());
int range(int l,int r){return Rand()%(r-l+1)+l;}
namespace c1{
    const int N=1000010;
    int a[N],qt=0;
    int qry(int x){if(a[x]) return a[x];++qt;int r=0;printf("? %d 1 1\n",x);fflush(stdout);scanf("%d",&r);return a[x]=r;}
    int getl(int l,int r){return l+(r-l+1)*0.38;}
    int getr(int l,int r){return r-(r-l+1)*0.38;}
    void solve(int l,int r,int p=0)
    {
        if(l==r){printf("! %d 1 1\n",l);cerr<<qt<<endl;fflush(stdout);exit(0);}
        if(r-l<=5)
        {
            int mid=(l+r)>>1;
            if(qry(mid)>qry(mid+1)) solve(l,mid);
            else solve(mid+1,r);
        }
        if(!p) p=getl(l,r);
        int mid=(l+r)>>1,u=0;
        if(p>=mid) u=getr(l,p);
        else u=getl(p,r),swap(u,p);
        if(qry(u)<qry(p)) solve(u+1,r,p);
        else solve(l,p-1,u);
    }
}
// int a[110][110];
namespace c2{
    const int N=1010;
    #define MP make_pair
    int mp[N][N],mx,my;
    void upd(int x,int y)
    {
        if(mp[x][y]<=mp[mx][my]) return;
        mx=x,my=y;
    }
    int qry(int x,int y)
    {
        if(x<=0 || y<=0 || x>n || y>m) return 0;
        if(mp[x][y]) return mp[x][y];
        int r=0;
        printf("? %d %d 1\n",x,y);fflush(stdout);scanf("%d",&r);
        // printf("%d\n",r);
        mp[x][y]=r;
        upd(x,y);
        return r;
    }
    int answer(int x,int y)
    {
        printf("! %d %d 1\n",x,y);
        cerr<<mx<<" "<<my<<endl;
        cerr<<x<<" "<<y<<endl;
        if(mx!=x || my!=y) throw;
        fflush(stdout);exit(0);
    }
    void solve(int xl,int xr,int yl,int yr)
    {
        if(xl==xr && yl==yr) answer(xl,yl);
        if(xr-xl>=yr-yl)
        {
            int xm=(xl+xr)>>1;
            int mw=0,p=0;
            for(int i=yl;i<=yr;i++)
            {
                int w=qry(xm,i);
                if(w>mw) mw=w,p=i;
            }
            qry(xm+1,p);qry(xm-1,p);
            if(mx==xm) answer(xm,p);
            if(mx>xm) solve(xm+1,xr,yl,yr);
            else solve(xl,xm-1,yl,yr);
        }
        else
        {
            int ym=(yl+yr)>>1;
            int mw=0,p=0;
            for(int i=xl;i<=xr;i++)
            {
                int w=qry(i,ym);
                if(w>mw) mw=w,p=i;
            }
            qry(p,ym+1);qry(p,ym-1);
            if(my==ym) answer(p,ym);
            if(my>ym) solve(xl,xr,ym+1,yr);
            else solve(xl,xr,yl,ym-1);
        }
    }
}
namespace c3{
    const int N=110;
    int mp[N][N][N],mx,my,mz;
    void upd(int x,int y,int z)
    {
        if(mp[x][y][z]<=mp[mx][my][mz]) return;
        mx=x,my=y,mz=z;
    }
    int qry(int x,int y,int z)
    {
        if(x<=0 || y<=0 || z<=0 || x>n || y>m || z>k) return 0;
        if(mp[x][y][z]) return mp[x][y][z];
        int r=0;
        printf("? %d %d %d\n",x,y,z);fflush(stdout);scanf("%d",&r);
        // printf("%d\n",r);
        mp[x][y][z]=r;
        upd(x,y,z);
        return r;
    }
    int answer(int x,int y,int z)
    {
        printf("! %d %d %d\n",x,y,z);
        fflush(stdout);exit(0);
    }
    void solve(int xl,int xr,int yl,int yr,int zl,int zr)
    {
        if(xl==xr && yl==yr && zl==zr) answer(xl,yl,zl);
        if(xr-xl>=yr-yl && xr-xl>=zr-zl)
        {
            int xm=(xl+xr)>>1;
            int mw=0,py=0,pz=0;
            for(int i=yl;i<=yr;i++)
                for(int j=zl;j<=zr;j++)
                {
                    int w=qry(xm,i,j);
                    if(w>mw) mw=w,py=i,pz=j;
                }
            qry(xm+1,py,pz);qry(xm-1,py,pz);
            if(mx==xm) answer(xm,py,pz);
            if(mx>xm) solve(xm+1,xr,yl,yr,zl,zr);
            else solve(xl,xm-1,yl,yr,zl,zr);
        }
        else if(yr-yl>=xr-xl && yr-yl>=zr-zl)
        {
            int ym=(yl+yr)>>1;
            int mw=0,px=0,pz=0;
            for(int i=xl;i<=xr;i++)
                for(int j=zl;j<=zr;j++)
                {
                    int w=qry(i,ym,j);
                    if(w>mw) mw=w,px=i,pz=j;
                }
            qry(px,ym+1,pz);qry(px,ym-1,pz);
            if(my==ym) answer(px,ym,pz);
            if(my>ym) solve(xl,xr,ym+1,yr,zl,zr);
            else solve(xl,xr,yl,ym-1,zl,zr);
        }
        else
        {
            int zm=(zl+zr)>>1;
            int mw=0,px=0,py=0;
            for(int i=xl;i<=xr;i++)
                for(int j=yl;j<=yr;j++)
                {
                    int w=qry(i,j,zm);
                    if(w>mw) mw=w,px=i,py=j;
                }
            qry(px,py,zm+1);qry(px,py,zm-1);
            if(mz==zm) answer(px,py,zm);
            if(mz>zm) solve(xl,xr,yl,yr,zm+1,zr);
            else solve(xl,xr,yl,yr,zl,zm-1);
        }
    }
}
namespace all{
    struct node{
        int x,y,z;
        node(int X=0,int Y=0,int Z=0):x(X),y(Y),z(Z){}
        bool operator <(const node a)const{return x==a.x?(y==a.y?z<a.z:y<a.y):x<a.x;}
    };
    const int N=1010;
    map<node,int>mp;
    int mx,my,mz;
    void upd(int x,int y,int z)
    {
        if(mp[node(x,y,z)]<=mp[node(mx,my,mz)]) return;
        mx=x,my=y,mz=z;
    }
    int answer(int x,int y,int z)
    {
        printf("! %d %d %d\n",x,y,z);
        fflush(stdout);exit(0);
    }
    int qry(int x,int y,int z)
    {
        if(x<=0 || y<=0 || z<=0 || x>n || y>m || z>k) return 0;
        if(mp[node(x,y,z)]) return mp[node(x,y,z)];
        int r=0;
        printf("? %d %d %d\n",x,y,z);fflush(stdout);scanf("%d",&r);
        // printf("%d\n",r);
        mp[node(x,y,z)]=r;
        upd(x,y,z);
        return r;
    }
    void init()
    {
        int t=Q/2;
        while(t --> 0)
        {
            int x=range(1,n),y=range(1,m),z=range(1,k);
            qry(x,y,z);Q--;
        }
    }
    node find_bigger(node a)
    {
        int t=qry(a.x,a.y,a.z);
        if(qry(a.x+1,a.y,a.z)>t) return node(a.x+1,a.y,a.z);
        if(qry(a.x,a.y+1,a.z)>t) return node(a.x,a.y+1,a.z);
        if(qry(a.x,a.y,a.z+1)>t) return node(a.x,a.y,a.z+1);
        if(qry(a.x-1,a.y,a.z)>t) return node(a.x-1,a.y,a.z);
        if(qry(a.x,a.y-1,a.z)>t) return node(a.x,a.y-1,a.z);
        if(qry(a.x,a.y,a.z-1)>t) return node(a.x,a.y,a.z-1);
        answer(a.x,a.y,a.z);exit(0);
    }
    void solve()
    {
        init();
        node a(mx,my,mz);
        while(true) a=find_bigger(a);
    }
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&k,&Q);
    if(m==1 && k==1) c1::solve(1,n);
    if(k==1) c2::solve(1,n,1,m);
    // c3::solve(1,n,1,m,1,k);
    all::solve();
    return 0;
}

E. Genetics

首先显然有一个 \(O(\frac{n^3}{\omega})\) 的做法,卡一卡常在 LOJ 上能过。这里考虑一个高明的 \(O(n^2)\) 做法:

定义每个字符串长度为 \(m\)

如果我们定义第 \(i\) 个字符串价值是 \(m^i\),定义 \(f(i,j)\) 表示第 \(i\) 个字符串与第 \(j\) 个字符串不同字符个数乘上 \(m^j\),要对于每个 \(i\)\(g_i=\sum f(i,j)\)。显然只要对每个位置的某个字符统计贡献即可 \(O(n)\) 计算。

可以发现 \(g_i=k\sum_{j\neq i}m^j\) 当且仅当字符串 \(i\) 是要找的那个串。

事实上并不需要权值为 \(m^i\),只需要钦定一个随机价值即可。可以证明上述做法在 long long 范围内随机的错误概率大约是 \(2^{-50}\)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<algorithm>
#include<chrono>
#include<random>
#include<cmath>
#define N 4100
#define ll long long
using namespace std;
char s[N][N];
int pos(char c){return c=='A'?0:(c=='C'?1:(c=='G'?2:3));}
mt19937 Rand(std::chrono::system_clock::now().time_since_epoch().count());
ll p[N],f[N][4];
int main()
{
    int n,m,k;scanf("%d%d%d",&n,&m,&k);
    bool only=true;ll sum=0;
    for(int i=0;i<n;i++)
    {
        scanf("%s",s[i]),p[i]=Rand(),sum+=p[i];
        for(int j=0;j<m;j++) f[j][pos(s[i][j])]-=p[i];
    }
    for(int i=0;i<m;i++) for(int j=0;j<4;j++) f[i][j]+=sum;
    for(int i=0;i<n;i++)
    {
        ll res=0;for(int j=0;j<m;j++) res+=f[j][pos(s[i][j])];
        if(res==k*(sum-p[i])){printf("%d\n",i+1);return 0;}
    }
    return 0;
}

F. Paths

签到题。

\(f_{i,S}\) 表示到点 \(i\),其中 \(S\) 子集的颜色已经走过的方案数。直接 dp 即可。复杂度 \(O(m2^k)\)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 300010
#define ll long long
#define M 5
using namespace std;
int col[N];
vector<int>g[N];
ll f[N][1<<M];
int main()
{
    int n,m,k;scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++) scanf("%d",&col[i]),col[i]--;
    for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),g[u].push_back(v),g[v].push_back(u);
    for(int i=1;i<=n;i++) f[i][1<<col[i]]=1;
    ll ans=0;
    for(int s=1;s<1<<k;s++)
    {
        for(int u=1;u<=n;u++) if(f[u][s])
            for(int v:g[u]) if(!(s>>col[v]&1)) f[v][s^(1<<col[v])]+=f[u][s];
    }
    for(int u=1;u<=n;u++)
        for(int s=1;s<1<<k;s++) ans+=f[u][s];
    printf("%lld\n",ans-n);
    return 0;
}
posted @ 2021-11-08 11:59  Flying2018  阅读(57)  评论(0)    收藏  举报