cf2009 Codeforces Round 971 (Div. 4)

A. Minimize!

签到题。计算(ca)+(bc)的最小值,其实值固定的,等于ba

int a, b;

void solve()
{
    cin >> a >> b;
    cout << b - a << endl;
}

B. Osu!mania

签到题。给定一个4k下落式的网格,求#下落顺序。直接数组记录就好了。

int n;
const int N = 500;
char s[N][4];
int v[N];

void solve(void)
{
    cin >> n;
    int vlen = 0;
    for (int i = 0; i < n; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            cin >> s[i][j];
            if (s[i][j] == '#')
            {
                v[vlen++] = j + 1;
            }
        }
    }

    for (int i = vlen - 1; i >= 0; --i)
    {
        cout << v[i] << " \n"[i == 0];
    }
}

C. The Legend of Freya the Frog

签到题。一个人从(0,0)出发跳跃到(x,y)。每步只能跳d格,0dk。并且,当朝着x/y方向跳了一次后,下次就会朝着y/x方向。这个人初始朝向x方向。给定(x,y),k求跳的最小次数。

小学数学算一下是x->y->x->y->x还是x->y->x->y就行了。

ll x, y, k;

void solve()
{
    cin >> x >> y >> k;
    ll dx = (x + k - 1) / k, dy = (y + k - 1) / k;
    ll res = -1;
    if (dx > dy)
    {
        res = (dx << 1) - 1;
    }
    else
    {
        res = dy << 1;
    }
    cout << res << endl;
}

D. Satyam and Counting

水题。现在有一个1xn的网格,给出n个这个网格上已有的格点坐标。求这些格点能组成多少个直角三角形。分情况讨论再加起来就行。

  1. 两个点能组成一条竖边,则剩下的n2个点都可以和它们组成三角形。遍历所有点,记录竖边数量c1,则这种三角形就有c1×(n2)个。

    image-20240924222508380
  2. 两个点能组成一条斜边。然后它们中间也有一个点。不要忘记(1,1)(2,0)(3,1)这样颠倒过来也能组成三角形就行了。遍历的时候记录一下即可。

image-20240924222710646
int n;
const int N = 2e5 + 5;
int a[N][2];

void solve()
{
    scanf("%d", &n);
    for (int i = 0; i <= n + 1; ++i)
    {
        a[i][0] = a[i][1] = 0;
    }
    int p, q;
    for (int i = 0; i < n; ++i)
    {
        scanf("%d%d", &p, &q);
        a[p][q] = 1;
    }
    ll one = 0, two = 0, three = 0;
    for (int i = 0; i <= n; ++i)
    {
        if (a[i][0] && a[i][1])
        {
            one = one + n - 2;
        }
        if (i + 2 <= n && a[i][0] && a[i + 1][1] && a[i + 2][0])
        {
            two++;
        }
        if (i + 2 <= n && a[i][1] && a[i + 1][0] && a[i + 2][1])
        {
            three++;
        }
    }
    printf("%lld\n", one + two + three);
}

E. Klee's SUPER DUPER LARGE Array!!!

Klee有一个长度为n的序列[k,k+1,...,k+n-1],他想选定一个下标i,使得x=|a1+a2+..+aiai+1...an|最小。2n,k109

水题,这个x随着i变大,必是单调增加的,所以二分即可。(狼狈回忆二分)

ll n, k;

ll check(ll x)
{
    ll right = (n + k - 1) * (n + k) / 2LL - (x + 1) * x / 2LL;
    ll left = (x + 1) * x / 2LL - (k - 1) * k / 2LL;
    return right - left;
}

void solve()
{
    cin >> n >> k;
    ll res = 1e18;
    ll l = 1, r = n + k - 1;
    while (l < r)
    {
        ll mid = (l + r) >> 1;
        ll cur = check(mid);
        res = min(res, abs(cur));
        if (cur <= 0)
        {
            r = mid;
        }
        else
        {
            l = mid + 1;
        }
    }
    cout << res << endl;
}

F. Firefly's Queries

小水题。有一个长度为n的数组a[n]。然后定义了一个cyclic shift的操作。(The x-th(1xn) cyclic shift of the array a is ax,ax+1,...,an,a1,a2,...,ax1)。ci就代表了a的第i个cyclic shift数组。然后又创建数组b等于c1,c2,...,cn(所有的ci concatenation到一起)。现有q次查询,每次查询输出b[l:r]的元素和。

