Codeforces Round #816 (Div. 2)/CodeForces1715
CodeForces1715
Crossmarket
解析:
题目大意
有一个 \(n \times m\) 的空间,Stanley 需要从左上角到右下角;Megan 则需要从左下角到右上角。两人可以耗费 \(1\) 能量到达相邻的格子。
为了加快到达的速度, Megan 会在经过的所有格子留下传送门,两人可以耗费 \(1\) 能量通过任意传送门通往任意有传送门的位置。
求两人均到达目的地耗费的最小总能量。
思路:
首先发现 Megan 不可能用传送门,因为这样折返最多让 Stanley 能够早点用传送门,但 Stanley 没走的路其实是让 Megan 走的,总能量并不优,正确的做法是 Megan 直接从左下,经过左上走到右上,而 Stanley 选择 \(n,m\) 中较大的边传送。
答案是 \(n-m-2\) (Megan 的能量)+ \(\min(n,m)\) (Stanley 的能量)。
code
#include <bits/stdc++.h>
using namespace std;
int n, m;
int solve ()
{
scanf ("%d%d", &n, &m);
if (n == 1 && m == 1) return 0;
if (n < m) swap (n, m);
return (n + m - 2) + m;
}
signed main()
{
int t = read ();
while (t--) printf ("%d\n", solve ());
return 0;
}
Beautiful Array
解析:
题目大意:
给定四个整数 \(n,k,b,s\),要求构造一个长度为 \(n\) 的序列 \(a\),使得 \(s=\sum\limits_{i=1}^na_i\) 且 \(b=\sum\limits_{i=1}^n\lfloor\frac{a_i}{k}\rfloor\)(\(a_i\) 必须为非负整数)。如果有多组解,输出任意一组解。对于无解的情况,输出 -1
。
思路:
大力构造,考虑总和 \(s\) 能凑出来的最大的 \(b\) 其实是 \(\frac{s}{k}\) ,感性理解就是我前面 \(n-1\) 个 \(a_i\) 都取 \(k\),最后剩了多少都给 \(a_n\),可以证明不存在更优的构造方案。
总和 \(s\) 可以构造的最小值可以类似考虑,我先给 \(n\) 个 \(a_i\) 都分 \(k-1\),那剩下的数不管放哪都会造成贡献了,那直接随便找一个放在一起就行了,因为我需要尽量不破坏其他 \(k-1\),可以证明这样凑出来的是最小的。
考虑现在判断了无解,构造方案可以参考最小值的构造,我先给 \(a_1\) 分 \(b\times k\),剩下的每次按照 \(k-1\) 分给 \(1-n\) 所有数,直到分完为止。
code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int INF = 0x3f3f3f3f;
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 << 1) + (x << 3) + (ch ^ 48); ch = getchar (); }
return x * f;
}
int n, k, b, s;
void solve ()
{
n = read (), k = read (), b = read (), s = read ();
int r = s / k, l = 0, sum = 0;
for (int i = 1; i < n; i++) sum += k - 1;
l = (s - sum) / k;
if (b < l || b > r) { printf ("-1\n"); return ; }
printf ("%lld ", min (s, k * (b + 1) - 1));
s -= min (s, k * (b + 1) - 1);
for (int i = 2; i <= n; i++)
{
if (s >= k - 1) { printf ("%lld ", k - 1); s -= (k - 1); }
else { printf ("%lld ", s); s -= s; }
}
printf ("\n");
}
signed main()
{
int t = read ();
while (t--) solve ();
return 0;
}
Monoblock
解析:
题目大意
我们定义连续连续极长且都是一个相同的数的子段被称为一个“块”。一个序列的连续极长的块的个数表示为 \(g(1,n)\)。
给定一个长度 \(n\) 的序列和 \(m\) 次询问。对于每次询问,有两个数 \(i,x\),先将 \(a_i\) 改为 \(x\),然后输出 \(\sum\limits_{l=1}^n\sum\limits_{r=l}^ng(l,r)\),其中 \(g(l,r)\) 表示 \(a_{l\sim r}\) 的“块”数。
思路:
诈骗题,第一眼发现什么数据结构都不能很好维护,就去看D了,切了D回来也秒了,事实证明这是个错误的决定(如果先写D排名能提好几百)。
首先观察到可以数组直接统计维护。
首先注意到一个序列的块的个数实际上等价于元素个数-有多少个数与前一个数的值相同,前者又等价于序列长度,可以直接维护,后者相当于考虑先找出来整个序列这样的数出现的位置,设其中一个数 \(a_i\) 和 \(a_{i-1}\) 值相同,那么包括这两个元素的子序列都会有一个 \(-1\) 的贡献,这样的子序列要求左端点 \(l\) 在 \(l\in[1,i-1]\),右端点 \(r\) 在 \(r\in[i,n]\) 之间,这样的子序列的个数有 \((i-1)\times(n-i+1)\) 个。
原问题转化为 \(\sum\text{所有子序列长度}-\sum{\text{所有贡献}}\),可以分开维护,前者直接 \(\mathcal O(1)\) 算,后者开个数组维护每个位置对答案的贡献是 \(0\) 还是 \(-(i-1)\times (n-i+1)\),单点改就直接改当前位置的数组值就行。
然后随便记个 \(sum\) 就解决了。
code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
typedef pair <int, int> pii;
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 << 1) + (x << 3) + (ch ^ 48); ch = getchar (); }
return x * f;
}
int n, q;
int a[N];
int c[N], sum, sum2;
void solve (int x) { if (a[x] == a[x - 1]) c[x] = (x - 1) * (n - x + 1); else c[x] = 0; }
signed main()
{
n = read (), q = read ();
for (int i = 1; i <= n; i++) a[i] = read ();
for (int i = 2; i <= n; i++) solve (i);
for (int i = 1; i <= n; i++) sum += c[i], sum2 += (1 + i) * i / 2;
while (q--)
{
int x = read (), y = read ();
a[x] = y;
sum -= c[x]; sum -= c[x + 1];
solve (x), solve (x + 1);
sum += c[x]; sum += c[x + 1];
printf ("%lld\n", sum2 - sum);
}
return 0;
}
2+ doors
解析:
题目大意:
Narrator有一个长度为 \(n\) 的整数数组 \(a\) ,但是他只会告诉你 \(n\) 的大小和 \(q\) 个要求,每个要求包括三个整数 \(i,j,x\) ,要求满足 \(a_i\mid a_j = x\),其中 \(|\) 表示按位或运算
找到满足所有要求的字典序最小的数组 \(a\)。数据保证有解。
思路:
大套路题,首先套路的发现每一个二进制位互补影响,可以分开考虑,原问题变成给你 \(q\) 个限制,每个限制要求 \(a_i|a_j\in{0,1}\)。
考虑字典序最小,我们需要让尽量前面的数分配 \(0\) 这样一定最优,我们考虑跟 \(a_1\) 有关的 \(q_1\) 个限制,如果 \(\exist i,j|(a_1|a_i=1,a_1|a_j=1\and a_i|a_j=0)\),那么必有 \(a_i,a_j\) 为 \(0\),\(a_1\) 为 \(1\),否则让 \(a_1=0\),\(\forall i|(a_1|a_i=0)\),令 \(a_i=1\) 一定最优。
那么考虑先把当前二进制位满足 \(a_i|a_j=0\) 的 \(i,j\) 强制为 \(0\),然后从 for 1 -> n
检索每一个 \(i\),如果 \(\exist j,j\in[i,n]\and a_j=0\),那么 \(a_i=1\),否则让有条件限制的都取1即可。
时间复杂度 \(\mathcal O(V(n+q))\),其中 \(V\) 取到 \(\log x\)。
code:
#include <bits/stdc++.h>
#define int long long
#define eb emplace_back
#define fi first
#define se second
#define mk make_pair
using namespace std;
const int N = 2e5 + 10;
const int INF = LLONG_MAX;
typedef pair <int, int> pii;
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 << 1) + (x << 3) + (ch ^ 48); ch = getchar (); }
return x * f;
}
int n, m;
vector <pii> vec[N];
struct node {
int x, y, k;
} s[N];
int a[32][N];
signed main()
{
n = read (), m = read (); int mn = INF, mx = 0;
for (int i = 1; i <= m; i++)
{
s[i].x = read (), s[i].y = read (), s[i].k = read ();
if (s[i].x > s[i].y) swap (s[i].x, s[i].y);
vec[s[i].x].eb (mk(s[i].y, s[i].k));
mx = max (mx, s[i].k);
}
if (mx == 0) { while (n--) printf ("0 "); return 0; } // 场上最终挂掉的特判,挂因是0之间忘加空格了
memset (a, -1, sizeof (a));
for (int i = 31; i >= 0; i--)
{
for (int j = 1; j <= m; j++)
{
if (!((s[j].k >> i) & 1)) a[i][s[j].x] = 0, a[i][s[j].y] = 0;
if (s[j].x == s[j].y) a[i][s[j].x] = (s[j].k >> i) & 1;
}
for (int j = 1; j <= n; j++)
{
if (a[i][j] != -1)
{
if (a[i][j] == 0)
for (int k = 0; k < vec[j].size (); k++)
if (((vec[j][k].se >> i) & 1) == 1) a[i][vec[j][k].fi] = 1;
continue;
}
for (int k = 0; k < vec[j].size (); k++)
if (a[i][vec[j][k].fi] == 0 && ((vec[j][k].se >> i) & 1) == 1) { a[i][j] = 1; break; }
if (a[i][j] == 1) continue;
a[i][j] = 0;
for (int k = 0; k < vec[j].size (); k++)
if (((vec[j][k].se >> i) & 1) == 1) a[i][vec[j][k].fi] = 1;
}
}
for (int i = 1; i <= n; i++)
{
int sum = 0;
for (int j = 31; j >= 0; j--) sum += (a[j][i] << j);
printf ("%lld ", sum);
}
return 0;
}
Long Way Home
解析:
题目大意:
有 \(n\) 座城市,城市间有 \(m\) 条双向道路,通过第 \(i\) 条道路需要花费 \(w_i\) 的时间,任意两个城市之间都有航班,乘坐城市 \(u\) 和 \(v\) 之间的航班需要花费 \((u-v)^2\) 的时间,但最多乘坐 \(k\) 次航班。
现在请对于任意城市 \(i(1 \le i \le n)\),求出从城市 \(1\) 出发,到达城市 \(i\) 所需要的最短时间。
思路:
大力 dp+dijkstra,场上想到了怎么设 dp 状态,但碍于时间没想出来,或许也是板子不熟吧,按理讲应该能一眼切的。
发现 \((u-v)^2\) 是斜率优化的板子,而 \(k\) 实际上就是一个常数,这告诉我们只需要做 \(k\) 次 \(dp\)。
我们考虑设 \(dp_{i,j}\) 表示从 \(1\) 到 \(i\) 飞了 \(j\) 次的最小距离,那么有:
把这个东西化简一下:
那就是个斜率优化板子,我们考虑设 \(-2k\) 为 \(k\),\(k^2+dp_{k,j-1}\) 为 \(b\),那么维护一下下凸壳即可。
每次乘坐完飞机都跑一个dijkstra更新一下即可。
code:
#include <bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define mk make_pair
using namespace std;
const int N = 1e5 + 10;
const int K = 20 + 10;
const int INF = 0x3f3f3f3f;
typedef pair <int, int> pii;
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 << 1) + (x << 3) + (ch ^ 48); ch = getchar ( );}
return x * f;
}
int n, m, k;
struct edge {
int ver, nxt, val;
} e[N << 1];
int head[N], tot;
void add_edge (int u, int v, int w) { e[++tot] = (edge) {v, head[u], w}; head[u] = tot; }
int dp[K][N];
bool vis[N];
int h, t;
struct node {
int k, b, i;
double x, y;
} l[N];
inline double calc(node x, node y) { return double(y.b - x.b) / double(x.k - y.k); }
void dijkstra (int s, int x)
{
priority_queue <pii> que;
memset (vis, 0, sizeof (vis));
for (int i = 1; i <= n; i++) if (dp[x][i] != INF) que.push (mk (-dp[x][i], i));
while (!que.empty ())
{
int u = que.top ().second; que.pop ();
if(vis[u]) continue;
vis[u] = true;
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].ver;
if (dp[x][v] > dp[x][u] + e[i].val)
{
dp[x][v] = dp[x][u] + e[i].val;
que.push (mk (-dp[x][v], v));
}
}
}
}
signed main()
{
n = read (), m = read (), k = read ();
for (int i = 1; i <= m; i++)
{
int u = read (), v = read (), w = read ();
add_edge (u, v, w);
add_edge (v, u, w);
}
memset (dp[0], 0x3f, sizeof (dp[0]));
dp[0][1] = 0;
for (int s = 0; s <= k; s++)
{
if (s)
{
h = 1; t = 0;
for (int i = 1; i <= n; i++)
{
if (dp[s - 1][i] == INF) continue;
node now = (node) { -2 * i, i * i + dp[s - 1][i], i, -INF, INF};
while (h <= t && calc (l[t], now) <= l[t].x) t--;
if (h <= t) l[t].y = now.x = calc (l[t], now);
l[++t] = now;
}
for (int i = 2; i <= n; i++)
{
while (h <= t && l[h].y < i) h++;
int k = l[h].i;
dp[s][i] = dp[s - 1][k] + (k - i) * (k - i);
}
for (int i = 1; i <= n; i++) dp[s][i] = min (dp[s - 1][i], dp[s][i]);
}
dijkstra (1, s);
}
for (int i = 1; i <= n; i++) printf ("%lld ", dp[k][i]);
return 0;
}
6
解析:
题目大意:
思路:
交互&人类智慧题,不会,咕了
code: