大三第二周学习笔记

训练!!!!

周一

hdu 5236(概率dp)

首先要猜测一下策略,策略是将n个字符均分,每敲固定的字符就保存一下。

比如将n分成i段,那么每一段的长度就是k或k+1,余数部分就分配到每个k,变成k+1

那么这样就要处理没有保存的情况下,打k个字符的期望

用dp[k]表示打k个字符的期望,注意这时不是逆推,因为需要的就是正推的。

那么有dp[i] = dp[i - 1] + p(1 + dp[i]) + (1 - p) 可化为dp[i] = (dp[i - 1] + 1) / (1 - p)

输出时6位小数即可,我输出了10位反而wa了

#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
double dp[N], p;
int n, x;

int main()
{
    int T, kase = 0; 
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%lf%d", &n, &p, &x);
        _for(i, 1, n) dp[i] = (dp[i - 1] + 1) / (1 - p);

        double ans = 1e18;
        _for(i, 1, n)
        {
            int k = n / i, r = n % i;
            ans = min(ans, r * dp[k + 1] + (i - r) * dp[k] + i * x);
        }
        printf("Case #%d: %.6f\n", ++kase, ans);
    }
    
	return 0;
}

迷宫游戏(期望dp+处理方程)

首先要写出方程,这个是简单的。注意这道题要求的是走过的边数的期望,而实际上三种情况只有一种情况会增加一条边,所以写方程时不要直接写1

写出方程后发现有dp[1]和dp[fa[i]]两个未知量,那么就设dp[i] = aidp[1]+bidp[fa[i]],然后代入dp方程中,化成dp[1]和dp[fa[i]]的形式,得到递推式。然后求一下叶子节点的式子即可。

无解的话就是除0的情况。因为一般情况下是可以算出来的,只有分母为0是不能的。

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e4 + 10;
double k[N], e[N], a[N], b[N], c[N], t[N];
vector<int> g[N];
int n;

bool dfs(int u, int fa)
{
    if(g[u].size() == 1 && g[u][0] == fa)
    {
        a[u] = k[u];
        b[u] = 1 - e[u] - k[u];
        c[u] = 1 - e[u] - k[u];
        return true;
    }

    double suma = 0, sumb = 0, sumc = 0;
    t[u] = (1 - e[u] - k[u]) / g[u].size();
    for(int v: g[u])
    {
        if(v == fa) continue;
        if(!dfs(v, u)) return false;
        suma += a[v];
        sumb += b[v];
        sumc += c[v];
    }
    if(fabs(1 - t[u] * sumb) < 1e-8) return false;
    a[u] = (k[u] + t[u] * suma) / (1 - t[u] * sumb);
    b[u] = t[u] / (1 - t[u] * sumb);
    c[u] = (t[u] * sumc + 1 - e[u] - k[u]) / (1 - t[u] * sumb);
    return true;
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    _for(i, 1, n) 
    {
        int x, y;
        scanf("%d%d", &x, &y);
        k[i] = 1.0 * x / 100;
        e[i] = 1.0 * y / 100;
    }

    if(!dfs(1, 0) || fabs(1 - a[1]) < 1e-8) puts("impossible");
    else printf("%.10f\n", c[1] / (1 - a[1]));
 
	return 0;
}

hdu 3535(混合背包)

这题真的把各种背包结合起来

强制选一个的操作很秀

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 100 + 10;
int dp[N][N], w[N], v[N], n, T, m, s;

int main()
{
    while(~scanf("%d%d", &n, &T))
    {
        memset(dp, 0, sizeof dp);
        _for(i, 1, n)
        {
            scanf("%d%d", &m, &s);
            _for(j, 1, m) scanf("%d%d", &w[j], &v[j]);

            if(s == 0)
            {
                _for(j, 0, T) dp[i][j] = -1e9;  //因为必须取一个有不合法的状态,所以初始化为负无穷
                _for(k, 1, m)
                    for(int j = T; j >= w[k]; j--)
                        dp[i][j] = max(dp[i][j], max(dp[i][j - w[k]] + v[k], dp[i - 1][j - w[k]] + v[k])); //强制选一个,以及和前面的状态做背包
            }
            else if(s == 1)
            {
                _for(j, 0, T) dp[i][j] = dp[i - 1][j];
                _for(k, 1, m)
                    for(int j = T; j >= w[k]; j--)
                        dp[i][j] = max(dp[i][j], dp[i - 1][j - w[k]] + v[k]);
            }
            else
            {
                _for(j, 0, T) dp[i][j] = dp[i - 1][j];
                _for(k, 1, m)
                    for(int j = T; j >= w[k]; j--)
                        dp[i][j] = max(dp[i][j], dp[i][j - w[k]] + v[k]);
            }
        }
        printf("%d\n", (dp[n][T] < 0) ? -1: dp[n][T]);
    }
 
	return 0;
}

周二

D. 2+ doors(位运算+贪心)

现在多刷刷cf的题,关键是学解题技巧。

首先是位运算的题很常见的思路就是每一位独立出来考虑,这道题也是一样

首先若边为0,那么肯定端点都是0,这是可以确定的,赋值。

然后考虑什么时候字典序最小,那么就从前往后,每一位看能不能为0

必须为1只有一种情况,就是边为1,而另一个端点为0。对于当前点,前面的点在已经确定了,为了尽量使得当前可以为1,我们可以把后面的位先全部设为1(除了零边赋值为0)

