Gym 104270 The 2018 ICPC Asia Qingdao Regional Programming Contest (The 1st Universal Cup, Stage 9: Qingdao)

发布时间 2023-09-29 13:02:51作者: Zhou_JK

A. Sequence and Sequence


B. Kawa Exam

可以发现,对答案会产生影响的只有割边,把所有边双缩起来,然后就是一个森林。

考虑一个树的时候怎么做,就是对于每条边求出这条边两端的众数个数,考虑线段树合并,每次动态维护子树内的众数和子树外的众数。


#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
const int N=100005;
int n,m,k;
int x[N],y[N],id[N];
int a[N];
struct Edge
{
    int to,next;
}edge[N<<1];
int head[N],cnt;
void add_edge(int u,int v)
{
    cnt++;
    edge[cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt;
    return;
}
struct Segment_Tree_RMQ
{
    int index(int l,int r)
    {
        return (l+r)|(l!=r);
    }
    #define ls index(l,mid)
    #define rs index(mid+1,r)
    struct Node
    {
        int mx; 
    }tree[N*2];
    void push_up(int i,int l,int r)
    {
        int mid=(l+r)/2;
        tree[i].mx=max(tree[ls].mx,tree[rs].mx);
        return;
    }
    void build(int i,int l,int r)
    {
        if(l==r)
        {
            tree[i].mx=0;
            return;
        }
        int mid=(l+r)/2;
        build(ls,l,mid);
        build(rs,mid+1,r);
        push_up(i,l,r);
        return;
    }
    void modify(int i,int l,int r,int u,int v)
    {
        if(l==r)
        {
            tree[i].mx+=v;
            return;
        }
        int mid=(l+r)/2;
        if(u<=mid) modify(ls,l,mid,u,v);
        else modify(rs,mid+1,r,u,v);
        push_up(i,l,r);
        return;
    }
    int query(int l,int r)
    {
        return tree[index(l,r)].mx;
    }
    #undef ls
    #undef rs
}rmq;
struct Segment_Tree
{
    struct Node
    {
        int ls,rs;
        int mx1,mx2;
    }tree[N*25];
    int tot,rt[N];
    void clear()
    {
        tot=0;
        for(int i=1;i<=n;i++)
            rt[i]=0;
        return;
    }
    int new_node()
    {
        int now=++tot;
        tree[now].ls=tree[now].rs=0;
        tree[now].mx1=tree[now].mx2=0;
        return now;
    }
    void push_up(int i,int l,int r)
    {
        int mid=(l+r)/2;
        tree[i].mx1=max(tree[tree[i].ls].mx1,tree[tree[i].rs].mx1);
        tree[i].mx2=max(tree[i].ls?tree[tree[i].ls].mx2:rmq.query(l,mid),tree[i].rs?tree[tree[i].rs].mx2:rmq.query(mid+1,r));
        return;
    }
    void add(int &i,int l,int r,int u,int v)
    {
        if(!i)
        {
            i=new_node();
            if(l==r) tree[i].mx1=0,tree[i].mx2=rmq.query(l,r);
        }
        if(l==r)
        {
            tree[i].mx1+=v,tree[i].mx2-=v;
            return;
        }
        int mid=(l+r)/2;
        if(u<=mid) add(tree[i].ls,l,mid,u,v);
        else add(tree[i].rs,mid+1,r,u,v);
        push_up(i,l,r);
        return;
    }
    int merge(int u,int v,int l,int r)
    {
        if(!u) return v;
        if(!v) return u;
        if(l==r)
        {
            tree[u].mx1+=tree[v].mx1;
            tree[u].mx2-=rmq.query(l,r)-tree[v].mx2;
            return u;
        }
        int mid=(l+r)/2;
        tree[u].ls=merge(tree[u].ls,tree[v].ls,l,mid);
        tree[u].rs=merge(tree[u].rs,tree[v].rs,mid+1,r);
        push_up(u,l,r);
        return u;
    }
    int query_t1(int i,int l,int r,int L,int R)
    {
        if(!i) return 0;
        if(L<=l&&r<=R) return tree[i].mx1;
        int mid=(l+r)/2;
        if(R<=mid) return query_t1(tree[i].ls,l,mid,L,R);
        else if(L>mid) return query_t1(tree[i].rs,mid+1,r,L,R);
        else return max(query_t1(tree[i].ls,l,mid,L,R),query_t1(tree[i].rs,mid+1,r,L,R));
    }
    int query_t2(int i,int l,int r,int L,int R)
    {
        if(!i) return rmq.query(l,r);
        if(L<=l&&r<=R) return tree[i].mx2;
        int mid=(l+r)/2;
        if(R<=mid) return query_t2(tree[i].ls,l,mid,L,R);
        else if(L>mid) return query_t2(tree[i].rs,mid+1,r,L,R);
        else return max(query_t2(tree[i].ls,l,mid,L,R),query_t2(tree[i].rs,mid+1,r,L,R));
    }
}T;
int dfn[N],low[N],Index;
bool bridge[N+N];
void tarjan(int u,int prev)
{
    dfn[u]=low[u]=++Index;
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(!dfn[v])
        {
            tarjan(v,i);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u]) bridge[i]=bridge[i^1]=true;
        }
        else if(i!=(prev^1)) low[u]=min(low[u],dfn[v]);
    }
    return;
}
int bel[N],tot;
vector<int>block[N]; 
void dfs(int u)
{
    bel[u]=tot;
    block[tot].emplace_back(u);
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(bel[v]||bridge[i]) continue;
        dfs(v);
    }
    return;
}
vector<int>G[N];
void rebuild()
{
    fill(dfn+1,dfn+n+1,0);
    fill(low+1,low+n+1,0);
    for(int i=1;i<=cnt;i++)
        bridge[i]=false;
    Index=0;
    for(int i=1;i<=n;i++)
        if(!dfn[i]) tarjan(i,0);
    fill(bel+1,bel+n+1,0);
    tot=0;
    for(int i=1;i<=n;i++)
        block[i].clear();
    for(int i=1;i<=n;i++)
        if(!bel[i])
        {
            tot++;
            dfs(i);
        }
    for(int i=1;i<=tot;i++)
        G[i].clear();
    for(int u=1;u<=n;u++)
        for(int i=head[u];i;i=edge[i].next)
        {
            int v=edge[i].to;
            if(bel[u]==bel[v]) continue;
            G[bel[u]].emplace_back(bel[v]);
        }
    return;
}
int siz[N],dep[N];
vector<int>color;
void init(int u,int father)
{
    siz[u]=block[u].size();
    dep[u]=dep[father]+1;
    for(int v:G[u])
    {
        if(v==father) continue;
        init(v,u);
        siz[u]+=siz[v];
    }
    for(int t:block[u])
        color.emplace_back(a[t]);
    return;
}
int res[N];
int top[N];
void dfs(int u,int father,int topf)
{
    top[u]=topf;
    for(int v:G[u])
    {
        if(v==father) continue;
        dfs(v,u,topf);
        T.rt[u]=T.merge(T.rt[u],T.rt[v],1,k);
    }
    for(int t:block[u])
        T.add(T.rt[u],1,k,a[t],1);
    res[u]=T.query_t1(T.rt[u],1,k,1,k)+T.query_t2(T.rt[u],1,k,1,k);
    return;
}
int ans[N];
void solve()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    fill(head+1,head+n+1,0);
    cnt=1;
    for(int i=1;i<=m;i++)
    {
        cin>>x[i]>>y[i];
        add_edge(x[i],y[i]);
        add_edge(y[i],x[i]);
        id[i]=cnt;
    }
    k=*max_element(a+1,a+n+1);
    rebuild();
    rmq.build(1,1,k);
    long long sum=0;
    T.clear();
    fill(siz+1,siz+n+1,0); 
    for(int i=1;i<=tot;i++)
        if(!siz[i])
        {
            color.clear();
            init(i,0);
            for(int c:color)
                rmq.modify(1,1,k,c,1);
            dfs(i,0,i);
            sum+=res[i];
            for(int c:color)
                rmq.modify(1,1,k,c,-1);
        }
    for(int i=1;i<=m;i++)
        if(!bridge[id[i]]) ans[i]=sum;
        else
        {
            if(dep[bel[x[i]]]<dep[bel[y[i]]]) swap(x[i],y[i]);
            ans[i]=res[bel[x[i]]]+sum-res[top[bel[x[i]]]];
        }
    for(int i=1;i<m;i++)
        cout<<ans[i]<<" ";
    cout<<ans[m]<<"\n";
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}

