2021 辽宁省赛vp 题解
省流:没有 A H K
2021年辽宁省赛 vp
五一集训第三天的模拟赛,这套题有种区分度不是很大的感觉,就是一开场疯狂过题,接着三小时直接坐牢,不过题目还是很不错的
后来补题的时候发现有的题确实思路就差点点
B. 阿强的路
这题是赛后补题的
弗洛伊德算法
一个路径的代价是这条路径的最大点权和最大边权的积
对于边权,我们用弗洛伊德来维护
对于点权,我们将其从小到大排序,弗洛伊德枚举中间点的时候就是从小到大
做这题的时候,发现对于弗洛伊德的理解还是不够透彻,其记忆化的方式在于,第一层循环的用意是,起点 -> 中间点 -> 终点,中间点仅由前 k 个点组成
因此弗洛伊德的转移为:当路径的最大边权变小时,尝试更新路径的最小代价,点权的最大值就是 起点、中间点、终点的点权最大值,即 \(max(a[i],a[k],a[j])\),因为点根据点权从小到大排,所以中间点的点权最大就为 \(a[k]\)
为何不尝试在边权变大的时候更新?因为是由点权从小到大排,因此对于同一起点和终点,他所搜索到的路径的最大点权只会越来越大
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 510;
typedef long long ll;
const ll inf = 1e18 + 10;
ll dis[maxn][maxn], ans[maxn][maxn], num[maxn];
struct node
{
ll val;
int id;
}x[maxn];
bool cmp(const node& a, const node& b)
{
return a.val < b.val;
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++)
{
scanf("%lld", &x[i].val);
num[i] = x[i].val;
x[i].id = i;
}
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) ans[i][j] = ans[j][i] = dis[j][i] = dis[i][j] = inf;
for(int i=0; i<m; i++)
{
ll a, b, c;
scanf("%lld%lld%lld", &a, &b, &c);
dis[a][b] = dis[b][a] = c;
ans[a][b] = ans[b][a] = min(ans[a][b], dis[a][b] * max(x[a].val, x[b].val));
}
sort(x + 1, x + 1 + n, cmp);
for(int kk=1; kk<=n; kk++)
{
int k = x[kk].id;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(i == j || k == i || k == j) continue;
ll temp = max(dis[i][k], dis[k][j]);
if(dis[i][j] > temp)
{
dis[i][j] = temp;
ans[i][j] = min(ans[i][j], dis[i][j] * max({num[i], num[j], num[k]}));
}
}
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(j != 1) printf(" ");
if(i == j) printf("0");
else if(ans[i][j] == inf) printf("-1");
else printf("%lld", ans[i][j]);
}
printf("\n");
}
return 0;
}
C. 传染病统计
简单模拟
按照能被互相传染的分组,判断一下分组的最大最小人数
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <ctime>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int inf = 2147483647;
const double PI = acos(-1.0);
int num[maxn];
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
int n;
scanf("%d", &n);
for(int i=0; i<n; i++) scanf("%d", &num[i]);
sort(num, num + n);
num[n++] = 1000;
int maxx = 0, minn = n;
int now = 1;
for(int i=1; i<n; i++)
{
if(num[i] - num[i-1] > 2)
{
maxx = now > maxx ? now : maxx;
minn = now < minn ? now : minn;
now = 1;
}
else
now++;
}
printf("%d %d\n", minn, maxx);
}
return 0;
}
D. 阿强与网格
贪心
-
当前点不与终点在同一行或同一列的时候,可以有斜着走一次和相邻地走两次,这里判断一下 2x 和 y 的关系贪心地找最优
-
当前点与终点在同一行或同一列的时候,可以考虑直接相邻地走或者斜着走上去,再斜着走下来,这里判断一下 x 和 y 的关系,贪心地找最优
注意:有一个边界条件,当图只有一行或一列的时候,这是的第二种情况下,只能相邻地走,不能斜着走
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <ctime>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int inf = 2147483647;
const double PI = acos(-1.0);
int main()
{
int n;
scanf("%d", &n);
for(int i=0; i<n; i++)
{
ll n, m, x, y;
scanf("%lld%lld%lld%lld", &n, &m, &x, &y);
if(n == 1 || m == 1)
{
printf("%lld\n", (max(n, m) - 1) * x);
continue;
}
n--, m--;
ll ans = 0;
ll temp = min(n, m);
if(y < 2 * x)
ans = temp * y;
else
ans = temp * x * 2;
n -= temp, m -= temp;
ll nex = max(n, m);
if(nex)
{
if(x <= y)
ans += nex * x;
else
{
if(nex & 1) ans += x;
nex /= 2;
nex *= 2;
ans += nex * y;
}
}
printf("%lld\n", ans);
}
return 0;
}
E. 生活大爆炸
组合数学
直接组合数学求解,即枚举要选取的男生数量 i,则女生数量为 t - i,这时候的组合数为 \(C(i, n) * C(t - i, m)\)
在枚举的时候注意上边界和下边界,例如男生的数量至少为4,并且要保证这样选取的话,女生是够人数组成队伍的,即男生至少为 max(4, t - m)
女生同男生一样,设置好上边界即可
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 110;
typedef long long ll;
ll c[maxn][maxn];
ll dps(ll up, ll down)
{
if(c[up][down]) return c[up][down];
if(up == down || up == 0) return c[up][down] = 1;
return c[up][down] = dps(up - 1, down - 1) + dps(up, down - 1);
}
int main()
{
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
ll ans = 0;
for(int i=max(4, k-m); k-i>=max(1, k-n); i++)
ans += dps(i, n) * dps(k - i, m);
printf("%lld\n", ans);
return 0;
}
F. Capslock
简单模拟
判断是否要修改,然后修改就好
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
cin >> s;
int fbig = 1, f = 1;
int len = s.length();
for(int i=0; i<len && fbig; i++)
if(s[i] >= 'a' && s[i] <= 'z') fbig = 0;
for(int i=1; i<len && f; i++)
if(s[i] >= 'a' && s[i] <= 'z') f = 0;
if(fbig || (s[0] >= 'a' && s[0] <= 'z' && f))
{
for(int i=0; i<len; i++)
{
if(s[i] >= 'a' && s[i] <= 'z') s[i] = s[i] - 'a' + 'A';
else s[i] = s[i] - 'A' + 'a';
}
}
cout << s << endl;
return 0;
}
G. 字节类型
简单分支语句
用字符串判断就行,但是不能直接判断字典序
要先判断长度,再判断字典序
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
cin >> s;
int len = s.length();
if(len < 3 || (len == 3 && s <= "127"))
cout << "byte" << endl;
else if(len < 4 || (len == 5 && s <= "32767"))
cout << "short" << endl;
else if(len < 10 || (len == 10 && s <= "2147483647"))
cout << "int" << endl;
else if(len < 19 || (len == 19 && s <= "9223372036854775807"))
cout << "long" << endl;
else
cout << "BigInteger" << endl;
return 0;
}
I. 完美主义
差分+set 或者 树状数组
我们知道差分的时候如果出现小于 0 的情况即为不满足,因此我们可以将差分中小于 0 的情况加入到 set 中
然后每次通过区间起点,二分查找离区间起点最近的 不满足条件的下标在哪里,判断它与区间终点的关系
修改值的时候,对 set 进行删除或插入操作即可
吐槽:这题数据是真的弱,集训队里有个队伍居然暴力直接过了,这数据居然没放边界数据
#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
const int maxn = 3e5 + 10;
set<int>vis;
int num[maxn];
int dif[maxn];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++) scanf("%d", &num[i]);
for(int i=1; i<n; i++)
{
dif[i] = num[i+1] - num[i];
if(dif[i] < 0) vis.insert(i);
}
for(int i=0; i<m; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a == 1)
{
num[b] = c;
if(b > 1)
{
dif[b-1] = c - num[b-1];
if(dif[b-1] >= 0)
{
vis.erase(b-1);
}
else vis.insert(b-1);
}
if(b < n)
{
dif[b] = num[b+1] - c;
if(dif[b] >= 0)
{
vis.erase(b);
}
else vis.insert(b);
}
}
else
{
auto it = vis.lower_bound(b);
if(it == vis.end() || *it >= c) printf("Yes\n");
else printf("No\n");
}
}
return 0;
}
J. 放棋子
类似于背包 dp
这题是赛后补的,感觉很不应该,前几天才做了一场 div2 的有个 dp 题和这个状态转移一模一样:https://codeforces.com/contest/1673/problem/C
如果我们能确定前 n 列中每一列所放的棋子的个数,那么第 n + 1 列的棋子个数,必然等于第 1 列的棋子个数
因此我们可以考虑只用 dp 算前 n 列的方案数,而后面相同的由于是一样的,则用快速幂来维护其代价即可
例如如果我们选定了第 1 列,那么 n + 1,2n + 1,都是一样的,则第 1 列放大到整个棋盘的方案数就是 pow(第 1 列的方案数, 对 n 取模结果为 1 的列的数量)
对于前 n 列的状态转移,设 \(dp[i][j] = x\) 为前 i 列中放了 j 个棋子的方案数为 x
状态转移为 \(dp[i][j] = \sum^{min(n, j)}_{k=0} dp[i-1][j-k] * g[k][i\leq tx]\)
tx = n % m,k 可以理解为当前列放入 k 个,g 数组为维护的快速幂的值,如果看不懂 g 数组建议按照思路自己敲一遍代码就知道了为啥这样写了
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 110;
const ll mod = 1e9 + 7;
ll dp[maxn][maxn * maxn], c[maxn][maxn], g[maxn][2];
ll dps(ll up, ll down)
{
if(c[up][down]) return c[up][down];
if(up == down || up == 0) return c[up][down] = 1;
return c[up][down] = (dps(up - 1, down - 1) + dps(up, down - 1)) % mod;
}
ll qpow(ll x, ll n)
{
ll ans = 1;
while(n)
{
if(n & 1) ans = x * ans % mod;
n >>= 1;
x = x * x % mod;
}
return ans % mod;
}
int main()
{
ll n, m, k;
cin >> n >> m >> k;
ll t = m / n, tx = m % n;
for(int i=0; i<=n; i++)
{
g[i][0] = qpow(dps(i, n), t);
g[i][1] = (g[i][0] * dps(i, n)) % mod;
}
dp[0][0] = 1;
for(int i=1; i<=n; i++)
{
ll e = min(i * n, k);
for(int j=0; j<=e; j++)
{
ll y = min(n, (ll)j);
for(int x=0; x<=y; x++)
{
dp[i][j] = (dp[i][j] + dp[i-1][j - x] * g[x][i <= tx]) % mod;
// cout << i << " " << j << " " << dp[i][j] << endl;
}
}
}
printf("%lld\n", dp[n][k]);
return 0;
}
L. 神奇的回答
签到
#include <iostream>
using namespace std;
int main()
{
int t;
cin >> t;
while(t--)
{
int n;
cin >> n;
n = n > 18 ? 18 : n;
cout << n << endl;
}
}
M. 比赛!
拓扑板子
一个字符串两个拓扑,直接看能不能生成拓扑排序就行
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <ctime>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int inf = 2147483647;
const double PI = acos(-1.0);
vector<char>gra[maxn];
int du[maxn], vis[maxn];
char last[maxn];
int tp = 0;
bool topu()
{
queue<char>q;
int ans = 0;
for(int i=0; i<300; i++)
if(vis[i] && du[i] == 0)
q.push(i);
for(int i=0; i<300; i++) if(vis[i]) ans++;
while(q.size())
{
char now = q.front();
q.pop();
last[tp++] = now;
for(int i=0; i<gra[now].size(); i++)
{
char nex = gra[now][i];
if(--du[nex] == 0)
q.push(nex);
}
}
return tp == ans;
}
int main()
{
int n;
cin >> n;
while(n--)
{
string s;
cin >> s;
du[s[0]]++;
du[s[3]]++;
gra[s[2]].push_back(s[0]);
gra[s[0]].push_back(s[3]);
vis[s[0]] = 1;
vis[s[2]] = 1;
vis[s[3]] = 1;
}
if(topu())
{
for(int i=0; i<tp; i++)
cout << last[i];
cout << endl;
}
else
cout << "No Answer" << endl;
}