那么就遍历当前点所有的边,看是否有不得不为1的情况,即一边且另一个端点是0。这样一位一位即可。有一个细节就是可能自己向自己连边,这个时候是一边的话那这一位就肯定为1了。

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
int ans[N], t[N], n, m;
vector<pair<int, int>> g[N];

int main()
{
    scanf("%d%d", &n, &m);
    while(m--)
    {
        int i, j, x;
        scanf("%d%d%d", &i, &j, &x);
        if(i > j) swap(i, j);
        g[i].push_back({j, x});
        g[j].push_back({i, x});
    }

    _for(j, 0, 30)
    {
        _for(i, 1, n) t[i] = 1;
        _for(i, 1, n)
            for(auto x: g[i])
                if(!((x.second >> j) & 1))
                    t[i] = t[x.first] = 0;
        _for(i, 1, n)
        {
            if(!t[i]) continue;
            int flag = 1;
            for(auto x: g[i])
                if(!t[x.first] || i == x.first)
                {
                    flag = 0;
                    break;
                }
            if(flag) t[i] = 0;
        }
        _for(i, 1, n) 
            if(t[i])
                ans[i] |= 1 << j;
    }
    _for(i, 1, n) printf("%d ", ans[i]);

	return 0;
}

D. Difference Array(暴力+优化)

这是一类题,暴力看似不可行,实际上是可行的,加一些小优化,或者是复杂度计算。

这题的关键就是发现计算的过程中会出现很多0,把0单独考虑可以大大加快速度,这样暴力也能过。

考虑0的话,维护最后的0的位置,每次计算和排序都考虑后面的数,然后继续维护0的位置。注意可能有当前没有0的情况,要判断一下。

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int a[N], b[N], n;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &a[i]);

        int pos = 0;
        _for(i, 1, n)
            if(a[i])
            {
                pos = i - 1;
                break;
            }

        while(n > 1 && a[n - 1])
        {
            _for(i, max(pos, 1), n) a[i] = a[i + 1] - a[i];
            n--;
            sort(a + max(pos, 1), a + n + 1);
            if(pos) pos--;
            while(pos + 1 <= n && !a[pos + 1]) pos++;
        }
        printf("%d\n", a[n]);
    }

	return 0;
}

C. Qpwoeirut And The City(括号匹配)

思路挺妙,首先左边问号全部左括号,右边全部右括号一定是一个解

然后看有无第二个解,那就把中间那里交换一下,也就是最右边的左括号和最左边的右括号,这样是影响最小的,然后看这样是否合法。

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

bool check(string s)
{
    int len = s.size(), cnt = 0;
    rep(i, 0, len)
    {
        if(s[i] == '(') cnt++;
        else cnt--;
        if(cnt < 0) return false;
    }
    return true;
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        string s;
        cin >> s;
        int len = s.size();

        vector<int> pos;
        int l = len / 2, r = len / 2;
        rep(i, 0, len)
        {
            if(s[i] == '(') l--;
            else if(s[i] == ')') r--;
            else pos.push_back(i);
        }
        rep(i, 0, l) s[pos[i]] = '(';
        rep(i, l, pos.size()) s[pos[i]] = ')';

        int ans = 0;
        if(l && r)
        {
            swap(s[pos[l - 1]], s[pos[l]]);
            if(check(s)) ans = 1;
        }
        puts(ans ? "NO" : "YES");
    }
   
    return 0;
}

C. Qpwoeirut And The City(细节)

这题不难,但写的时候写错了一个细节,调了很久才发现

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
ll h[N], a[N];
int n;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%lld", &h[i]);
        _for(i, 2, n - 1) a[i] = max(max(h[i - 1], h[i + 1]) + 1 - h[i], 0ll);

        if(n % 2 == 1)
        {
            ll ans = 0;
            for(int i = 2; i <= n; i += 2) ans += a[i];
            printf("%lld\n", ans);
            continue;
        }

        vector<pair<ll, ll>> ve;
        for(int i = 2; i + 1 <= n; i += 2) ve.push_back({a[i], a[i + 1]});
        //这里两个两个打进去时,注意退出条件是i+1<=n 不是i <= n 要用这一块的终点而不是起点

        ll cur = 0;
        for(auto x: ve) cur += x.second;
        ll ans = cur;
        for(auto x: ve) 
        {
            cur += x.first - x.second;
            ans = min(ans, cur);
        }
        printf("%lld\n", ans);
    }
   
    return 0;
}

Yet Another FFT Problem?(复杂度计算)

这题的关键在于,O(n^2)的算法看起来会T,实际上不会T

先判断a,b中有没有相等的,都有的话直接输出答案,否则去重,之后就暴力枚举a,b

因为要输出下标,我开始的做法是将值和下标绑在一起用pair,但是这种方法写起来复杂,而且交上去MLE了,应该是vector里面直接存下标即可。

学了学菜狗的代码,学到了很多

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e7 + 10;