C. Flippy Sequence

如果 \(s=t\),两次操作肯定是同个区间,答案就是非空区间的数量。

如果 \(s_i!=t_i\)\(i\) 构成的区间个数大于 \(2\),显然无解。

如果 \(s_i!=t_i\)\(i\) 构成的区间个数等于 \(2\),手玩一下发现答案是 \(6\)

如果 \(s_i!=t_i\)\(i\) 构成的区间个数等于 \(1\),手玩一下发现答案是 \(2(n-1)\)


#include<iostream>
#include<cstdio>
using namespace std;
const int N=1000005;
int n;
char s[N],t[N];
int a[N];
void solve()
{
    cin>>n;
    cin>>s;
    cin>>t;
    for(int i=0;i<n;i++)
        if(s[i]==t[i]) a[i]=1;
        else a[i]=0;
    int cnt=0;
    for(int i=0,j=0;i<n;i=j)
    {
        while(j<n&&a[i]==a[j]) j++;
        if(a[i]) continue;
        cnt++;
    }
    long long ans=0;
    if(cnt==0) ans=(long long)n*(n-1)/2+n;
    else if(cnt==1) ans=2*(n-1);
    else if(cnt==2) ans=6;
    cout<<ans<<"\n";
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}

D. Magic Multiplication

可以发现,我们如果确定了 \(a_1\)\(b\) 也就确定了,我们可以再用 \(b\) 推出 \(a\),枚举 \(a_1\) 判断是否合法就可以了。


