大二上第九周学习笔记

周一

Stone Games(思维+主席树)

这里弄了好久,其实也是因为缺的知识点有点多

首先是思路,这道题的思路挺妙的

如果已经能表示[1, x]了,那么多来一个数t,如果这个数 > x + 1

那么显然不会改变能表示的区间,否则区间变为[1, x + t],可以由[1, x]每个数加一个t得到

那么可以一直加入新的数,重复这个过程,直到无法拓展区间

考虑怎么加速这个过程

可以一次性把小于等于x + 1的数一起加上,这些数都是可以拓展区间的

小于等于x + 1的数还包括之前构成[1, x]的数 所以小于等于x + 1的数为sum 

那么区间就变成[1, sum]

总结一下,一开始区间为[1, x] 可以先设x = 0

然后统计所有小于等于x + 1的数的和为sum 区间就拓展为[1, sum]

显然如果得出的sum = x说明不能再拓展了

那么答案就是x + 1

这样更新是很快的,log级别的

那么我们就需要再一段区间内迅速求出小于等于某一个数的和

所以用主席树就可以了

主席树就是加了一个复制前面节点+动态开点的操作,就可以转化为普通的线段树了,普通线段树能做的它都能做。一般是权值线段树。要理解本质

细节看注释

#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 = 1e6 + 10;
int ls[N << 5], rs[N << 5], root[N], n, m, cnt;              //主席树左移5位
ll t[N << 5];

void add(int pre, int &k, int l, int r, int x)               //注意统一参数位置,先pre后k
{   
    k = ++cnt;                                               //建立新节点 不写!k 直接k = ++cnt 因为一定是新的值 和一般的动态开点不同 
    ls[k] = ls[pre]; rs[k] = rs[pre]; t[k] = t[pre] + x;     //动态开点写 if(!k) k = ++cnt  这里已经复制了前面的值不为0,直接k = ++cnt
    if(l == r) return;
    int m = l + r >> 1;
    if(x <= m) add(ls[pre], ls[k], l, m, x);
    else add(rs[pre], rs[k], m + 1, r, x);
}

ll query(int pre, int k, int l, int r, int L, int R)        
{
    if(t[k] - t[pre] == 0 || L > r || R < l) return 0;         //注意动态开点可能询问到空节点
    if(L <= l && r <= R) return t[k] - t[pre];                //和正常线段树操作一样。主席树只是说复制了前面的节点+动态开点
    int m = l + r >> 1;
    return query(ls[pre], ls[k], l, m, L, R) + query(rs[pre], rs[k], m + 1, r, L, R); //这种写法更好
}

int main()
{ 
    scanf("%d%d", &n, &m);
    _for(i, 1, n)
    {
        int x; scanf("%d", &x);
        add(root[i - 1], root[i], 1, 1e9, x);                //注意这里是权值线段树,区间不是1到n
    }

    ll ans = 0;
    while(m--)
    {
        int l, r; ll x = 0;
        scanf("%d%d", &l, &r);
        l = (l + ans) % n + 1;                                                      //注意分开写 不要写一起!!!!
        r = (r + ans) % n + 1;
        if(l > r) swap(l, r);
        while(1)
        {
            ll sum = query(root[l - 1], root[r], 1, 1e9, 1, min(x + 1, (ll)1e9));   //注意这里取一下min 关键
            if(sum == x) break;
            x = sum;
        }
        printf("%lld\n", ans = x + 1);
    }

    return 0;
}

补题时遇到了反悔型贪心和FWT

可以补一补知识点

P2949(反悔贪心之反悔堆)

有一个比较好想到的贪心策略,就是ddl前的先做,然后尽量做利益大的

这样的话有个问题,选择了做某一个之后,可能占用了后面的利益更大的。

也就是这个贪心有点问题

为了解决这个问题,让后面的利益更大的能做,可以用一个堆维护

也就是说每次做不了的时候,就找已经做了的任务里面利益最小的和它比较,如果它更优,就更换

这就是反悔贪心,先贪心,后面可以再反悔

#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;
struct node
{
    int d, v;
}a[N];
int n;

bool cmp(node a, node b)
{
    return a.d < b.d;
}

int main()
{ 
    scanf("%d", &n);
    _for(i, 1, n) scanf("%d%d", &a[i].d, &a[i].v);
    sort(a + 1, a + n + 1, cmp);
   
    priority_queue<int, vector<int>, greater<int>> q;
    _for(i, 1, n)
    {
        if(q.size() + 1 <= a[i].d) q.push(a[i].v);
        else if(a[i].v > q.top()) q.pop(), q.push(a[i].v);
    }

    ll ans = 0;
    while(!q.empty()) ans += q.top(), q.pop();
    printf("%lld\n", ans);

    return 0;
}

周日

这周学校课内容有点多,训练的少了

C. Hakase and Nano(博弈论)

可以发现只能拿一次的人获胜的情况很少

可以用sg函数的思想

全为1的时候结果是固定的,无论怎么取都一样

从这种情况逆推

如果拿一次的可以先手,且可以转化为全1的情况,只有这种情况可能赢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值