Gym 104172 The 2023 ICPC Asia Hong Kong Regional Programming Contest (The 1st Universal Cup, Stage 2Hong Kong)

发布时间 2023-09-27 22:28:34作者: Zhou_JK

A. TreeScript

\(f_u\) 表示 \(u\)\(u\) 子树中的节点都创建的最小数量。

如果 \(u\) 只有一个儿子,那么可以将子树最后一个节点存储在当前的 \(u\) 中,答案就是 \(f_v\)

\(u\) 有多个儿子:

\(t=\max\limits_{v\in \operatorname{son}(u)}f_v\),若 \(t\)\(f_v\) 中只出现了一次,那么可以先创建其他的儿子,最后创建 \(v\) 子树中的点,把最后一个节点存储到 \(u\) 中,答案就是 \(t\)。如果 \(t\) 出现超过一次,那么只能依次创建,答案为 \(t+1\)


#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int N=200005;
int n;
int p[N];
vector<int>G[N];
int f[N];
void dfs(int u,int father)
{
    int cnt=0,mx=0;
    for(int v:G[u])
    {
        if(v==father) continue;
        dfs(v,u);
        if(f[v]>mx) mx=f[v],cnt=1;
        else if(f[v]==mx) cnt++;
    }
    if(mx==0) f[u]=1;
    else if(cnt>1) f[u]=mx+1;
    else f[u]=mx;
    return;
}
void solve()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>p[i];
    for(int i=1;i<=n;i++)
        G[i].clear();
    for(int i=2;i<=n;i++)
        G[p[i]].emplace_back(i),G[i].emplace_back(p[i]);
    dfs(1,0);
    cout<<f[1]<<"\n";
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}

B. Big Picture

黑色格子的连通块数量肯定为 \(1\),我们只需要计算白色格子的期望连通块数量就可以了。

可以发现,一个白色连通块只会有一个点右边和下面都是黑色的,计算每个格子作为这个点的概率相加即可。