#include<iostream>
#include<cstdio>
using namespace std;
const int N=200005;
int n,m,len;
int c[N];
int a[N],b[N];
bool check(int v)
{
    int cur=1;
    a[1]=v;
    for(int i=1;i<=m;i++)
    {
        if(cur<=len&&c[cur]%a[1]==0) b[i]=c[cur]/a[1],cur++;
        else if(cur+1<=len&&(c[cur]*10+c[cur+1])%a[1]==0&&(c[cur]*10+c[cur+1])/a[1]<=9) b[i]=(c[cur]*10+c[cur+1])/a[1],cur+=2;
        else return false;
    }
    for(int i=2;i<=n;i++)
    {
        a[i]=-1;
        for(int j=1;j<=m;j++)
            if(b[j]!=0)
            {
                int val=-1;
                if(cur<=len&&c[cur]%b[j]==0) val=c[cur]/b[j],cur++;
                else if(cur+1<=len&&(c[cur]*10+c[cur+1])%b[j]==0&&(c[cur]*10+c[cur+1])/b[j]<=9) val=(c[cur]*10+c[cur+1])/b[j],cur+=2;
                else return false;
                if(a[i]!=-1&&a[i]!=val) return false;
                else a[i]=val;
            }
            else
            {
                if(cur<=len&&c[cur]==0) cur++;
                else return false;
            }
    }
    return cur==len+1;
}
void solve()
{
    cin>>n>>m;
    string str;
    cin>>str;
    len=str.size();
    for(int i=1;i<=len;i++)
        c[i]=str[i-1]-'0';
    for(int v=1;v<=9;v++)
        if(check(v))
        {
            for(int i=1;i<=n;i++)
                cout<<a[i];
            cout<<" ";
            for(int i=1;i<=m;i++)
                cout<<b[i];
            cout<<"\n";
            return;
        }
    cout<<"Impossible\n";
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}

E. Plants vs. Zombies

二分答案,问题转换成判断每个点 \(i\) 能否都被经过 \(t_i\) 次。可以发现,我们从左到右,依次浇每棵植物肯定不会比最优方案劣,那么我们只需要在 \(i\)\(i+1\) 之间反复游走就可以了。还有个细节,就是如果右边的植物都不需要浇水,那么我们也不需要花费 \(1\) 的代价走过去。


#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005;
int n;
long long m;
int a[N];
long long b[N];
bool check(long long x)
{
    long long tot=0;
    for(int i=1;i<=n;i++)
        b[i]=0;
    for(int i=1;i<=n;i++)
        if(b[i]<x)
        {
            long long t=(x-b[i]+a[i]-1)/a[i];
            tot+=t*2-1;
            if(tot>m) return false;
            b[i+1]+=a[i+1]*(t-1);
        }
        else tot++;
    return true;
}
void solve()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    long long l=1,r=*max_element(a+1,a+n+1)*m,ans=0;
    while(l<=r)
    {
        long long mid=(l+r)/2;
        if(check(mid)) ans=mid,l=mid+1;
        else r=mid-1; 
    }
    cout<<ans<<"\n";
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}