int main()
{ 
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n, m;
    cin >> n >> m;

    vector<int> a(n + 1), b(m + 1);
    _for(i, 1, n) cin >> a[i];
    _for(i, 1, m) cin >> b[i];

    vector<int> posa(N, -1), posb(N, -1), va, vb;
    array<int, 4> ans = {-1, -1, -1, -1};
    _for(i, 1, n)
    {
        if(posa[a[i]] == -1)
        {
            posa[a[i]] = i;
            va.push_back(i);
        }
        else
        {
            ans[0] = posa[a[i]];
            ans[1] = i;
        }
    }
    _for(i, 1, m)
    {
        if(posb[b[i]] == -1)
        {
            posb[b[i]] = i;
            vb.push_back(i);
        }
        else
        {
            ans[2] = posb[b[i]];
            ans[3] = i;
        }
    }
    if(ans[0] != -1 && ans[2] != -1)
    {
        cout << ans[0] << " " << ans[1] << " " << ans[2] << " " << ans[3] << "\n";
        return 0;
    }

    vector<array<int, 2>> vis(2 * N, {-1, -1});
    for(auto i : va)
        for(auto j: vb)
        {
            if(vis[a[i] + b[j]][0] == -1) vis[a[i] + b[j]] = {i, j};
            else
            {
                cout << i << " " << vis[a[i] + b[j]][0] << " " << j << " " << vis[a[i] + b[j]][1];
                return 0;
            }
        }
    cout << "-1\n" ;
   
    return 0;
}

Good red-string(贪心)

这道题是比较复杂的贪心,思维量比较大

首先不能直接从左到右先填r,再填e,再填e,比如r?d???  先填r的话就会导致在前缀中d比e多

因此我们要先处理这种特殊情况,再贪心

首先对所有前缀需要满足cntr >= cnte >= cntd

对于所有后缀需要满足cntd >= cnte >= cntr

结合那个特例,可以化简成对前缀需要d >= e 对后缀需要e >= r 满足这两个条件即满足上面的式子

因为是对称的,所以我们只用考虑前缀,后缀类似处理即可。

对于前缀,统计e的数量和d数量,如果不满足d比e多,那就把最近一个问号填为e,显然e越靠中间越优,所以是最近的。后缀同理填e

这样填完后再进行r e d的贪心,最后再判断是否合法。

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

bool solve()
{
    string s; cin >> s;
    int len = s.size();
    if(len % 3) return false;

    vector<int> pos;
    int cnte = 0, cntd = 0, cntr = 0;
    rep(i, 0, len)
    {
        if(s[i] == 'e') cnte++;
        else if(s[i] == 'd') cntd++;
        else if(s[i] == '?') pos.push_back(i);
        if(cnte < cntd)
        {
            if(!pos.size()) return false;
            s[pos.back()] = 'e'; pos.pop_back();
            cnte++;
        }
    }
    cntr = cnte = cntd = 0;
    pos.clear();
    for(int i = len - 1; i >= 0; i--)
    {
        if(s[i] == 'e') cnte++;
        else if(s[i] == 'r') cntr++;
        else if(s[i] == '?') pos.push_back(i);
        else cntd++;
        if(cnte < cntr)
        {
            if(!pos.size()) return false;
            s[pos.back()] = 'e'; pos.pop_back();
            cnte++;
        }
    }

    int r = len / 3 - cntr, e = len / 3 - cnte, d = len / 3 - cntd;
    if(r < 0 || e < 0 || d < 0) return false;
    rep(i, 0, len)
        if(s[i] == '?')
        {
            if(r) s[i] = 'r', r--;
            else if(e) s[i] = 'e', e--;
            else if(d) s[i] = 'd', d--;
        }
    
    cntr = cntd = cnte = 0;
    rep(i, 0, len)
    {
        if(s[i] == 'r') cntr++;
        if(s[i] == 'e') cnte++;
        if(s[i] == 'd') cntd++;
        if(!(cntr >= cnte && cnte >= cntd)) return false;
    }
    cntr = cntd = cnte = 0;
    for(int i = len - 1; i >= 0; i--)
    {
        if(s[i] == 'r') cntr++;
        if(s[i] == 'e') cnte++;
        if(s[i] == 'd') cntd++;
        if(!(cntd >= cnte && cnte >= cntr)) return false;
    }
    return true;
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--) puts(solve() ? "Yes" : "No");
    return 0;
}

周三

poj 2287(贪心+分类讨论)

比较需要思考的贪心,挺有趣的

先看两边最高的

首先如果左边最高的大于右边最高的,那就比一场。显然要以最小代价赢,这样赢得话两边得差是最小的,比较划算

如果左边最高的小于右边最高的,那么这样左边怎么样都赢不了,那就拿左边最差的去送,代价最小

如果左边最高的等于右边最高的,那就需要讨论一下

这时有两种方法,一种是最高的比一个平局,一个是拿左边最差的去比。

那么就需要讨论左边最差的那个有没有价值。

如果左边最差的小于右边最差的:如果最高的比一个平局,那么就是一个平局,最差的输了,减200。如果拿左边最差的去比,输200,然而左边最好的可能大于右边第二好的。所以第二种要更优秀。

如果左边最差的等于右边最差的,那么这时其实两种策略都是平手,都可以。

如果左边最差的强于右边最差的。如果第一种策略,那就是平局加上赢一场,加200。如果第二种策略,那就是输一场,然后左边最高的可能赢右边第二高的。第一种策略必为+200,第二种可能+200,选第一种。

所以综合一下上述的选择,可以发现就是最好的比一下,如果能赢就这么比,否则看最差的比一下,如果能赢就比,否则就最差的去比最好的。

其实就是分类讨论,然后分析那种策略最优,然后总结

#include <cstdio>
#include <algorithm>
#include <functional>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int a[N], b[N], n;