#include<iostream>
#include<cstdio>
using namespace std;
const int N=1005;
const int MOD=998244353;
int n,m;
int p[N][N],q[N][N];
int sp[N][N],sq[N][N];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>p[i][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>q[i][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            sp[i][j]=(sp[i][j-1]+p[i][j])%MOD;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            sq[i][j]=(sq[i-1][j]+q[i][j])%MOD;
    int ans=2;
    for(int i=1;i<n;i++)
        for(int j=1;j<m;j++)
        {
            int a=(long long)sp[i][j-1]*sq[i-1][j]%MOD,b=(long long)(1-sp[i+1][j-1]+MOD)*(1-sq[i-1][j+1]+MOD)%MOD;
            ans=(ans+(long long)a*b)%MOD;
        }
    cout<<ans;
    return 0;
}

C. Painting Grid

\(n,m\) 都为奇数的时候,\(n> 2^m\)\(m> 2^n\) 时无解。

不妨令 \(m\) 为偶数,\(n\) 为偶数时同理。

我们可以通过前 \(k=\lceil \log_2 m\rceil\) 行使得 \(m\) 列互不相同。具体的说,令每列成为 \(0\sim 2^k-1\) 中的某个数,某列是它取反的结果。

剩下的行直接暴力在上一个行 \(+1\) 调整一下就可以了。

#include<iostream>
#include<cstdio>
#include<map>
#include<numeric>
#include<random>
#include<chrono>
#include<algorithm>
using namespace std;
const int N=1005;
int n,m;
string a[N];
void solve()
{
    cin>>n>>m;
    if(n*m%2==1)
    {
        cout<<"NO\n";
        return;
    }
    if(n<=30&&(1<<n)<m)
    {
        cout<<"NO\n";
        return;
    }
    if(m<=30&&(1<<m)<n)
    {
        cout<<"NO\n";
        return;
    }
    bool rev=false;
    if(m%2!=0) rev=true,swap(n,m);
    for(int i=1;i<=n;i++)
        a[i].resize(m+1);
    int k=ceil(log2(m)),v=0;
    static bool vis[N*2];
    for(int i=0;i<=(1<<k);i++)
        vis[i]=false;
    auto getstatusrev=[=](int x)
    {
        return (~x)&((1<<k)-1);
    };
    int cur=0;
    for(int j=1;j<=m;j+=2)
    {
        int val;
        while(cur<k&&(vis[1<<cur]||vis[getstatusrev(1<<cur)])) cur++;
        if(cur<k) val=1<<cur,cur++;
        else
        {
            while(vis[v]||vis[getstatusrev(v)]) v++;
            val=v;
        }
        vis[val]=vis[getstatusrev(val)]=true;
        for(int i=1;i<=k;i++)
            a[i][j]=((val>>(i-1))&1)+'0',a[i][j+1]=(((val>>(i-1))&1)^1)+'0';
    }
    if(k<n)
    {
        sort(a+1,a+k+1);
        map<string,bool>book;
        for(int i=1;i<=k;i++)
            book[a[i]]=true;
        auto getrev=[=](string s)
        {
            string t;
            t.resize(m+1);
            for(int i=1;i<=m;i++)
                if(s[i]=='1') t[i]='0';
                else t[i]='1';
            return t;
        };
        for(int i=k+2;i<=n;i+=2)
        {
            if(i-2>k)
            {
                for(int j=1;j<=m;j++)
                    a[i][j]=a[i-2][j];
                bool flag=false;
                do
                {
                    a[i][m]++;
                    for(int j=m;j>=1;j--)
                        if(a[i][j]=='2')
                        {
                            if(j-1>=1) a[i][j-1]++;
                            else
                            {
                                flag=true;
                                break;
                            }
                            a[i][j]='0';
                        }
                        else break;
                    if(flag) break;
                }
                while(book.count(a[i])||book.count(getrev(a[i])));
                if(!flag) a[i-1]=getrev(a[i]);
                else
                {
                    for(int j=1;j<=m;j++)
                        a[i][j]='0';
                    while(book.count(a[i]))
                    {
                        a[i][m]++;
                        for(int j=m;j>=1;j--)
                            if(a[i][j]=='2')
                            {
                                if(j-1>=1) a[i][j-1]++;
                                a[i][j]='0';
                            }
                            else break;
                    }
                    for(int j=1;j<=m;j++)
                        a[i-1][j]=a[i][j];
                    do
                    {
                        a[i-1][m]++;
                        for(int j=m;j>=1;j--)
                            if(a[i-1][j]=='2')
                            {
                                if(j-1>=1) a[i-1][j-1]++;
                                a[i-1][j]='0';
                            }
                            else break;
                    }
                    while(book.count(a[i-1]));
                }
            }
            else
            {
                for(int j=1;j<=m;j++)
                    a[i][j]='0';
                while(book.count(a[i])||book.count(getrev(a[i])))
                {
                    a[i][m]++;
                    for(int j=m;j>=1;j--)
                        if(a[i][j]=='2')
                        {
                            if(j-1>=1) a[i][j-1]++;
                            a[i][j]='0';
                        }
                        else break;
                }
                a[i-1]=getrev(a[i]);
            }
            book[a[i]]=book[a[i-1]]=true;
        }
        if((n-k)%2==1)
        {
            static int p[N];
            static mt19937_64 rnd(chrono::system_clock::now().time_since_epoch().count());
            iota(p+1,p+m+1,1);
            do
            {
                shuffle(p+1,p+m+1,rnd);
                for(int i=1;i<=m/2;i++)
                    a[n][p[i]]='1';
                for(int i=m/2+1;i<=m;i++)
                    a[n][p[i]]='0';
            }
            while(book.count(a[n]));
        }
    }
    cout<<"YES\n";
    if(rev)
    {
        for(int j=1;j<=m;j++)
        {
            for(int i=1;i<=n;i++)
                cout<<a[i][j];
            cout<<"\n";
        }
    }
    else
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;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;
}

D. Shortest Path Query


E. Goose, Goose, DUCK?

可以发现,这个 \(k\) 个人的限制相当于是两个区间。那么可以类似扫描线从左网友扫,每次覆盖一个区间求没有被覆盖的位置个数,直接用总数减去覆盖了的位置个数就行了。


#include<iostream>
#include<cstdio>
#include<vector>
#include<tuple>
#include<algorithm>
using namespace std;
const int N=1000005;
int n,m,k;
int a[N];
vector<int>pos[N];
vector<tuple<int,int,int>>seg[N];
struct Segment_Tree
{ 
    #define ls i*2
    #define rs i*2+1
    struct Node
    {
        int l,r,cnt;
        long long sum;
    }tree[N*4];
    void build(int i,int l,int r)
    {
        tree[i].l=l,tree[i].r=r,tree[i].cnt=tree[i].sum=0;
        if(l==r) return;
        int mid=(l+r)/2;
        build(ls,l,mid);
        build(rs,mid+1,r);
        return;
    }
    void push_up(int i)
    {
        if(tree[i].cnt) tree[i].sum=tree[i].r-tree[i].l+1;
        else tree[i].sum=tree[ls].sum+tree[rs].sum;
        return;
    }
    void modify(int i,int l,int r,int k)
    {
        if(l<=tree[i].l&&tree[i].r<=r)
        {
            tree[i].cnt+=k;
            if(tree[i].cnt) tree[i].sum=tree[i].r-tree[i].l+1;
            else if(tree[i].l==tree[i].r) tree[i].sum=0;
            else tree[i].sum=tree[ls].sum+tree[rs].sum;
            return;
        }
        if(l<=tree[ls].r) modify(ls,l,r,k);
        if(r>=tree[rs].l) modify(rs,l,r,k);
        push_up(i);
        return;
    }
    int query(int i,int l,int r)
    {
        if(l<=tree[i].l&&tree[i].r<=r) return tree[i].sum;
        int res=0;
        if(l<=tree[ls].r) res+=query(ls,l,r);
        if(r>=tree[rs].l) res+=query(rs,l,r);
        return res;
    }
    #undef ls
    #undef rs
}T;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>n>>k;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    m=*max_element(a+1,a+n+1);
    for(int u=1;u<=m;u++)
        pos[u].emplace_back(0); 
    for(int i=1;i<=n;i++)
        pos[a[i]].emplace_back(i);
    for(int u=1;u<=m;u++)
    {
        pos[u].emplace_back(n+1);
        for(int i=1;i<(int)pos[u].size();i++)
            if(i+k<(int)pos[u].size())
            {
                int ll=pos[u][i-1]+1,lr=pos[u][i];
                int rl=pos[u][i+k-1],rr=pos[u][i+k]-1;
                seg[ll].emplace_back(rl,rr,1),seg[lr+1].emplace_back(rl,rr,-1);
            }
    }
    T.build(1,1,n);
    long long ans=0;
    for(int i=1;i<=n;i++)
    {
        for(auto [l,r,v]:seg[i])
            T.modify(1,l,r,v);
        ans+=n-i+1-T.query(1,i,n);
    }
    cout<<ans;
    return 0;
}

