2019-2020 ICPC Asia Hong Kong Regional Contest 部分题解

Problem A. Axis of Symmetry

留坑。

 

Problem B. Binary Tree

因为每次取出的满二叉树的节点个数是奇数,所以n是奇数等价于先手必赢。

#include<bits/stdc++.h>
using namespace std ;
int main()
{
    std::ios::sync_with_stdio(false) , cin.tie(0) ;
    int T ;
    cin >> T ;
    while(T --)
    {
        int n ;
        cin >> n ;
        for(int i = 0 ; i < n - 1 ; i ++)
        {
            int x , y ;
            cin >> x >> y ;
        }
        if(n % 2 == 1)  cout << "Alice\n" ;
        else  cout << "Bob\n" ;
    }
    return 0 ;
}

 

Problem C. Constructing Ranches

多边形的最长边的二倍大于多边形的周长。点分治维护即可。

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define fi first
#define se second
using namespace std;
using namespace __gnu_pbds;
using ll = long long;
using pil = pair<int, ll>;
using pli = pair<ll, int>;
template<class T> using ordered_set = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
const int N = 2e5 + 5;
int n, a[N], tot;
int rt, sz[N], dp[N], mxsz;
pil mes[N];
ll ans;
vector<int> G[N];
bool vis[N];
void findroot(int u, int fa) 
{
    dp[u] = 0, sz[u] = 1;
    for(int v : G[u])
    {
        if(vis[v]||v==fa) continue;
        findroot(v, u);
        dp[u] = max(dp[u], sz[v]);
        sz[u] += sz[v];
    }
    dp[u] = max(dp[u], mxsz-sz[u]);
    if(dp[u]<dp[rt]) rt = u;
}
void getmes(int u, int fa, pil d)
{
    pil nd = {max(a[u], d.fi), a[u]+d.se};
    mes[++tot] = nd;
    for(int v : G[u])
    {
        if(vis[v]||v==fa) continue;
        getmes(v, u, nd);
    }
}
ll calcu(int u, pil d, int ext)
{
    ll ans = 0;
    tot = 0;
    getmes(u, 0, d);
    sort(mes+1, mes+tot+1);
    ordered_set<pli> tr;
    for(int i=1; i<=tot; i++)
    {
        ans += i - 1 - tr.order_of_key({mes[i].fi*2-mes[i].se+1+ext, 0});
        tr.insert({mes[i].se, i});
    }
    return ans; 
}
void Divide(int u) 
{
    vis[u] = 1;
    ans += calcu(u, {0, 0}, a[u]); 
    for(int v : G[u])
    {
        if(vis[v]) continue;
        ans -= calcu(v, {a[u], a[u]}, a[u]); 
        mxsz = sz[v], rt = 0;
        findroot(v, u);
        Divide(rt);
    }
}
void solve()
{
    scanf("%d", &n);
    for(int i=1; i<=n; i++) scanf("%d", a+i), G[i].clear(), vis[i] = 0;
    for(int i=1; i<n; i++)
    {
        int u, v; scanf("%d%d", &u, &v);
        G[u].push_back(v), G[v].push_back(u);
    }
    rt = 0, ans = 0;
    dp[0] = mxsz = n;
    findroot(1, 0);
    Divide(rt);
    printf("%lld\n", ans);
}
int main()
{
    int _; scanf("%d", &_);
    while(_--) solve();
    return 0;
}

 

Problem D. Defining Labels

先固定答案的长度,再从高位到低位枚举每一位的数值。

#include<bits/stdc++.h>
using namespace std ;
int main()
{
    //std::ios::sync_with_stdio(false) , cin.tie(0) ;
    int T ;
    cin >> T ;
    while(T --)
    {
        int k ;
        long long x ;
        cin >> k >> x ;
        int d = 10 - k ;
        int t = 9 - d + 1 ;
        int len = 1 ;
        long long res = 1 ;
        //long long tk = k ;
        while(1)
        {
            res *= t ;
            //cout << k << ' ' << res << '\n' ;
            if(x - res > 0)  x -= res , len ++ ;
            else  break ;
        }
        //cout << len << '\n' ;
        res = 1 ;
        for(int i = 1 ; i <= len ; i ++)  res *= t ;
        //cout << d << '\n' ;
        for(int i = 1 ; i <= len ; i ++)
        {
            res /= t ;
            for(int j = d ; j <= 9 ; j ++)
            {
                //cout << res << '\n' ;
                if(x > res)  x -= res ;
                else
                {
                    cout << j ;
                    break ;
                }
            }
        }
        cout << '\n' ;
    }
    return 0 ;
}

 