int main()
{ 
    while(scanf("%d", &n) && n)
    {
        _for(i, 1, n) scanf("%d", &a[i]);
        _for(i, 1, n) scanf("%d", &b[i]);
        sort(a + 1, a + n + 1, greater<int>());
        sort(b + 1, b + n + 1, greater<int>());

        int al = 1, ar = n, bl = 1, br = n, ans = 0;
        while(al <= ar)
        {
            if(a[al] > b[bl])
            {
                ans += 200;
                al++; bl++;
            }
            else if(a[ar] > b[br])
            {
                ans += 200;
                ar--; br--;
            }
            else
            {
                if(a[ar] < b[bl]) ans -= 200;
                ar--; bl++;
            }
        }
        printf("%d\n", ans);
    }

    return 0;
}

hdu 6534(莫队+树状数组+离散化)

因为数据范围比较小,所以可以考虑莫队。

我离散化那里卡了一下,因为不仅要把a[i]离散化,还有差为k这个限制

比较好写的做法是将a[i]-k,a[i]+k同样扔到离散化数组里面,这样就很好处理了

#include <cstdio>
#include <algorithm>
#include <functional>
#include <cmath>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 27000 + 10;
int a[N], L[N], R[N], lsh[N * 3], cnt, n, m, k, block, t;
struct query
{
    int l, r, bl, id;
}q[N];
ll ans[N], f[N * 3], sum;

bool cmp(query x, query y)
{
    if(x.bl != y.bl) return x.bl < y.bl;
    if(x.bl & 1) return x.r < y.r;
    return x.r > y.r;
}

int lowbit(int x) { return x & -x; }

void modify(int x, int p)
{
    for(; x <= t; x += lowbit(x))
        f[x] += p;
}

ll s(int x)
{
    ll res = 0;
    for(; x; x -= lowbit(x))    
        res += f[x];
    return res;
}

void add(int x)
{
    sum += s(R[x]) - s(L[x] - 1);
    modify(a[x], 1);
}

void erase(int x)
{
    modify(a[x], -1);
    sum -= s(R[x]) - s(L[x] - 1);
}

int main()
{ 
    scanf("%d%d%d", &n, &m, &k);
    _for(i, 1, n) 
    {
        scanf("%d", &a[i]);
        lsh[++cnt] = a[i];
        lsh[++cnt] = a[i] - k;
        lsh[++cnt] = a[i] + k;
    }
    sort(lsh + 1, lsh + cnt + 1);
    t = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;
    _for(i, 1, n)
    {
        L[i] = lower_bound(lsh + 1, lsh + t + 1, a[i] - k) - lsh;
        R[i] = lower_bound(lsh + 1, lsh + t + 1, a[i] + k) - lsh;
        a[i] = lower_bound(lsh + 1, lsh + t + 1, a[i]) - lsh;
    }

    block = sqrt(n);
    _for(i, 1, m)
    {
        int l, r; scanf("%d%d", &l, &r);
        q[i] = {l, r, l / block, i};
    }
    sort(q + 1, q + m + 1, cmp);

    int l = 1, r = 0;
    _for(i, 1, m)
    {
        int ll = q[i].l, rr = q[i].r;
        while(l < ll) erase(l++);
        while(l > ll) add(--l);
        while(r < rr) add(++r);
        while(r > rr) erase(r--);
        ans[q[i].id] = sum;
    }
    _for(i, 1, m) printf("%lld\n", ans[i]);

    return 0;
}

Chiitoitsu(期望dp)

最优策略就是能配对的配对,否则仍掉

以手上的单牌和剩下牌的总数作为状态,用期望dp

注意边界情况

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int mod = 1e9 + 7;
ll dp[20][150];

ll binpow(ll a, ll b)
{
    ll res = 1;
    for(; b; b >>= 1)
    {
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}

ll inv(ll x) { return binpow(x, mod - 2); }

int main()
{ 
    _for(i, 1, 13)
        _for(j, 3 * i, 123)
            dp[i][j] = (1 + 3 * i * inv(j) % mod * dp[max(i - 2, 0)][j - 1] % mod
                        + (j - 3 * i) * inv(j) % mod * dp[i][j - 1] % mod) % mod;
    
    int T, kase = 0;
    scanf("%d", &T);
    while(T--)
    {
        string s; cin >> s;
        int len = s.size();
        map<string, int> mp;
        for(int i = 0; i < len; i += 2)
            mp[s.substr(i, 2)]++;

        int cnt = 0;
        for(auto x: mp) 
            if(x.second == 1)
                cnt++;
        printf("Case #%d: %lld\n", ++kase, dp[cnt][123]);
    }  

    return 0;
}

B. Mainak and Interesting Sequence

比赛时当n为偶数,m为奇数的时候,构造了一下,发现构造不出来,就猜了一个肯定不成立,然后直接交了,A了。

赛后可以证明一下,首先除了最大的数,其他的数都是成对的

首先如果全部为一个数,那么n为偶数,和肯定为偶数,而m为奇数,排除

那么不是全部为一个数,除了最大数外,其他的数因为是成对的,所以和为偶数,那么剩下最大数的位置也是偶数的,那么最后的和为偶数,不可能m为奇数

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int a[N], n, m;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--) 
    {
        scanf("%d%d", &n, &m);
        if(n % 2 == 1)
        {
            if(n > m) puts("No");
            else
            {
                _for(i, 1, n - 1) a[i] = 1, m--;
                a[n] = m;
                puts("Yes");
                _for(i, 1, n) printf("%d ", a[i]); puts("");
            }
        }
        else
        {
            if(m % 2 == 1) puts("No");
            else
            {
                if(n > m) puts("No");
                else
                {
                    _for(i, 1, n - 2) a[i] = 1, m--;
                    a[n - 1] = a[n] = m / 2;
                    puts("Yes");
                    _for(i, 1, n) printf("%d ", a[i]); puts("");
                }
            }
        }
    }

    return 0;
}

