2020ICPC南京区域赛 补题 & 总结
作者:互联网
前言
题目链接
https://ac.nowcoder.com/acm/contest/10272
参考题解
A - Ah, It’s Yesterday Once More
简要题意:
对于给定的 n × m n \times m n×m 的方格, 0 0 0 代表障碍, 1 1 1 代表袋鼠。有一串随机生成的长为 5 × 1 0 4 5 \times 10^4 5×104 的指令,仅包含 LRUD \text{LRUD} LRUD 字符,分别表示将所有袋鼠同时向某个方向移动(若能移动,即不经过障碍、不超出方格范围)。现要求构造一个 n × m n \times m n×m 方格图,使得对于随机生成的 500 500 500 串指令,至少有 125 125 125 个满足执行后,所有袋鼠不都在同一个方格。要求构造的袋鼠方格连通、且不含环。
1 ≤ n , m ≤ 20 1 \le n, m \le 20 1≤n,m≤20。
解题思路:
构造尽可能长且不对称的路径让袋鼠移动,还有就是构造一些分岔路口。可以构造一条沿对角线方向的 Z \text{Z} Z 形路径。
参考代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
char ans[21][21] = {
"20 20",
"11011111011111011111",
"10110100110100110100",
"11101101101101101101",
"10011011011011011001",
"10110110110110110111",
"01101101101101101101",
"11011011011011011011",
"10110110110110110110",
"11101101101101101101",
"10011011011011011001",
"10110110110110110111",
"01101101101101101101",
"11011011011011011011",
"10110110110110110110",
"11101101101101101101",
"10011011011011011001",
"10110110110110110111",
"01101101101101101101",
"01011001011001011001",
"11110111110111110111",
};
for(int i = 0; i <= 20; ++i){
cout << ans[i] << endl;
}
return 0;
}
附上顺手写的
checker
\text{checker}
checker,检验袋鼠方格是否连通、无环,输出值为
500
500
500 组随机指令中通过的组数。
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int xp[] = {0, 0, -1, 1};
const int yp[] = {-1, 1, 0, 0};
char ss[23][23];
pii pre[23][23];
int a[23][23], b[23][23], siz[23][23];
int n, m;
mt19937 rnd(time(0));
void mov(int d){
memset(b, 0, sizeof b);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
if(!a[i][j]) continue;
int x = i + xp[d], y = j + yp[d];
if(x < 1 || x > n || y < 1 || y > m || ss[x][y] == '0') x = i, y = j;
if(a[i][j]) b[x][y] += a[i][j];
}
}
memcpy(a, b, sizeof b);
}
int check(){
int ret = 0;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
if(a[i][j]) ++ret;
}
}
return ret > 1;
}
int isHack(){
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j) a[i][j] = ss[i][j] - '0';
}
for(int i = 1; i <= 50000; ++i){
mov(rnd() % 4);
}
return check();
}
pii fin(pii x){
auto &fx = pre[x.first][x.second];
return x == fx ? x : fx = fin(fx);
}
int unite(pii x, pii y){
auto fx = fin(x), fy = fin(y);
if(fx == fy) return 0;
pre[fx.first][fx.second] = fy;
siz[fy.first][fy.second] += siz[fx.first][fx.second];
return 1;
}
int isValid(){
int tot = 0, x = 0, y = 0;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
pre[i][j] = pii{i, j};
siz[i][j] = 1;
if(ss[i][j] == '1') ++tot, x = i, y = j;
}
}
if(!x) return 0;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
if(ss[i][j] != '1') continue;
if(i > 1 && ss[i - 1][j] == '1'){
if(!unite(pii{i - 1, j}, pii{i, j})) return 0;
}
if(j > 1 && ss[i][j - 1] == '1'){
if(!unite(pii{i, j - 1}, pii{i, j})) return 0;
}
}
}
auto fx = fin(pii{x, y});
return siz[fx.first][fx.second] == tot;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; ++i){
cin >> ss[i] + 1;
}
if(!isValid()){
cout << "0" << endl;
return 0;
}
int ret = 0;
for(int i = 1; i <= 500; ++i){
if(isHack()) ++ret;
}
cout << ret << endl;
return 0;
}
B - Baby’s First Suffix Array Problem
咕。
C - Certain Scientific Railgun
咕咕。
D - Degree of Spanning Tree
简要题意:
给定一个 n n n 个顶点、 m m m 条边的无向图,保证图连通,可能含有自环和重边。判断是否存在一棵生成树使得没有顶点的度数大于 n 2 \cfrac{n}{2} 2n ,存在则输出任意方案。
多组数据, 2 ≤ n ≤ 1 0 5 , n − 1 ≤ m ≤ 2 × 1 0 5 , ∑ n i ≤ 5 × 1 0 5 , ∑ m i ≤ 1 0 6 2 \le n \le 10^5, n - 1 \le m \le 2 \times 10^5, \sum n_i \le 5 \times 10^5, \sum m_i \le 10^6 2≤n≤105,n−1≤m≤2×105,∑ni≤5×105,∑mi≤106 。
解题思路:
对于 n n n 个顶点的树,用 d ( i ) d(i) d(i) 表示顶点 i i i 的度数,对任意两个顶点 u , v u, v u,v,有 d ( u ) + d ( v ) ≤ n d(u) + d(v) \le n d(u)+d(v)≤n,取等号当且仅当 u , v u, v u,v 邻接。故任意取一棵生成树,度数大于 n 2 \cfrac{n}{2} 2n 的顶点最多只有一个,若没有这样的点,则输出答案。否则将其设为根 r t rt rt 并试图调整其度数。接下来遍历其他所有边,若边 e ( u , v ) e(u, v) e(u,v) 能替换 r t rt rt 邻接的一条边,则替换,直到 d ( r t ) = ⌊ n 2 ⌋ d(rt) = \lfloor\cfrac{n}{2}\rfloor d(rt)=⌊2n⌋ 。
注意到这个过程可能让某个顶点 u u u 的度数大于 n 2 \cfrac{n}{2} 2n,这样的 u u u 只可能是 r t rt rt 的邻接点,因为当 d ( r t ) = ⌊ n 2 ⌋ d(rt) = \lfloor\cfrac{n}{2}\rfloor d(rt)=⌊2n⌋ 时, d ( u ) + d ( r t ) = ( ⌊ n 2 ⌋ + 1 ) + ⌊ n 2 ⌋ ≥ n d(u) + d(rt) = (\lfloor\cfrac{n}{2}\rfloor + 1) + \lfloor\cfrac{n}{2}\rfloor \ge n d(u)+d(rt)=(⌊2n⌋+1)+⌊2n⌋≥n ,上面提到若两顶点不邻接,度数和将小于 n n n,故得证。
故替换边的过程,优先让 r t rt rt 的非邻接顶点度数增加,若枚举的边 e ( u , v ) e(u, v) e(u,v) 都是 r t rt rt 的邻接顶点呢?优先让度数小的度数增加,可以证明只有 n = 3 n = 3 n=3 时可能出现 d ( u ) > n 2 d(u) \gt \cfrac{n}{2} d(u)>2n,而 n = 3 n = 3 n=3 是无解情况,特判即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
const int maxn = 2e5 + 5;
struct Edge{
int u, v, p;
} ei[maxn];
vector<Edge> G[maxn];
int deg[maxn], pre[maxn], vis[maxn], fe[maxn], dep[maxn];
int n, m;
int fin(int x){
return x == pre[x] ? x : pre[x] = fin(pre[x]);
}
void dfs(int u, int f, int t){
pre[u] = t, dep[u] = dep[f] + 1;
for(auto &e : G[u]){
int v = e.v;
if(v == f) continue;
dfs(v, u, t);
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T;
while(T--){
cin >> n >> m;
for(int i = 1; i <= n; ++i){
G[i].clear();
deg[i] = 0;
pre[i] = i;
}
for(int i = 1; i <= m; ++i){
vis[i] = 0;
}
for(int i = 1; i <= m; ++i){
int u, v; cin >> u >> v;
ei[i] = Edge{u, v, i};
if(fin(u) == fin(v)) continue;
G[u].pb(Edge{u, v, i}), G[v].pb(Edge{v, u, i});
pre[fin(u)] = fin(v);
++deg[u], ++deg[v], vis[i] = 1;
}
int rt = max_element(deg + 1, deg + 1 + n) - deg;
auto print = [](const string &ans){
cout << ans << "\n";
if(ans == "No") return;
for(int i = 1; i <= m; ++i){
if(vis[i]) cout << ei[i].u << " " << ei[i].v << "\n";
}
};
if(n == 3){
print("No");
continue;
}
if(deg[rt] <= n / 2){
print("Yes");
continue;
}
pre[rt] = rt, dep[rt] = 0;
for(auto &e : G[rt]){
int v = e.v;
fe[v] = e.p;
dfs(v, rt, v);
}
for(int i = 1; i <= m; ++i){
int u = ei[i].u, v = ei[i].v;
if(u == rt || v == rt) continue;
int fu = fin(u), fv = fin(v);
if(fu == fv) continue;
if(dep[u] < dep[v]) swap(u, v), swap(fu, fv);
if(dep[v] == 1 && deg[u] > deg[v]) swap(u, v), swap(fu, fv);
++deg[u], ++deg[v];
--deg[rt], --deg[fv];
vis[ei[i].p] = 1, vis[fe[fv]] = 0;
pre[fv] = fu;
if(deg[rt] <= n / 2) break;
}
print(deg[rt] <= n / 2 ? "Yes" : "No");
}
return 0;
}
E - Evil Coordinate
简要题意:
二维网格图上,给定起点 ( 0 , 0 ) (0, 0) (0,0),再给一个障碍 ( m x , m y ) (m_x, m_y) (mx,my) 和一串长为 n n n 的指令,仅包含 LRUD \text{LRUD} LRUD 字符,分别表示向某个方向移动,求调整指令顺序让移动过程不经过 ( m x , m y ) (m_x, m_y) (mx,my),无解输出 impossible \text{impossible} impossible 。
多组数据, 1 ≤ n ≤ 1 0 5 , − 1 0 9 ≤ m x , m y ≤ 1 0 9 , ∑ n i ≤ 1 0 6 1 \le n \le 10^5, -10^9 \le m_x, m_y \le 10^9, \sum n_i \le 10^6 1≤n≤105,−109≤mx,my≤109,∑ni≤106 。
解题思路:
可以证明如果有解,一定存在一种解是每个方向都连续走完,故枚举四个方向的排列即可。证明免了,看我代码分类讨论吧。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
const int xp[] = {0, 0, -1, 1};
const int yp[] = {1, -1, 0, 0};
int has[256];
char s[maxn];
int n, mx, my;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
has['U'] = 0, has['D'] = 1, has['L'] = 2, has['R'] = 3;
int T; cin >> T;
while(T--){
cin >> mx >> my;
cin >> s + 1;
n = strlen(s + 1);
int x = 0, y = 0;
map<int, int> cnt;
for(int i = 1; i <= n; ++i){
++cnt[s[i]];
x += xp[has[s[i]]], y += yp[has[s[i]]];
}
string ans = "";
auto walk4 = [&](const string &s){
for(int i = 0; i < 4; ++i){
ans += string(cnt[s[i]], s[i]);
}
};
auto sign = [](int x){
return x > 0 ? 1 : -1;
};
int ret = 1;
if(mx == 0 && my == 0 || x == mx && y == my) ret = 0;
else if(mx == 0){
if(x != 0) walk4("LRUD");
else{
int dy = cnt['U'] - cnt['D'];
if(sign(dy) == sign(my) && abs(dy) >= abs(my)){
if(!cnt['L']) ret = 0;
else walk4("LUDR");
}
else if(my > 0) walk4("DULR");
else walk4("UDLR");
}
}
else if(my == 0){
if(y != 0) walk4("UDLR");
else{
int dx = cnt['R'] - cnt['L'];
if(sign(dx) == sign(mx) && abs(dx) >= abs(mx)){
if(!cnt['U']) ret = 0;
else walk4("ULRD");
}
else if(mx > 0) walk4("LRUD");
else walk4("RLUD");
}
}
else if(mx == x){
walk4("UDLR");
}
else if(my == y){
walk4("LRUD");
}
else{
walk4("UDLR");
}
if(ret) cout << ans << endl;
else cout << "Impossible" << endl;
}
return 0;
}
F - Fireworks
简要题意:
制作一支烟花需要花费 n n n 分钟,每支烟花有 p × 1 0 − 4 p \times 10^{-4} p×10−4 的概率是完美的,每次可以花费 m m m 分钟点燃之前制作的所有烟花,若发现至少有一支完美的,则停止。问最优策略下,最短的停下的时间期望是多少?
多组数据, 1 ≤ T ≤ 1 0 4 , 1 ≤ n , m ≤ 1 0 9 , 1 ≤ p ≤ 1 0 4 1 \le T \le 10^4, 1 \le n, m \le 10^9, 1 \le p \le 10^4 1≤T≤104,1≤n,m≤109,1≤p≤104 。
解题思路:
不是完美烟花的概率 q = 1 − ( p × 1 0 − 4 ) q = 1 - (p \times 10^{-4}) q=1−(p×10−4),假设最优策略为连续制作 k k k 支烟花后点燃检验,若发现有完美烟花则停止,若没有则回到初始状态,继续执行最优策略,制作轮数 X X X 服从几何分布,故期望制作轮数 E ( X ) = 1 1 − q k E(X) = \cfrac{1}{1 - q^k} E(X)=1−qk1,期望时间为 f ( k ) = n × k + m 1 − q k f(k) = \cfrac{n \times k + m}{1 - q^k} f(k)=1−qkn×k+m ,这是一个单峰函数,可以直接三分答案。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
typedef long long ll;
#define eps 1e-7
ll n, m;
double p;
double cal(ll k){
return ((double)k * n + m) / (1 - pow(1 - p, k));
}
double solve(){
ll L = 0, R = 2e18, x = L, pw = R - L;
double fx = cal(x);
while(pw > 0){
ll l = max(L, x - pw), r = min(R, x + pw);
double fl = cal(l), fr = cal(r);
if(fl < fx) x = l, fx = fl;
else if(fr < fx) x = r, fx = fr;
else pw >>= 1;
}
return fx;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cout << fixed << setprecision(10);
int T; cin >> T;
while(T--){
ll P; cin >> n >> m >> P;
p = P * 0.0001;
cout << solve() << endl;
}
return 0;
}
G - Go
咕咕咕。
H - Harmonious Rectangle
简要题意:
给定
n
n
n 和
m
m
m ,问有多少个不同的
n
×
m
n \times m
n×m 矩阵满足:矩阵元素
a
i
,
j
∈
{
1
,
2
,
3
}
a_{i, j} \in \{1,2,3\}
ai,j∈{1,2,3},并且存在
1
≤
x
1
<
x
2
≤
n
1 \le x_1 \lt x_2 \le n
1≤x1<x2≤n 和
1
≤
y
1
<
y
2
≤
m
1 \le y_1 \lt y_2 \le m
1≤y1<y2≤m 满足
{
a
(
x
1
,
y
1
)
=
a
(
x
1
,
y
2
)
a
(
x
2
,
y
1
)
=
a
(
x
2
,
y
2
)
或
{
a
(
x
1
,
y
1
)
=
a
(
x
2
,
y
1
)
a
(
x
1
,
y
2
)
=
a
(
x
2
,
y
2
)
\left\{\begin{aligned} a(x_1, y_1) = a(x_1, y_2) \\ a(x_2, y_1) = a(x_2, y_2) \end{aligned}\right. ~\text{或}~ \left\{\begin{aligned} a(x_1, y_1) = a(x_2, y_1) \\ a(x_1, y_2) = a(x_2, y_2) \end{aligned}\right.
{a(x1,y1)=a(x1,y2)a(x2,y1)=a(x2,y2) 或 {a(x1,y1)=a(x2,y1)a(x1,y2)=a(x2,y2)
输出答案模上
1
0
9
+
7
10^9 + 7
109+7 。
多组数据, 1 ≤ T ≤ 1 0 4 , 1 ≤ n , m ≤ 2 × 1 0 3 1 \le T \le 10^4, 1 \le n, m \le 2 \times 10^3 1≤T≤104,1≤n,m≤2×103 。
解题思路:
由于不同的两个元素组合只有 9 9 9 种,当 max ( n , m ) > 9 \max(n, m) \gt 9 max(n,m)>9 时,根据抽屉原理,任意矩阵都满足条件。对于其他情况,暴搜打表找出不符合的情况即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int mod = 1e9 + 7;
// int a[10][10], row[10][10][16], col[10][10][16];
// int n, m;
// int check(int x, int y){
// for(int i = 1; i < y; ++i){
// if(row[i][y][a[x][i] * 4 + a[x][y]]) return 0;
// }
// for(int i = 1; i < x; ++i){
// if(col[i][x][a[i][y] * 4 + a[x][y]]) return 0;
// }
// return 1;
// }
// void cal(int x, int y, int d){
// for(int i = 1; i < y; ++i){
// row[i][y][a[x][i] * 4 + a[x][y]] += d;
// }
// for(int i = 1; i < x; ++i){
// col[i][x][a[i][y] * 4 + a[x][y]] += d;
// }
// }
// int dfs(int x, int y){
// if(x > n) return 1;
// if(y > m) return dfs(x + 1, 1);
// int ret = 0;
// for(int i = 1; i <= 3; ++i){
// a[x][y] = i;
// if(!check(x, y)) continue;
// cal(x, y, 1);
// ret += dfs(x, y + 1);
// cal(x, y, -1);
// }
// return ret;
// }
ll qpow(ll a, ll b){
ll ret = 1;
while(b){
if(b & 1) ret = ret * a % mod;
a = a * a % mod;
b >>= 1;
}
return ret;
}
int ans[10][10] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 15, 339, 4761, 52929, 517761, 4767849, 43046721, 387420489,
0, 0, 339, 16485, 518265, 14321907, 387406809, 460338013, 429534507, 597431612,
0, 0, 4761, 518265, 43022385, 486780060, 429534507, 792294829, 175880701, 246336683,
0, 0, 52929, 14321907, 486780060, 288599194, 130653412, 748778899, 953271190, 644897553,
0, 0, 517761, 387406809, 429534507, 130653412, 246336683, 579440654, 412233812, 518446848,
0, 0, 4767849, 460338013, 792294829, 748778899, 579440654, 236701429, 666021604, 589237756,
0, 0, 43046721, 429534507, 175880701, 953271190, 412233812, 666021604, 767713261, 966670169,
0, 0, 387420489, 597431612, 246336683, 644897553, 518446848, 589237756, 966670169, 968803245,
};
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T;
while(T--){
int n, m; cin >> n >> m;
ll ret = n <= 9 && m <= 9 ? ans[n][m] : qpow(3, n * m);
if(n == 1 || m == 1) ret = 0;
cout << ret << "\n";
}
return 0;
}
I - Interested in Skiing
简要题意:
二维平面 [ − m , m ] × R [-m, m] \times \mathbb{R} [−m,m]×R 上有 n n n 条线段作为障碍,以端点坐标 ( x 1 , y 1 ) (x_1, y_1) (x1,y1) 和 ( x 2 , y 2 ) (x_2, y_2) (x2,y2) 表示,起点为 ( 0 , − inf ) (0, -\inf) (0,−inf) 的一个点以 v y v_y vy 的速度向 y \text{y} y 轴正方向移动,终点为 ( 0 , inf ) (0, \inf) (0,inf) 。求最小的 v x ∗ v_x^* vx∗,使得当水平速度 v x > v x ∗ v_x \gt v_x^* vx>vx∗ 时,能从起点移动到终点,无解则输出 − 1 -1 −1 。
1 ≤ n ≤ 100 , 1 ≤ m ≤ 1 0 4 , 1 ≤ v y ≤ 10 , − m ≤ x 1 , x 2 ≤ m , − 1 0 − 4 ≤ y 1 , y 2 ≤ 1 0 4 1 \le n \le 100, 1 \le m \le 10^4, 1 \le v_y \le 10, -m \le x_1, x_2 \le m, -10^{-4} \le y_1, y_2 \le 10^4 1≤n≤100,1≤m≤104,1≤vy≤10,−m≤x1,x2≤m,−10−4≤y1,y2≤104 。
解题思路:
最优策略是经过一些线段的端点,将所有端点按 y \text{y} y 升序排序,以该顺序拓扑转移, d p i dp_i dpi 表示到达第 i i i 个点所需的最小 v x v_x vx ,转移时枚举 O ( n ) O(n) O(n) 判断两点间是否能判断,总时间复杂度 O ( n 3 ) O(n^3) O(n3) 。
参考代码:
#include<bits/stdc++.h>
using namespace std;
struct Point{
int x, y;
};
struct Line{
Point s, e;
};
inline int sign(int x){
return x == 0 ? 0 : x > 0 ? 1 : -1;
}
inline int cross(const Point &a, const Point &b, const Point &c){
return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}
inline int isIns(const Line &l1, const Line &l2){
return
max(l1.s.x, l1.e.x) > min(l2.s.x, l2.e.x) &&
max(l2.s.x, l2.e.x) > min(l1.s.x, l1.e.x) &&
max(l1.s.y, l1.e.y) > min(l2.s.y, l2.e.y) &&
max(l2.s.y, l2.e.y) > min(l1.s.y, l1.e.y) &&
sign(cross(l1.s, l2.s, l1.e)) * sign(cross(l1.s, l2.e, l1.e)) < 0 &&
sign(cross(l2.s, l1.s, l2.e)) * sign(cross(l2.s, l1.e, l2.e)) < 0;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cout << fixed << setprecision(10);
int n, m, vy; cin >> n >> m >> vy;
vector<Point> pi(2 * n);
vector<Line> li(n);
for(int i = 0; i < n; ++i){
cin >> pi[i << 1].x >> pi[i << 1].y;
cin >> pi[i << 1 | 1].x >> pi[i << 1 | 1].y;
li[i] = Line{pi[i << 1], pi[i << 1 | 1]};
}
auto isOK = [&](const Point &a, const Point &b) -> int{
Line l = Line{a, b};
for(int i = 0; i < n; ++i){
if(isIns(l, li[i])) return 0;
}
return 1;
};
sort(pi.begin(), pi.end(), [](const Point &a, const Point &b){ return a.y < b.y; });
vector<double> dp(2 * n, 1e20);
double ans = 1e20;
for(int i = 0; i < 2 * n; ++i){
if(pi[i].x <= -m || pi[i].x >= m) continue;
if(isOK(Point{pi[i].x, -10001}, pi[i])) dp[i] = 0;
for(int j = 0; j < i; ++j){
if(pi[j].y == pi[i].y) break;
if(isOK(pi[j], pi[i])){
double ki = 1.0 * (pi[i].x - pi[j].x) / (pi[i].y - pi[j].y);
dp[i] = min(dp[i], max(dp[j], abs(ki)));
}
}
if(isOK(Point{pi[i].x, 10001}, pi[i])) ans = min(ans, dp[i]);
// cout << i << " " << pi[i].x << " " << pi[i].y << " " << ans << endl;
}
if(isOK(Point{0, -10001}, Point{0, 10001})) ans = 0;
if(ans > 1e10) cout << "-1" << endl;
else cout << ans * vy << endl;
return 0;
}
J - Just Another Game of Stones
简要题意:
给定一个长度为 n n n 的数组 a i a_i ai ,有 q q q 次操作:
- 1 l r x 1~l~r~x 1 l r x ,表示令 a i = max ( a i , x ) , i ∈ [ l , r ] a_i = \max(a_i, x), i \in [l, r] ai=max(ai,x),i∈[l,r] 。
- 2 l r x 2~l~r~x 2 l r x ,表示询问用 [ l , r ] [l, r] [l,r] 的石堆和额外的一堆有 x x x 个石子的石堆一起,进行 Nim \text{Nim} Nim 博弈的必胜情况的第一步操作种数。
1 ≤ n , q ≤ 2 × 1 0 5 , 1 ≤ l , r ≤ n , 0 ≤ a i , x < 2 30 − 1 1 \le n, q \le 2 \times 10^5, 1 \le l, r \le n, 0 \le a_i, x \lt 2^{30} - 1 1≤n,q≤2×105,1≤l,r≤n,0≤ai,x<230−1 。
解题思路:
Nim \text{Nim} Nim 博弈,当石子数量异或和不为 0 0 0 时为必胜态,否则为必败态。假设异或和为 S S S,第一步操作就是取一堆石子,让异或和变成 0 0 0,即对石子个数为 a i a_i ai 的石堆,取走 a i − a i ⊕ S a_i - a_i \oplus S ai−ai⊕S 个石子,故询问答案就是 a i ≥ a i ⊕ S a_i \ge a_i \oplus S ai≥ai⊕S 的 i i i 的个数。
考虑 S S S 二进制最高位 1 1 1,若 a i a_i ai 对应位也是 1 1 1 ,则必有 a i ≥ a i ⊕ S a_i \ge a_i \oplus S ai≥ai⊕S ,反之不成立。修改操作使用 segment tree beats \text{segment tree beats} segment tree beats 的维护技巧。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int inf = 1 << 30;
const int maxn = 2e5 + 5;
int ans[30];
int a[maxn], n, q;
struct SegTree{
int mn[maxn << 2], mn2[maxn << 2], sum[maxn << 2][30], cov[maxn << 2], num[maxn << 2];
void pushUp(int rt){
for(int i = 0; i < 30; ++i) sum[rt][i] = sum[lson][i] + sum[rson][i];
if(mn[lson] == mn[rson]){
mn[rt] = mn[lson], num[rt] = num[lson] + num[rson];
mn2[rt] = min(mn2[lson], mn2[rson]);
}
else if(mn[lson] < mn[rson]){
mn[rt] = mn[lson], num[rt] = num[lson];
mn2[rt] = min(mn2[lson], mn[rson]);
}
else{
mn[rt] = mn[rson], num[rt] = num[rson];
mn2[rt] = min(mn[lson], mn2[rson]);
}
}
void build(int l, int r, int rt){
cov[rt] = -1;
if(l == r){
for(int i = 0; i < 30; ++i) sum[rt][i] = (a[l] >> i) & 1;
mn[rt] = a[l], mn2[rt] = inf, num[rt] = 1;
return;
}
int mid = gmid;
build(l, mid, lson);
build(mid + 1, r, rson);
pushUp(rt);
}
void pushDown2(int rt, int son){
if(cov[rt] <= mn[son]) return;
for(int i = 0; i < 30; ++i){
sum[son][i] -= num[son] * ((mn[son] >> i) & 1);
sum[son][i] += num[son] * ((cov[rt] >> i) & 1);
}
mn[son] = cov[son] = cov[rt];
}
void pushDown(int l, int r, int rt){
if(cov[rt] != -1){
int mid = gmid;
pushDown2(rt, lson);
pushDown2(rt, rson);
cov[rt] = -1;
}
}
void update(int l, int r, int rt, int L, int R, int val){
if(val <= mn[rt]) return;
if(l >= L && r <= R && val < mn2[rt]){
cov[0] = val, pushDown2(0, rt);
return;
}
int mid = gmid; pushDown(l, r, rt);
if(L <= mid) update(l, mid, lson, L, R, val);
if(R > mid) update(mid + 1, r, rson, L, R, val);
pushUp(rt);
}
void query(int l, int r, int rt, int L, int R){
if(l >= L && r <= R){
for(int i = 0; i < 30; ++i){
ans[i] += sum[rt][i];
}
return;
}
int mid = gmid; pushDown(l, r, rt);
if(L <= mid) query(l, mid, lson, L, R);
if(R > mid) query(mid + 1, r, rson, L, R);
}
} tr;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> q;
for(int i = 1; i <= n; ++i){
cin >> a[i];
}
tr.build(1, n, 1);
while(q--){
int opt, l, r, x; cin >> opt >> l >> r >> x;
if(opt == 1){
tr.update(1, n, 1, l, r, x);
}
else{
memset(ans, 0, sizeof ans);
tr.query(1, n, 1, l, r);
int ret = 0;
for(int i = 29; i >= 0; --i){
ans[i] += (x >> i) & 1;
if(ans[i] & 1) { ret = ans[i]; break; }
}
cout << ret << "\n";
}
}
return 0;
}
K - K Co-prime Permutation
简要题意:
给定 n n n 和 k k k ,构造一个排列 p i p_i pi 使得 gcd ( p i , i ) = 1 \gcd(p_i, i) = 1 gcd(pi,i)=1 的 i i i 的个数恰有 k k k 个,无解输出 − 1 -1 −1 。
1 ≤ n ≤ 1 0 6 , 0 ≤ k ≤ n 1 \le n \le 10^6, 0 \le k \le n 1≤n≤106,0≤k≤n 。
解题思路:
当 i > 1 i \gt 1 i>1 , gcd ( i , i − 1 ) = 1 \gcd(i, i - 1) = 1 gcd(i,i−1)=1 。特判 k = 0 k = 0 k=0 无解。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int ans[maxn];
int n, k;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> k;
if(k == 0) cout << "-1" << endl;
else{
for(int i = 2; i <= k; ++i) ans[i - 1] = i;
ans[k] = 1;
for(int i = k + 1; i <= n; ++i) ans[i] = i;
for(int i = 1; i < n; ++i) cout << ans[i] << " ";
cout << ans[n] << endl;
}
return 0;
}
L - Let’s Play Curling
简要题意:
给定长度为 n n n 的数组 a i a_i ai 和长度为 m m m 的数组 b i b_i bi ,对于一个数 c c c,若 a i a_i ai 满足对于所有 j j j , ∣ c − a i ∣ < ∣ c − b j ∣ \vert c - a_i \vert \lt \vert c - b_j \vert ∣c−ai∣<∣c−bj∣ 则得分加 1 1 1 ,最终得分 f ( c ) f(c) f(c) 为满足的 i i i 的个数,求最大的 f ( c ) f(c) f(c) 。
多组数据, 1 ≤ n , m ≤ 1 0 5 , 1 ≤ a i , b i ≤ 1 0 9 , ∑ n i , ∑ m i ≤ 5 × 1 0 5 1 \le n, m \le 10^5, 1 \le a_i, b_i \le 10^9, \sum n_i, \sum m_i \le 5 \times 10^5 1≤n,m≤105,1≤ai,bi≤109,∑ni,∑mi≤5×105 。
解题思路:
选择若干 a i a_i ai ,需要满足 max { ∣ c − a i ∣ } < min { ∣ c − b j ∣ } \max\{~\vert c - a_i\vert~\} \lt \min\{~\vert c - b_j\vert~\} max{ ∣c−ai∣ }<min{ ∣c−bj∣ } ,若确定 a i a_i ai ,则 c c c 取值为 max { a i } + min { a i } 2 \cfrac{\max\{a_i\} + \min\{a_i\}}{2} 2max{ai}+min{ai} 最优,显然不能有 b j b_j bj 落在 [ min { a i } , max { a i } ] [\min\{a_i\}, \max\{a_i\}] [min{ai},max{ai}] 。故答案就是 ( b j , b j + 1 ) (b_j, b_{j + 1}) (bj,bj+1) 最多有多少个 a i a_i ai 。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int a[maxn], b[maxn];
int n, m;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T;
while(T--){
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> a[i];
for(int i = 1; i <= m; ++i) cin >> b[i];
b[++m] = 0, b[++m] = 1e9 + 1;
sort(a + 1, a + 1 + n);
sort(b + 1, b + 1 + m);
int ret = 0;
for(int i = 1; i < m; ++i){
int p1 = lower_bound(a + 1, a + 1 + n, b[i] + 1) - a;
int p2 = lower_bound(a + 1, a + 1 + n, b[i + 1]) - a;
ret = max(ret, p2 - p1);
}
if(ret > 0) cout << ret << endl;
else cout << "Impossible" << endl;
}
return 0;
}
M - Monster Hunter
简要题意:
给一棵 n n n 个结点的有根树, 1 1 1 号结点为根结点,每个结点上有一个血量为 h p i hp_i hpi 的怪物。必须从根到叶逐一消灭怪物,消灭第 u u u 号怪物的代价为 h p u + ∑ h p v hp_u + \sum hp_v hpu+∑hpv ,其中 v v v 是 u u u 的直接子结点。使用一次魔法可以让一只怪物血量归零,求使用魔法次数为 0 , 1 , 2 , ⋯ , n 0, 1, 2, \cdots, n 0,1,2,⋯,n 时所需的最小代价分别是多少。
多组数据, 2 ≤ n ≤ 2 × 1 0 3 , 1 ≤ h p i ≤ 1 0 9 , ∑ n i ≤ 2 × 1 0 3 2 \le n \le 2 \times 10^3, 1 \le hp_i \le 10^9, \sum n_i \le 2 \times 10^3 2≤n≤2×103,1≤hpi≤109,∑ni≤2×103 。
解题思路:
考虑一次魔法操作能减少的代价,如果对 u u u 结点使用,首先对父结点 f f f,消灭父结点 f f f 的代价减少 h p u hp_u hpu ,然后是减少 h p u + ∑ h p v hp_u + \sum hp_v hpu+∑hpv ,可以想象一次魔法操作对应将边 ( u , f ) (u, f) (u,f) 和所有 ( u , v ) (u, v) (u,v) 覆盖,将结点 u u u 覆盖。多次魔法操作后,一条边、一个结点被覆盖多次,也只能减少一次代价,然后就是树上背包枚举子树合并转移了。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define sz(a) ((int)a.size())
#define pb push_back
const int maxn = 2e3 + 5;
typedef long long ll;
const ll oo = 1ll << 60;
vector<int> G[maxn];
int siz[maxn];
ll a[maxn], sum[maxn], dp[maxn][maxn][2], ans[maxn];
int n;
void dfs(int u, int f){
dp[u][0][0] = 0, dp[u][1][1] = sum[u], siz[u] = 1;
for(auto &v : G[u]){
if(v == f) continue;
dfs(v, u);
for(int i = siz[u]; i >= 0; --i){
for(int j = siz[v]; j >= 0; --j){
dp[u][i + j][0] = max(dp[u][i + j][0], dp[u][i][0] + max(dp[v][j][0], dp[v][j][1]));
dp[u][i + j][1] = max(dp[u][i + j][1], dp[u][i][1] + max(dp[v][j][0], dp[v][j][1] - a[v]));
}
}
siz[u] += siz[v];
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T;
while(T--){
cin >> n;
for(int i = 1; i <= n; ++i){
G[i].clear();
for(int j = 0; j <= n; ++j){
dp[i][j][0] = dp[i][j][1] = -oo;
}
}
for(int i = 2; i <= n; ++i){
int u; cin >> u;
G[u].pb(i);
}
for(int i = 1; i <= n; ++i){
cin >> a[i];
}
for(int i = 0; i <= n; ++i){
ans[i] = 0;
}
for(int i = 1; i <= n; ++i){
sum[i] = a[i];
for(auto &v : G[i]) sum[i] += a[v];
ans[0] += sum[i];
if(i > 1) sum[i] += a[i];
}
dfs(1, 0);
for(int i = 1; i <= n; ++i){
ans[i] = ans[0] - max(dp[1][i][0], dp[1][i][1]);
}
for(int i = 0; i < n; ++i) cout << ans[i] << " ";
cout << ans[n] << endl;
}
return 0;
}
标签:总结,rt,le,return,10,int,cin,2020ICPC,补题 来源: https://blog.csdn.net/weixin_44059127/article/details/112850536