F. Tournament

打个表找找规律,可以发现 \(k\le \operatorname{lowbit}(i)-1\) 时才有解。且每行就是在前一行的基础上翻转长度为 \(\operatorname{lowbit}(i) \times 2\) 的区间。


#include<iostream>
#include<cstdio>
using namespace std;
const int N=1005;
int n,k;
int a[N][N];
int lowbit(int x)
{
    return x&-x; 
}
void solve()
{
    cin>>n>>k;
    if(k>lowbit(n)-1)
    {
        cout<<"Impossible\n";
        return;
    }
    for(int i=1;i<=n;i++)
        a[0][i]=i;
    for(int i=1;i<=k;i++)
    {
        for(int j=1;j<=n;j++)
            a[i][j]=a[i-1][j]; 
        int len=lowbit(i);
        for(int j=1;j<=n;j+=len*2)
            for(int k=0;k<len;k++)
                swap(a[i][j+k],a[i][j+len*2-1-k]);
    }
    for(int i=1;i<=k;i++)
    {
        for(int j=1;j<=n;j++)
            cout<<a[i][j]<<" ";
        cout<<"\n";
    }
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}

I. Soldier Game

将问题可以转化成我们有长度为 \(1,2\) 的线段,每个线段有一个权值,覆盖 \([1,n]\) 的最大值减去最小值最小。

考虑枚举最小值 \(v\),权值 \(\lt v\) 的线段都不能选。考虑用线段树维护这个东西,\(s_{0/1,0/1}\) 表示左端点是不是由 \([a_{l-1},a_l]\) 覆盖的,右端点是不是由 \([a_r,a_{r+1}]\) 覆盖的。

合并节点就是 \(s_{i,j}=\min\{\max\{s_{i,k},s_{k,j}\}\)

初值设成 \(s_{0,0}=a_i,s_{0,1}=a_i+a_{i+1},s_{1,0}=-\infty,s_{1,1}=\infty\)

将所有区间排序,从小到大,依次删除每个区间就行了,删除的时候可以将线段树上的节点设成 \(\infty\)


#include<iostream>
#include<cstdio>
#include<vector>
#include<tuple>
#include<algorithm>
using namespace std;
const int N=100005;
const long long INF=4557430888798830399;
int n;
int a[N];
struct Segment_Tree
{
    #define ls i*2
    #define rs i*2+1
    struct Node
    {
        int l,r;
        long long mx[2][2];
    }tree[N*4];
    Node merge(const Node &a,const Node &b)
    {
        Node c;
        c.l=a.l,c.r=b.r;
        for(int i=0;i<=1;i++)
            for(int j=0;j<=1;j++)
                c.mx[i][j]=INF;
        for(int i=0;i<=1;i++)
            for(int j=0;j<=1;j++)
                for(int k=0;k<=1;k++)
                    c.mx[i][j]=min(c.mx[i][j],max(a.mx[i][k],b.mx[k][j]));
        return c;
    }
    void push_up(int i)
    {
        tree[i]=merge(tree[ls],tree[rs]);
        return;
    }
    void build(int i,int l,int r)
    {
        tree[i].l=l,tree[i].r=r;
        if(l==r)
        {
            tree[i].mx[0][0]=a[l];
            if(l+1<=n) tree[i].mx[0][1]=a[l]+a[l+1];
            else tree[i].mx[0][1]=INF;
            if(l-1>=1) tree[i].mx[1][0]=-INF;
            else tree[i].mx[1][0]=INF;
            tree[i].mx[1][1]=INF;
            return;
        }
        int mid=(l+r)/2;
        build(ls,l,mid);
        build(rs,mid+1,r);
        push_up(i);
        return;
    }
    void modify(int i,int u,int v)
    {
        if(tree[i].l==tree[i].r)
        {
            if(v==1) tree[i].mx[0][0]=INF;
            else tree[i].mx[0][1]=INF;
            return;
        }
        if(u<=tree[ls].r) modify(ls,u,v);
        else modify(rs,u,v);
        push_up(i);
        return;
    }
    Node query(int i,int l,int r)
    {
        if(l<=tree[i].l&&tree[i].r<=r) return tree[i];
        if(r<=tree[ls].r) return query(ls,l,r);
        else if(l>=tree[rs].l) return query(rs,l,r);
        else return merge(query(ls,l,r),query(rs,l,r));
    }
    #undef ls
    #undef rs
}T;
void solve()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    T.build(1,1,n);
    vector<tuple<int,int,int>>seg;
    for(int i=1;i<=n;i++)
    {
        seg.emplace_back(a[i],i,1);
        if(i+1<=n) seg.emplace_back(a[i]+a[i+1],i,2);
    }
    sort(seg.begin(),seg.end());
    long long ans=INF;
    for(auto [v,i,len]:seg)
    {
        ans=min(ans,T.query(1,1,n).mx[0][0]-v);
        T.modify(1,i,len);
    }
    cout<<ans<<"\n";
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}