Problem E. Erasing Numbers

枚举判断第i个数能不能保留,大于p[i]的数看作1,小于p[i]的数看作0。能保留第i个数的局面是0,1个数相等。

首先发现跨越p[i]并保留p[i]的删除操作不能改变0,1个数的相对大小。那就可以把p[i]左右两边分开考虑。

可以发现如果一个子序列0,1的个数的差值大于等于3,那就可以使这个差值减小2。所以扫一遍就好了,具体实现看代码。

#include<bits/stdc++.h>
using namespace std ;
int main()
{
    std::ios::sync_with_stdio(false) , cin.tie(0) ;
    int T ;
    cin >> T ;
    while(T --)
    {
        int n ;
        cin >> n ;
        vector<int> p(n + 1) ;
        for(int i = 1 ; i <= n ; i ++)  cin >> p[i] ;
        function<int(int)> check = [&](int id)
        {
            int cnt0 = 0 , cnt1 = 0 ;
            vector<int> b(n + 1) ;
            for(int i = 1 ; i <= n ; i ++)
                if(p[i] < p[id])  b[i] = 0 , cnt0 ++ ;
                else if(p[i] > p[id])  b[i] = 1 , cnt1 ++ ;
            int res = abs(cnt0 - cnt1) ;
            if(res == 0)  return 1 ;
            int cnt = 0 ;
            for(int i = 1 ; i <= id - 1 ; i ++)
            {
                if(cnt0 > cnt1)
                {
                    if(b[i] == 0)  cnt ++ ;
                    else  cnt -- ;
                    cnt = max(cnt , 0) ;
                    if(cnt == 3)  cnt -= 2 , res -= 2 ;
                }
                else
                {
                    if(b[i] == 1)  cnt ++ ;
                    else  cnt -- ;
                    cnt = max(cnt , 0) ;
                    if(cnt == 3)  cnt -= 2 , res -= 2 ;
                }
            }
            cnt = 0 ;
            for(int i = id + 1 ; i <= n ; i ++)
            {
                if(cnt0 > cnt1)
                {
                    if(b[i] == 0)  cnt ++ ;
                    else  cnt -- ;
                    cnt = max(cnt , 0) ;
                    if(cnt == 3)  cnt -= 2 , res -= 2 ;
                }
                else
                {
                    if(b[i] == 1)  cnt ++ ;
                    else  cnt -- ;
                    cnt = max(cnt , 0) ;
                    if(cnt == 3)  cnt -= 2 , res -= 2 ;
                }
            }
            if(res <= 0)  return 1 ;
            else return 0 ;
        } ;
        for(int i = 1 ; i <= n ; i ++)  cout << check(i) ;
        cout << '\n' ;
    }
    return 0 ;
}

 

Problem F. Falling Objects

留坑。

 

Problem G. Game Design

考虑用二叉树构造。

f(u)表示以u为根的二叉树的方案数,l,ru的左右儿子,容易发现f(u)=f(l)*f(r)+1

考虑从根节点逐步到叶子节点构造。

当前k是奇数,左儿子的方案数设置为2,右儿子的方案数设置为\left \lfloor \frac{k}{2} \right \rfloor

当前k是偶数,左儿子的方案数设置为1,右儿子的方案数设置为k-1,这样就转成了奇数做。

根节点初始权值设置为2^{29},每个儿子的权值是父亲的一半。假如当前权值是1,儿子不再具有权值了,那么直接挂一个长度是k的链就好了。

#include<bits/stdc++.h>
using namespace std ;
const int maxn = 1e5 + 10 ;
int cnt = 0 ;
int fa[maxn] ;
int c[maxn] ;
void dfs(int u , int val , int k)
{
    c[u] = val ;
    if(val == 1)
    {
        for(int i = 1 ; i <= k - 1 ; i ++)
        {
            cnt ++ ;
            fa[cnt] = cnt - 1 ;
            c[cnt] = 1 ;
        }
        return ;
    }
    if(k == 1)  return ;
    if(k % 2 == 1)
    {
        cnt ++ ;
        fa[cnt] = u ;
        dfs(cnt , val / 2 , 2) ;
        cnt ++ ;
        fa[cnt] = u ;
        dfs(cnt , val / 2 , k / 2) ;
    }
    else
    {
        if(k == 2)
        {
            cnt ++ ;
            fa[cnt] = u ;
            dfs(cnt , val / 2 , 1) ;
            cnt ++ ;
            fa[cnt] = u ;
            dfs(cnt , val / 2 , 1) ;
        }
        else
        {
            cnt ++ ;
            fa[cnt] = u ;
            dfs(cnt , val / 2 , 1) ;
            cnt ++ ;
            fa[cnt] = u ;
            dfs(cnt , val / 2 , k - 1) ;
        }
    }
}
int main()
{
    std::ios::sync_with_stdio(false) , cin.tie(0) ;
    int k ;
    cin >> k ;
    if(k == 1)
    {
        cout << "2\n" ;
        cout << "1\n" ;
        cout << "2 1\n" ;
    }
    else if(k == 2)
    {
        cout << "2\n" ;
        cout << "1\n" ;
        cout << "1 1\n" ;
    }
    else
    {
        cnt = 1 ;
        dfs(1 , 1 << 29 , k) ;
        cout << cnt << '\n' ;
        for(int i = 2 ; i <= cnt ; i ++)  cout << fa[i] << " \n"[i == cnt] ;
        for(int i = 1 ; i <= cnt ; i ++)  cout << c[i] << " \n"[i == cnt] ;
    }
    return 0 ;
}

