The 2022 ICPC Asia Nanjing Regional Contest(2022 南京 ABDEGIM)

Problem I. 完美回文
思路:
由题意得,肯定是都变成一个字符才满足
暴力枚举 a − z a-z az m i n min min
code

void solve(){
    string s;
    cin >> s;
    int ans = INF;
    for(char i = 'a'; i <= 'z'; i ++){
        int res = 0;
        for(auto j : s){
            if(j != i)
                res ++;
        }
        ans = min(res, ans);
    }
    cout << ans << endl;
}

Problem G. 邪恶铭刻
思路:
显然,对于op1和op2,执行op2的优先级肯定是最高的
所以按照这个思路去贪心,sum表示攻击力总和,cnt表示怪兽数,初始都为1
1、操作1的时候
s u m + + , c n t + + sum++,cnt++ sum++cnt++

2、操作3的时候计数 k + + k ++ k++
①如果有两只及以上的怪兽肯定是优先合并的
②否则 s u m + + , c n t + + sum++,cnt++ sum++cnt++

3、操作2的时候
遵从能合并就合并,合并不了就把操作3反悔一次 k − − k-- k
如果还是不能合并的话,最终答案就是-1

code

const int N = 1e6 + 10;
int sum, cnt, k, st;
int s[N];
int n;
 
void solve(){
    cin >> n;
    sum = cnt = 1;
    k = st = 0;
    rep(i,1,n){
        int x; cin >> x;
        if(x == 1) sum ++, cnt ++;
        else if(x == 0){
            if(cnt > 1) k ++, cnt --;
            else sum ++, cnt ++;
        }
        else{
            if(cnt > 1) cnt --;
            else if(k >= 1) sum ++, cnt ++, k --;
            else st = 1;
        }
    }
 
    if(st) cout << "-1" << endl;
    else{
        cout << sum / __gcd(sum, cnt) << ' ' << cnt / __gcd(sum, cnt) << endl;
    }
}

Problem A. 停停,昨日请不要再重现
思路:
首先,我们假设固定洞不动,去移动整个矩形,那么整个矩形A最大移动范围是 2 ∗ n 和 2 ∗ m 2*n 和2 * m 2n2m设为矩形B
首先我们可以确定图形的移动方式,那么我们反向模拟洞的移动
U:上边界下移最大的行坐标
D:下边界上移最小的行坐标
L:左边界右移最大的列坐标
R:有边界左移最小的列坐标

根据上述定义,只有 ( D − U + 1 ) ∗ ( R − L + 1 ) (D-U+1)*(R-L+1) (DU+1)(RL+1)范围内的袋鼠会被保留
1、U > D或者R < L
如果 k k k 为0,那么每个点都可以是,所以答案是&n*m&
否则,每个点都不可能是,答案是0

2、如果 ( D − U + 1 ) ∗ ( R − L + 1 ) (D-U+1)*(R-L+1) (DU+1)(RL+1)小于等于 k k k那么必定哪个点都不会是
答案为0

3、特判的结束了,接下来是正题
接下来模拟洞的移动,我们假定洞在矩形B的正中央,记为(n+1,m+1),然后模拟图形移动,因为每个点只能计数一次,所以 f [ x ] [ y ] f[x][y] f[x][y]表示这个点是否有洞经过
然后做一个 f [ x ] [ y ] f[x][y] f[x][y]的前缀和 f [ i ] [ j ] + = f [ i − 1 ] [ j ] + f [ i ] [ j − 1 ] − f [ i − 1 ] [ j − 1 ] f[i][j] += f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] f[i][j]+=f[i1][j]+f[i][j1]f[i1][j1]
假设枚举到(i,j),那么这个矩形中的袋鼠个数为 f [ x + D ] [ y + R ] − f [ U + x − 1 ] [ R + y ] − f [ D + x ] [ L + y − 1 ] + f [ U + x − 1 ] [ L + y − 1 ] f[x + D][y + R] - f[U + x - 1][R + y] - f[D + x][L + y - 1] + f[U + x - 1][L + y - 1] f[x+D][y+R]f[U+x1][R+y]f[D+x][L+y1]+f[U+x1][L+y1]
如果这个数字等于k,答案+1