C. Jatayu's Balanced Bracket Sequence

有两种边,一种是括号匹配的,一种是匹配与匹配之间的。比赛时用并查集写的,实际上并不会成环,可以直接统计右括号和)(的个数。

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
int f[N], st[N], top, n, cnt;

int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }

void merge(int x, int y)
{
    int fx = find(x), fy = find(y);
    if(fx != fy)
    {
        cnt--;
        f[fx] = fy;
    }
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--) 
    {
        scanf("%d", &n);
        _for(i, 1, 2 * n) f[i] = i;
        
        top = 0;
        cnt = 2 * n;
        string s; cin >> s;
        s = " " + s;
        int pre;
        _for(i, 1, 2 * n)
        {
            if(s[i] == '(') 
            {
                if(s[i - 1] == ')') merge(i, pre);
                st[++top] = i;
            }
            else 
            {
                pre = i;
                merge(i, st[top--]);
            }
        }
        printf("%d\n", cnt);
    }

    return 0;
}

D. Edge Split

这题等相通了发现不难,但比赛时还是想了一个小时,很不应该。

显然可以生成树一种颜色,非树边一种颜色。当非数边成环时,将一条边加到生成树中,生成树新成的环删去一条边即可

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
vector<pair<int, int>> g[N];
int ans[N], vis[N], d[N], f[N], n, m; 
vector<array<int, 3>> edge;

void dfs(int u, int fa)
{
    d[u] = d[fa] + 1;
    vis[u] = 1;
    for(auto x: g[u])
    {
        int v = x.first, id = x.second;
        if(v == fa) 
        {
            f[u] = id;
            continue;
        }
        if(vis[v]) 
        {
            int flag = 1;
            for(auto x: edge)
                if(x[2] == id)
                {
                    flag = 0;
                    break;
                }
            if(flag) edge.push_back({u, v, id});
        }
        else dfs(v, u);
    }
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--) 
    {
        scanf("%d%d", &n, &m);
        edge.clear();
        _for(i, 1, n) g[i].clear(), vis[i] = 0;
        _for(i, 1, m) ans[i] = 0;

        _for(i, 1, m)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            g[u].push_back({v, i});
            g[v].push_back({u, i});
        }

        dfs(1, 0);

        int flag = 0;
        if(edge.size() == 3)
        {
            set<int> s;
            for(auto x: edge) 
            {
                s.insert(x[0]);
                s.insert(x[1]);
            }
            if(s.size() == 3) flag = 1;
        }
        if(!flag) for(auto x: edge) ans[x[2]] = 1;
        else
        {
            
            ans[edge[0][2]] = ans[edge[1][2]] = 1;
            int u = edge[2][0], v = edge[2][1];
            if(d[u] < d[v]) swap(u, v);
            ans[f[u]] = 1;
        }
        _for(i, 1, m) printf("%d", ans[i]); puts("");
    }

    return 0;
}

A2. Burenka and Traditions (hard version)(异或+贪心区间覆盖)

这题是由三个关键点嵌套起来。

1.花费为区间除以2向上取整

任何一个操作的花费都可以拆分成长度为1和长度为2的操作,这样考虑一定包含的最优的答案。这样拆分可以简化问题

这样子答案的上界就是n,每个数都区间长度为1的操作

为了让花费最小,显然要尽可能多的操作区间为2的且一起为0,也就是相等。

考虑对于ai-1 和ai  如果本来就不等,那么如果使它们相等呢。在ai-1之前可以一直操作区间为2的,比如说a6,它可以通过前面的操作变成a6^a5,a6^a5^a4,a6^a5^a4^a3……

那么怎么判断是否可以使得相等呢,这就用到了异或的性质,是第二个关键点

2.异或的性质

直接判断比较难,我们加入前缀异或和,也就是从1到ai-1,记为sum

那么也就是存在前缀异或和为sum ^ a[i],注意这个前缀异或和包括0

也就是对于一个ai,可以判断可能存在一个区间,使得答案更小。

那么就由很多个区间,就转化为区间覆盖问题

3.贪心求区间覆盖

结论是按照右端点排序,然后遍历,能放就放

因为可以按照右端点排序,所以可以直接枚举a1到an,然后如果当前可以使答案更小,那就操作,操作之后前缀异或和要重新记录,注意0始终存在。

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int a[N], n;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--) 
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &a[i]);

        set<int> s;
        s.insert(0);
        int ans = n, sum = 0;
        _for(i, 1, n)
        {
            if(s.count(sum ^ a[i])) 
            {
                ans--;
                s.clear();  s.insert(0);
                sum = 0;
            }
            else sum ^= a[i], s.insert(sum);
        }
        printf("%d\n", ans);
    }

    return 0;
}

周四

D. Madoka and The Corruption Scheme(思维+组合数)

这题的关键在于反过来看,我一直按照题目的意思来看,结果感觉比较复杂。

逆向思考,其实就是从根节点,每次选一条红边往下到叶子,叶子就是胜者。