check

Problem H. Hold the Line

直接的做法是线段树套set,常数巨大过不去。

然后考虑通过离线优化常数。具体实现是线段树套单调栈。

离线时候对于修改和询问按照输入的顺序都打上时间戳v

修改操作记为(x,h1,v1),表示在时间戳为vx位置放上高度是h1的物品。

询问操作记为(l,r,h,v),表示在时间戳为v,询问区间是[l,r],询问高度是h

对于修改和询问,按照右端点r升序排列。

对于询问(l,r,h,v),需要找到满足x\geqslant l,v1<v且最接近h的修改操作。

考虑以h为下标建造权值线段树,最接近可以分成两半做,[1,h]最靠右的满足要求的操作的权值,[h+1,10^9]最靠左的满足要求的操作的权值。

在权值线段树上二分至叶子节点即可找到答案,下面分析如何check当前节点是否存在符合要求的操作。

对于当前权值范围是[a,b]的节点,每个节点存一个二元组(v1,x),因为每个节点判断的是合法性,不考虑是否最优,所以省去了权值h,合法性持续至叶子节点即可找到最优权值。

下面分析如何判断合法性,满足x\geqslant l,v1<v,分析对于两个二元组(x_t,v1_t)。如果x_s<x_t,v1_s>v1_t,那么(x_s,v1_s)没有存在的必要,可以被(x_t,v1_t)覆盖。

因此单调栈性质很明显了,对于每个节点维护一个随着x递增,v1递增的单调栈即可。

对于询问(l,v)可以在单调栈上二分判定是否存在满足x\geqslant l,v1<v的二元组。

正确性保证:如果一个节点合法,那么它的所有祖先节点都合法。

这题真难,写了4个小时。

