CF,AT杂题
ABC452F
step1:注意到对于满足条件的区间,\(r\) 递增时 \(l\) 也是单增的,所以可以用双指针 \(O(n)\) 扫。
step2:注意到左右指针移动一步,新增(减少)的逆序对个数就是当前区间比新增(减去)的数大(小)的数的个数。所以可以用值域树状数组维护逆序对个数。
trick1:对于每一个 \(r\),求对应满足等于 \(k\) 个的 \(l\) 可以用大于等于 \(k\) 的 \(l\) 位置减去大于的 \(k\) 的 \(l\) 位置,中间的就是等于的。于是扫两次分别维护即可。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e5 + 5;
int n, k, p[N];
int ans;
struct tree{
int a[N];
inline int lowbit(int x){ return x & (-x); }
inline void modify(int x, int k){ x++; for(; x < N; x += lowbit(x)) a[x] += k; }
inline int query(int x){ x++; int ans = 0; for( ; x; x -= lowbit(x)) ans += a[x]; return ans;}
}t;
signed main(){
cin >> n >> k;
for(int i = 1; i <= n; i++) cin >> p[i];
int l = 1, tot = 0;
for(int r = 1; r <= n; r++){
tot += (t.query(N - 1) - t.query(p[r]));
t.modify(p[r], 1);
while(tot > k && l <= r){
t.modify(p[l], -1);
tot -= t.query(p[l]);
l++;
}
ans -= l;
}
l = 1, tot = 0;
memset(t.a, 0 ,sizeof(t.a));
for(int r = 1; r <= n; r++){
tot += (t.query(N - 1) - t.query(p[r]));
t.modify(p[r], 1);
while(tot >= k && l <= r){
t.modify(p[l], -1);
tot -= t.query(p[l]);
l++;
}
ans += l;
}
cout << ans;
return 0;
}
CF2147D
step0:注意到这个游戏比较特殊,两个人的目标都只是让自己的得分最大而和对手没有关系,如果根据这个目标研究策略会很难研究,因为你会感觉双方在各玩各的没有博弈。
step1:注意到无论游戏怎么进行,两个人获得的总分是不变的,一定是所有数之和。所以我们可以转化为求两个人分数差。现在我们可以将策略转化为:第一个人要使分差尽量大,第二个人要使分差尽量小。
step2:如果考虑得分差就很容易研究策略。直观感受发现选偶数应该是劣的,因为如果只剩偶数,那么另外一个人跟着选一定可以选到相同的次数,无法拉开分差;如果还有奇数,那么选了偶数之后这个偶数归到奇数,另一个人还能得到更多分。所以在有奇数时一定先选奇数(因为在偶数中双方一定选到相同的次数,所以选奇数让奇数归到偶数是不会亏的)。
step3:既然偶数是不会影响分差,也不会在策略中选的,那么我们可以直接不考虑偶数。那么接下来的策略就很显然了:每次选出现次数最多的奇数即可。选了之后就变成了偶数,因为偶数不用考虑,就相当于删掉了。所以就是先手选最多的奇数,后手选第二多的奇数,先手选第三多的奇数......一直进行下去。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int T, n, a[N], tong[N];
inline void clear(){
memset(tong, 0, sizeof(tong));
}
signed main(){
cin >> T;
while(T--){
clear();
int tot = 0, sum = 0;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
sum += a[i];
}
sort(a + 1, a + n + 1);
for(int i = 1, j = 1; i <= n; i = j){
while(a[i] == a[j] && j <= n) j++;
if(a[i] & 1){
tot++;
tong[tot] = j - i;
}
}
sort(tong + 1, tong + tot + 1, greater<>());
int del = 0, flag = -1;
for(int i = 1; i <= tot; i++){
del += tong[i] * flag;
flag = -flag;
}
cout << (sum - del) / 2 << " " << (sum + del) / 2 << '\n';
}
return 0;
}
CF1787C
step1:转化条件 \((x_i-s)(y_i-s)\ge0\),\(x_i+y_i=a_i\),可以得到当 $a_i \ge 2s $ 时 \(x_i\) 的范围为 \((s,a_i-s)\),\(\le 2s\) 时反之。
tips:注意到此时写 \(O(n^3)\) 的 \(dp\) 是容易的,写了之后观察转移可以发现最优路径上所取的 \(x_i\) 不是最大值就是最小值,然后可以直接猜测结论。
step2:注意到 \(x_i\) 和 \(y_i\) 的贡献只有 \(y_{i-1} \cdot x_i + y_i \cdot x_{i+1}\),如果 \(y_{i-1} > x_{i+1}\),那么显然把贡献尽量往 \(x_i\) 给是更优的,反之亦然。所以直接在这两种情况中 \(dp\) 即可。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int T, n, s, a[N], dp[N][2], l[N], r[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
cin >> n >> s;
for(int i = 1; i <= n; i++){
cin >> a[i];
if(a[i] >= 2 * s) l[i] = s;
else l[i] = max(0ll, a[i] - s);
r[i] = a[i] - l[i];
}
dp[2][0] = a[1] * l[2];
dp[2][1] = a[1] * r[2];
for(int i = 3; i <= n - 1; i++){
dp[i][0] = min(dp[i - 1][0] + r[i - 1] * l[i], dp[i - 1][1] + l[i - 1] * l[i]);
dp[i][1] = min(dp[i - 1][0] + r[i - 1] * r[i], dp[i - 1][1] + l[i - 1] * r[i]);
}
cout << min(dp[n - 1][0] + r[n - 1] * a[n] , dp[n - 1][1] + l[n - 1] * a[n]) << '\n';
}
return 0;
}
CF2082B
自己的方法
step1:注意到当 \(n + m\) 足够大时,如 \(n + m \ge 100\) 时,答案肯定 \(\le 1\)。直接讨论即可。
step2:当 \(n+m\) 比较小时,\(O(n \cdot m)\) 的 \(dp\) 即可。
更帅的做法
注意到:
上面肯定是小于等于下面的,所以可以贪心一下,最小值就先做所有上取整再做所有下取整,最大值反之。
代码
#include<bits/stdc++.h>
using namespace std;
int T, x, n, m;
int _floor(int x, int n){
for(int i = 1; i <= n; i++){
x >>= 1;
if(!x) return x;
}
return x;
}
int _ceil(int x, int m){
for(int i = 1; i <= m; i++){
x = (x + 1) >> 1;
if(x < 2) return x;
}
return x;
}
int main(){
cin >> T;
while(T--){
cin >> x >> n >> m;
cout << _floor(_ceil(x, m), n) << " " << _ceil(_floor(x, n), m) << '\n';
}
return 0;
}
CF1766D
终于有一道自己能颗秒的了。
设最后二者 \(\gcd\) 不为 \(1\) 时两个数有一个质公因子为 \(p\),那么可以把两个数表示为 \(p \cdot n\) 和 \(p \cdot m\)。
因为两个数的差始终是不变的,设其为 \(c\),所以可以写出:
所以直接分解 \(c\) 的质因子就可以了。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 5;
int n, tot, p[N], vis[N];
void init(){
for(int i = 2; i < N; i++){
if(!vis[i]) p[++tot] = i;
for(int j = 1; j <= tot && i * p[j] < N; j++){
vis[i * p[j]] = 1;
if(i % p[j] == 0) break;
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
init();
cin >> n;
while(n--){
int x, y, ans = 1e9;
cin >> x >> y;
int c = y - x;
if(c == 1){
cout << -1 << '\n';
continue;
}
for(int i = 1; i <= tot && p[i] <= c; i++){
if(c % p[i] == 0){
ans = min(ans, (p[i] * (int)ceil((double)x / p[i]) - x));
while(c % p[i] == 0) c /= p[i];
}
}
if(c != 1) ans = min(ans, (c * (int)ceil((double)x / c) - x));
cout << ans << '\n';
}
return 0;
}
CF1921K
又一道能自己做的。
step1:注意到只有“奇奇偶”,“奇偶奇”,“偶奇奇”和“偶偶偶”三种情况下三个连续的数的是和偶数。
step2:注意到不需要对于每种长度的链单独讨论,只需要关注长度为以以上四种情况结尾的链的方案书即可。
step3:注意到可以用 \(dp_{i,j}\) 表示长度为 \(i\),属于上面四种情况的第 \(j\) 种的链的数量,然后发现转移是容易的,只需要在同一种情况的链中按长度转移,然后注意一下长度为 \(3\) 的链也可以由长度为 \(3\) 的另一种链转移而来。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
const int mod = 998244353;
int n, a[N], dp[4][5];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++){
if(a[i] & 1){
dp[3][3] = (dp[3][3] + dp[2][3] + dp[3][2]) % mod;
dp[3][2] = (dp[3][2] + dp[2][2] + dp[3][1]) % mod;
dp[2][1] = (dp[2][1] + dp[1][1]) % mod;
dp[2][3] = (dp[2][3] + dp[1][3]) % mod;
dp[1][1] = (dp[1][1] + 1) % mod;
dp[1][2] = (dp[1][2] + 1) % mod;
}
else {
dp[3][4] = (dp[3][4] + dp[2][4] + dp[3][4]) % mod;
dp[3][1] = (dp[3][1] + dp[2][1] + dp[3][3]) % mod;
dp[2][2] = (dp[2][2] + dp[1][2]) % mod;
dp[2][4] = (dp[2][4] + dp[1][4]) % mod;
dp[1][3] = (dp[1][3] + 1) % mod;
dp[1][4] = (dp[1][4] + 1) % mod;
}
}
cout << (dp[3][1] + dp[3][2] + dp[3][3] + dp[3][4]) % mod;
return 0;
}
CF1801B
multiset 优化贪心。可以先排序一边,然后讨论另外一边。解释比较麻烦不想写了。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;
int n, T, ans;
pair a[N];
multiset st1, st2;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
ans = 1e9;
st1.clear();
st2.clear();
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i].first >> a[i].second;
st2.insert(a[i].second);
}
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; i++){
st2.erase(st2.find(a[i].second));
int mx;
if(st2.empty()) mx = -1e9;
else mx = *st2.rbegin();
if(mx > a[i].first){
ans = min(ans, mx - a[i].first);
}
else {
int near;
if(st1.empty()) near = 1e9;
else {
auto it = st1.lower_bound(a[i].first);
if(it == st1.end()) near = a[i].first - *st1.rbegin();
else if(it == st1.begin()) near = *it - a[i].first;
else {
int left = *it - a[i].first;
it--;
int right = a[i].first - *it;
near = min(left,right);
}
}
ans = min(ans, min(a[i].first - mx, near));
}
st1.insert(a[i].second);
}
cout << ans << '\n';
}
return 0;
}
CF1851G
step1:假设走到的第一个山的高度是 \(h_1\),第二个山是 \(h_2\),第 \(n\) 个山是 \(h_n\),那么最后消耗的总能量就是:
所以在任意两个点之间的移动消耗的能量就是终点和起点的高度差。
step2:由于在路径上任意一点时能量不能小于 \(0\),所以对于路径上每一点都有:\(e - (h_v - h_u) \ge 0\),整理一下就是 \(h_v \le h_i + e\)。
step3:我们可以把连接 \(u,v\) 的两点的边的边权定为 \(\max(h_u, h_v)\),这样问题就转换为了求目标两点间最大边权的最小值,然后判断和 \(h_u + e\) 的大小。可以用 kruskal 重构树解决。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int T, n, m, q, h[2 * N], f[2 * N][30], fa[2 * N], val[2 * N], deep[2 * N];
struct edge{
int u, v, w;
}e[N];
inline void clear(){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= 22; j++){
f[i][j] = 0;
}
val[i] = deep[i] = 0;
}
}
inline int find(int x){
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
inline bool cmp(edge a, edge b){
return a.w < b.w;
}
inline void kruskal(){
sort(e + 1, e + m + 1, cmp);
for(int i = 1; i <= m; i++){
int f1 = find(e[i].u);
int f2 = find(e[i].v);
if(f1 == f2) continue;
n++;
val[n] = e[i].w;
f[f1][0] = f[f2][0] = fa[f1] = fa[f2] = fa[n] = n;
}
}
inline int lca(int u, int v){
if(deep[u] < deep[v]) swap(u, v);
for(int i = 22; i >= 0; i--){
if(deep[f[u][i]] >= deep[v]) u = f[u][i];
}
if(u == v) return v;
for(int i = 22; i >= 0; i--){
if(f[u][i] != f[v][i]){
u = f[u][i];
v = f[v][i];
}
}
return f[u][0];
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
clear();
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> h[i];
fa[i] = i;
val[i] = 0;
}
for(int i = 1; i <= m; i++){
cin >> e[i].u >> e[i].v;
e[i].w = max(h[e[i].u] , h[e[i].v]);
}
kruskal();
for(int i = n; i >= 1; i--){
deep[i] = deep[f[i][0]] + 1;
}
for(int j = 1; j <= 22; j++){
for(int i = 1; i <= n; i++){
f[i][j] = f[f[i][j - 1]][j - 1];
}
}
cin >> q;
while(q--){
int a, b, e;
cin >> a >> b >> e;
if(find(a) != find(b)) cout << "No" << '\n';
else if(val[lca(a, b)] <= h[a] + e) cout << "Yes" << '\n';
else cout << "No" << '\n';
}
}
return 0;
}
CF567E
考试 T3。差个hash做出来。
我们可以从 \(s\) 和 \(t\) 出发分别做一次 dij,第一的结果记为 \(dis1\), 第二次的记为 \(dis2\)。那么我们考虑一条连接 \(u\) 和 \(v\) 边时可以比较 \(dis1_u + dis2_v\) 和最短路长度 \(mi\) 的大小。显然至少要把边权降为 \(mi - (dis1_u + dis2_v) - 1\) 才可以。然后判断是否时正整数是就可以判断是否合法了。
注意到有可能有一些边是必须要走的,这些边就不需要修改。可以在 dij 过程中记录最短路数量。用 \(dp1, dp2\) 表示正反走走到某一个点的最短路数量,如果 \(dp1_u \times dp2_v\) 等于最短路总数,那么显然这条路就必须走。
取模需要用点阴间的质数,不然会被卡。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
const int M = 5e5 + 5;
const int mod = 910910999;
int n, m, s, t, tot, mi, dis1[N], dis2[N], head1[N], head2[N], dp1[M], dp2[M];
bool vis1[N], vis2[N];
pair out[M];
vector > mp[N];
struct node{
int id, val;
friend bool operator < (node a, node b){
return a.val > b.val;
}
};
struct edge{
int u, v, w, next, id;
}e1[M], e2[M];
inline void add(int u, int v, int w){
tot++;
e1[tot].u = u;
e1[tot].v = v;
e1[tot].w = w;
e1[tot].id = tot;
e1[tot].next = head1[u];
head1[u] = tot;
e2[tot].u = v;
e2[tot].v = u;
e2[tot].w = w;
e2[tot].id = tot;
e2[tot].next = head2[v];
head2[v] = tot;
}
priority_queue q;
void dij1(){
while(!q.empty()) q.pop();
for(int i = 1; i <= n; i++) dis1[i] = 1e18;
dis1[s] = 0;
dp1[s] = 1;
q.push((node){s, 0});
while(!q.empty()){
int u = q.top().id;
q.pop();
if(vis1[u]) continue;
vis1[u] = 1;
for(int i = head1[u]; i; i = e1[i].next){
int v = e1[i].v;
int w = e1[i].w;
if(dis1[u] + w < dis1[v]){
dis1[v] = dis1[u] + w;
dp1[v] = dp1[u];
q.push((node){v, dis1[v]});
}
else if(dis1[u] + w == dis1[v]) dp1[v] = (dp1[v] + dp1[u]) % mod;
}
}
}
void dij2(){
while(!q.empty()) q.pop();
for(int i = 1; i <= n; i++) dis2[i] = 1e18;
dis2[t] = 0;
dp2[t] = 1;
q.push((node){t, 0});
while(!q.empty()){
int u = q.top().id;
q.pop();
if(vis2[u]) continue;
vis2[u] = 1;
for(int i = head2[u]; i; i = e2[i].next){
int v = e2[i].v;
int w = e2[i].w;
if(dis2[u] + w < dis2[v]){
dis2[v] = dis2[u] + w;
dp2[v] = dp2[u];
q.push((node){v, dis2[v]});
}
else if(dis2[u] + w == dis2[v]) dp2[v] = (dp2[v] + dp2[u]) % mod;
}
}
}
signed main(){
cin >> n >> m >> s >> t;
for(int i = 1, a, b, l; i <= m; i++){
cin >> a >> b >> l;
add(a, b, l);
}
dij1();
dij2();
mi = dis1[t];
for(int i = 1; i <= tot; i++){
if(dis1[e1[i].u] + e1[i].w + dis2[e1[i].v] == mi){
if(dp1[e1[i].u] * dp2[e1[i].v] % mod == dp1[t]) out[e1[i].id] = {"YES", -1};
else if(e1[i].w == 1) out[e1[i].id] = {"NO", -1};
else out[e1[i].id] = {"CAN", 1};
}
else if((dis1[e1[i].u] + dis2[e1[i].v] - mi + 1 >= 0) || (e1[i].w == 1)) out[e1[i].id] = {"NO", -1};
else out[e1[i].id] = {"CAN", dis1[e1[i].u] + dis2[e1[i].v] + e1[i].w - mi + 1};
}
for(int i = 1; i <= tot; i++){
if(out[i].first == "CAN") cout << out[i].first << " " << out[i].second << '\n';
else if(out[i].second == -1) cout << out[i].first << '\n';
else cout << "NO" << '\n';
}
return 0;
}
CF1067A
step1:考虑 dp,注意到取值范围只有 \(200\),所以可以想到应该要枚举当前位置是哪个数。注意到题目对相邻的数有大小要求,所以考虑再设计一维状态 \(k\):
\(k = 0,1,2\) 分别表示当前数大于,等于,小于上一个数。
step2:考虑转移:
-
如果 \(k = 0\),那么显然等于上一个数比当前数小时的方案数之和。
-
如果 \(k = 1\),那么显然等于上一个数和当前数相等时方案数之和。
-
如果 \(k = 2\),那么显然等于上一个数比当前数大时的方案数,减去上一个数比上上个数大的方案数。(因为上一个数不能比左右的数都大)。
step3:用前缀和优化。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 998244353;
const int N = 1e5 + 5;
int n, ans, a[N], dp[N][205][3], sum1[N], sum2[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
if(a[1] == -1) for(int i = 1; i <= 200; i++) dp[1][i][0] = 1;
else dp[1][a[1]][0] = 1;
for(int i = 2; i <= n; i++){
for(int j = 1; j <= 200; j++){
sum1[j] = (sum1[j - 1] + (dp[i - 1][j][0] + dp[i - 1][j][1] + dp[i - 1][j][2]) % mod) % mod;
}
for(int j = 200; j >= 1; j--){
sum2[j] = (sum2[j + 1] + dp[i - 1][j][1] +dp[i - 1][j][2]) % mod;
}
for(int j = 1; j <= 200; j++){
if(a[i] == -1 || a[i] == j){
dp[i][j][0] = sum1[j - 1] % mod;
dp[i][j][1] = (dp[i - 1][j][0] + dp[i - 1][j][1] + dp[i - 1][j][2]) % mod;
dp[i][j][2] = sum2[j + 1] % mod;
}
}
}
for(int i = 1; i <= 200; i++) ans = (ans + dp[n][i][1] + dp[n][i][2]) % mod;
cout << ans;
return 0;
}
CF1244C
诈骗题。
一眼 exgcd,结果爆 long long 了,于是用 __int128 过了。
然后一看题解:原来暴力枚举就能过了。。。
代码
#include<bits/stdc++.h>
#define int __int128
using namespace std;
int n, p, w, d, x, y;
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
x = x * 10 + ch - '0', ch = getchar();
return x * f;
}
void write(int x){
if(x < 0)
putchar('-'), x = -x;
if(x > 9)
write(x / 10);
putchar(x % 10 + '0');
return;
}
int exgcd(int a, int b, int &x, int &y){
if(b == 0){
x = 1; y = 0;
return a;
}
int g = exgcd(b, a % b, x, y);
int z = x;
x = y;
y = z - (a / b) * y;
return g;
}
signed main(){
n = read(); p = read(); w = read(); d = read();
int g = exgcd(w, d, x, y);
if(p % g != 0){ cout << -1; return 0; }
x *= (p / g); y *= (p / g);
w /= g; d /= g;
if(x < 0){
int tmp = (-x + d - 1) / d;
x += tmp * d;
y -= tmp * w;
}
else if(y < 0){
int tmp = (-y + w - 1) / w;
x -= tmp * d;
y += tmp * w;
}
if(y >= w){
int tmp = y / w;
y -= tmp * w;
x += tmp * d;
}
if(x + y > n || x < 0 || y < 0) cout << -1;
else write(x), cout << " ", write(y), cout << " ", write(n - x - y);
return 0;
}
CF2109D
因为可以重复走一个点,那么我们显然可以考虑在一条边上反复走刷次数的一个操作。因为一个来回是两次,所以只能刷奇偶性相同的。
换句话说,如果我们直接走到某一个点的步数比我们选择的步数小,而且二者奇偶性相同,那么用这个步数就能走到这个点。
所以我们可以考虑处理出每一个点到 \(1\) 号点的偶数最短路和奇数最短路,再和 多重集 \(A\) 能凑出的最大奇数以及最大偶数比较。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
const int M = 4e5 + 5;
int T, n, m, l, tot, mx1, mx2, a[N], head[N], dis1[N], dis2[N];
struct edge{
int u, v, next;
}e[2 * M];
queue > q;
inline void add(int u, int v){
tot++;
e[tot].u = u;
e[tot].v = v;
e[tot].next = head[u];
head[u] = tot;
tot++;
e[tot].u = v;
e[tot].v = u;
e[tot].next = head[v];
head[v] = tot;
}
inline void dij(){
for(int i = 1; i <= n; i++) dis1[i] = dis2[i] = 1e18;
dis2[1] = 0;
q.push({1, dis2[1]});
while(!q.empty()){
int u = q.front().first;
int w = q.front().second;
q.pop();
for(int i = head[u]; i; i = e[i].next){
int v = e[i].v;
if((w + 1) & 1){
if(w + 1 < dis1[v]){
dis1[v] = w + 1;
q.push({v, dis1[v]});
}
}
else if(w + 1 < dis2[v]){
dis2[v] = w + 1;
q.push({v, dis2[v]});
}
}
}
}
inline void init(){
int mi = 1e18;
for(int i = 1; i <= l; i++){
mx1 += a[i];
if(a[i] & 1) mi = min(mi, a[i]);
}
if(mx1 & 1) mx2 = mx1 - mi;
else if(mi != 1e18) { mx2 = mx1; mx1 = mx2 - mi; }
else mx2 = mx1, mx1 = 0;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
tot = mx1 = mx2 = 0;
for(int i = 1; i <= n; i++) head[i] = 0;
cin >> n >> m >> l;
for(int i = 1; i <= l; i++){
cin >> a[i];
}
init();
for(int i = 1, u, v; i <= m; i++){
cin >> u >> v;
add(u, v);
}
dij();
for(int i = 1; i <= n; i++){
if(dis1[i] <= mx1 || dis2[i] <= mx2) cout << 1;
else cout << 0;
}
cout << '\n';
}
return 0;
}
CF900C
一眼题
维护最大值和次大值,然后扫一遍:
- 如果这个数是最大值,那么这个数本身就是“记录”,让 \(is[now] = 1\)。
- 如果这个数是当前次大值,那么删掉当前最大值这个数就能成为“记录”,所以让 \(tong[nowmax]\) 加上 \(1\)。
- 如果不是最大值也不是次大值,那么这个数对答案没有影响。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, tong[N], is[N];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
int mx = 0, nxmx = 0;
for(int i = 1, x; i <= n; i++){
cin >> x;
if(x > mx){
nxmx = mx;
mx = x;
is[x] = 1;
}
else if(x > nxmx){
nxmx = x;
tong[mx]++;
}
}
int ans = 1;
for(int i = n; i >= 1; i--){
if((tong[i] - is[i]) >= (tong[ans] - is[ans])) ans = i;
}
cout << ans;
return 0;
}
CF1691D
不一眼题。
维护一个递增的单调栈。如果放入的新元素比栈顶元素大,那么说明栈顶到当前元素的区间内的最大值是当前元素。那么就比较这段区间的区间和与当前元素的大小,并弹出栈顶。重复此操作直到栈顶大于当前元素。
如果栈顶大于当前元素,那么栈顶就是栈顶到当前元素之间的区间的最大值,所以就比较这个区间的区间和与栈顶的大小。
有人就要问了,栈里面的其他元素与当前元素构成的区间不用判断吗?事实上是不用的。
用数学语言证明一下就是:设当前是第 \(i\) 个数,\(sum\) 表示前缀和,栈顶是第 \(j\) 个数,栈顶后面一个数是第 \(k\) 个数。
我们已经知道了区间 \((k, j)\) 和 \((j, i)\) 都是合法的,也就是说:
上式加下式得:
所以区间 \((k, i)\) 也是合法的。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int T, n, a[N], sum[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
vector st;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
}
bool flag = 0;
for(int i = 1; i <= n; i++){
if(a[i] <= 0) continue;
while(!st.empty() && a[st.back()] <= a[i]){
if(sum[i] - sum[st.back() - 1] > a[i]) flag = 1;
st.pop_back();
}
if(!st.empty()) if(sum[i] - sum[st.back() - 1] > a[st.back()]) flag = 1;
st.push_back(i);
if(flag) break;
}
if(flag) cout << "NO" << '\n';
else cout << "YES" << '\n';
}
return 0;
}
CF1815C
考虑对于每一个 \(a_i\) 和 \(b_i\),建一条 \(b_i \rightarrow a_i\) 的边。因为每一个节点的数只能在父亲两边各放一个,所以每一个节点的出现次数就应该是父亲节点中最少的出现次数加 \(1\)。注意到出现次数就是到 \(1\) 号点的最短路长度。如果有点和 \(1\) 号点不连通就是无限长的。
容易发现出现次数越多,在它外层的数就越少,所以构造的序列肯定是出现次数多的包着出现次数小的,所以我们把出现次数为 \(x\) 的数放到集合 \(s_x\) 中。设最多的出现次数为 \(m\),然后构造序列:
容易证明这个序列满足条件。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1505;
const int M = 5005;
int T, n, m, dis[N], vis[N], ans;
struct node{
int id, val;
friend bool operator < (node a, node b){
return a.val > b.val;
}
};
priority_queue<node> q;
vector<int> mp[N];
inline void clear(){
ans = 0;
for(int i = 1; i <= n; i++) mp[i].clear();
}
inline void dij(){
for(int i = 1; i <= n; i++){
dis[i] = 1e9;
vis[i] = 0;
}
dis[1] = 1;
q.push((node){1, 1});
while(!q.empty()){
int u = q.top().id; q.pop();
if(vis[u]) continue;
vis[u] = 1;
for(auto v : mp[u]){
if(dis[v] > dis[u] + 1){
dis[v] = dis[u] + 1;
q.push((node){v, dis[v]});
}
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
clear();
vector<int> out[N];
cin >> n >> m;
for(int i = 1; i <= m; i++){
int a, b;
cin >> a >> b;
if(a == 1) continue;
mp[b].push_back(a);
}
dij();
bool flag = 0;
int mx = 0;
for(int i = 1; i <= n; i++){
if(!vis[i]){
flag = 1;
break;
}
ans += dis[i];
mx = max(mx, dis[i]);
out[dis[i]].push_back(i);
}
if(flag) cout << "INFINITE" << '\n';
else {
cout << "FINITE" << '\n' << ans << '\n';
for(int i = mx; i >= 1; i--){
for(int j = i; j <= mx; j++){
for(auto k : out[j]){
cout << k << " ";
}
}
}
cout << '\n';
}
}
return 0;
}
CF1083E
一眼题。
先按横坐标递增排序,由于没有嵌套,所以纵坐标肯定就是递减的。
用 \(dp_i\) 表示用前 \(i\) 个矩形的最大值。显然有 \(O(n^2)\) 的 dp:
化简写成:
注意到 \(\max\) 内是一次函数形式,用李超线段树优化,复杂度 \(O(n\log n)\)。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 5;
int n, len, tot, d[N], tg[4 * N], dp[N];
struct rect{
int x, y, v;
}r[N];
struct line{
int k, b;
line(int x = 0, int y = 0){
k = x; b = y;
}
}l[N];
bool cmp(rect a, rect b){
return a.x < b.x;
}
inline void init(){
sort(r + 1, r + n + 1, cmp);
for(int i = 1; i <= n; i++){
d[i] = r[i].y;
}
sort(d + 1, d + n + 1);
len = unique(d + 1, d + n + 1) - d - 1;
}
inline int g(int x){
return lower_bound(d + 1, d + len + 1, x) - d;
}
inline int f(int x, int id){
return l[id].k * x + l[id].b;
}
inline void update(int x, int l, int r, int id){
int &v = tg[x];
int mid = (l + r) >> 1;
if(f(d[mid], id) > f(d[mid], v)) swap(id, v);
if(f(d[l], id) > f(d[l], v)) update(2 * x, l, mid, id);
if(f(d[r], id) > f(d[r], v)) update(2 * x + 1, mid + 1, r, id);
}
inline int query(int x, int l, int r, int k){
int ans = f(d[k], tg[x]);
if(l == r) return ans;
int mid = (l + r) >> 1;
if(k <= mid) return max(ans, query(2 * x, l, mid, k));
else return max(ans, query(2 * x + 1, mid + 1, r, k));
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> r[i].x >> r[i].y >> r[i].v;
}
init();
int ans = 0;
for(int i = 1; i <= n; i++){
dp[i] = r[i].x * r[i].y - r[i].v + query(1, 1, len, g(r[i].y));
l[++tot] = line(-r[i].x, dp[i]);
update(1, 1, len, tot);
ans = max(ans, dp[i]);
}
cout << ans;
return 0;
}
CF2131G
一眼题。
容易发现对于数 \(S\) 需要操作 \(2^{S-1}\) 次,\([1, S)\) 中的每个数 \(a\) 的贡献是 \(\large a^{2^{S - a - 1}}\)。
如果 \(2^{S-1} \le k\) 那么直接减去 \(2^{S-1}\) 然后加上贡献即可。否则先删去 \(S\) 消耗一次操作,加入 \([1,S)\)。然后二分找到最大的 \(2^{a} - 1 \le k\) 的数 \(a\),删去 \([1,a)\) 并加上贡献后继续二分。
要开 unsigned long long。
代码
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
const int N = 2e5 + 5;
const int mod = 1e9 + 7;
int T, n, k, ans, s[N];
inline int qpow1(int a, int b){
int m = 1;
for(; b; b >>= 1){
if(b & 1) m = (m * a);
a = a * a;
}
return m;
}
inline int qpow(int a, int b, int mod){
int m = 1;
for(; b; b >>= 1){
if(b & 1) m = (m * a) % mod;
a = (a * a) % mod;
}
return m;
}
inline void solve(int x){
ans = (ans * x) % mod;
k--;
int l = 0, r = x - 1, res = 0;
while(l <= r){
int mid = (l + r) >> 1;
if((qpow1(2, mid) - 1) <= k) res = mid, l = mid + 1;
else r = mid - 1;
}
res++;
for(int i = 1; i < res; i++) ans = (ans * qpow(i, qpow1(2, res - i - 1), mod)) % mod;
k -= (qpow1(2, res - 1) - 1);
if(res - 1 == x - 1 || k == 0) return;
solve(res);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
ans = 1;
cin >> n >> k;
for(int i = 1; i <= n; i++) cin >> s[i];
sort(s + 1, s + n + 1);
for(int i = 1; i <= n; i++){
if(k != 0) solve(s[i]);
}
cout << ans << '\n';
}
return 0;
}
CF1857G
不一眼题。
注意到加一条边使得最小生成树变化当且仅当最小生成树上有一条边和新加的这条边连接的是两个相同的块,且新加的边权值与原边相等或更小。
所以我们正常做 kruskal,合并两个块时显然可以加入权值在 \([s, w]\) 范围内的边,\(w\) 是当前遍历到的边的权值。显然可以加入边的位置数量是两个块的大小乘积减一。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 998244353;
const int N = 2e5 + 5;
int T, n, s, f[N], siz[N];
struct edge{
int u, v, w;
friend bool operator < (edge a, edge b){
return a.w < b.w;
}
}e[N];
inline int find(int x){
if(f[x] == x) return x;
return f[x] = find(f[x]);
}
inline int qpow(int a, int b, int mod){
int m = 1;
for(; b; b >>= 1){
if(b & 1) m = (m * a) % mod;
a = (a * a) % mod;
}
return m;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
int ans = 1;
cin >> n >> s;
for(int i = 1; i < n; i++){
cin >> e[i].u >> e[i].v >> e[i].w;
}
sort(e + 1, e + n);
for(int i = 1; i <= n; i++) f[i] = i, siz[i] = 1;
for(int i = 1; i < n; i++){
int f1 = find(e[i].u);
int f2 = find(e[i].v);
if(f1 == f2) continue;
ans = ans * qpow(s - e[i].w + 1, siz[f1] * siz[f2] - 1, mod) % mod;
f[f1] = f2;
siz[f2] += siz[f1];
}
cout << ans << '\n';
}
return 0;
}
CF1881G
代码长题。
修改操作显然可以线段树维护。注意到如果一个区间有长度大于 \(2\) 的回文串,那么就肯定有长度等于 \(2\) 或 \(3\) 的回文串。所以我们只需要在线段树上维护一个区间最左边,此左边,最右边,此右边的字符,左右儿子合并时就看合并的位置有没有形成回文串。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int T, n, m;
string s;
struct node{
int mr, nr, ml, nl;
bool w;
node(int a = 0, int b = 0, int c = 0, int d = 0, bool e = 0){
mr = a; nr = b; ml = c; nl = d; w = e;
}
};
struct tree{
int l, r, tg;
int mr, nr, ml, nl;
bool w;
}t[4 * N];
inline void pushup(int x){
t[x].ml = t[2 * x].ml;
t[x].mr = t[2 * x + 1].mr;
if(!t[2 * x].nl) t[x].nl = t[2 * x + 1].ml;
else t[x].nl = t[2 * x].nl;
if(!t[2 * x + 1].nr) t[x].nr = t[2 * x].mr;
else t[x].nr = t[2 * x + 1].nr;
t[x].w = (t[2 * x].w || t[2 * x + 1].w);
if(t[2 * x].mr == t[2 * x + 1].ml && t[2 * x].mr != 0) t[x].w = 1;
if(t[2 * x].mr == t[2 * x + 1].nl && t[2 * x].mr != 0) t[x].w = 1;
if(t[2 * x].nr == t[2 * x + 1].ml && t[2 * x].nr != 0) t[x].w = 1;
}
inline void build(int x, int l, int r){
t[x].l = l; t[x].r = r; t[x].w = 0; t[x].tg = 0;
if(l == r){
t[x].mr = t[x].ml = s[l] - 'a' + 1;
t[x].nr = t[x].nl = 0;
return;
}
int mid = (l + r) >> 1;
build(2 * x, l, mid);
build(2 * x + 1, mid + 1, r);
pushup(x);
}
inline int add(int x, int k){
if(x == 0) return 0;
return (x - 1 + k) % 26 + 1;
}
inline void update(int x, int k){
t[x].tg = (t[x].tg + k) % 26;
t[x].mr = add(t[x].mr, k);
t[x].nr = add(t[x].nr, k);
t[x].nl = add(t[x].nl, k);
t[x].ml = add(t[x].ml, k);
}
inline void pushdown(int x){
update(2 * x, t[x].tg);
update(2 * x + 1, t[x].tg);
t[x].tg = 0;
}
inline void modify(int x, int l, int r, int k){
if(t[x].l > r || t[x].r < l) return;
if(t[x].l >= l && t[x].r <= r){
update(x, k);
return;
}
pushdown(x);
modify(2 * x, l, r, k);
modify(2 * x + 1, l, r, k);
pushup(x);
}
inline node query(int x, int l, int r){
if(t[x].l > r || t[x].r < l) return node();
if(t[x].l >= l && t[x].r <= r) return node(t[x].mr, t[x].nr, t[x].ml, t[x].nl, t[x].w);
pushdown(x);
node L = query(2 * x, l, r);
node R = query(2 * x + 1, l, r);
node res;
res.ml = L.ml;
res.mr = R.mr;
if(!L.ml) return R;
if(!R.mr) return L;
if(!L.nl) res.nl = R.ml;
else res.nl = L.nl;
if(!R.nr) res.nr = L.mr;
else res.nr = R.nr;
res.w = (L.w || R.w);
if(L.mr == R.ml && L.mr != 0) res.w = 1;
if(L.mr == R.nl && L.mr != 0) res.w = 1;
if(L.nr == R.ml && L.nr != 0) res.w = 1;
return res;
}
inline void solve1(){
int l, r, x;
cin >> l >> r >> x;
modify(1, l, r, x);
}
inline void solve2(){
int l, r;
cin >> l >> r;
if(!query(1, l, r).w) cout << "Yes" << '\n';
else cout << "No" << '\n';
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
cin >> n >> m; cin >> s;
s = ' ' + s;
build(1, 1, n);
while(m--){
int op;
cin >> op;
if(op == 1) solve1();
else solve2();
}
}
return 0;
}
CF1176F
只抓攻击牌的来了
考虑到一回合只有 \(7\) 种打法:什么都不打,1,2,3,1 + 1,1 + 2, 1 + 1 + 1。由于打牌顺序自定,所以我们可以先对每种费用的牌按伤害从大到小排序,打牌时优先用伤害高的牌。
\(dp_{i,j}\) 表示第 \(i\) 回合,前面已经打出了 \(j\) 张牌的答案,对上面 \(7\) 中情况分别转移即可。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int n, k, dp[N][15];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 0; i <= n; i++) for(int j = 0; j <= 9; j++) dp[i][j] = -1e18;
dp[0][0] = 0;
for(int i = 1; i <= n; i++){
cin >> k;
vector<int> v1, v2, v3;
for(int j = 1, c, d; j <= k; j++){
cin >> c >> d;
if(c == 1) v1.push_back(d);
else if(c == 2) v2.push_back(d);
else if(c == 3) v3.push_back(d);
}
sort(v1.begin(), v1.end(), greater<int>());
sort(v2.begin(), v2.end(), greater<int>());
sort(v3.begin(), v3.end(), greater<int>());
// 0, 1 + 1, 1 + 1 + 1, 1 + 2, 1, 2, 3;
for(int r = 0; r <= 9; r++) dp[i][r] = dp[i - 1][r]; // 什么都不选
for(int r = 0; r <= 9; r++){
if(!v1.empty()){ // 1
if(r == 9) dp[i][(r + 1) % 10] = max(dp[i][(r + 1) % 10], dp[i - 1][r] + v1[0] * 2);
else dp[i][(r + 1) % 10] = max(dp[i][(r + 1) % 10], dp[i - 1][r] + v1[0]);
}
if(!v2.empty()){ // 2
if(r == 9) dp[i][(r + 1) % 10] = max(dp[i][(r + 1) % 10], dp[i - 1][r] + v2[0] * 2);
else dp[i][(r + 1) % 10] = max(dp[i][(r + 1) % 10], dp[i - 1][r] + v2[0]);
}
if(!v3.empty()){ // 3
if(r == 9) dp[i][(r + 1) % 10] = max(dp[i][(r + 1) % 10], dp[i - 1][r] + v3[0] * 2);
else dp[i][(r + 1) % 10] = max(dp[i][(r + 1) % 10], dp[i - 1][r] + v3[0]);
}
if(v1.size() > 1){ // 1 + 1
if(r == 9 || r == 8) dp[i][(r + 2) % 10] = max(dp[i][(r + 2) % 10], dp[i - 1][r] + v1[0] * 2 + v1[1]);
else dp[i][(r + 2) % 10] = max(dp[i][(r + 2) % 10], dp[i - 1][r] + v1[0] + v1[1]);
}
if(v1.size() > 2){ // 1 + 1 + 1
if(r == 9 || r == 8 || r == 7) dp[i][(r + 3) % 10] = max(dp[i][(r + 3) % 10], dp[i - 1][r] + v1[0] * 2 + v1[1] + v1[2]);
else dp[i][(r + 3) % 10] = max(dp[i][(r + 3) % 10], dp[i - 1][r] + v1[0] + v1[1] + v1[2]);
}
if(!v1.empty() && !v2.empty()){ // 1 + 2
if(r == 9 || r == 8) dp[i][(r + 2) % 10] = max(dp[i][(r + 2) % 10], dp[i - 1][r] + max(v1[0], v2[0]) * 2 + min(v1[0], v2[0]));
else dp[i][(r + 2) % 10] = max(dp[i][(r + 2) % 10], dp[i - 1][r] + v1[0] + v2[0]);
}
}
}
int ans = -1e18;
for(int r = 0; r <= 9; r++){
ans = max(ans, dp[n][r]);
}
cout << ans;
return 0;
}

