周一
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的情况,只有这种情况可能赢