F. Sum of Numbers

最后肯定是让每段的长度尽可能平均,令 \(len=\lfloor \frac{n}{k+1}\rfloor+2\),那么每段的长度肯定不会超过 \(len\),按照这个 DP 就行了。


#include<iostream>
#include<iomanip>
#include<cstdio>
#include<vector>
using namespace std;
struct BigInteger
{
    static const long long base_=(long long)1e18;
    static const int width_=18;
    vector<long long> val_;
    BigInteger(int num=0)
    {
        do
        {
            val_.push_back(num%base_);
            num/=base_;
        }
        while(num);
    }
    BigInteger(long long num)
    {
        do
        {
            val_.push_back(num % base_);
            num/=base_;
        }
        while(num);
    }
    BigInteger(const string &str)
    {
        int be=0,en=str.size();
        while((en-=width_)>=be)
            val_.push_back(stoll(str.substr(en, width_)));
        if((en+=width_)>be)
            val_.push_back(stoll(str.substr(be,en-be)));
    }
    BigInteger &operator=(int num)
    {
        return *this=BigInteger(num);
    }
    BigInteger &operator=(long long num)
    {
        return *this=BigInteger(num);
    }
    BigInteger &operator=(const string &str)
    {
        return *this=BigInteger(str);
    }
    bool operator<(const BigInteger &obj)const
    {
        if(val_.size()!=obj.val_.size()) return val_.size()<obj.val_.size();
        for(int i=val_.size()-1;i>=0;i--)
            if(val_[i]!=obj.val_[i]) return val_[i]<obj.val_[i];
        return false;
    }
    BigInteger operator+(const BigInteger &obj)const
    {
        BigInteger result;
        int os=max(val_.size(),obj.val_.size());
        result.val_.resize(os);
        for(int i=0;i<os;i++)
        {
            long long tmp=result.val_[i];
            if(i<(int)val_.size()) tmp+=val_[i];
            if(i<(int)obj.val_.size()) tmp+=obj.val_[i];
            if(tmp>=base_)
            {
                tmp-=base_;
                if(i+1>=(int)result.val_.size()) result.val_.push_back(0); 
                result.val_[i+1]++;
            }
            result.val_[i]=tmp;
        }
        return result;
    }
    friend ostream &operator<<(ostream &,const BigInteger &);
};
ostream &operator<<(ostream &out,const BigInteger &obj)
{
    out<<obj.val_.back();
    for(int i=obj.val_.size()-2;i>=0;i--)
        out<<setw(BigInteger::width_)<<setfill('0')<<obj.val_[i];
    return out;
}
const int N=200005; 
int n,k;
string s;
bool vis[N];
BigInteger dp[N];
void solve()
{
    cin>>n>>k;
    cin>>s;
    for(int i=1;i<=n;i++)
        vis[i]=false;
    int len=n/(k+1)+2;
    for(int t=0;t<=k;t++)
        for(int i=n;i>=max(1,n-len*(k-t));i--)
            for(int j=max(0,i-len);j<=t*len&&j<i;j++)
                if(j==0||vis[j])
                {
                    BigInteger v=dp[j]+s.substr(j,i-j);
                    if(!vis[i]||v<dp[i]) dp[i]=v,vis[i]=true;
                }
    cout<<dp[n]<<"\n";
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}