#include<bits/stdc++.h>
using namespace std ;
const int maxn = 1e6 + 10 ;
vector<pair<int , int>> p[maxn << 2] ;
int lson(int x)
{
    return x << 1 ;
}
int rson(int x)
{
    return x << 1 | 1 ;
}
void build(int id , int l , int r)
{
    p[id].clear() ;
    if(l == r)  return ;
    int mid = (l + r) / 2 ;
    build(lson(id) , l , mid) ;
    build(rson(id) , mid + 1 , r) ;
}
void push(int id , int h , int pos , int v)
{
    while(p[id].size() > 0 && p[id].back().second > v)  p[id].pop_back() ;
    p[id].push_back({pos , v}) ;
}
void update(int id , int l , int r , int h , int pos , int v)
{
    push(id , h , pos , v) ;
    if(l == r)  
    {
        //cout << v << ' ' << h << ' ' << l << ' ' << r << ' ' << p[id].size() << '\n' ;
        return ;
    }
    int mid = (l + r) / 2 ;
    if(h <= mid)  update(lson(id) , l , mid , h , pos , v) ;
    else  update(rson(id) , mid + 1 , r , h , pos , v) ;
    //cout << v << ' ' << h << ' ' << l << ' ' << r << ' ' << p[id].size() << '\n' ;
}
bool ok(int id , int L , int id2)
{
    pair<int , int> pp = {L , 0} ;
    //cout << "??? " << id << ' ' << L << ' ' << id2 << '\n' ;
    //cout << "!!! " << p[id][0].first << ' ' << p[id][0].second << '\n' ;
    auto it = lower_bound(p[id].begin() , p[id].end() , pp) ;
    //cout << "!!! " << (*it).second << '\n' ;
    if(it != p[id].end())  return (*it).second < id2 ;
    else  return false ;
}
int query_r(int id , int l , int r , int x , int y , int L , int id2)
{
    int mid = (l + r) / 2 ;
    int res = -1 ;
    if(r < x)  return -1 ;
    if(y < l)  return -1 ;
    if(l == r)
    {
        if(ok(id , L , id2))  return l ;
        else  return -1 ;
    }
    if(x <= l && r <= y && !ok(id , L , id2))  return -1 ;
    res = max(res , query_r(rson(id) , mid + 1 , r , x , y , L , id2)) ;
    if(res == -1)  res = max(res , query_r(lson(id) , l , mid , x , y , L , id2)) ;
    return res ;
}
int query_l(int id , int l , int r , int x , int y , int L , int id2)
{
    int mid = (l + r) / 2 ;
    int res = 1e9 + 1e8 ;
    //cout << "*** " << id2 << '\n' ;
    if(r < x)  return 1e9 + 1e8 ;
    if(y < l)  return 1e9 + 1e8 ;
    if(l == r)
    {
        if(ok(id , L , id2))  return l ;
        else  return 1e9 + 1e8 ;
    }
    if(x <= l && r <= y && !ok(id , L , id2))  return 1e9 + 1e8 ;
    res = min(res , query_l(lson(id) , l , mid , x , y , L , id2)) ;
    if(res > 1e9 + 1e7)  res = min(res , query_l(rson(id) , mid + 1 , r , x , y , L , id2)) ;
    return res ;
}
int main()
{
    std::ios::sync_with_stdio(false) , cin.tie(0) ;
    int T ;
    cin >> T ;
    while(T --)
    {
        int n , m ;
        cin >> n >> m ;
        vector<vector<pair<int , int>>> v1(n + 1) ; //change
        vector<vector<array<int , 3>>> v2(n + 1) ; //query
        vector<int> val ;
        for(int i = 1 ; i <= m ; i ++)
        {
            int op ;
            cin >> op ;
            if(op == 0)
            {
                int x , h ;
                cin >> x >> h ;
                v1[x].push_back({h , i}) ;
                val.push_back(h) ;
            }
            else
            {
                int l , r , h ;
                cin >> l >> r >> h ;
                v2[r].push_back({l , h , i}) ;
                val.push_back(h) ;
            }
        }
        sort(val.begin() , val.end()) ;
        val.erase(unique(val.begin() , val.end()) , val.end()) ;
        int siz = val.size() ;
        build(1 , 0 , siz - 1) ;
        function<int(int , int , int)> ask = [&](int l , int id , int h)
        {
            int t1 = query_r(1 , 0 , siz - 1 , 0 , h , l , id) ; //right first
            int t2 = query_l(1 , 0 , siz - 1 , h + 1 , siz - 1 , l , id) ; //left first
            //cout << t1 << ' ' << t2 << '\n' ;
            int res = 1e9 + 1e8 ;
            //cout << "^^^ " << id << '\n' ;
            if(t1 >= 0)  res = min(res , val[h] - val[t1]) ;
            if(t2 < 1e9 + 1e7)  res = min(res , val[t2] - val[h]) ;
            if(res < 1e9 + 1e7)  return res ;
            else  return -1 ;
        } ;
        vector<pair<int , int>> ans ;
        for(int i = 1 ; i <= n ; i ++)
        {
            for(auto u : v1[i])
            {
                int h = lower_bound(val.begin() , val.end() , u.first) - val.begin() ;
                int v = u.second ;
                int pos = i ;
                //cout << "$$$ " << h << ' ' << pos << ' ' << v << '\n' ;
                update(1 , 0 , siz - 1 , h , pos , v) ;
            }
            for(auto u : v2[i])
            {
                int l = u[0] ;
                int h = u[1] ;
                int id = u[2] ;
                //cout << "^^^ " << id << '\n' ;
                ans.push_back({id , ask(l , id , lower_bound(val.begin() , val.end() , h) - val.begin())}) ;
            }
        }
        sort(ans.begin() , ans.end()) ;
        for(auto u : ans)  cout << u.second << '\n' ;
    }
    return 0 ;
}

 

Problem I. Incoming Asteroids

每个人的每个观测点全部设置一个阈值\left \lceil \frac{y}{k} \right \rceil,假如某个观测点超过阈值,我就把这个人对应的每个观测点的阈值都更新。

具体实现可以用优先队列或者set维护。set压着时限过的,建议优先队列,不过我更喜欢用set