CF1256E
贪心的考虑一下,如果对 \(a\) 序列从大到小排序,那么安排的所有队伍都是序列中连续的一段一定是不劣的。
用 \(dp_i\) 表示划分前 \(i\) 个人的最小答案,显然有转移:
复杂度 \(O(n^2)\)。
提出 \(a_i\) 就是:
所以记录 \(dp_{j-1}-a_j\) 的最小值即可。复杂度 \(O(n)\)。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int n, k = 1, out[N], dp[N];
pair<int, int> st[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 1, x; i <= n; i++){
cin >> x;
st[i] = {x, i};
dp[i] = 1e18;
}
dp[0] = 0;
sort(st + 1, st + n + 1);
int mi = -st[1].first;
for(int i = 3; i <= n; i++){
dp[i] = st[i].first + mi;
mi = min(mi, dp[i - 2] - st[i - 1].first);
}
int now = n;
for(int i = n; i >= 1; i--){
if(dp[now] == dp[i - 1] + st[now].first - st[i].first && now - i >= 2){
out[st[i].second] = k;
now = i - 1;
k++;
}
else out[st[i].second] = k;
}
cout << dp[n] << " " << k - 1 << '\n';
for(int i = 1; i <= n; i++) cout << out[i] << " ";
return 0;
}
CF1332E
不会题。
首先注意到合不合法只和奇偶有关系(止步于此,但这不是是个人都能看出来吗)。后面想成容斥了,结果是正面做。
然后注意到操作二实质就是在奇数或偶数中随意改变;操作一其实就是同时改变两个相邻格子的奇偶,然后发现其实可以变成同时改变任意两个格子的奇偶(这步挺关键的)。
设 \(e\) 为 \([L,R]\) 中偶数的个数,\(o\) 为 \([L,R]\) 中奇数的个数。
如果 \(nm\) 为奇数,那么要么 \(e\) 为偶数,要么 \(o\) 为偶数,那么就一定可以通过操作一变成同奇偶的。也就是说一定合法,方案数就是总方案数: $$\large (R-L+1)^{nm}$$
如果 \(nm\) 为偶数,那么就要保证 \(o\) 为偶数。设有 \(2i\) 个位置为奇数,所以方案数就是:$$\large \sum_{i=0}^{\frac{nm}{2}} \binom{nm}{2i}o^{2i} e^{nm-2i}$$
根据二项式定理:$$\large (x+y)^k= \sum_{i=0}^{k} \binom{k}{i} x^i y^{k-i}$$
注意到答案就是:$$\large \frac{(o+e)^{nm} + (o-e)^{nm}}{2}$$
CF1772F
能做题。
注意到如果一个格子能被转化后,形成的十字形就不会再被改变了。所以我们可以先数每一个照片的十字形个数,个数多的一定排在后面。这样我们就得到了照片的顺序。
然后我们直接按顺序一个一个转化就可以了。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 35;
const int M = 105;
int n, m, k, ft, lt, a[M][N][N], num[M];
vector<int> s;
struct ans{
int op, x, y;
};
vector<ans> out;
inline bool check(int i, int j, int p){
if(j == 1 || j == n || p == 1 || p == m) return 0;
if(a[i][j][p] == a[i][j - 1][p] && a[i][j][p] == a[i][j][p + 1] && a[i][j][p] == a[i][j + 1][p] && a[i][j][p] == a[i][j][p - 1]) return 1;
return 0;
}
inline void solve(int x, int y){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(a[x][i][j] != a[y][i][j]){
out.push_back({1, i, j});
}
}
}
}
bool cmp(int i, int j){
return num[i] < num[j];
}
int main(){
cin >> n >> m >> k;
for(int i = 1; i <= k + 1; i++){
for(int j = 1; j <= n; j++){
for(int p = 1; p <= m; p++){
char ch;
cin >> ch;
a[i][j][p] = ch - '0';
}
}
}
num[0] = 1e9;
for(int i = 1; i <= k + 1; i++){
s.push_back(i);
for(int j = 1; j <= n; j++){
for(int p = 1; p <= m; p++){
if(check(i, j, p)) num[i]++;
}
}
}
sort(s.begin(), s.end(), cmp);
cout << s[0] << '\n';
for(int i = 0; i < k; i++){
solve(s[i], s[i + 1]);
out.push_back({2, s[i + 1], 0});
}
cout << out.size() << '\n';
for(auto q : out){
if(q.op == 1) cout << q.op << " " << q.x << " " << q.y << '\n';
else cout << q.op << " " << q.x << '\n';
}
return 0;
}
CF2014F
一眼题。这玩意怎么评到 *2000 的。
一眼树形 dp,状态显然,转移显然。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int T, n, c, a[N], dp[N][2];
vector<int> mp[N];
inline void clear(){
for(int i = 1; i <= n; i++) dp[i][0] = dp[i][1] = 0, mp[i].clear();
}
inline void dfs(int u, int fa){
dp[u][1] = a[u];
for(auto v : mp[u]){
if(v == fa) continue;
dfs(v, u);
dp[u][0] += max(dp[v][1], dp[v][0]);
dp[u][1] += max(dp[v][1] - 2 * c, dp[v][0]);
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
clear();
cin >> n >> c;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
for(int i = 1, u, v; i < n; i++){
cin >> u >> v;
mp[u].push_back(v);
mp[v].push_back(u);
}
dfs(1, 0);
cout << max(dp[1][0], dp[1][1]) << '\n';
}
return 0;
}
CF1974G
反悔贪心板子。
开一个大根堆存已经买了的物品的价值,能买直接买,不能买看能不能反悔堆顶来买当前的。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int T, n, x;
priority_queue<int> q;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
while(!q.empty()) q.pop();
cin >> n >> x;
int now = 0;
for(int i = 1, c; i <= n; i++){
cin >> c;
if(now >= c){
now -= c;
q.push(c);
}
else if(!q.empty() && q.top() >= c){
now = (now - c + q.top());
q.pop();
q.push(c);
}
now += x;
}
cout << q.size() << '\n';
}
return 0;
}
CF1763C
脑电波题,这道题也充分说明了手造样例和打表的重要性。
如果你手模一下 \(n \ge 4\) 的数据,会发现一定可以把数组中所有的数变成最大值。方法是容易的:对最大值两边的区间连续进行两次操作全变成 \(0\),然后再和最大值操作就能全变成最大值。
分类讨论 \(n \le 3\) 的情况: \(n = 1\) 和 \(n = 2\) 是简单的,答案分别是 \(a_1\) 和 \(\max(2|a_1 - a_2|, a_1 + a_2)\)。
对于 \(n = 3\),发现如果最大值在 \(a_1\) 或 \(a_3\) 也可以把整个数组变成最大值;如果最大值在 \(a_2\),那么有四种可能:
-
对区间 \([1,2]\) 操作。这样最优是把数组变成 \(|a_1 - a_2|\) 和 \(a_3\) 的更大者。
-
对区间 \([2,3]\) 操作。同理最后变成 \(|a_2-a_3|\) 和 \(a_1\) 的更大者。
-
对区间 \([1,3]\) 操作。这样做一定是劣的因为 \(3a_1\) 或 \(3a_3\) 显然就比 \(3|a_1 - a_3|\) 要优,不用考虑。
-
不操作。答案就是 \(a_1 + a_2 + a_3\)。
对所有可能取最大值即可。
这道题思维难点在于如果你不去手玩 \(n \ge 4\) 的情况,容易下意识认为把一段区间进行两次操作变成 \(0\) 是劣的,因此不往这方面想,但这恰恰是这道题的突破口。这再次说明了手造数据模拟的重要性。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 5;
int T, n, a[N];
signed main(){
cin >> T;
while(T--){
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
if(n == 1) cout << a[1] << '\n';
else if(n == 2) cout << max(2 * abs(a[1] - a[2]), a[1] + a[2]) << '\n';
else if(n == 3) cout << max({3 * a[1], 3 * a[3], 3 * abs(a[1] - a[2]), 3 * abs(a[2] - a[3]), a[1] + a[2] + a[3]}) << '\n';
else {
sort(a + 1, a + n + 1);
cout << a[n] * n << '\n';
}
}
return 0;
}
CF1841D
注意到如果将所有最后配成一对的线段合并,那么这些合并的线段是不会有交集的。
所以我们先合并所有线段。因为要删的尽量的少,所以就是要求最多的不相交线段的个数。这是一个经典贪心,按右端点排序一个一个选即可。设最多有 \(ans\) 个线段不相交,因为一个线段由两个原线段拼成,所以就是最多保留 \(2 \times ans\) 个,那么就最少删除 \(n - 2 \times ans\) 个。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2005;
int T, n;
struct seg{
int l, r;
}a[N], b[N * N];
bool cmp(seg a, seg b){
return a.r < b.r;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
int tot = 0, ans = 0;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i].l >> a[i].r;
}
for(int i = 1; i <= n; i++){
for(int j = i + 1; j <= n; j++){
if(!(a[i].l > a[j].r || a[i].r < a[j].l)) b[++tot] = {min(a[i].l, a[j].l), max(a[i].r, a[j].r)};
}
}
sort(b + 1, b + tot + 1, cmp);
int mr = -1;
for(int i = 1; i <= tot; i++){
if(b[i].l > mr){
ans++;
mr = b[i].r;
}
}
cout << n - ans * 2 << '\n';
}
return 0;
}
CF1951D
打打表就会了,分类讨论。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int T, n, k;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
cin >> n >> k;
if(k > n) cout << "NO" << '\n';
else if(n == k){
cout << "YES" << '\n';
cout << 1 << '\n' << 1 << '\n';
}
else if(k <= (n / 2)) {
cout << "YES" << '\n';
cout << 2 << '\n' << n - k + 1 << " " << 1 << '\n';
}
else {
if(n & 1){
if(k == (n / 2) + 1) {
cout << "YES" << '\n';
cout << 2 << '\n' << 2 << " " << 1 << '\n';
}
else cout << "NO" << '\n';
}
else cout << "NO" << '\n';
}
}
return 0;
}
CF1970E2
比较模板的矩阵加速 dp。
回顾 E1 的做法:用 \(dp_{i,j}\) 表示第 \(i\) 天以第 \(j\) 个房子为终点的方案数。转移是显然的:
其中 \(t_i\) 表示 \(s_i + l_i\)。
现在 \(n\le10^9\),再枚举天数显然时间空间上都无法接受。但是我们可以发现 \(t_jt_k - l_jl_k\) 这部分是不会随着转移而改变的,所以我们可以构造下面两个矩阵:
由上面的推导可知:
用矩阵快速幂解决即可。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
const int N = 105;
int m, n, s[N], l[N];
struct matrix{
int a[N][N];
matrix(){
memset(a, 0, sizeof(a));
}
void one(){
for(int i = 1; i <= m; i++) a[i][i] = 1;
}
}a, b;
inline int add(int x, int y){
return (x + y >= mod) ? (x + y - mod) : (x + y);
}
matrix operator * (matrix a, matrix b){
matrix c;
for(int i = 1; i <= m; i++){
for(int j = 1; j <= m; j++){
for(int k = 1; k <= m; k++){
c.a[i][j] = add(c.a[i][j], a.a[i][k] * b.a[k][j] % mod);
}
}
}
return c;
}
matrix qpow(matrix a, int b, int mod){
matrix m;
m.one();
for(; b; b >>= 1){
if(b & 1) m = m * a;
a = a * a;
}
return m;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> m >> n;
for(int i = 1; i <= m; i++) cin >> s[i];
for(int i = 1; i <= m; i++) cin >> l[i];
for(int i = 1; i <= m; i++){
for(int j = 1; j <= m; j++){
b.a[i][j] = (add(s[i], l[i]) * add(s[j], l[j]) % mod - l[i] * l[j] % mod) % mod;
}
}
a.a[1][1] = 1;
b = qpow(b, n, mod);
a = a * b;
int ans = 0;
for(int i = 1; i <= m; i++){
for(int j = 1; j <= m; j++){
ans = add(ans, a.a[i][j]);
}
}
cout << ans << '\n';
return 0;
}
CF1913D
用 \(dp_{i,0}\) 表示以 \(i\) 结尾,且 \(i\) 被删掉的方案数;\(dp_{i,1}\) 表示以 \(i\) 结尾,且 \(i\) 保留的方案数。
考虑转移。设 \(i\) 前面第一个比 \(i\) 小的数为 \(pr_i\)。容易发现如果 \(i\) 被删掉,那么 \(pr_i\) 和 \(i\) 之间的数一定也被删掉,所以方案数只能从 \(pr_i\) 转移而来。
如果 \(i\) 不被删掉,那么久也可以从 \(pr_i\) 和 \(i\) 之间转移而来。综上有:
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3e5 + 5;
const int mod = 998244353;
int T, n, p[N], dp[N][2], sum[N];
vector<int> st;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
p[0] = -1e18;
sum[0] = dp[0][1] = 1;
cin >> T;
while(T--){
st.clear();
st.push_back(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> p[i];
for(int i = 1; i <= n; i++){
while(!st.empty() && p[i] < p[st.back()]) st.pop_back();
int pr = st.back();
if(pr == 0) dp[i][0] = 0;
else dp[i][0] = (dp[pr][1] + dp[pr][0]) % mod;
dp[i][1] = ((((dp[pr][1] + dp[pr][0]) % mod + sum[i - 1]) % mod - sum[pr]) % mod + mod) % mod;
sum[i] = (sum[i - 1] + dp[i][1]) % mod;
st.push_back(i);
}
cout << (dp[n][0] + dp[n][1]) % mod << '\n';
}
return 0;
}
CF2057D
贪心的考虑一下:找定最大值最小值之后把他们分别放在左右端点一定是最优的。
所以可以化式子:$$\large \max(a_l, a_{l + 1}, \ldots, a_r) - \min(a_l, a_{l + 1}, \ldots, a_r) - (r - l)$$
如果 \(a_l > a_r\) 就是:
否则就是:
用线段树分别维护 \(a_i+i\) 和 \(a_i-i\) 的最大值和最小值,答案就是最大值减去最小值。pushup 时答案要么就是左右两个区间的答案,要么最大值和最小值分别来自左右区间。维护这三种情况的最大值即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int T, n, q, a[N];
struct tree{
int l, r, mx1, mi1, mx2, mi2, val1, val2;
}t[4 * N];
void pushup(int x){
int ls = 2 * x;
int rs = 2 * x + 1;
t[x].mx1 = max(t[ls].mx1, t[rs].mx1);
t[x].mx2 = max(t[ls].mx2, t[rs].mx2);
t[x].mi1 = min(t[ls].mi1, t[rs].mi1);
t[x].mi2 = min(t[ls].mi2, t[rs].mi2);
t[x].val1 = max({t[ls].val1, t[rs].val1, t[ls].mx1 - t[rs].mi1});
t[x].val2 = max({t[ls].val2, t[rs].val2, t[rs].mx2 - t[ls].mi2});
}
void build(int x, int l, int r){
t[x].l = l; t[x].r = r;
if(l == r){
t[x].mx1 = t[x].mi1 = a[l] + l;
t[x].mx2 = t[x].mi2 = a[l] - l;
t[x].val1 = t[x].val2 = 0;
return;
}
int mid = (l + r) >> 1;
build(2 * x, l, mid);
build(2 * x + 1, mid + 1, r);
pushup(x);
}
void modify(int x, int p, int k){
if(t[x].l == t[x].r){
t[x].mx1 = t[x].mi1 = k + t[x].l;
t[x].mx2 = t[x].mi2 = k - t[x].l;
return ;
}
int mid = (t[x].l + t[x].r) >> 1;
if(p <= mid) modify(2 * x, p, k);
else modify(2 * x + 1, p, k);
pushup(x);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while(T--){
cin >> n >> q;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
build(1, 1, n);
cout << max(t[1].val1, t[1].val2) << '\n';
while(q--){
int p, x;
cin >> p >> x;
modify(1, p, x);
cout << max(t[1].val1, t[1].val2) << '\n';
}
}
return 0;
}
CF489F
\(O(n^3)\) dp 显然,卡卡常数就过了。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 505;
int n, m, mod, dp[N][N], f[N][N], cnt[N], cnt0, cnt1, C[N][N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> mod;
for(int i = 0; i <= n; i++){
C[i][0] = C[i][i] = 1;
for(int j = 1; j <= i; j++){
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
}
}
cnt0 = n;
for(int i = 1; i <= m; i++){
char s[N];
cin >> s;
for(int i = 0; i < n; i++){
if(s[i] == '1'){
cnt[i]++;
if(cnt[i] == 1) cnt0--, cnt1++;
else if(cnt[i] == 2) cnt1--;
}
}
}
dp[cnt0][cnt1] = 1;
for(int i = m + 1; i <= n; i++){
for(int j = 0; j <= n; j++){
for(int k = 0; k + j <= n; k += 2){
if(k >= 2) f[j][k] = f[j][k] + dp[j + 2][k - 2] * C[j + 2][2];
f[j][k] = f[j][k] + dp[j + 1][k] * (j + 1) % mod * k;
f[j][k] = f[j][k] + dp[j][k + 2] * C[k + 2][2];
f[j][k] %= mod;
}
}
memcpy(dp, f, sizeof(dp));
memset(f, 0, sizeof(f));
}
cout << dp[0][0] << '\n';
return 0;
}
CF1428E
床上两分钟想到的做法,和其他题解做法略有不同。
要分成 \(k\) 段显然要切 \(k-n\) 刀,我们可以先把这 \(k-n\) 刀平均分给每一个段,然后再进行调整,不断把切多了的刀拿给切少了的段,直到无法再改进。
我们可以开两个堆,分别是多切一刀的收益的大根堆和少切一刀的损失的小根堆,每次取出两个堆的堆顶,如果收益更大则进行修改,如果损失更大说明已经调整到最优状态。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n, k, tot, a[N], p[N];
struct node{
int x, d, id;
};
inline int calc(int x, int d){
int q = x / d;
int r = x % d;
return (d - r) * q * q + r * (q + 1) * (q + 1);
}
struct cmp1{
bool operator() (node a, node b){ return calc(a.x, a.d) - calc(a.x, a.d + 1) < calc(b.x, b.d) - calc(b.x, b.d + 1); }
};
struct cmp2{
bool operator() (node a, node b){ return calc(a.x, a.d - 1) - calc(a.x, a.d) > calc(b.x, b.d - 1) - calc(b.x, b.d); }
};
priority_queue<node, vector<node>, cmp1> q1;
priority_queue<node, vector<node>, cmp2> q2;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for(int i = 1; i <= n; i++) cin >> a[i], p[i] = 1;
sort(a + 1, a + n + 1, greater<>());
int tot = k - n;
for(int i = 1, w = 1; i <= tot; i++, w = w % n + 1) p[w]++;
for(int i = 1; i <= n; i++){
q1.push({a[i], p[i], i});
if(p[i] != 1) q2.push({a[i], p[i], i});
}
while(!q1.empty() && !q2.empty()){
node u = q1.top(); q1.pop();
node v = q2.top(); q2.pop();
if(calc(u.x, u.d) - calc(u.x, u.d + 1) <= calc(v.x, v.d - 1) - calc(v.x, v.d)) break;
p[u.id]++;
p[v.id]--;
q1.push({u.x, p[u.id], u.id});
if(p[v.id] != 1) q2.push({v.x, p[v.id], v.id});
}
int ans = 0;
for(int i = 1; i <= n; i++) ans += calc(a[i], p[i]);
cout << ans << '\n';
return 0;
}
CF1520G
弱智题,bfs 做完了。
代码
#include<bits/stdc++.h>
#define int long long
#define pi pair<int, int>
using namespace std;
const int N = 2e3 + 5;
const int inf = 1e18;
int n, m, w, a[N][N], vis[2][N][N], dis[2][N][N];
void init() {
cin >> n >> m >> w;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> a[i][j];
dis[0][i][j] = dis[1][i][j] = 1e18;
}
}
}
queue<pi> q;
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, -1, 0, 1};
void bfs(int sx, int sy, int op){
q.push({sx, sy});
dis[op][sx][sy] = 0;
vis[op][sx][sy] = 1;
while(!q.empty()){
pi u = q.front(); q.pop();
for(int i = 0; i < 4; i++){
int nx = u.first + dx[i];
int ny = u.second + dy[i];
if(nx < 1 || nx > n || ny < 1 || ny > m || vis[op][nx][ny] || a[nx][ny] == -1) continue;
dis[op][nx][ny] = dis[op][u.first][u.second] + w;
vis[op][nx][ny] = 1;
q.push({nx, ny});
}
}
}
int ans = inf, mi1 = inf, mi2 = inf;
void solve() {
bfs(1, 1, 0);
bfs(n, m, 1);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(vis[0][i][j] && vis[1][i][j]){
ans = min(ans, dis[0][i][j] + dis[1][i][j]);
}
if(a[i][j] > 0) mi1 = min(mi1, dis[0][i][j] + a[i][j]), mi2 = min(mi2, dis[1][i][j] + a[i][j]);
}
}
ans = min(ans, mi1 + mi2);
cout << (ans == inf ? -1 : ans) << '\n';
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
init();
solve();
return 0;
}

浙公网安备 33010602011771号