code

#define rep(a,b,c) for(int a=b;a<=c;a++)
const int N = 2e3 + 10;
int f[N][N];
string s;
int n, m, k;
 
void solve(){
    cin >> n >> m >> k >> s;
    rep(i, 0, 2 * n + 2)
        rep(j, 0, 2 * m + 2)
            f[i][j] = 0;
    int len = sz(s);
    s = " " + s;
 
    int U, D, L, R;
    U = 1, D = n, L = 1, R = m;
    for(int i = 1, u = 1, d = n, l = 1, r = m; i <= len; i ++){
        if(s[i] == 'U') u --, d --;
        else if(s[i] == 'R') l ++, r ++;
        else if(s[i] == 'L') l --, r --;
        else u ++, d ++;
        U = max(U, u), D = min(D, d), R = min(R, r), L = max(L, l);
    }
 
    if(U > D || R < L){
        if(k == 0) cout << n * m << endl;
        else cout << 0 << endl;
        return;
    }
 
    k = (D - U + 1) * (R - L + 1) - k;    
    if(k < 0){
        cout << 0 << endl;
        return;
    }
 
    for(int i = 1, x = n + 1, y = m + 1; i <= len; i ++){
        if(s[i] == 'U') x --;
        else if(s[i] == 'R') y ++;
        else if(s[i] == 'L') y --;
        else x ++;
        f[x][y] = 1;
    }
    f[n + 1][m + 1] = 1;
    rep(i,1,2 * n + 1)
        rep(j,1,2 * m + 1)
            f[i][j] += f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1];
 
    int ans = 0;
    rep(i,1,n)
        rep(j,1,m){
            int x = n + 1 - i, y = m + 1 - j;
            int num = f[x + D][y + R] - f[U + x - 1][R + y] - f[D + x][L + y - 1] + f[U + x - 1][L + y - 1];
 
            if(num == k)
                ans ++;
        }
 
    cout << ans << endl;
}

Problem D. 聊天程序
思路:
二分第 k k k 大的最大值,假设这个值是 x x x
那么就分为,大于x的部分和小于x的部分,当大于x的个数为k个的时候,那么这个数就是第k大
按照这个思路
我们已经知道x了,然后枚举原序列a
1、 a i > = x a_i>=x ai>=x的时候 s i + 1 s_i +1 si+1
2、如果 a i a_i ai加上等差数列的最大值值变成 > = x >=x >=x 的状态的话,那么就必定有一个 j j j,使得 a i a_i ai加上当前等差数列的值恰好 > x >x >x,设这个等差数列的长度为cnt,然后这样就相当于在 m a x ( 1 , i − m + 1 ) max(1, i - m + 1) max(1,im+1) m i n ( i , i − c n t ) + 1 min(i, i - cnt) + 1 min(i,icnt)+1的位置上, i i i 提供价值为1的贡献

code

const int N = 2e5 + 10;
ll a[N];
ll n, m, k, c, d;
 
int check(ll x){
    vector<int> s(n + 1, 0);
    for(ll i = 1; i <= n; i ++){
        if(a[i] >= x)
            s[1] ++;
        else if(a[i] + c + min(m - 1, i - 1) * d >= x){
            s[max(1ll, i - m + 1)] ++;
            ll cnt = 0;
            if(d != 0)
                cnt = (x - a[i] - c + d - 1) / d;
            s[min(i, i - cnt) + 1] --;
        }
    }
    for(int i = 1; i <= n - m + 1; i ++){
        s[i] += s[i - 1];
        if(s[i] >= k)
            return 1;
    }
    return 0;
}
 