包含整个ci的部分就是a[1:n],然后再加上一个小尾巴就行了。

int n, q;
const int N = 2e5 + 5;
int a[N];
ll pre[N];

ll cal(ll x)
{
    if (x < 0LL)
        return 0LL;
    ll p = x / n, q = x % n;
    if (p == 0)
    {
        return pre[x];
    }
    ll left = pre[n - 1] * p;
    ll pos = (p + q) % n;
    ll right = 0;
    if (pos >= p)
    {
        right = pre[pos] - pre[p - 1];
    }
    else
    {
        right = pre[n - 1] - pre[p - 1] + pre[pos];
    }
    return left + right;
}

void solve()
{
    cin >> n >> q;
    for (int i = 0; i < n; ++i)
    {
        cin >> a[i];
        pre[i] = (i == 0) ? (a[i]) : (pre[i - 1] + a[i]);
    }
    ll l, r;
    while (q--)
    {
        cin >> l >> r;
        ll lres = cal(l - 2), rres = cal(r - 1);
        cout << rres - lres << endl;
    }
}

G1. Yunli's Subarray Queries (easy version)

有点思维的水题。题面:

对于任意数组b(下标从1开始),Yunli可以执行以下操作任意次:

  • 选择下标i,令b[i]=xx可以是任意整数。

f(b)为她把b变成一个满足以下条件的数组的最小操作次数:存在长度k的子序列bsub(下标从1开始),j>1,bsub[j]=bsub[j1]+1。(consecutive subarray)

现给定数组a[n]以及q次查询,在每次查询时,输出j=l+k1rf([al,al+1,..,aj])。在easy version中,rl+k1。也就是说,输出的是f([al,al+1,..,aj])

数据范围:n2×105,q2×105,1a[i]n

思路:

首先,我们要知道这样一件事:怎么找一个consecutive subarray?

(1)(2)(3)(4)(5)(6) k=3
 2  3  4  9  6  11

对于这个a[n],我们知道可以把2,3,4,x,6中的x改为5,也可以将9,x,11中的x改为10。或者我们可以换一种角度看问题:2,3,4,(x),6是属于一组的,9,(x),11是属于另一组的。想要判断数与数之间有没有属于同一组的机会,我们只要对数组a做一个简单的操作。

a[i]=a[i]i,此时

(1)(2)(3)(4)(5)(6) k=3
 1  1  1  5  1  5

能看出这件事,这道题就结束了。做一个长度为k的滑动窗口,记录目前的窗口中数量最多的元素即可。

int n, k, q;
const int N = 2e5 + 5;
int a[N], res[N];

void solve()
{
    cin >> n >> k >> q;
    for (int i = 0; i < n; ++i)
    {
        cin >> a[i];
    }
    int last = a[0];
    map<int, int> mp;
    set<pii, greater<pii>> s;

    for (int i = 0; i < n; ++i)
    {
        if (i >= k)
        {
            res[i - k] = k - s.begin()->first;
        }
        // add nxt
        if (mp.count(a[i] - i))
        {
            pii cur = pii(mp[a[i] - i], a[i] - i);
            if (s.count(cur))
                s.erase(cur);
        }
        mp[a[i] - i]++;
        s.insert(pii(mp[a[i] - i], a[i] - i));
        // erase last
        if (i >= k)
        {
            s.erase(pii(mp[last - i + k], last - i + k));
            if (mp[last - i + k] == 1)
            {
                mp.erase(last - i + k);
            }
            else
            {
                mp[last - i + k]--;
                s.insert(pii(mp[last - i + k], last - i + k));
            }
            last = a[i - k + 1];
        }
    }
    res[n - k] = k - s.begin()->first;

    while (q--)
    {
        int l, r;
        cin >> l >> r;
        cout << res[l - 1] << endl;
    }
}

G2. Yunli's Subarray Queries (hard version)

这题是上题的困难版本,其中r的条件改变为rl+k1,求j=l+k1rf([al,al+1,..,aj])

这题需要用到上一题的结论,令pi为滑动窗口的起始位置为i时,记录的a[i:i+k1]重复数量最多的元素数。ci=kpi,也就是ci=f([al,al+1,..,al+k1])。则f([al,al+1,..,aj])=min(cl,cl+1,..,cjk+1)