#include<bits/stdc++.h>
using namespace std ;
const int maxn = 2e5 + 10 ;
set<array<long long , 3>> s[maxn] ;
long long tag[maxn] ;
int main()
{
    std::ios::sync_with_stdio(false) , cin.tie(0) ;
    int n , m ;
    cin >> n >> m ;
    int id = 0 ;
    vector<vector<pair<long long , int>>> p(m + 1) ;
    vector<int> val(m + 1 , 0) ;
    int lst = 0 ;
    while(m --)
    {
        int op ;
        cin >> op ;
        if(op == 1)
        {
            int y , k ;
            cin >> y >> k ;
            y ^= lst ;
            id ++ ;
            for(int i = 0 ; i < k ; i ++)
            {
                int x ;
                cin >> x ;
                x ^= lst ;
                p[id].push_back({(y + k - 1) / k + tag[x] , x}) ;
                val[id] = y ;
                s[x].insert({(y + k - 1) / k + tag[x] , id , tag[x]}) ;
            }
        }
        else
        {
            int x , y ;
            cin >> x >> y ;
            x ^= lst ;
            y ^= lst ;
            tag[x] += y ;
            vector<int> ans ;
            while(!s[x].empty())
            {
                auto it = s[x].begin() ;
                long long t = (*it)[0] ;
                int id = (*it)[1] ;
                //cout << t << ' ' << tag[x] << '\n' ;
                if(t <= tag[x])
                {
                    long long sum = 0 ;
                    for(auto u : p[id])
                    {
                        long long val2 = u.first ;
                        int id2 = u.second ;
                        auto it2 = s[id2].lower_bound({val2 , id , 0}) ;
                        sum += tag[id2] - (*it2)[2] ;
                        s[id2].erase(it2) ;
                    }
                    // cout << sum << '\n' ;
                    if(val[id] <= sum)  ans.push_back(id) ;
                    else
                    {
                        val[id] -= sum ;
                        vector<pair<long long , int>> tmp ;
                        int k = p[id].size() ;
                        for(auto u : p[id])
                        {
                            int id2 = u.second ;
                            s[id2].insert({tag[id2] + (val[id] + k - 1) / k , id , tag[id2]}) ;
                            tmp.push_back({tag[id2] + (val[id] + k - 1) / k , id2}) ;
                        }
                        swap(tmp , p[id]) ;
                    }
                }
                else  break ;
            }
            lst = ans.size() ;
            sort(ans.begin() , ans.end()) ;
            cout << ans.size() ;
            for(auto u : ans)  cout << ' ' << u ;
            cout << '\n' ;
        }
    }
    
    return 0 ;
}

 

Problem J. Junior Mathematician

显然有一个N*61*61*61的做法,只需要记录前缀%m的值,前缀的f(x)%m的值,以及前缀各个数字和%m的值(用于递推f(x)),容易发现可以只记录第一维和第二维的差值

#include <bits/stdc++.h>
using namespace std;
const int N = 5005, mod = 1e9 + 7;
char L[N], R[N];
int t[N];
int m, dp[N][61][61], dm[N];
inline void Add(int &x, int y) { x += y; if(x>=mod) x -= mod; }
int DP(int b, int lim, int x, int y)
{
    if(b==-1) return !x;
    int &v = dp[b][x][y];
    if(~v && !lim) return v;
    int ans = 0, up = (lim ? t[b] : 9);
    for(int i=0; i<=up; i++)
    {
        int nx = (x + i*dm[b] - i*y)%m;
        nx = (nx + m)%m;
        int ny = (y + i)%m;
        Add(ans, DP(b-1, lim&&i==up, nx, ny));
    }
    if(!lim) v = ans;
    return ans;
}
int cal(char *s, int n)
{
    reverse(s, s+n);
    for(int i=0; i<n; i++) t[i] = s[i] - '0';
    return DP(n-1, 1, 0, 0);
}
bool check(char *s, int n)
{
    int a = 0, b = 0;
    for(int i=n-1; i>=0; i--)
    {
        int d = s[i] - '0';
        a = (a + d*dm[i] - d*b);
        a = (a + m)%m;
        b = (b + d)%m;
    }
    return !a;
}
void solve()
{
    scanf("%s%s", L, R);
    scanf("%d", &m);
    int up = strlen(R);
    dm[0] = 1;
    for(int i=1; i<up; i++) dm[i] = dm[i-1]*10%m;
    for(int i=0; i<up; i++) 
        for(int j=0; j<m; j++) memset(dp[i][j], -1, m<<2);
    int ans = (cal(R, strlen(R)) - cal(L, strlen(L)) + check(L, strlen(L)) + mod)%mod;
    printf("%d\n", ans);
}
int main()
{
    int _; scanf("%d", &_);
    while(_--) solve();
    return 0;
}

 

Problem K. Key Project

模拟费用流

可以O(n)的模拟每次找费用最小的增广路经,并更新反向边的流量

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值