void solve(){
    cin >> n >> k >> m >> c >> d;
    for(int i = 1; i <= n; i ++)
        cin >> a[i];
    ll l = 0, r = 1e17;
    while(l < r){
        ll mid = l + r >> 1;
        if(check(mid))
            l = mid + 1;
        else  r = mid;
    }
    cout << r - 1 << endl;
}

Problem M. 清空水箱
思路:
因为是逆时针给的点坐标,所以可以相连连成一个多边形
1、首先是第一个状态
在这里插入图片描述

首先只有①是合法的状态,②④状态可以用to_left测试判断,③状态可以用向量 v 1 v_1 v1的y值判断(y需要为负值)

2、然后是第二个状态
在这里插入图片描述
首先①和②都是合法的,而且平台只视为一个点,其余的都是非法的
平台的判定,就是可以假设以平台最右边的点为起点,然后枚举所有的点直到不在一个平面上,然后判断是不是向上的一个向量

code

using point_t=long double;  //全局数据类型,可修改为 long long 等
 
constexpr point_t eps=1e-8;
#define PI acos(-1)
 
// 点与向量
template<typename T> struct point
{
    T x,y;
 
    bool operator==(const point &a) const {return (abs(x-a.x)<=eps && abs(y-a.y)<=eps);}
    bool operator<(const point &a) const {if (abs(x-a.x)<=eps) return y<a.y-eps; return x<a.x-eps;}
    bool operator>(const point &a) const {return !(*this<a || *this==a);}
    point operator+(const point &a) const {return {x+a.x,y+a.y};}
    point operator-(const point &a) const {return {x-a.x,y-a.y};}
    point operator-() const {return {-x,-y};}
    point operator*(const T k) const {return {k*x,k*y};}
    point operator/(const T k) const {return {x/k,y/k};}
    T operator*(const point &a) const {return x*a.x+y*a.y;}  // 点积
    T operator^(const point &a) const {return x*a.y-y*a.x;}  // 叉积,注意优先级
    int toleft(const point &a) const {const auto t=(*this)^a; return (t>eps)-(t<-eps);}  // to-left 测试, a是向量,1是左
    T len2() const {return (*this)*(*this);}  // 向量长度的平方
    T dis2(const point &a) const {return (a-(*this)).len2();}  // 两点距离的平方
 
    // 涉及浮点数
    long double len() const {return sqrtl(len2());}  // 向量长度
    long double dis(const point &a) const {return sqrtl(dis2(a));}  // 两点距离
    long double ang(const point &a) const {return acosl(max(-1.0l,min(1.0l,((*this)*a)/(len()*a.len()))));}  // 向量夹角
    point rot(const long double rad) const {return {x*cos(rad)-y*sin(rad),x*sin(rad)+y*cos(rad)};}  // 逆时针旋转(给定角度)
    point rot(const long double cosr,const long double sinr) const {return {x*cosr-y*sinr,x*sinr+y*cosr};}  // 逆时针旋转(给定角度的正弦与余弦)
};
 
using Point=point<point_t>;
 
const int N = 2e3 + 10;
int n;
Point a[N];
 
void solve(){
    cin >> n;
    for(int i = 0; i < n; i ++)
        cin >> a[i].x >> a[i].y;
    int ans = 0;
    for(int i = 0; i < n; i ++){
        Point x = a[(i - 1 + n) % n], y = a[i], z = a[(i + 1) % n];
        Point P = y;
        Point v1 = y - x, v2 = z - y;
        if(v1.y < 0 && v2.y > 0 && v1.toleft(v2) == 1)
            ans ++;
        else if(v1.y < 0 && v2.y == 0){
            int j = i;
            while((++ j %= n) != i && (a[j] - a[(j - 1 + n) % n]).y == 0 && (a[(j + 1) % n] - a[j]).y == 0){
                x = a[(j - 1 + n) % n], y = a[j], z = a[(j + 1) % n];
                v2 = z - y;
            }
            if(j != i && (a[(j + 1) % n] - a[j]).y > 0 && (a[j] - P).x > 0)
                ans ++;
        }
    }
    cout << ans << endl;
}   