G. Paddle Star


H. Another Goose Goose Duck Problem

等待时间肯定越小越好,然后直接算就行。


#include<iostream>
#include<cstdio>
using namespace std;
int l,r,b,k;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>l>>r>>b>>k;
    long long ans=(long long)(l+b-1)/b*b*k;
    cout<<ans;
    return 0;
}

I. Range Closest Pair of Points Query


J. Dice Game


K. Maximum GCD

对于每个数,我们可以将它替换成 \([1,\lfloor\frac{x-1}{2}\rfloor]\) 中的任何一个数。

\(a_i\) 的最小值为 \(d\),如果每个数都能替换成这个数或者所有数都被 \(d\) 整除,答案即为 \(d\),否则答案为 \(\frac{d}{2}\)


#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005;
int n;
int a[N];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    int v=*min_element(a+1,a+n+1);
    bool f1=true,f2=true;
    for(int i=1;i<=n;i++)
    {
        if(a[i]%v!=0) f1=false;
        if(a[i]!=v&&(a[i]-1)/2<v) f2=false;
    }
    if(f1||f2) cout<<v;
    else cout<<v/2;
    return 0;
}

L. Permutation Compression

可以发现,最优策略肯定是从大到小删,线段树上二分找每个数左边和右边第一个比他大的数,将所有 \(l_i\) 放进一个 std::multiset 里,找到第一个比当前区间大的操作使用就可以了。操作完就把这个位置设成 \(-\infty\)