区间求最值可以用线段树or树状数组来做,但是还要解决另一个事情,我们要求的是j=l+k1rmin{c[l:jk+1]},还有个区间求和。再进一步观察,可以看到另一条性质。假设数组c如下:

(1)(2)(3)(4)(5)(6)(7)
 1  3  7  4  5  3  6

现在我们算c[4]c[7]这段的值

(1)(2)(3)(4)(5)(6)(7)
          4  4  3  3

可以看到,值是单调非增的。也就是说,当l=4时,c[4]=4,这个值会一直覆盖后面的值,直到发现了c[6]<4,则再往后更新就用c[6]的值更新后面的。那我们就可以想到用一个单调栈来解决这件事。从后往前遍历,当遍历到c[cur]时,弹出栈顶元素c[top],直到c[top]<c[cur]。然后将c[cur]c[top1]的值都更新为c[cur]。然后再通过区间求和算出此时的i=currci即可,时间复杂度O(nlogn)

int n, k, q;
const int N = 2e5 + 5;
ll a[N], c[N];
ll res[N];

struct node{
    int l, r;
    ll v, lz;
};
node seg[N<<2];

void pushup(int oo){
    seg[oo].v = seg[ls].v + seg[rs].v;
}

void pushdown(int oo){
    int mid = seg[oo].l + seg[oo].r >> 1;
    seg[ls].v = seg[oo].lz * (mid - seg[oo].l + 1LL);
    seg[rs].v = seg[oo].lz * (seg[oo].r - mid);
    seg[ls].lz = seg[rs].lz = seg[oo].lz;
    seg[oo].lz = 0;
}

void build(int oo, int l, int r){
    seg[oo] = node{l, r, 0LL, 0LL};
    if (l >= r) return;
    int mid = l + r >> 1;
    build(ls, l, mid);
    build(rs, mid+1, r);
    pushup(oo);
}

void update(int oo, int x, int y, ll val){
    int l = seg[oo].l, r = seg[oo].r;
    if (x <= l && y >= r){
        seg[oo].v = val * (r - l + 1);
        seg[oo].lz = val;
        return;
    }
    int mid = l + r >> 1;
    if (seg[oo].lz) pushdown(oo);
    if (mid >= x) update(ls, x, y, val);
    if (y > mid) update(rs, x, y, val);
    pushup(oo);
}

ll query(int oo, int x, int y){
    int l = seg[oo].l, r = seg[oo].r;
    if (x <= l && y >= r){
        return seg[oo].v;
    }
    ll ans = 0;
    int mid = l + r >> 1;
    if (seg[oo].lz) pushdown(oo);
    if (x <= mid) ans += query(ls, x, y);
    if (y > mid) ans += query(rs, x, y);
    return ans;
}

void calc(){
    map<int, int> mp;
    set<pii, greater<pii> > st;
    for (int i = 1; i <= n; ++i){
        // pop last
        if (i > k){
            int last = a[i - k] - i + k;
            st.erase(pii(mp[last], last));
            mp[last]--;
            if (mp[last] == 0) mp.erase(last);
            else st.insert(pii(mp[last], last));
        }
        // push cur
        st.erase(pii(mp[a[i] - i], a[i] - i));
        mp[a[i] - i]++;
        st.insert(pii(mp[a[i] - i], a[i] - i));
        // calculate c value
        if (i >= k){
            pii x = *st.begin();
            c[i - k + 1] = - x.first;
        }
    }
}

void solve(){
    cin >> n >> k >> q;
    vector<vector<pii> > vec(n - k + 2);
    for (int i = 1; i <= n; ++i){
        cin >> a[i];
    }
    int l, r;
    for (int i = 1; i <= q; ++i){
        cin >> l >> r;
        vec[l].push_back(pii(r - k + 1, i));
    }
    calc();
    build(1, 1, n - k + 1);
    stack<int> stk;
    for (int i = n - k + 1; i >= 1; --i){
        while (!stk.empty() && c[i] <= c[stk.top()]){
            stk.pop();
        }
        int ed = stk.empty() ? (n - k + 1) : (stk.top() - 1);
        stk.push(i);
        update(1, i, ed, c[i]);
        for (auto p: vec[i]){
            res[p.second] = 1LL * (p.first - i + 1) * k + query(1, i, p.first);
        }
    }
    for (int i = 1; i <= q; ++i){
        cout << res[i] << endl;
    }
}
posted @   跳岩  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示