J. Books

先把价格为 \(0\) 的书去掉。最大的钱肯定是买前面 \(m\) 本书,加上后面 \(n-m\) 本书的最小值 \(-1\)


#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005;
int n,m;
int a[N];
int b[N],tot;
void solve()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    tot=0;
    for(int i=1;i<=n;i++)
        if(a[i]==0) m--;
        else b[++tot]=a[i];
    if(m<0)
    {
        cout<<"Impossible\n";
        return;
    }
    if(m==tot)
    {
        cout<<"Richman\n";
        return;
    }
    long long ans=0;
    for(int i=1;i<=m;i++)
        ans+=b[i];
    int v=*min_element(b+m+1,b+tot+1);
    ans+=v-1;
    cout<<ans<<"\n";
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}

K. Airdrop


L. Sub-cycle Graph

不会数数爬了,可以看官方的题解

https://sua.ac/wiki/2018-icpc-qingdao/l/


#include<iostream>
#include<cstdio>
using namespace std;
const int N=100005;
const int MOD=1000000007;
const int INV2=500000004;
int qpow(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1) res=(long long)res*a%MOD;
        a=(long long)a*a%MOD,b>>=1;
    }
    return res;
}
int getinv(int a)
{
    return qpow(a,MOD-2);
}
int fac[N+N],ifac[N+N];
void init(int n=200000)
{
    fac[0]=1;
    for(int i=1;i<=n;i++)
        fac[i]=(long long)fac[i-1]*i%MOD;
    ifac[n]=qpow(fac[n],MOD-2);
    for(int i=n;i>=1;i--)
        ifac[i-1]=(long long)ifac[i]*i%MOD;
    return;
}
int C(int n,int m)
{
    if(m>n) return 0;
    if(m==0||m==n) return 1;
    return (long long)fac[n]*ifac[m]%MOD*ifac[n-m]%MOD;
}
int n;
long long m;
void solve()
{
    cin>>n>>m;
    if(m==0) cout<<1<<"\n";
    else if(m>n) cout<<0<<"\n";
    else if(m==n)
    {
        int ans=(long long)fac[n-1]*INV2%MOD;
        cout<<ans<<"\n";
    }
    else
    {
        int ans=0;
        int pre=1;
        for(int i=1;i<=n-m;i++)
        {
            pre=(long long)pre*(2*i-1)%MOD;
            ans=(ans+(long long)C(n,n-m-i)*fac[m-i]%MOD*C(m-1,i-1)%MOD*C(m+i,2*i)%MOD*pre)%MOD;
        }
        cout<<ans<<"\n";
    }
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    init();
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}

M. Function and Function

可以发现最后就是在 \(0,1\) 之间反转,这个东西收束的很快,所以暴力做到 \(0,1\) 之后判下奇偶性就是对的。


#include<iostream>
#include<cstdio>
using namespace std;
int x,k;
int calc(int x)
{
    if(x==0) return 1;
    if(x==1||x==2||x==3) return 0;
    if(x==4) return 1;
    if(x==5) return 0;
    if(x==6) return 1;
    if(x==7) return 0;
    if(x==8) return 2;
    if(x==9) return 1;
    return -1;
}
int f(int x)
{
    if(x==0) return 1;
    int sum=0;
    while(x>0)
        sum+=calc(x%10),x/=10;
    return sum;
}
void solve()
{
    cin>>x>>k;
    for(int i=1;i<=k;i++)
    {
        x=f(x);
        if(x==0||x==1)
        {
            x^=(k-i)&1;
            break;
        }
    }
    cout<<x<<"\n";
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}