mns 1021
A
tag:博弈?diff:Y
水题。
发现实际上操作只会在把一个奇数和一个偶数合并起来之后才会出现 \(1\) 的损失。所以必然是先手把两个奇数合并在一起,然后后手把一个奇数和一个偶数合并在一起(第一次合并之后数列里面必然有偶数)。
所以设数列里面有 \(c\) 个奇数时,会先损掉 \(\lfloor \frac{c}{3} \rfloor\) 个奇数。然后,在这么损之后,如果数列里面还有一个奇数,那么还得损掉一个。所以会损失掉 \(\lfloor \frac{c}{3} \rfloor + [c \bmod 3 = 1]\),模拟即可。\(O(n)\)。
#include <bits/stdc++.h>
#define int ll
#define ctz __builtin_ctzll
#define popc __builtin_popcountll
#define lowbit(x) ((x) & -(x))
#define ct1(x) ctz((x) + 1)
//#define DEBUG
using namespace std;
using ll = long long;
const int kMod = int(1e9) + 7, kInf = 0x3f3f3f3f3f3f3f3f;
template<class T>T read(istream &is = cin) {
T x;
is >> x;
return x;
}
int readInt(istream &is = cin) {return read<int>(is);}
int qpow(int x, int y) {
int res = 1;
for(; y; y >>= 1) {
if(y & 1) res = res * x % kMod;
x = x * x % kMod;
}
return res;
}
int qinv(int x) {return qpow(x, kMod - 2);}
int n, arr[100010];
signed main() {
freopen("makise.in", "r", stdin);
freopen("makise.out", "w", stdout);
#ifndef DEBUG
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> n;
for(int i = 1; i <= n; i++) cin >> arr[i];
cout << arr[1] << ' ';
int c = arr[1] % 2, s = arr[1];
for(int i = 2; i <= n; i++) {
s += arr[i], c += (arr[i] % 2);
cout << s - (c / 3) - (c % 3 == 1) << ' ';
}
cout << '\n';
return 0;
}
B
tag:枚举,贪心? diff:G/C
想了好一会儿。
一开始以为贪心选最大值就可以了,但事实上这样连样例都过不去。
可以枚举最后重复购买的东西,然后贪心把重复购买的东西用更大价值的物品(只可以单次购买的)替换。1实现的好是 \(O(n \log n)\) 的。
我一开始以为这是网络流。
#include <bits/stdc++.h>
#define int ll
#define ctz __builtin_ctzll
#define popc __builtin_popcountll
#define lowbit(x) ((x) & -(x))
#define ct1(x) ctz((x) + 1)
//#define DEBUG
using namespace std;
using ll = long long;
const int kMod = int(1e9) + 7, kInf = 0x3f3f3f3f3f3f3f3f;
template<class T>T read(istream &is = cin) {
T x;
is >> x;
return x;
}
int readInt(istream &is = cin) {return read<int>(is);}
int qpow(int x, int y) {
int res = 1;
for(; y; y >>= 1) {
if(y & 1) res = res * x % kMod;
x = x * x % kMod;
}
return res;
}
int qinv(int x) {return qpow(x, kMod - 2);}
struct node {
int a, b;
}arr[300030];
int t, n, m, ax[300030], as[300030];
signed main() {
freopen("shop.in", "r", stdin);
freopen("shop.out", "w", stdout);
#ifndef DEBUG
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
for(cin >> t; t--; ) {
cin >> n >> m;
for(int i = 1; i <= m; i++) {
cin >> arr[i].a >> arr[i].b;
}
sort(arr + 1, arr + m + 1, [](node a, node b) {return a.a < b.a;});
for(int i = m; i >= 1; i--) as[i] = as[i + 1] + (ax[i] = arr[i].a);
int ans = 0;
for(int i = 1; i <= m; i++) {
int tx = lower_bound(ax + 1, ax + m + 1, arr[i].b) - ax;
int curr = ax[i], rst = n - 1;
if(i >= tx) curr = 0, rst = n;
//cout << i << ' ' << curr << ' ' << rst << ' ' << m - rst + 1 << ' ' << tx << '\n';
curr += as[max(tx, m - rst + 1)], rst -= min(rst, m - tx + 1);
curr += rst * arr[i].b;
ans = max(ans, curr);
//cout << i << ' ' << curr << ' ' << rst << '\n';
}
cout << ans << '\n';
fill(ax + 1, ax + m + 1, 0);
fill(as + 1, as + m + 1, 0);
}
return 0;
}
C
tag:dp,线段树,单调栈,实现困难 diff:G/C
可以设 \(dp_i\) 表示只考虑前 \(i\) 个元素的最小代价。有转移
其中 \(w(j + 1, i)\) 为把 \(j + 1\) 到 \(i\) 直接分为一段时所能得到的代价。
发现 \(w(j + 1, i)\) 相同的 \(j\) 可以合并处理。线段树维护区间最大值,单调栈维护可能的转移,std::set 维护最终转移到 \(i\) 的值即可。注意可能 \(j + 1\) 到 \(i\) 的最小的 \(a_x\) 刚好是 \(a_i\)。\(O(n \log n)\)。
#include <bits/stdc++.h>
#define int ll
#define ctz __builtin_ctzll
#define popc __builtin_popcountll
#define lowbit(x) ((x) & -(x))
#define ct1(x) ctz((x) + 1)
//#define DEBUG
using namespace std;
using ll = long long;
const int kMod = int(1e9) + 7, kInf = 0x3f3f3f3f3f3f3f3f;
template<class T>T read(istream &is = cin) {
T x;
is >> x;
return x;
}
int readInt(istream &is = cin) {return read<int>(is);}
int qpow(int x, int y) {
int res = 1;
for(; y; y >>= 1) {
if(y & 1) res = res * x % kMod;
x = x * x % kMod;
}
return res;
}
int qinv(int x) {return qpow(x, kMod - 2);}
int n, dp[100010], arr[100010], brr[100010], d[400040];
vector<int> stk;
multiset<int, greater<int> > st;
void build(int id = 1, int l = 0, int r = n) {
if(l == r) {
d[id] = -kInf * (!!l);
return ;
}
int mid = (l + r) >> 1;
build(id * 2, l, mid);
build(id * 2 + 1, mid + 1, r);
d[id] = max(d[id * 2], d[id * 2 + 1]);
}
void modify(int x, int v, int id = 1, int l = 0, int r = n) {
if(l == r) {
d[id] = v;
return ;
}
int mid = (l + r) >> 1;
if(x <= mid) modify(x, v, id * 2, l, mid);
else modify(x, v, id * 2 + 1, mid + 1, r);
d[id] = max(d[id * 2], d[id * 2 + 1]);
}
int query(int ql, int qr, int id = 1, int l = 0, int r = n) {
if(ql > qr) return 0;
if(r < ql || qr < l) return -kInf;
if(ql <= l && r <= qr) return d[id];
int mid = (l + r) >> 1;
return max(query(ql, qr, id * 2, l, mid), query(ql, qr, id * 2 + 1, mid + 1, r));
}
signed main() {
freopen("divide.in", "r", stdin);
freopen("divide.out", "w", stdout);
#ifndef DEBUG
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> n;
for(int i = 1; i <= n; i++) cin >> arr[i];
for(int i = 1; i <= n; i++) cin >> brr[i];
arr[0] = n + 1;
brr[0] = n + 1;
stk.push_back(0);
st.insert(n + 1);
for(int i = 1; i <= n; i++) {
//cout << i << '\n';
for(; stk.size() && arr[stk.back()] > arr[i]; stk.pop_back()) {
//cout << i << ' ' << stk.size() - 2 << '\n';
//cout << i << ' ' << stk.back() << ' ' << brr[stk.back()] + query((stk.size() == 1? 0 : stk[stk.size() - 2]), stk.back() - 1) << '\n';
if(st.find(brr[stk.back()] + query((stk.size() == 1? 0 : stk[stk.size() - 2]), stk.back() - 1)) != st.end()) {
st.erase(st.find(brr[stk.back()] + query((stk.size() == 1? 0 : stk[stk.size() - 2]), stk.back() - 1)));
}
//cout << i << '\n';
}
//cout << i << '\n';
int tmp = brr[i] + query((stk.size()? stk.back() : 0), i - 1);
//cout << i << ' ' << brr[i] << ' ' << query((stk.size()? stk.back() : 0), i - 1) << ' ' << tmp << ' ' << (st.size()? *st.begin() : -1) << ' ';
if(st.size()) tmp = max(tmp, *st.begin());
dp[i] = tmp;
modify(i, dp[i]);
st.insert(query((stk.size()? stk.back() : 0), i - 1) + brr[i]);
stk.push_back(i);
//cout << dp[i] << '\n';
}
cout << dp[n] << '\n';
return 0;
}
/*
5
1 2 3 5 4
1 5 3 2 4
5
1 4 3 2 5
-3 4 -10 2 7
*/
D
tag:点分治,DSU on tree,实现困难 diff:K
虽然我觉得这个题思维难度没有 K,但是这两个算法加起来,加上实现困难,确实得评 K 了。
考虑点分治,现在状况是我们有了一个根 \(x\),要统计求 \(dis\) 时经过 \(x\) 的贡献。有两种情况:
【这里一张图】
有两个点的 LCA 不是 \(x\),此时 \(d_1 + d_2 = d_3 = d_4\)。
【这里一张图】
任意两个点的 LCA 都是 \(x\),此时 \(d_1 = d_2 = d_3\)。
第一种情况可以 DSU on tree 解决,第二种情况对于每一种深度维护 \(dp_{dep, i}\) 表示这种深度选 \(i\) 个产生的方案数,搞一搞就可以了。\(O(n \log^2 n)\)?
顺便吐槽一嘴,这个题写了我 1.5h,期间漏了好多贡献,特别难调。
赛后发现这个题正解是长链剖分,可以做到 \(O(n)\)。糖丸了。
#include <bits/stdc++.h>
#define int ll
#define ctz __builtin_ctzll
#define popc __builtin_popcountll
#define lowbit(x) ((x) & -(x))
#define ct1(x) ctz((x) + 1)
//#define DEBUG
using namespace std;
using ll = long long;
const int kMod = int(1e9) + 7, kInf = 0x3f3f3f3f3f3f3f3f;
template<class T>T read(istream &is = cin) {
T x;
is >> x;
return x;
}
int readInt(istream &is = cin) {return read<int>(is);}
int qpow(int x, int y) {
int res = 1;
for(; y; y >>= 1) {
if(y & 1) res = res * x % kMod;
x = x * x % kMod;
}
return res;
}
int qinv(int x) {return qpow(x, kMod - 2);}
int n, u, v;
vector<int> to[100010];
int del[100010], sb[100010], dep[100010], dp[100010][4];
map<int, int> DSU[100010]; // for DSU on tree
void _get(int x, int pa) {
sb[x] = 1;
for(auto i : to[x]) {
if(i != pa && !del[i]) {
_get(i, x);
sb[x] += sb[i];
}
}
}
int get(int x) {
_get(x, 0);
int u = x, fa = 0, ss = sb[x] / 2;
for(; ; ) {
for(auto i : to[u]) {
if(i != fa && !del[i] && sb[i] > ss) {
fa = u;
u = i;
goto nxt;
}
}
break;
nxt:;
}
return u;
}
void DFS1(int x, int pa) {
for(auto i : to[x]) {
if(i != pa && !del[i]) {
dep[i] = dep[x] + 1;
DFS1(i, x);
}
}
}
// DSU on tree
int DSUot(int x, int pa, int rt) {
int res = 0;
for(auto i : to[x]) {
if(i != pa && !del[i]) {
//cout << x << ' ' << i << '\n';
res += DSUot(i, x, rt);
if(DSU[x].size() < DSU[i].size()) swap(DSU[x], DSU[i]);
for(auto j : DSU[i]) {
if(DSU[rt].find(j.first - dep[x] - dep[x]) != DSU[rt].end() && DSU[x].find(j.first) != DSU[x].end()) {
res += DSU[rt][j.first - dep[x] - dep[x]] * j.second * DSU[x][j.first];
}
DSU[x][j.first] += j.second;
}
DSU[i].clear();
}
}
DSU[x][dep[x]]++;
//cout << x << '\n';
//for(auto i : DSU[x]) cout << i.first << ',' << i.second << ' ';
//cout << '\n';
return res;
}
int solve(int x) {
//cout << "------------------" << '\n';
x = get(x);
//cout << x << '\n';
int res = 0;
dep[x] = 0;
DFS1(x, 0);
DSU[x][0]++;
//*
for(auto i : to[x]) {
if(!del[i]) {
int tmp = DSUot(i, x, x);
//cout << x << ' ' << i << ' ' << tmp << '\n';
res += tmp;
for(auto j : DSU[i]) {
//cout << j.first << ' ' << j.second << '|';
//cout << dp[j.first][1] << ' ' << dp[j.first][2] << ' ' << dp[j.first][3] << ',';
DSU[x][j.first] += j.second;
dp[j.first][0] = 1;
dp[j.first][3] += dp[j.first][2] * j.second;
dp[j.first][2] += dp[j.first][1] * j.second;
dp[j.first][1] += dp[j.first][0] * j.second;
//cout << dp[j.first][1] << ' ' << dp[j.first][2] << ' ' << dp[j.first][3] << '\n';
}
DSU[i].clear();
}
}
//*/
//*
for(auto i : DSU[x]) {
//cout << x << ' ' << i.first << '|' << dp[i.first][0] << ' ' << dp[i.first][1] << ' ' << dp[i.first][2] << ' ' << dp[i.first][3] << '\n';
res += dp[i.first][3];
dp[i.first][0] = dp[i.first][1] = dp[i.first][2] = dp[i.first][3] = 0;
}
//*/
DSU[x].clear();
reverse(to[x].begin(), to[x].end());
//*
for(auto i : to[x]) {
if(!del[i]) {
int tmp = DSUot(i, x, x);
//cout << x << ' ' << i << ' ' << tmp << '\n';
res += tmp;
for(auto j : DSU[i]) {
DSU[x][j.first] += j.second;
}
DSU[i].clear();
}
}
//*/
DSU[x].clear(), del[x] = 1;
for(auto i : to[x]) {
if(!del[i]) res += solve(i);
}
return res;
}
signed main() {
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
#ifndef DEBUG
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
#endif
cin >> n;
for(int i = 1; i < n; i++) {
cin >> u >> v;
to[u].push_back(v);
to[v].push_back(u);
}
cout << solve(1) << '\n';
return 0;
}
/*
12
1 2
1 3
1 4
1 5
1 6
4 7
7 8
7 9
8 10
9 11
4 12
*/

浙公网安备 33010602011771号