那么显然,要改变的话,只有改变这条路径上的边才有用,而且不同改变的结果一定是不同的,可以看作线段树向下,每次向左边或者向右边,只要有一次不同,结果一定不同。

考虑改变能产生的不同的结果,修改0次就是c(n, 0) 修改1次就是c(n,1)……

令k=min(k, n) 那么结果一共有c(n, 0) + c(n,1)……c(n, k)这么多可能

那改变的人肯定取这么多可能的最大值,那么布置的人一定是布置1,2,3……这样可以使得最大值最小。

所以最终的答案就是这个和

预处理阶乘即可

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
const int mod = 1e9 + 7;
ll f[N];

ll binpow(ll a, ll b)
{
    ll res = 1;
    for(; b; b >>= 1)
    {
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}

ll inv(ll x) { return binpow(x, mod - 2); } 

ll C(ll n, ll m)
{
    return f[n] * inv(f[m]) % mod * inv(f[n - m]) % mod;
}

int main()
{ 
    f[0] = 1;
    _for(i, 1, 1e5) f[i] = f[i - 1] * i % mod;

    int n, k;
    scanf("%d%d", &n, &k);
    k = min(k, n);

    ll ans = 0;
    _for(i, 0, k) ans = (ans + C(n, i)) % mod;
    printf("%lld\n", ans);

    return 0;
}

Build a Tree and That Is It(思维+解方程)

可以发现只有两种情况,要不是链式,要不是4号为根,然后1 2 3连向4

第一种情况很好处理,第二种情况需要解方程。如果解出小数或者小于等于0就无解。

最后还要注意一下点数和边数要满足限制。

写的时候有想到,但是以为不会超,但实际上是会超的,WA了一发。其实写了也没错,应该一开始就写上去。

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int n, d12, d23, d13, cnt, root;
vector<pair<int, int>> ans;
int dis[5][5];

void add(int u, int x)
{
    vector<int> ve;
    ve.push_back(u);
    _for(i, 1, x - 1) ve.push_back(cnt++);
    ve.push_back(root);
    rep(i, 0, ve.size() - 1) ans.push_back({ve[i], ve[i + 1]});
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        cnt = 4;
        ans.clear();
        scanf("%d%d%d%d", &n, &d12, &d23, &d13);
        dis[1][2] = dis[2][1] = d12;
        dis[2][3] = dis[3][2] = d23;
        dis[1][3] = dis[3][1] = d13;

        //case 1
        vector<pair<int, int>> ve;
        ve.push_back({d12, 3});
        ve.push_back({d23, 1});
        ve.push_back({d13, 2});
        sort(ve.begin(), ve.end());
        if(ve[0].first + ve[1].first == ve[2].first)
        {
            root = ve[2].second;
            _for(i, 1, 3)
                if(i != root)
                    add(i, dis[i][root]);
        }
        else //case 2
        {
            if((d13 + d12 - d23) % 2 != 0)
            {
                puts("NO");
                continue;
            }
            int a, b, c;
            a = (d13 + d12 - d23) / 2;
            c = d13 - a;
            b = d23 - c;
            if(a <= 0 || b <= 0 || c <= 0)
            {
                puts("NO");
                continue;
            }

            root = cnt++;
            if(root > n) 
            {
                puts("NO");
                continue;
            }
            add(1, a); add(2, b); add(3, c);
        }
        while(cnt <= n) ans.push_back({cnt++, root});
        if(cnt > n + 1) puts("NO");
        else
        {
            puts("YES");
            for(auto [u, v]: ans) printf("%d %d\n", u, v);
        }
    }

    return 0;
}

D. Magical Array(思维)

妙啊,我自己思考的时候就觉得操作1和操作2会使得序列的某个性质改变,通过这个性质判断

一开始想的是和,但是都是使得和不变的

突破口是操作2的下标不同,所以我们把下标加入到序列的性质中,也就是ai * i的求和,可以发现操作2使得这个值+1,操作一不变,通过这个即可得出答案

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
vector<pair<ll, int>> ve;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        ve.clear();
        int n, m;
        scanf("%d%d", &n, &m);
        _for(i, 1, n)
        {
            ll sum = 0;
            _for(j, 1, m)
            {
                ll x; scanf("%lld", &x);
                sum += x * j;
            }
            ve.push_back({sum, i});
        }
        sort(ve.begin(), ve.end());
        printf("%d %lld\n", ve.back().second, ve.back().first - ve[0].first);
    }

    return 0;
}

G1. Passable Paths(dfs)

我的思路是找所有点的lca,然后从这开始搜,看经过这一点的路径最大值是否为k。

题目的思路简单一些,找深度最大的节点dfs,这个节点一定是路径的一端,然后后dfs能否走过所有选择的点

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
vector<int> g[N];
int d[N], up[N][21], c[N], n, q, k, ans, root;

void dfs(int u, int fa)
{
    d[u] = d[fa] + 1;
    up[u][0] = fa;
    _for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];

    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs(v, u);
    }
}

int lca(int u, int v)
{
    if(d[u] < d[v]) swap(u, v);
    for(int j = 20; j >= 0; j--)
        if(d[up[u][j]] >= d[v])
            u = up[u][j];
    if(u == v) return u;
    for(int j = 20; j >= 0; j--)
        if(up[u][j] != up[v][j])
            u = up[u][j], v = up[v][j];
    return up[u][0];
}

