赛前最后的努力了,加油!!
前几天疯狂写作业,现在还有三场比赛的题没补,加油!
这题的关键在于只有n个人,怎么利用这个保证复杂度
考虑维护这n个人的离开时间,用一个优先队列维护
那么对于当前状态,可以处理处当前每个队的人数,怎么快速求最值呢
这个其实就是一个可以修改的堆,可以用set或者线段树实现
#include
#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 long long ll;
typedef pair pii;
const int N = 2e5 + 10;
pair b[N];
int t[N << 2], n, m;
ll last[N];
void up(int k)
{
t[k] = min(t[l(k)], t[r(k)]);
}
void clear(int k, int l, int r)
{
t[k] = 0;
if(l == r) return;
int m = l + r >> 1;
clear(l(k), l, m);
clear(r(k), m + 1, r);
}
void modify(int k, int l, int r, int x, int p)
{
if(l == r)
{
t[k] += p;
return;
}
int m = l + r >> 1;
if(x <= m) modify(l(k), l, m, x, p);
else modify(r(k), m + 1, r, x, p);
up(k);
}
int ask(int k, int l, int r)
{
if(l == r) return l;
int m = l + r >> 1;
if(t[l(k)] <= t[r(k)]) return ask(l(k), l, m);
else return ask(r(k), m + 1, r);
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d%d", &b[i].first, &b[i].second);
sort(b + 1, b + n + 1);
_for(i, 1, m) last[i] = 0;
clear(1, 1, m);
ll ans = 0;
priority_queue, greater> q;
_for(i, 1, n)
{
auto [a, s] = b[i];
while(!q.empty() && a >= q.top().first)
{
modify(1, 1, m, q.top().second, -1);
q.pop();
}
int cur = ask(1, 1, m);
if(a >= last[cur]) last[cur] = a + s;
else last[cur] += s;
ans = max(ans, last[cur]);
modify(1, 1, m, cur, 1);
q.push({last[cur], cur});
}
printf("%lld\n", ans);
}
return 0;
}
每一层加一个点,这一层连到它,然后它连向d+k层的所有点,然后跑最短路即可
#include
#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 = 2e6 + 10;
ll d[N];
struct node
{
int v; ll w;
bool operator < (const node& rhs) const
{
return w > rhs.w;
}
};
vector> g[N];
vector ve[N];
int n, k, p, s, t, cnt;
void dfs(int u, int fa, int dep)
{
ve[dep].push_back(u);
for(auto [v, w]: g[u])
{
if(v == fa) continue;
dfs(v, u, dep + 1);
}
}
void solve()
{
_for(i, 1, 2 * n) d[i] = 1e18;
d[s] = 0;
priority_queue q;
q.push({s, d[s]});
while(!q.empty())
{
node x = q.top(); q.pop();
int u = x.v;
if(d[u] != x.w) continue;
for(auto [v, w]: g[u])
if(d[v] > d[u] + w)
{
d[v] = d[u] + w;
q.push({v, d[v]});
}
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, 2 * n) g[i].clear(), ve[i].clear();
_for(i, 1, n - 1)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
g[u].push_back({v, w});
g[v].push_back({u, w});
}
scanf("%d%d%d%d", &k, &p, &s, &t);
dfs(1, 0, 1);
cnt = n;
_for(i, 1, n)
{
if(!ve[i].size()) break;
cnt++;
for(int x: ve[i]) g[x].push_back({cnt, 0});
for(int x: ve[i + k]) g[cnt].push_back({x, p});
if(i - k >= 1) for(int x: ve[i - k]) g[cnt].push_back({x, p});
}
solve();
printf("%lld\n", d[t]);
}
return 0;
}
这是一类博弈题,就是说操作次数其实是一定的,直接算操作次数的奇偶即可,关键是怎么算。
对于这题,可以发现限制为0把数分成了很多段。
每一段内,每个数尽可能的小,从最小的数开始往上。
#include
#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;
map mp;
int n, k;
ll a[N];
ll cal(vector ve, int st) //函数这里不要忘记开long long
{
if(!ve.size()) return 0;
ll sum = 0;
for(auto x: ve) sum += x - st;
int pos = st - 1;
while(mp.count(pos + 1) && mp[pos + 1]) pos++;
if(pos == st - 1) return sum;
ll res = 0, num = ve.size();
_for(i, st, pos)
{
if(num <= mp[i])
{
res += 1LL * (i - st) * num; //相乘的时候注意开1LL
num = 0;
break;
}
else
{
res += 1LL * (i - st) * mp[i];
num -= mp[i];
}
}
res += num * (pos + 1 - st);
return sum - res;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
mp.clear();
scanf("%d%d", &n, &k);
_for(i, 1, n) scanf("%lld", &a[i]);
sort(a + 1, a + n + 1);
vector ve;
while(k--)
{
int x, y;
scanf("%d%d", &x, &y);
mp[x] = y;
if(!y) ve.push_back(x);
}
ve.push_back(-1);
ve.push_back(2e9);
sort(ve.begin(), ve.end());
ll ans = 0;
rep(i, 0, ve.size() - 1)
{
int l = lower_bound(a + 1, a + n + 1, ve[i]) - a;
int r = lower_bound(a + 1, a + n + 1, ve[i + 1]) - a - 1;
vector v;
_for(j, l, r) v.push_back(a[j]);
ans += cal(v, ve[i] + 1);
}
puts(ans % 2 == 1 ? "Pico" : "FuuFuu");
}
return 0;
}
比赛时是推出结论的
其实如果打表找一下规律,能出的更快
首先异或与加法联系紧密
对于A^x对A的改变为正负x
可以这么理解,异或的作用就是为1的地方取反,那么最多就是全部把0变1,也就是加x,相反最小就是减x。
范围宽放一点话就是二进制位
那么kx^x=kx+a
gcd(kx ^ x, x) = gcd(kx + a, x) = gcd(a, x)
也就是说其实gcd取决于异或后改变了多少
那么题目给的x是1e6,这个非常关键,意味着不会改变太多
再考虑二进制位,会发现如果k的范围可以遍历二进制位的所有数,那么一定会有重复,那么重复就可以想到循环节
考虑t为大于x的最小的二次幂数
那么kx = (k + t) x (mod t)
因为改变的就是低位的二进制,所以只用看低位二进制的,那么发现t是循环 节
也就是说,gcd(kx ^ x, x)以t为循环节
那么l和r转化为前缀和
对于当前,看有几个t,多出来的部分用前缀和
#include
#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 = 2e6 + 10;
ll s[N], mod;
ll gcd(ll a, ll b) { return !b ? a: gcd(b, a % b); }
ll cal(ll r)
{
ll k = r / mod;
return k * s[mod] + s[r - k * mod];
}
int main()
{
ll x, n;
scanf("%lld%lld", &x, &n);
ll t = 1;
while(t <= x) t <<= 1;
mod = t;
_for(i, 1, mod) s[i] = s[i - 1] + (gcd(i * x ^ x, x) == 1);
while(n--)
{
ll l, r;
scanf("%lld%lld", &l, &r);
printf("%lld\n", cal(r) - cal(l - 1));
}
return 0;
}
这题的重点是贪心
首先有一个结论,就是对于当前最高的2次幂,如果把它分配给大于等于它的,那么一定不会更差。因为在一个合法解里面,可以进行替换。
那么2的次幂从高到低遍历,首先把大于等于它的都分配给它,如果用完了就下一个,否则把多出来的给前cnt大的,这样肯定是浪费最少的。
二分答案的上界需要思考一下,要小心check的时候爆long long
分母部分为b的和,分子部分为maxa,这是上界。这样子check的时候不会爆long long
#include
#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 = 5e4 + 10;
int a[N], b[25], cnt[25], n, k;
ll c[N];
bool check(ll key)
{
_for(i, 1, n) c[i] = a[i] * key;
_for(i, 1, k) cnt[i] = b[i];
for(int j = k; j >= 1; j--)
{
_for(i, 1, n)
{
ll cur = min(c[i] / (1 << (j - 1)), (ll)cnt[j]);
cnt[j] -= cur;
c[i] -= cur * (1 << (j - 1));
if(!cnt[j]) break;
}
if(cnt[j])
{
cnt[j] = min(cnt[j], n);
nth_element(c + 1, c + cnt[j], c + n + 1, greater());
_for(i, 1, cnt[j]) c[i] = 0;
}
}
_for(i, 1, n)
if(c[i])
return false;
return true;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &k);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, k) scanf("%d", &b[i]);
int mx = 0;
_for(i, 1, n) mx = max(mx, a[i]);
ll l = 0, r = 2e15 / mx;
while(l + 1 < r)
{
ll m = l + r >> 1;
if(check(m)) l = m;
else r = m;
}
printf("%lld\n", l);
}
return 0;
}
推一下三个不等式即可
注意每个位置非负有Si - Si-1 >= 0
这道题是求一个变量的最值,那么建立一个超级源点,对所有点连边权为0的边,然后从这个点开始spfa。注意不要漏掉0
养成好习惯,打完代码后不急着编译,从头肉眼查错,重点看有没有变量名打错
#include
#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;
int d[N], vis[N], cnt[N], S, n, k;
vector> g[N];
bool spfa()
{
_for(i, 0, n + 1) d[i] = -1e9, vis[i] = cnt[i] = 0; //最长路,有负权边,初始化为负无穷
d[S] = 0;
deque q;
q.push_back(S);
while(!q.empty())
{
int u = q.front(); q.pop_front();
vis[u] = 0;
for(auto [v, w]: g[u])
if(d[v] < d[u] + w) //注意这里是最长路
{
d[v] = d[u] + w;
if(!vis[v])
{
if(!q.empty() && d[v] > d[q.front()]) q.push_front(v); //注意判空
else q.push_back(v);
vis[v] = 1;
if(++cnt[v] > n) return false;
}
}
}
return true;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &k);
_for(i, 0, n + 1) g[i].clear();
S = n + 1;
_for(i, 1, n)
{
int p; scanf("%d", &p);
g[max(i - k, 0)].push_back({min(i + k - 1, n), p});
g[i - 1].push_back({i, 0});
}
_for(i, 0, n) g[S].push_back({i, 0});
int q; scanf("%d", &q);
_for(i, 1, q)
{
int l, r, b;
scanf("%d%d%d", &l, &r, &b);
g[r].push_back({l - 1, -b});
}
if(!spfa()) puts("-1");
else printf("%d\n", d[n]);
}
return 0;
}
首先图比较小,可以用矩阵乘法。两个矩阵相乘就是恰好就是计算方案数,于是可以用矩阵来维护。
容易发现可以双指针求,关键是如何迅速求一段的值
可以直接用线段树求,但是会T
因为只关心1到m的答案,所以可以用一个向量不断右乘矩阵,这也可以优化掉一个m
继续养成习惯,写完检查一遍,防止变量。
#include
#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 long long ll;
const int N = 5e3 + 10;
struct martix
{
ll s[25][25];
martix() { memset(s, 0, sizeof s); }
}t[N << 2];
int n, m, k;
ll v[25];
martix mul(martix A, martix B)
{
martix C;
_for(i, 1, m)
_for(j, 1, m)
_for(k, 1, m)
C.s[i][j] += A.s[i][k] * B.s[k][j];
return C;
}
void mul(martix A)
{
ll res[25] = {0};
_for(i, 1, m)
_for(j, 1, m)
res[i] += v[j] * A.s[j][i];
_for(i, 1, m) v[i] = res[i];
}
void up(int k)
{
t[k] = mul(t[l(k)], t[r(k)]);
}
void build(int k, int l, int r)
{
if(l == r)
{
int l; scanf("%d", &l);
memset(t[k].s, 0, sizeof t[k].s);
_for(i, 1, m) t[k].s[i][i] = 1;
while(l--)
{
int u, v;
scanf("%d%d", &u, &v);
t[k].s[u][v] = 1;
}
return;
}
int m = l + r >> 1;
build(l(k), l, m);
build(r(k), m + 1, r);
up(k);
}
void ask(int k, int l, int r, int L, int R)
{
if(L <= l && r <= R)
{
mul(t[k]);
return;
}
int m = l + r >> 1;
if(L <= m) ask(l(k), l, m, L, R);
if(R > m) ask(r(k), m + 1, r, L, R);
}
bool check(int l, int r)
{
_for(i, 1, m) v[i] = 0;
v[1] = 1;
ask(1, 1, n, l, r);
return v[m] <= k;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d%d", &n, &m, &k);
build(1, 1, n);
int r = 0, ans = 0;
_for(l, 1, n)
{
while(r + 1 <= n && check(l, r + 1)) r++;
ans = max(ans, r - l + 1);
}
printf("%d\n", ans);
}
return 0;
}
还有一种骚操作可以避免删除。
在l和r中加一个mid,每次计算拆成[l, mid] 乘上 [mid + 1, r]
右半部分r拓展的时候维护
左半部分用一个bi维护i到mid的值
当l超过mid的时候,mid=r,然后计算bi。
这样依然是O(n)的,同时避免了删除操作
#include
#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 = 5e3 + 10;
struct martix
{
ll s[25][25];
martix() { memset(s, 0, sizeof s); }
}a[N], b[N];
int n, m, k;
martix mul(martix A, martix B)
{
martix C;
_for(i, 1, m)
_for(j, 1, m)
_for(k, 1, m)
C.s[i][j] += A.s[i][k] * B.s[k][j];
return C;
}
void init(martix& A) //初始化为单位矩阵 注意引用
{
memset(A.s, 0, sizeof A.s);
_for(i, 1, m) A.s[i][i] = 1;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d%d", &n, &m, &k);
_for(i, 1, n)
{
init(a[i]);
int l; scanf("%d", &l);
while(l--)
{
int u, v;
scanf("%d%d", &u, &v);
a[i].s[u][v] = 1;
}
}
int l = 1, mid = 1, r = 1, ans = 0;
martix cur; init(cur);
b[1] = a[1];
_for(l, 1, n)
{
if(l > mid)
{
mid = r;
init(cur);
martix t; init(t);
for(int i = mid; i >= l; i--)
{
t = mul(a[i], t);
b[i] = t;
}
}
while(r + 1 <= n && mul(b[l], mul(cur, a[r + 1])).s[1][m] <= k) cur = mul(cur, a[++r]);
ans = max(ans, r - l + 1);
}
printf("%d\n", ans);
}
return 0;
}