Problem B. 索道
思路:
首先可以想到dp,然后想到,我们需要从前缀中代价最小的点转移过来
这个就很容易想到单调队列优化
但是我们发现,在n+1的位置同样有一个点可以对答案有影响
所以同理做一个后缀dp

修改的话,首先如果当前点必选,直接计算就行了,输入 x x x r e s res res a n s = p r e [ x ] + s u f [ x ] − 2 ∗ a [ x ] + r e s ; ans = pre[x] + suf[x] - 2 * a[x] + res; ans=pre[x]+suf[x]2a[x]+res;
可以确定一个点的修改,前后最大的影响半径是 k k k,所以,我们可以重新选择中间点,这样的话就是当前点向前枚举到 i i i n u m [ i ] num[i] num[i] 表示从 x 到 i x到i xi的最小前缀,然后再向后枚举 i i i,答案就是 a n s = m i n ( a n s , s u f [ i ] + n u m [ m a x ( p o s , i − k ) ] ) ; ans = min(ans, suf[i] + num[max(pos, i - k)]); ans=min(ans,suf[i]+num[max(pos,ik)]);

code

const int N = 5e5 + 10;
ll a[N];
int n, k, q;
ll pre[N], suf[N];
int st[N];
ll num[N];
 
void solve(){ 
    memset(num, 0x3f, sizeof num);
    memset(pre, 0, sizeof pre);
    memset(suf, 0, sizeof suf);
    memset(st, 0, sizeof st);
    cin >> n >> k;
    for(int i = 1; i <= n; i ++)
        cin >> a[i];
    for(int i = 1; i <= n; i ++){
        char c; cin >> c;
        st[i] = c - '0';
    }
 
    deque<ll> de;
    
    for(int i = 1; i <= n; i ++){
        while(de.size() && pre[i - 1] < pre[de.back()])
            de.pop_back();
        de.pb(i - 1);
        while(de.size() && i - de[0] > k)
            de.pop_front();
        if(st[i - 1]){
            de.clear();
            de.pb(i - 1);
        }
        pre[i] = pre[de[0]] + a[i];
    }
 
    de.clear();
    for(int i = n; i >= 1; i --){
        while(de.size() && suf[i + 1] < suf[de.back()])
            de.pop_back();
        de.push_back(i + 1);
        while(de.size() && de[0] - i > k)
            de.pop_front();
        if(st[i + 1]){
            de.clear();
            de.pb(i + 1);
        }
        suf[i] = suf[de[0]] + a[i];
    }
    st[0] = st[n + 1] = 1;
    cin >> q;
    while(q --){
        ll x, res;
        cin >> x >> res;
        ll ans = pre[x] + suf[x] - 2 * a[x] + res;
        if(!st[x]){
            num[x] = LNF;
            int pos = x;
            for(int i = x - 1; x - i < k; i --){
                num[i] = min(num[i + 1], pre[i]);
                pos = i;
                if(st[i]) break;
            }
            for(int i = x + 1; i - x < k; i ++){
                ans = min(ans, suf[i] + num[max(pos, i - k)]);
                if(st[i]) break;
            }
            for(int i = x - 1; x - i < k; i --){
                num[i] = LNF;
                if(st[i]) break;
            }
        }
        cout << ans << endl;
    }
 
}  

Problem E. 树的染色
思路:
先说tag,虚树,然后还需要线段树去维护一个最小代价(或者什么维护都行)

