2022“杭电杯”中国大学生算法设计超级联赛(7)
[题目链接](Search Result (hdu.edu.cn))
D Black Magic
题目大意
给出n个方块,每个方块的左和右都可能是黑或白。将这些方块排成一列,如果两个相邻方块相连接的面都是黑色,那么这两个方块会连在一起。求连通块的最大和最小数量。
题解
思维题。
最大数量:所有L可以全放在最左边,所有R可以全放在最右边,E和B相互间隔放在中间。
最小数量:所有B可以拼在一起,然后再加一个左一个右连成一块,之后把一个L和一个R分为一组拼起来,E放在最后。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int t, e, l, r, b, res1, res2;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
cin >> e >> l >> r >> b;
res1 = res2 = e + l + r + b;
if (b)
{
if (l && r)
res1 -= b + 1, l--, r--;
else if (l)
res1 -= b, l--;
else if (r)
res1 -= b, r--;
else
res1 -= b - 1;
}
if (l && r)
res1 -= min(l, r);
if (b - e - 1 > 0)
res2 -= b - e - 1;
cout << res1 << " " << res2 << endl;
}
return 0;
}
H Triangle Game
题目大意
一个非退化三角形,三边边长分别为a,b,c。现 Kate 和 Emilico 二人做游戏,每轮需要令三角形的一边长度减去一正整数,使这个三角形退化的一方负。Kate 先手,双方均采用最优策略,问 Kate 是否会获胜。
题解
官方题解证明。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int t, a, b, c, ans;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
cin >> a >> b >> c;
ans = (a - 1) ^ (b - 1) ^ (c - 1);
if (ans == 0)
cout << "Lose" << endl;
else
cout << "Win" << endl;
}
return 0;
}
C Counting Stickmen
题目大意
有一棵无根树,问能数出多少个火柴人。火柴人有一个头,两个手,一个身体,两条腿。
题解
可以枚举每个点作为火柴人的脖子。
当选定u点是脖子时,对于u的子节点,都可以做头;度数大于等于2的子节点,可以做手;度数大于等于3的子节点,可以做身体。
对于每个u点,可以枚举子节点做身体。统计剩下的子节点中,能做手的方案,再乘以做头的方案。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 5;
const int mod = 998244353;
ll t, n, h, b, d[maxn], hand[maxn], body[maxn], res;
vector<int> g[maxn];
ll C(ll x)
{
return x * (x - 1) / 2;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
cin >> n;
res = 0;
for (int i = 1; i <= n; i++)
{
g[i].clear();
d[i] = hand[i] = body[i] = 0;
}
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
d[u]++, d[v]++;
}
for (int i = 1; i <= n; i++)
{
hand[i] = d[i] - 1;
if (d[i] > 2)
body[i] = C(d[i] - 1);
}
for (int i = 1; i <= n; i++)
{
if (d[i] < 4)
continue;
h = b = 0;
for (auto e : g[i])
{
h += hand[e];
b += body[e];
}
for (auto e : g[i])
{
res = (res + body[e] * (C(h - hand[e]) - (b - body[e])) % mod * (d[i] - 3)) % mod;
}
}
cout << res << endl;
}
return 0;
}
B Independent Feedback Vertex Set
题目大意
有一个图,现在要从中找到一个独立点集(点集中的点没有边连接),使得剩下的点可以组成一个森林。每个点都有点权,求最大独立点集的权值。
题解
该题的加边的方式比较特别,加入的边和原边构成一个三元环,答案必须包含每个三元环中的恰好一个点,因为一个点都不选会破坏森林约束,选两个及以上则会破坏独立集约束。
所以初始的三元环中,1,2,3三个点, 有且仅有一个点在独立点集中。
所以一共只有三种情况,在每种情况中,逐个遍历加入的边。对于加入的边,当加入点x,而y和z都不在独立点集中时,x一定得加入独立点集;否则x一定不能加入独立点集。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e5 + 5;
ll t, n, temp, res;
ll w[maxn], u[maxn], v[maxn];
bool vis[maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
res = 0;
cin >> n;
for (ll i = 1; i <= n; i++)
cin >> w[i];
for (ll i = 4; i <= n; i++)
{
ll x, y;
cin >> x >> y;
u[i] = x, v[i] = y;
}
for (ll i = 1; i <= 3; i++)
{
for (ll j = 1; j <= n; j++)
vis[j] = 0;
temp = w[i];
vis[i] = 1;
for (ll j = 4; j <= n; j++)
{
if (vis[u[j]] == 0 && vis[v[j]] == 0)
vis[j] = 1, temp += w[j];
}
res = max(res, temp);
}
cout << res << endl;
}
return 0;
}
F Sumire
题目大意
给定一个公式,∑fk(i,B,d) (l<=i<=r),其中f(x,B,d)表示,x在B进制的情况下,出现了多少个d。
题解
数位DP,用dp[i] [j] [0/1] [0/1]表示统计到第i位,从第i位开始的剩余的数字中,有j个位上是d,是否取数字上界,以及是否有前导零。
用记忆化搜索维护转移。转移时需分四种情况考虑:
1、当前位卡住上界。
2、当前位为0。
3、当前位为d。
4、以上都不满足(大大减少遍历的次数)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e2 + 5;
const ll mod = 1e9 + 7;
ll t, k, B, d, l, r, ans;
ll a[maxn], dp[maxn][maxn][2][2];
ll qpow(ll a, ll n, ll mod)
{
ll res = 1;
while (n)
{
if (n & 1)
res = (res * a) % mod;
a = (a * a) % mod;
n >>= 1;
}
return res;
}
ll dfs(ll pos, ll cnt, bool limit, bool zero)
{
if (pos < 0 && cnt == 0)
return 1;
if (pos < 0)
return 0;
if (cnt < 0)
return 0;
if (dp[pos][cnt][limit][zero] != -1)
return dp[pos][cnt][limit][zero];
dp[pos][cnt][limit][zero] = 0;
ll up = limit ? a[pos] : B - 1, now = 0, used = 0;
ll cost = (now == d);
if (zero && d == 0)
cost = 0;
dp[pos][cnt][limit][zero] = (dp[pos][cnt][limit][zero] + dfs(pos - 1, cnt - cost, limit && now == up, zero && now == 0)) % mod;
used++;
if (now != d && d <= up)
{
now = d;
used++;
dp[pos][cnt][limit][zero] = (dp[pos][cnt][limit][zero] + dfs(pos - 1, cnt - 1, limit && now == up, 0)) % mod;
}
if (now != up)
{
now = up;
used++;
dp[pos][cnt][limit][zero] = (dp[pos][cnt][limit][zero] + dfs(pos - 1, cnt, limit && now == up, 0)) % mod;
}
dp[pos][cnt][limit][zero] = (dp[pos][cnt][limit][zero] + dfs(pos - 1, cnt, 0, 0) * max(0ll, up - used + 1) % mod) % mod;
return dp[pos][cnt][limit][zero];
}
ll solve(ll x)
{
ll len = 0;
while (x)
{
a[len++] = x % B;
x /= B;
}
memset(dp, -1, sizeof dp);
ans = 0;
for (ll i = 1; i <= len; i++)
ans = (ans + dfs(len - 1, i, 1, 1) * qpow(i, k, mod) % mod) % mod;
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
cin >> k >> B >> d >> l >> r;
cout << (solve(r) - solve(l - 1) + mod) % mod << endl;
}
return 0;
}