2022杭电多校(七)
文章目录
一、比赛小结
二、题目分析及解法(基础题)
1002、Independent Feedback Vertex Set
题目链接:Problem - 7210 (hdu.edu.cn)
题意:
给你一张特殊的图,你需要将这 n 个点分为两个集合s1, s2
,s1
中为独立集合,所有的点不能有边相连;s2
中的点不能有环,只能是森林,每个点都有权值,求s1
的最大权值和。
题解:
答案必须包含每个三元环中的恰好一个点,因为一个点都不选则会破坏森林约束,选至少两个则会破坏独立集约束。
同时对于一对有至少两个公共点的三元环,确定了答案包含其中一个的某个点之后另一个也随之确定了。
因此答案只可能有三种,分别对应图中唯一的三染色方案(去重后)中的每一种颜色的点。
代码:
#include <bits/stdc++.h>
using namespace std;
int main(void) {
typedef pair<int, int> pii;
int T;
scanf("%d", &T);
while (T--) {
int n;
scanf("%d", &n);
vector<int> w(n);
vector<int> c(n, 0);
c[0] = 0;
c[1] = 1;
c[2] = 2;
for (int i = 0; i < n; ++i) scanf("%d", &w[i]);
for (int i = 3; i < n; ++i) {
int j, k;
scanf("%d %d", &j, &k);
j--;
k--;
c[i] = (3 - c[j] - c[k]) % 3;
}
long long ans[3] = {0, 0, 0};
for (int i = 0; i < n; ++i) ans[c[i]] += w[i];
cout << max({ans[0], ans[1], ans[2]}) << endl;
}
return 0;
}
1003、Counting Stickmen
题目链接:Problem - 7211 (hdu.edu.cn)
题意:
给出一张图,计数 “火柴人” 的个数
题解:
将火柴人划分为头, 手臂 × 2 \times 2 ×2 , 手掌 × 2 \times 2 ×2 , 身体 × 2 \times 2 ×2 , 腿 × 2 \times 2 ×2 的 8 8 8 条边.
O ( n ) O(n) O(n) 预处理 d e g x , ∑ u ∈ a d j ( x ) ( d e g u − 1 ) , ∑ u ∈ a d j ( x ) ( d e g u − 1 2 ) \displaystyle deg_x, \sum_{u\in adj(x)}(deg_u-1), \sum_{u\in adj(x)}{deg_u-1 \choose 2} degx,u∈adj(x)∑(degu−1),u∈adj(x)∑(2degu−1) ,再扫一遍答案即可
代码:
#include <bits/stdc++.h>
using namespace std;
#define gc c = getchar()
#define r(x) read(x)
#define ll long long
template <typename T>
inline void read(T &x) {
x = 0;
T k = 1;
char gc;
while (!isdigit(c)) {
if (c == '-') k = -1;
gc;
}
while (isdigit(c)) {
x = x * 10 + c - '0';
gc;
}
x *= k;
}
const int N = 1e7 + 7;
const int p = 998244353;
vector<int> G[N];
int deg[N];
int s0[N];
int s1[N];
int s2[N];
inline int add(int a, int b) { return (a += b) >= p ? a - p : a; }
inline int sub(int a, int b) { return (a -= b) < 0 ? a + p : a; }
inline ll calc(int x) { return (ll)x * (x - 1) / 2 % p; }
int main() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int T;
r(T);
while (T--) {
int n;
r(n);
for (int i = 1; i <= n; ++i)
G[i].clear(), deg[i] = s0[i] = s1[i] = s2[i] = 0;
for (int i = 1; i < n; ++i) {
int u, v;
r(u), r(v);
G[u].push_back(v);
G[v].push_back(u);
++deg[u], ++deg[v];
}
for (int x = 1; x <= n; ++x) {
s0[x] = deg[x];
for (auto &y : G[x]) {
s1[x] = add(s1[x], deg[y] - 1);
s2[x] = add(s2[x], calc(deg[y] - 1));
}
}
int ans = 0;
for (int x = 1; x <= n; ++x) {
if (s0[x] >= 4) {
for (auto &y : G[x]) {
if (s0[y] >= 3) {
int head = sub(s0[x], 3);
int foot = sub(s0[y], 1);
int hand = sub(s1[x], foot);
ans = add(ans, (ll)head *
sub(calc(hand), sub(s2[x], calc(deg[y] - 1))) %
p * calc(foot) % p);
}
}
}
}
printf("%d\n", ans);
}
return 0;
}
1004、Black Magic
题目链接:Problem - 7212 (hdu.edu.cn)
题意:
给出 n n n 个方块,每个方块的左/右都可能是黑或白。将这些方块排成一列,如果两个相邻方块相连接的面都是黑色,那么这两个方块会连在一起。求连通块的最大/最小数量。
题解:
很签到,见代码
代码:
#include <bits/stdc++.h>
using namespace std;
int e, l, r, b;
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int _;
cin >> _;
while (_--) {
cin >> e >> l >> r >> b;
int m, M;
if (l || r)
m = e + max(l, r);
else if (b)
m = e + 1;
else
m = e;
M = e + l + r + min(e + 1, b);
cout << m << " " << M << endl;
}
return 0;
}
1006、Sumire
题目链接:Problem - 7214 (hdu.edu.cn)
题意:
定义 f ( x , B , d ) f(x, B, d) f(x,B,d) 为 B B B 进制下数码 d d d 在 x x x 这个数里面出现的次数, 0 0 = 0 0^0=0 00=0
计算: ∑ i = 1 r f k ( i , B , d ) \displaystyle \sum_{i=1}^rf^k(i, B, d) i=1∑rfk(i,B,d)
题解:
数位DP,用 d p [ i ] [ j ] [ 0 / 1 ] [ 0 / 1 ] dp[i][j][0/1][0/1] dp[i][j][0/1][0/1] 表示已经统计了前 i i i 位,其中有 j j j 个位上是 d d d ,是否取数字上界,以及是否有前导零即可。用记忆化搜索维护转移。转移时需分四种情况考虑:
1、当前位卡住上界,且之前也卡住数字上界。
2、当前位为0,且目前还在维护前导零。
3、当前位为
4、以上都不满足
要注意上述几种情况可能也会合并成一种。用这种讨论的形式能够优化转移的复杂度。最终复杂度为 O ( log B 2 r ) O(\log_B^2r) O(logB2r) 。
代码:
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define SZ(x) ((int)x.size())
#define lowbit(x) x & -x
#define pb push_back
#define ALL(x) (x).begin(), (x).end()
#define UNI(x) sort(ALL(x)), x.resize(unique(ALL(x)) - x.begin())
#define GETPOS(c, x) (lower_bound(ALL(c), x) - c.begin())
#define LEN(x) strlen(x)
#define MS0(x) memset((x), 0, sizeof((x)))
#define Rint register int
#define ls (u << 1)
#define rs (u << 1 | 1)
typedef unsigned int unit;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> Vi;
typedef vector<ll> Vll;
typedef vector<pii> Vpii;
template <class T>
void _R(T &x) {
cin >> x;
}
void _R(int &x) { scanf("%d", &x); }
void _R(ll &x) { scanf("%lld", &x); }
void _R(ull &x) { scanf("%llu", &x); }
void _R(double &x) { scanf("%lf", &x); }
void _R(char &x) { scanf(" %c", &x); }
void _R(char *x) { scanf("%s", x); }
void R() {}
template <class T, class... U>
void R(T &head, U &...tail) {
_R(head);
R(tail...);
}
template <class T>
void _W(const T &x) {
cout << x;
}
void _W(const int &x) { printf("%d", x); }
void _W(const ll &x) { printf("%lld", x); }
void _W(const double &x) { printf("%.16f", x); }
void _W(const char &x) { putchar(x); }
void _W(const char *x) { printf("%s", x); }
template <class T, class U>
void _W(const pair<T, U> &x) {
_W(x.fi);
putchar(' ');
_W(x.se);
}
template <class T>
void _W(const vector<T> &x) {
for (auto i = x.begin(); i != x.end(); _W(*i++))
if (i != x.cbegin()) putchar(' ');
}
void W() {}
template <class T, class... U>
void W(const T &head, const U &...tail) {
_W(head);
putchar(sizeof...(tail) ? ' ' : '\n');
W(tail...);
}
const int MOD = 1e9 + 7, mod = 998244353;
ll qpow(ll a, ll b) {
ll res = 1;
a %= MOD;
assert(b >= 0);
for (; b; b >>= 1) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
}
return res;
}
const int MAXN = 5e5 + 10, MAXM = 1e7 + 10;
const int INF = INT_MAX, SINF = 0x3f3f3f3f;
const ll llINF = LLONG_MAX;
const int inv2 = (MOD + 1) / 2;
const int Lim = 1 << 20;
template <int _P>
struct Modint {
static constexpr int P = _P;
private:
int v;
public:
Modint() : v(0) {}
Modint(ll _v) {
v = _v % P;
if (v < 0) v += P;
}
explicit operator int() const { return v; }
explicit operator long long() const { return v; }
explicit operator bool() const { return v > 0; }
bool operator==(const Modint &o) const { return v == o.v; }
bool operator!=(const Modint &o) const { return v != o.v; }
Modint operator-() const { return Modint(v ? P - v : 0); }
Modint operator+() const { return *this; }
Modint &operator++() {
v++;
if (v == P) v = 0;
return *this;
}
Modint &operator--() {
if (v == 0) v = P;
v--;
return *this;
}
Modint operator++(int) {
Modint r = *this;
++*this;
return r;
}
Modint operator--(int) {
Modint r = *this;
--*this;
return r;
}
Modint &operator+=(const Modint &o) {
v += o.v;
if (v >= P) v -= P;
return *this;
}
Modint operator+(const Modint &o) const { return Modint(*this) += o; }
Modint &operator-=(const Modint &o) {
v -= o.v;
if (v < 0) v += P;
return *this;
}
Modint operator-(const Modint &o) const { return Modint(*this) -= o; }
Modint &operator*=(const Modint &o) {
v = (int)(((ll)v) * o.v % P);
return *this;
}
Modint operator*(const Modint &o) const { return Modint(*this) *= o; }
Modint &operator/=(const Modint &o) { return (*this) *= o.Inv(); }
Modint operator/(const Modint &o) const { return Modint(*this) /= o; }
friend Modint operator+(const Modint &x, const ll &o) {
return x + (Modint)o;
}
friend Modint operator+(const ll &o, const Modint &x) {
return x + (Modint)o;
}
friend Modint operator-(const Modint &x, const ll &o) {
return x - (Modint)o;
}
friend Modint operator-(const ll &o, const Modint &x) {
return (Modint)o - x;
}
friend Modint operator*(const Modint &x, const ll &o) {
return x * (Modint)o;
}
friend Modint operator*(const ll &o, const Modint &x) {
return x * (Modint)o;
}
friend Modint operator/(const Modint &x, const ll &o) {
Modint c = o;
return x * c.Inv();
}
friend Modint operator/(const ll &o, const Modint &x) {
Modint c = o;
return c * x.Inv();
}
Modint operator^(ll o) const {
Modint r = 1, t = v;
while (o) {
if (o & 1) r *= t;
t *= t;
o >>= 1;
}
return r;
}
Modint operator~() { return (*this) ^ (P - 2); }
Modint Inv() const { return (*this) ^ (P - 2); }
};
using mi = Modint<MOD>;
template <int P>
void _W(Modint<P> x) {
printf("%d", (int)x);
}
template <int P>
void _R(Modint<P> &x) {
ll t;
scanf("%lld", &t);
x = t;
}
mi dp[75][75][2][2], vis[75][75][2][2];
;
ll t;
int s[75], k, b, d, n, m, c;
mi dfs(int dep, int tot, int lim, bool zero) {
if (dep == m + 1 && tot == 0) return 1;
if (dep == m + 1) return 0;
if (tot < 0) return 0;
if (vis[dep][tot][lim][zero]) return dp[dep][tot][lim][zero];
vis[dep][tot][lim][zero] = 1;
int up = lim ? s[dep] : b - 1;
int ct = 0, i = 0;
int c = (i == d);
if (zero && (d == 0)) c = 0;
dp[dep][tot][lim][zero] += dfs(dep + 1, tot - c, lim && (s[dep] == i), zero);
ct++;
if (i != d && d <= up) {
ct++;
i = d;
int c = (i == d);
if (zero && (d == 0)) c = 0;
dp[dep][tot][lim][zero] += dfs(dep + 1, tot - c, lim && (s[dep] == i), 0);
}
if (i != up) {
ct++;
i = up;
dp[dep][tot][lim][zero] +=
dfs(dep + 1, tot, lim && (s[dep] == i), zero && (i == 0));
}
dp[dep][tot][lim][zero] += dfs(dep + 1, tot, 0, 0) * max(0, up - ct + 1);
return dp[dep][tot][lim][zero];
}
mi calc(bool f) {
MS0(dp);
MS0(vis);
R(t);
m = 0;
t -= f;
while (t) {
s[++m] = t % b;
t /= b;
}
reverse(s + 1, s + m + 1);
mi ans = 0;
for (int i = 1; i <= m; i++) {
mi t = i;
ans += dfs(1, i, 1, 1) * (t ^ k);
}
return ans;
}
void solve() {
R(k, b, d);
mi ans = calc(1);
ans = calc(0) - ans;
W(ans);
}
int main() {
srand(time(0));
int T = 1;
scanf("%d", &T);
for (int kase = 1; kase <= T; kase++) {
// printf("Case #%d: ",kase);
solve();
}
return 0;
}
1007、Weighted Beautiful Tree
题目链接:Problem - 7215 (hdu.edu.cn)
题意:
给出一棵带权树,其边,点均有权,称一个边是 “好的” ,当且仅当 ω u ≤ ω ≤ ω v \omega_u \leq \omega\leq \omega_v ωu≤ω≤ωv ,可以执行操作,改变一个节点的权, c o s t = ∣ ω − ω ′ ∣ cost = |\omega-\omega'| cost=∣ω−ω′∣
求最小的 c o s t cost cost 使得全部边都变成 “好的” 。
题解:
树形 dp 即可,用 d p [ u ] [ 0 / 1 ] dp[u][0/1] dp[u][0/1] 代表处理完以节点 u u u 为根的子树,并且当前节点的 weight 小于等于其父边/大于等于其父边时的最小代价。转移的时候需要把当前节点的所有子边按照边的权值排序后,再扫一遍以统计花费。设当前节点为 u u u ,连接到的子节点为 v v v ,两者之间连接的边权值为 ω \omega ω 当前枚举到的花费是 j j j ,父边的权值是 f ω f\omega fω 。则有:
v a l = { d p [ v ] [ 0 ] j > ω e min ( d p [ v ] [ 0 ] , d p [ v ] [ 1 ] ) j = ω e d p [ v ] [ 1 ] j < ω e val = \begin{cases} dp[v][0] & j>\omega_e \\ \min(dp[v][0], dp[v][1]) & j=\omega_e \\ dp[v][1] & j<\omega_e\end{cases} val=⎩ ⎨ ⎧dp[v][0]min(dp[v][0],dp[v][1])dp[v][1]j>ωej=ωej<ωe
{ d p [ u ] [ 0 ] = min ( d p [ u ] [ 0 ] , v a l ) j ≤ f ω d p [ u ] [ 1 ] = min ( d p [ u ] [ 1 ] , v a l ) j ≥ f ω \begin{cases} dp[u][0] = \min(dp[u][0], val)& j \leq f\omega \\ dp[u][1] = \min(dp[u][1], val) & j\geq f\omega\end{cases} {dp[u][0]=min(dp[u][0],val)dp[u][1]=min(dp[u][1],val)j≤fωj≥fω
在对权值进行扫描的时候需要对相同权值子节点的进行批量处理。最终复杂度 O ( n log n ) O(n\log n) O(nlogn) 。
代码:
#pragma comment(linker, "/STACK:1024000000,1024000000")
/*
Author: elfness@UESTC
*/
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <string>
#include <vector>
using namespace std;
typedef long long LL;
typedef pair<LL, LL> PII;
#define PB push_back
#define fi first
#define se second
#define MP make_pair
const int ooo = 1000000000;
const LL oo = (LL)ooo * ooo;
const int P = 1000000007;
const int V = 100100;
vector<PII> f[V];
LL a[V], pre[V], inc[V], de[V];
LL dp[V][2];
LL cal(int u, int val) {
if (a[u] < val)
return (LL)inc[u] * (val - a[u]);
else
return de[u] * (a[u] - val);
}
void dfs(int u, int fa) {
vector<PII> son;
vector<LL> vals;
for (int i = 0; i < f[u].size(); ++i) {
int v = f[u][i].fi;
LL w = f[u][i].se;
if (v == fa) continue;
pre[v] = w;
dfs(v, u);
son.PB(MP(w, v));
vals.PB(w);
}
vals.PB(pre[u]);
vals.PB(a[u]);
dp[u][0] = dp[u][1] = oo;
sort(son.begin(), son.end());
int sz = son.size();
sort(vals.begin(), vals.end());
vector<LL> s(sz + 1);
LL ps = -1, pb = 0;
LL ss = 0, sb = 0;
for (int i = 0; i < sz; ++i) {
int v = son[i].se;
LL w = son[i].fi;
if (w == vals[0])
pb = i + 1;
else
sb += dp[v][1];
s[i + 1] = s[i] + min(dp[v][0], dp[v][1]);
}
for (int i = 0; i < vals.size(); ++i) {
LL val = vals[i];
while (ps != sz - 1 && son[ps + 1].fi < val) {
++ps;
int v = son[ps].se;
ss += dp[v][0];
}
while (pb != sz && son[pb].fi <= val) {
int v = son[pb].se;
sb -= dp[v][1];
++pb;
}
LL se = s[pb] - s[ps + 1];
LL st = 0;
if (val <= pre[u]) dp[u][0] = min(dp[u][0], ss + se + sb + cal(u, val));
if (val >= pre[u]) dp[u][1] = min(dp[u][1], ss + se + sb + cal(u, val));
}
}
int _, n, u, v, w;
int main() {
scanf("%d", &_);
for (int ca = 1; ca <= _; ++ca) {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &inc[i]);
// for (int i = 1; i <= n; ++i) scanf("%d", &de[i]);
for (int i = 1; i <= n; ++i) de[i] = inc[i];
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1; i <= n; ++i) f[i].clear();
for (int i = 1; i < n; ++i) {
scanf("%d%d%d", &u, &v, &w);
f[u].PB(MP(v, w));
f[v].PB(MP(u, w));
}
pre[1] = 0;
dfs(1, -1);
LL ans = min(dp[1][0], dp[1][1]);
printf("%lld\n", ans);
}
return 0;
}
1008、Triangle Game
题目链接:Problem - 7216 (hdu.edu.cn)
题意:
一个博弈问题,对于一个非退化三角形,三边边长分别为 a , b , c a, b, c a,b,c 。现 Kate 和 Emilico 二人做游戏,每轮需要令三角形的一边长度减去一正整数,使这个三角形退化的一方负。Kate 先手,双方均采用最优策略,问 Kate 是否会获胜。
题解:
结论是:Kate 获胜当且仅当 ( a − 1 ) ⊕ ( b − 1 ) ⊕ ( c − 1 ) ≠ 0 (a-1)\oplus(b-1)\oplus(c-1)\not=0 (a−1)⊕(b−1)⊕(c−1)=0 。其中 ⊕ \oplus ⊕ 为异或运算。
不妨令 x , y , z x, y, z x,y,z 分别为三角形最短,次短和最长的边长。由于 x + y > z x+y>z x+y>z 且 x , y , z x, y, z x,y,z 都是正整数,则有 ( x − 1 ) + ( x − 1 ) ≥ ( z − 1 ) (x-1)+(x-1)\geq(z-1) (x−1)+(x−1)≥(z−1) 。
不妨定义如下状态:
-
(L 态) ( x − 1 ) ⊕ ( y − 1 ) ⊕ ( z − 1 ) = 0 (x-1)\oplus(y-1)\oplus(z-1)=0 (x−1)⊕(y−1)⊕(z−1)=0
-
(W 态) ( x − 1 ) ⊕ ( y − 1 ) ⊕ ( z − 1 ) ≠ 0 (x-1)\oplus(y-1)\oplus(z-1)\not=0 (x−1)⊕(y−1)⊕(z−1)=0
当玩家目前处于 L 态时,由于 ( x − 1 ) ⊕ ( y − 1 ) = ( z − 1 ) (x-1)\oplus(y-1)=(z-1) (x−1)⊕(y−1)=(z−1) ,则有 ( x − 1 ) + ( x − 1 ) ≥ ( x − 1 ) ⊕ ( y − 1 ) = ( z − 1 ) ) (x-1)+(x-1)\geq(x-1)\oplus(y-1)=(z-1)) (x−1)+(x−1)≥(x−1)⊕(y−1)=(z−1)) ,此时一定为一个非退化三角形。
在 L 态时,转移有如下情况:
-
x = 1 x=1 x=1 ,则 y = z y=z y=z 。这种情况任何玩家移动都会判负,因此为必败态。
-
x > 1 x>1 x>1 ,则 1 < x < y < z 1<x<y<z 1<x<y<z ,此时一定存在一种减少边长的方案,使得减少边长后三角形不退化。不妨考虑 x x x 减少为 x ′ x' x′ ,则由于减去的是正整数,会有 ( y − 1 ) ⊕ ( z − 1 ) = ( x − 1 ) ≠ ( x ′ − 1 ) (y-1)\oplus(z-1)=(x-1)\not=(x'-1) (y−1)⊕(z−1)=(x−1)=(x′−1) 。则 ( x ′ − 1 ) ⊕ ( y − 1 ) ⊕ ( z − 1 ) ≠ 0 (x'-1)\oplus(y-1)\oplus(z-1)\not=0 (x′−1)⊕(y−1)⊕(z−1)=0 。如果改变的是其他边,也可以类似地利用此式。
综上,L 态一定转移到某必败态或 W 态。
当玩家处于 W 态时,有 ( x − 1 ) ⊕ ( y − 1 ) ≠ ( z − 1 ) (x-1)\oplus(y-1)\not=(z-1) (x−1)⊕(y−1)=(z−1) 。令 r = ( x − 1 ) ⊕ ( y − 1 ) ⊕ ( z − 1 ) r=(x-1)\oplus(y-1)\oplus(z-1) r=(x−1)⊕(y−1)⊕(z−1) ,则 ( ( x − 1 ) ⊕ r ) + 1 < x ((x-1)\oplus r)+1<x ((x−1)⊕r)+1<x 或 ( ( y − 1 ) ⊕ r ) + 1 < y ((y-1)\oplus r)+1<y ((y−1)⊕r)+1<y 或 ( ( z − 1 ) ⊕ r ) + 1 < z ((z-1)\oplus r)+1<z ((z−1)⊕r)+1<z ,三式中必有一式成立。考
虑 r r r 的二进制中每一位 1 1 1 都一定出现了奇数次,对于最高位的 1 1 1 ,将其异或 r r r 后一定会变小。因此可以将成立的不等式作为这一步操作进行代换,即可转为 L 态。
由此,每次移动三角形某边长均会减小,W 态会转化为 L 态,而 L 态均转化为某些必败态和 W 态。由此,所
有 W 态均为必胜态,L 态均为必败态。结论证毕。
时间复杂度: O ( 1 ) O(1) O(1) ,空间复杂度: O ( 1 ) O(1) O(1) 。
代码:
#include <bits/stdc++.h>
using namespace std;
int a, b, c;
int main() {
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int _;
cin >> _;
while (_--) {
cin >> a >> b >> c;
int ans = (a - 1) ^ (b - 1) ^ (c - 1);
if (ans) {
cout << "Win\n";
} else {
cout << "Lose\n";
}
}
return 0;
}
三、题目分析及解法(进阶题)
不会做X