#include<iostream>
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
const int N=200005;
const int INF=1061109567;
int n,m,k;
int a[N],b[N];
int l[N];
int pos[N];
bool vis[N];
struct Segment_Tree
{
    #define ls i*2
    #define rs i*2+1
    struct Node
    {
        int l,r;
        int mx,sum;
    }tree[N*4];
    void push_up(int i)
    {
        tree[i].mx=max(tree[ls].mx,tree[rs].mx);
        tree[i].sum=tree[ls].sum+tree[rs].sum;
        return;
    }
    void build(int i,int l,int r)
    {
        tree[i].l=l,tree[i].r=r;
        tree[i].mx=0;
        if(l==r)
        {
            tree[i].mx=a[l];
            tree[i].sum=1;
            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,int w)
    {
        if(tree[i].l==tree[i].r)
        {
            tree[i].mx=v;
            tree[i].sum=w;
            return;
        }
        if(u<=tree[ls].r) modify(ls,u,v,w);
        else modify(rs,u,v,w);
        push_up(i);
        return; 
    }
    int querypre(int i,int u,int v)
    {
        if(tree[i].mx<=v) return 0;
        if(tree[i].l==tree[i].r) return tree[i].l;
        if(u<=tree[ls].r) return querypre(ls,u,v);
        else
        {
            if(tree[rs].mx<=v) return querypre(ls,u,v);
            int lst=querypre(rs,u,v);
            if(lst!=0) return lst;
            else return querypre(ls,u,v);
        }
    }
    int querysuf(int i,int u,int v)
    {
        if(tree[i].mx<=v) return n+1;
        if(tree[i].l==tree[i].r) return tree[i].l;
        if(u>=tree[rs].l) return querysuf(rs,u,v);
        else
        {
            if(tree[ls].mx<=v) return querysuf(rs,u,v);
            int lst=querysuf(ls,u,v);
            if(lst!=n+1) return lst;
            else return querysuf(rs,u,v);
        }
    }
    int query(int i,int l,int r)
    {
        if(l<=tree[i].l&&tree[i].r<=r) return tree[i].sum;
        int res=0;
        if(l<=tree[ls].r) res+=query(ls,l,r);
        if(r>=tree[rs].l) res+=query(rs,l,r);
        return res;
    }
    #undef ls
    #undef rs 
}T;
void solve()
{
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=m;i++)
        cin>>b[i];
    for(int i=1;i<=k;i++)
        cin>>l[i];
    for(int i=1;i<=n;i++)
        pos[a[i]]=i,vis[i]=false;
    for(int i=1;i<=m;i++)
        vis[b[i]]=true;
    for(int i=1;i<=m;i++)
        if(pos[b[i]]<pos[b[i-1]])
        {
            cout<<"NO\n";
            return;
        }
    multiset<int,greater<int>>s;
    for(int i=1;i<=k;i++)
        s.insert(l[i]);
    T.build(1,1,n);
    for(int i=1;i<=n;i++)
        if(vis[a[i]]) T.modify(1,i,a[i],1);
    for(int i=n;i>=1;i--)
        if(!vis[i])
        {
            int p=pos[i];
            int l=T.querypre(1,p,i),r=T.querysuf(1,p,i);
            int sum=T.query(1,l+1,r-1);
            auto it=s.lower_bound(sum);
            if(it==s.end())
            {
                cout<<"NO\n";
                return;
            }
            s.erase(it);
            T.modify(1,p,-INF,0);
        }
    cout<<"YES\n";
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    int T;
    cin>>T;
    while(T--)
        solve();
    return 0;
}