int dfs2(int u, int fa)
{
    int mx1 = 0, mx2 = 0;
    for(int v: g[u])
    {
        if(v == fa) continue;
        int t = dfs2(v, u);
        if(t > mx1) mx2 = mx1, mx1 = t;
        else mx2 = max(mx2, t);
    }
    if(u == root && c[u] + mx1 + mx2 == k) ans = 1;
    return c[u] + mx1;
}

int main()
{ 
    scanf("%d", &n);
    _for(i, 1, n) g[i].clear();
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, 0);

    scanf("%d", &q);
    _for(i, 1, q)
    {
        root = 0;
        _for(i, 1, n) c[i] = 0;
        scanf("%d", &k);
        _for(j, 1, k)
        {
            int x; scanf("%d", &x);
            c[x] = 1;
            if(!root) root = x;
            else root = lca(root, x);
        }
        ans = 0;
        dfs2(root, 0);
        puts(ans ? "YES" : "NO");
    }

    return 0;
}

D. Permutation Restoration(贪心)

首先可以算出一个可能的取值范围,注意当分数小于一个值,转化为开区间可以向下取整+1

然后就变成了怎么给每个区间分配点,我在这里卡住了,想把区间排序,然后不太行。

正解是反过来思考,给点分配区间,从1遍历到n,对于当前点,给它分配一个右端点最小的区间。

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5e5 + 10;
vector<int> v[N];
int b[N], a[N], n;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) v[i].clear();
        _for(i, 1, n) 
        {
            scanf("%d", &b[i]);
            int l = i / (b[i] + 1) + 1;
            v[l].push_back(i);
        }

        set<pair<int, int>> s;
        _for(i, 1, n)
        {
            for(int id: v[i]) s.insert({(b[id] == 0) ? n : id / b[id], id});
            a[s.begin()->second] = i;
            s.erase(s.begin());
        }
        _for(i, 1, n) printf("%d ", a[i]); puts("");
    }

    return 0;
}

周五

D. Guess The String(交互题+二分)

交互题做的非常少……

这题提供的主要信息就是一段区间右多少种字符

考虑一个一个字符来猜,如果前面字符已知,现在猜第i个字符

考虑f(j, i) 和f(j, i-1)  f(x,y)表示[x, y]中有多少种字符

f(j, i) 和f(j, i-1)的差别就是多了一个s[i]

显然,如果s[i]在(j,i-1)未出现过,那么f(j, i)会大1,否则相等

当j离i越近,(j,i-1)就越小,越难包含s[i],如果j离i越远,那么就越可能包含s[i]

考虑f(j, i) - f(j, i - 1) 那么随着j的增大,它的值一定是0 0 0 0 0 1 1 1 1

最右边的0的位置就是s[i]

因为0011具有单调性,所以我们可以二分来找这个s[i]

当然,右可能前面每出现过找不到,此时就询问i

那么这种做法询问是比较多的,因为有log(1000)    6000的数据范围意味着每个i最多6次

考虑优化,显然只有每个字母最后出现的位置是有意义的,那么我们可以只存这个,那么二分次数就大大减小。

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
int pos[30], cnt, n;
char ans[N];

char ask1(int x)
{
    printf("? 1 %d\n", x);
    fflush(stdout);
    string s; cin >> s;   //输入一个字符时要额外小心 应该这么写
    return s[0];
}

int ask2(int l, int r)
{
    printf("? 2 %d %d\n", l, r);
    fflush(stdout);
    int t; scanf("%d", &t);
    return t;
}

int main()
{ 
    scanf("%d", &n);
    ans[1] = ask1(1);
    pos[++cnt] = 1;
    _for(i, 1, n)
    {
        sort(pos + 1, pos + cnt + 1);
        int l = 0, r = cnt + 1;
        while(l + 1 < r)
        {
            int m = l + r >> 1;
            if(ask2(pos[m], i) == (cnt - m + 1)) l = m;
            else r = m;
        }
        if(l == 0)
        {
            ans[i] = ask1(i);
            pos[++cnt] = i;
        }
        else
        {
            ans[i] = ans[pos[l]];
            pos[l] = i;
        }
    }

    printf("! ");
    _for(i, 1, n) putchar(ans[i]);
    fflush(stdout);

    return 0;
}

D. Permutation Graph(单调栈+线段树)

这题妙啊,有两种做法。

第一种做法比较直接,但写起来复杂一些,第二种不太好想到,但是写起来简单

第一种做法

首先,有一个贪心猜测,每一次走尽可能的远。我一开始有这个想法,但不知道这个贪心是不是对的。其实很好证明,如果u最远能达到v,那么u和v中间的节点一定不能连一条边超过v。因为如果可以,比如中间有个节点a,连一条边到v右边的b,那么可以发现u是可以连边到b的,就不满足v是最远的这个前提。

有这个结论后,考虑怎么迅速求这个最远的点,比如当前节点是i,那么a[i]与a[i+1]的大小关系显然决定了a[i]是最大值还是最小值,如果a[i] > a[i+1]那么显然a[i]要作为最大值

如果a[i]是作为最大值的话,设j为离i最近的使得a[j] > a[i],那么可能得答案在[i + 1, j - 1]中

这个j可以用单调栈O(n)求出

在可能的答案区间中,求最小值,这个最小值就是我们要的答案。因为不可能有边从i越过这个最小值,因为i已经是最小值了。所以可以用线段树预处理区间最值,同时存对应的下标(用pair写起来非常简洁),那么就可以迅速求出