首先,假设当前是操作D
那么对于每个点u,u是v的祖先节点,如果u到v的距离大于D,对于u就毫无意义了,可以直接删掉
这样u可以影响到的点就是与u相距D的点,以此建虚树
在虚树上的状态转移为
res表示当前子树全部染色的最小代价
如果当前节点有儿子节点时 r e s u = ∑ v ∈ s o n [ u ] r e s v res_u = \sum_{v\in son[u]} res_v resu=vson[u]resv
否则 r e s = i n f res=inf res=inf
r e s = m i n ( r e s , q u e r y ( 1 , 1 , n , D − d [ u ] + 1 , D − f a D ) ) res = min(res, query(1, 1, n, D - d[u] + 1, D - faD)) res=min(res,query(1,1,n,Dd[u]+1,DfaD))
f a D faD faD表示父节点的深度, d [ u ] d[u] d[u]表示当前点 u u u的深度
code

const int N = 1e5 + 10;
ll minn[N << 2];
vector<int> g[N];
ll a[N];
int n;
 
int d[N], f[N][25];
vector<int> vd[N];
 
int stk[N], tp;
vector<int> vtr[N];
 
#define ls u << 1
#define rs u << 1 | 1
void build(int u, int l, int r){
    if(l == r){
        minn[u] = a[l];
        return;
    }
 
    int mid = l + r >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);
    minn[u] = min(minn[ls], minn[rs]);
}
 
ll query(int u, int l, int r, int lx, int rx){
    if(lx <= l && r <= rx)
        return minn[u];
    int mid = l + r >> 1;
    ll res = LNF;
    if(lx <= mid) res = min(res, query(ls, l, mid, lx, rx));
    if(rx > mid) res = min(res, query(rs, mid + 1, r, lx, rx));
    return res;
}
 
void dfs(int u, int fa, int dep){
    f[u][0] = fa;
    for(int i = 1; i <= 24; i ++)
        f[u][i] = f[f[u][i - 1]][i - 1];
    d[u] = dep;
    vd[dep].pb(u);
    for(auto v : g[u])
        if(v != fa)
            dfs(v, u, dep + 1);
}
 
int lca(int x, int y){
    if(d[x] > d[y])
        swap(x, y);
    for(int i = 24; i >= 0; i --){
        if(d[f[y][i]] >= d[x])
            y = f[y][i];
    }
    if(x == y)
        return x;
    for(int i = 24; i >= 0; i --)
        if(f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    return f[x][0];
}
 
ll dp(int u, int faD, int dep){
    ll res;
    if(!vtr[u].size())
        res = LNF;
    else{
        res = 0;
        for(auto v : vtr[u])
            res += dp(v, d[u], dep);
    }
    res = min(res, query(1, 1, n, dep - d[u] + 1, dep - faD));
    return res;
}
 
ll work(int D){
    vtr[1].clear();
    stk[tp = 1] = 1;
    for(auto v : vd[D]){
        vtr[v].clear();
        int a = lca(v, stk[tp]);
        if(a == stk[tp]){
            stk[++ tp] = v;
            continue;
        }
        while(d[stk[tp - 1]] > d[a])
            vtr[stk[tp - 1]].pb(stk[tp --]);
        if(a == stk[tp - 1])
            vtr[a].pb(stk[tp --]);
        else{
            vtr[a].clear();
            vtr[a].pb(stk[tp]);
            stk[tp] = a;
        }
        stk[++ tp] = v;
    }
    while(tp > 1)
        vtr[stk[tp - 1]].pb(stk[tp --]);
    return dp(1, 0, D);
}
 
void solve(){
    cin >> n;
    for(int i = 1; i <= n; i ++)
        cin >> a[i];
    build(1, 1, n);
    for(int i = 1; i < n; i ++){
        int u, v;
        cin >> u >> v;
        g[u].pb(v), g[v].pb(u);
    }
 
    dfs(1, 0, 1);
    ll ans = a[1];
    for(int i = 2; i <= n && vd[i].size(); i ++){
        ll k = work(i);
        ans += k;
    }
    cout << ans << endl;
    for(int i = 1; i <= n; i ++)
        g[i].clear(), vd[i].clear();
}  
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值