每次跳一次的复杂度是线段树的O(logn),而每次至少可以往右移动一个,那么最多跳n次,所以复杂度是O(nlogn)的

#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef pair<int, int> pa;
const int N = 2.5e5 + 10;
int a[N], rmax[N], rmin[N], st[N], top, n;
pa tmax[N << 2], tmin[N << 2];

void up(int k)
{
    tmax[k] = max(tmax[l(k)], tmax[r(k)]);
    tmin[k] = min(tmin[l(k)], tmin[r(k)]);
}

void build(int k, int l, int r)
{
    if(l == r)
    {
        tmax[k] = tmin[k] = {a[l], l};
        return;
    }
    int m = l + r >> 1;
    build(l(k), l, m);
    build(r(k), m + 1, r);
    up(k);
}

pa ask_max(int k, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return tmax[k];
    int m = l + r >> 1;
    pa res = {0, 0};
    if(L <= m) res = max(res, ask_max(l(k), l, m, L, R));
    if(R > m) res = max(res, ask_max(r(k), m + 1, r, L, R));
    return res;
}

pa ask_min(int k, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return tmin[k];
    int m = l + r >> 1;
    pa res = {1e9, 0};
    if(L <= m) res = min(res, ask_min(l(k), l, m, L, R));
    if(R > m) res = min(res, ask_min(r(k), m + 1, r, L, R));
    return res;
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &a[i]);

        top = 0;
        for(int i = n; i >= 1; i--)   //单调栈不用先加入 直接从n开始  
        {
            while(top && a[i] > a[st[top]]) top--;
            rmax[i] = top ? st[top] : n + 1;   //注意栈未空的情况 
            st[++top] = i;
        }
        top = 0;
        for(int i = n; i >= 1; i--)
        {
            while(top && a[i] < a[st[top]]) top--;
            rmin[i] = top ? st[top] : n + 1;
            st[++top] = i;
        }

        build(1, 1, n);
        int ans = 0, cur = 1;
        while(cur != n)
        {
            if(a[cur + 1] > a[cur])
            {
                pa t = ask_max(1, 1, n, cur + 1, rmin[cur] - 1);
                cur = t.second;
            }
            else
            {
                pa t = ask_min(1, 1, n, cur + 1, rmax[cur] - 1);
                cur = t.second;
            }
            ans++;
        }
        printf("%d\n", ans);
    }

    return 0;
}

第二种做法

核心是一个点:不会有边跨过最值

对于ai=n,一定不会有边跨过它,对于aj=1也是

不能跨国它意味着一定要经过它 

同时i和j存在一条边

假设i<j

所以dis(1, n) = dis(1, i) + 1 + dis(j, n)

那么可以这样一直递归下去,只不过最值变成了当前区间的最值

这里的区间要不是l=1,要不是r=n,所以只需要处理前缀最值和后缀最值

还要存值对应的下标

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef pair<int, int> pa;
const int N = 2.5e5 + 10;
int a[N], lmax[N], lmin[N], rmax[N], rmin[N], id[N], n;

int dfs(int l, int r)
{
    if(l == r) return 0;
    int x, y;
    if(l == 1)
    {
        x = min(id[lmax[r]], id[lmin[r]]);
        y = max(id[lmax[r]], id[lmin[r]]);
    }
    else
    {
        x = min(id[rmax[l]], id[rmin[l]]);
        y = max(id[rmax[l]], id[rmin[l]]);
    }
    return dfs(l, x) + 1 + dfs(y, r);
}

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &a[i]), id[a[i]] = i;

        lmax[1] = lmin[1] = a[1];
        _for(i, 2, n)
        {
            lmax[i] = max(lmax[i - 1], a[i]);
            lmin[i] = min(lmin[i - 1], a[i]);
        }
        rmax[n] = rmin[n] = a[n];
        for(int i = n - 1; i >= 1; i--)
        {
            rmax[i] = max(rmax[i + 1], a[i]);
            rmin[i] = min(rmin[i + 1], a[i]);
        }

        printf("%d\n", dfs(1, n));
    }

    return 0;
}

D. Lena and Matrix(拆式子)

这题的关键在于把题目给的式子变换,拆开

分类一下把那个绝对值拆开,发现最值一定是4个点之一,所以有用的只有4个点

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
char s[N][N];
int n, m;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
        vector<pair<int, int>> node(4, {-1, -1});

        scanf("%d%d", &n, &m);
        _for(i, 1, n)
        {
            scanf("%s", s[i] + 1);
            _for(j, 1, m)
            {
                if(s[i][j] == 'W') continue;
                if(node[0].first == -1) 
                {
                    rep(k, 0, 4)
                        node[k] = {i, j};
                }
                else
                {
                    if(i + j > node[0].first + node[0].second) node[0] = {i, j};
                    if(i + j < node[1].first + node[1].second) node[1] = {i, j};
                    if(i - j > node[2].first - node[2].second) node[2] = {i, j};
                    if(i - j < node[3].first - node[3].second) node[3] = {i, j};
                }
            }
        }

        int ans = 1e9, x, y;
        _for(i, 1, n)
            _for(j, 1, m)
            {
                int cur = 0;
                for(auto [xx, yy]: node)
                    cur = max(cur, abs(xx - i) + abs(yy - j));
                if(cur < ans)
                {
                    ans = cur;
                    x = i; y = j;
                }
            }
        printf("%d %d\n", x, y);
    }

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值