2024XJTUPC西安交通大学校赛VP题解
每次vp都自闭,已成习惯,时间还是分配的不合理,debug时做太多无用功。
一键做题
A.交小西的礼物
输出 a + b + 2c + 3d 即可
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e5 + 7;
void solve()
{
ll a,b,c,d;
cin >> a>> b >> c>> d;
cout << a + b + c*2 + d *3 << '\n';
}
C.榕树之心
直接按题目要求计算
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e5 + 7;
const int mod = 998244353;
void solve()
{
double x, y;
cin >> x >> y;
double x0 = 0.5 * x + 0.5 * y;
double y0 = sqrt(3) * 0.5 * x - sqrt(3) * 0.5 * y;
cout << fixed << setprecision(7) << x0 << " " << y0 << '\n';
}
B.转呀转
直接用半径和圆心角求弦长即可,注意sin中参数是弧度制
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e5 + 7;
const double pi = acos(-1.0);
void solve()
{
double x, y;
cin >> x >> y;
double r = (x * x) + y * y;
r = sqrt(r);
double t;
cin >> t;
double v;
cin >> v;
v = 1.0 / v;
double p = t / v;
p = p - (int)p;
double q = 1.0 - p;
if (p > q)
swap(p, q);
double ans = p * pi;
ans = sin(ans) * 2.0 * r;
cout << fixed << setprecision(8) << ans << '\n';
}
F.Everyone’s ALL IN
预处理计算即可
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e6 + 7;
const double pi = acos(-1.0);
vector<int>g[N];
void solve()
{
int n,m;
cin>>n>>m;
map<ll,ll>mp;
for(int i=1;i<=n;i++)
{
ll x;
cin>>x;
g[x].push_back(i);
mp[x]+=i;
}
while(m--)
{
int x,y;
cin>>x>>y;
cout<<1ll*mp[x]*mp[y]<<endl;
}
}
D.瑟莉姆的宴会
直接构造菊花图即可,对于每个点的正负贡献都计算一下,如果该点非负,即以该点为根构造菊花图
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e6 + 7;
const double pi = acos(-1.0);
void solve()
{
int n,m;
cin >> n >> m;
vector<int>a(n + 1);
for(int i=0;i<m;i++){
int u,v;
cin >> u >> v;
a[u]++,a[v]--;
}
int rt = 0;
for(int i=1;i<=n;i++){
if(a[i]>=0){
rt = i;
}
}
for(int i=1;i<=n;i++)
{
if(i==rt){
cout << 0 << ' ';
}else {
cout << rt << " " ;
}
}
}
O.筛法
打表猜答案为
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e6 + 7;
const double pi = acos(-1.0);
void solve()
{
ll n;
cin >> n;
cout << n * n << endl;
}
M.生命游戏
就直接暴力模拟即可,使用bfs模拟,若度为k进队列执行后删除,每个点最多进队一次
最后再使用遍历一遍图得到答案
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e6 + 7;
const int mod = 998244353;
const double eps = 1e-9;
vector<int> s[N];
void solve()
{
int n, k;
cin >> n >> k;
vector<int> du(n + 1);
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
s[u].push_back(v);
s[v].push_back(u);
du[u]++, du[v]++;
}
queue<int> q;
for (int i = 1; i <= n; i++)
{
if (du[i] == k)
{
q.push(i);
du[i] = -1;
}
}
vector<int> vis(n + 1);
int ans = 0;
while (q.size())
{
set<int> st;
while (q.size())
{
int u = q.front();
q.pop();
vis[u] = 1;
for (auto v : s[u])
{
if (du[v] == -1)
continue;
du[v]--;
if (du[v] == k)
{
st.insert(v);
}
else
{
if (st.count(v))
st.erase(v);
}
}
}
for (auto v : st)
{
q.push(v);
du[v] = -1;
}
}
auto bfs = [&](int u)
{
queue<int> qp;
qp.push(u);
vis[u] = 1;
while (qp.size())
{
int now = qp.front();
qp.pop();
for (auto v : s[now])
{
if (!vis[v])
{
qp.push(v);
vis[v] = 1;
}
}
}
};
for (int i = 1; i <= n; i++)
{
if (!vis[i])
{
bfs(i);
ans++;
}
}
cout << ans << '\n';
}
K.崩坏:星穹铁道
没玩崩铁导致的,读假题演了半小时(
一看数据范围加上求方案数自然想到矩阵快速幂
考虑如何构造矩阵
发现对于每一时刻,只需用当前所剩技能点进行转移即可,矩阵大小6*6
通过dfs构造矩阵,枚举初始技能点i,搜索经过四次(一轮)攻击后所到达的状态j有多少方案
得到转移矩阵后进行n/4次转移即可
最后要与初始状态相乘,也就是有k个技能点
由于n不一定是4的倍数,所以要再用dfs转移完余数的贡献
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e6 + 7;
const int mod = 998244353;
const double eps = 1e-9;
#define int long long
struct Mat
{
int v[130][130] = {0};
int x, y;
Mat() {}
Mat(int _x, int _y) { x = _x, y = _y; }
// 清空
void zero()
{
memset(v, 0, sizeof(v));
x = y = 0;
}
// 单位矩阵化
void ones()
{
memset(v, 0, sizeof(v));
for (int i = 0; i <= x; i++)
{
v[i][i] = 1;
}
}
// 乘法
void Mul(Mat a, Mat b)
{
zero();
x = a.x, y = b.y;
int c = a.y;
for (int i = 0; i <= x; ++i)
{
for (int j = 0; j <= y; ++j)
{
for (int k = 0; k <= c; ++k)
{
v[i][j] += (long long)a.v[i][k] * b.v[k][j] % mod;
v[i][j] %= mod;
}
}
}
return;
}
// 打印
void show()
{
for (int i = 0; i <= x; i++)
{
for (int j = 0; j <= y; j++)
{
cout << v[i][j] << " ";
}
cout << '\n';
}
}
};
Mat operator*(const Mat &x, const Mat &y)
{
Mat res;
res.Mul(x, y);
return res;
}
Mat ksm(Mat a, long long b)
{
Mat x = a;
x.ones();
while (b)
{
if (b & 1)
x = x * a;
b >>= 1;
a = a * a;
}
return x;
}
int ps[5];
Mat a(5, 5);
void dfs(int u, int i, int r, int lim)
{
if (i == lim)
{
a.v[r][u]++;
return;
}
if (ps[i] == 1)
{
u++;
if (u > 5)
u = 5;
dfs(u, i + 1, r, lim);
}
if (ps[i] == 2)
{
if (u == 0)
u++;
else
u--;
dfs(u, i + 1, r, lim);
}
if (ps[i] == 3)
{
int v = u;
v++;
if (v > 5)
v = 5;
dfs(v, i + 1, r, lim);
if (u != 0)
{
u--;
dfs(u, i + 1, r, lim);
}
}
}
void solve()
{
ll n, k;
cin >> n >> k;
for (int os = 1; os <= 4; os++)
{
cin >> ps[os];
}
for (int i = 0; i <= 5; i++)
{
dfs(i, 1, i, 5);
}
Mat p(0, 5);
ll nm = n % 4;
p.v[0][k] = 1;
Mat res = p * ksm(a, n / 4);
ll ans = 0;
a.zero();
a = Mat(5, 5);
for (int i = 0; i <= 5; i++)
{
dfs(i, 1, i, n % 4 + 1);
}
for (int i = 0; i <= 5; i++)
{
ll sb = 0;
for (int j = 0; j <= 5; j++)
{
sb += res.v[0][i] * a.v[i][j] % mod;
sb %= mod;
}
ans += sb;
ans %= mod;
}
cout << ans << '\n';
}
E.雪中楼
没想到题解期望难度这么高
看完题的第一想法就是直接模拟,考虑如何快速实现,我这里使用了一个双端链表
l记录左边编号,r记录右边编号,直接插入即可
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 2e5 + 7;
const double pi = acos(-1.0);
int l[N],r[N];
void solve()
{
int n;
cin >> n;
for(int i=1;i<=n;i++){
int x;
cin >> x;
if(x == 0){
int p = r[0];
l[r[0]] = i;
r[0] = i;
l[i] = 0;
r[i] = p;
}else {
int p = r[x];
l[r[x]] = i;
r[x] = i;
l[i] = x;
r[i] = p;
}
}
int x = r[0];
while(x!=0){
cout << x << ' ';
x = r[x];
}
}
I.命令行
从写完E之后就一直在想这道题,感觉题目描述有些不清晰一直wa最后一个点,结束后看别人代码才发现坑点,如果串为空那么补全时集合T为所有指令串(空串是所有串的前缀?)
要求前缀考虑使用字典树,用cnt记录每个前缀有多少串经过,并记录有每个串的终点out
我们执行操作的时候记录当前指针p,使用p在字典树上移动,如果该节点不存在,此时p = -1,同时用一个k记录-1的个数,一个lst记录最后在字典树上的节点编号。当p=-1时,增加操作使k增加,退格操作同理,执行操作无解,补全操作也是没有行动。
考虑不是-1的情况,即当前输入串还在字典树上,如果是加入操作就直接执行,如果是删除操作,我们可以对于字典树上每个节点p都记录一个父亲节点,那么删除操作就是在字典树上跳父亲。
对于执行操作,如果当前p是终点out,那么输出out[p],否则无解。
对于补全操作,一个最朴素的想法是在树上一直跳,直到有分叉为止,但是会被一个很长的串卡掉
考虑优化,我们在字典树预处理时再记录一下每个节点有哪些串经过in(可能有多个,但是不影响答案),再记录对于每个串的每个字母对应的节点mp,这样我们可以在补全操作时对于当前串二分,如果二分到的位置的节点的cnt小于最初的cnt就不行,这样就得到了补全后的节点p,此时即可通过此题
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 5e6 + 7;
const int mod = 998244353;
// const int N = 1000050;
int trie[N][26]; // 所构造的查找树,其对应的值是子节点的编号,【它的编号】【子节点的字母】
// 等于0表示没有该字母;
int cnt[N]; // N是一个单词终点的编号,表示有无该单词(0表示没有)
int id; // 结点计数器,初始值为0
// 每次插入和查询复杂度都是O(n)
int fa[N];
int in[N];
int out[N];
void solve()
{
int n, m;
cin >> n >> m;
vector<string> str(n + 1);
vector<vector<int>> mp(n + 1);
// in[0]=1;
for (int i = 1; i <= n; i++)
{
cin >> str[i];
int p = 0;
mp[i].resize(str[i].size());
in[p] = i;
cnt[p]++;//记录0节点
for (int j = 0; j < str[i].size(); j++)
{
int x = str[i][j] - 'a';
if (trie[p][x] == 0)
trie[p][x] = ++id;
int suv = p;
p = trie[p][x];
fa[p] = suv;
cnt[p]++;
mp[i][j] = p;//每个字符对应节点
in[p] = i;//每个节点被哪个经过
}
out[p] = i;//是否是一个串的终点
}
string TS;
cin >> TS;
int p = 0, lst = 0, k = 0;
for (auto it : TS)
{
if (it == 'T')
{
if (p == -1)
{
continue;
}
int ls = cnt[p];//当前节点经过串个数
int i = in[p];//经过当前节点的一个串的编号
int l = 0, r = str[i].size() - 1;
while (l < r)
{
int mid = (l + r + 1) >> 1;
if (cnt[mp[i][mid]] >= ls)
{
l = mid;
}
else
{
r = mid - 1;
}
}
if(cnt[mp[i][l]]<ls){//检查最终结果是否合法(主要针对空串情况)
p = 0;
}else
p = mp[i][l];//跳到补全后的位置
}
else if (it == 'B')
{
if (p == 0)
continue;
if (p == -1)
{
k--;
if (k == 0)//没有-1,当前输入又回到树上
{
p = lst;
}
}
else
{
p = fa[p];//跳父亲
}
}
else if (it == 'E')
{
if (p != -1 && out[p])
{
cout << out[p] << ' ';
}
else
{
cout << -1 << ' ';
}
p = 0;//清空输入
lst = 0;
k = 0;
}
else
{
if (p == -1)
{
k++;
continue;
}
int x = it - 'a';
int suv = p;
p = trie[p][x];
if (!p)//开始没有对应节点
{
lst = suv;
k++;
p = -1;
}
}
}
}
N.圣诞树
十分简单的启发式合并
一个贪心的想法是只要当前子树有k个不同,就对答案贡献
令1为根
对每个节点开一个set存子树中节点颜色,如果子树已经贡献答案则不进入set
合并时候从小到大合并,确保复杂度
整体复杂度
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 5e5 + 7;
const int mod = 998244353;
vector<int> s[N];
set<int> st[N];
void solve()
{
int n, k;
cin >> n >> k;
vector<int> col(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> col[i];
}
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
s[u].push_back(v);
s[v].push_back(u);
}
int ans = 0;
auto dfs = [&](auto &dfs, int u, int fa) -> void
{
st[u].insert(col[u]);
for (auto v : s[u])
{
if (v == fa)
continue;
dfs(dfs, v, u);
if(st[v].size()>st[u].size()){
swap(st[u],st[v]);//启发式合并
}
for(auto x:st[v]){
st[u].insert(x);
}
}
if (st[u].size() >= k)
{
ans++;
st[u].clear();
}
};
dfs(dfs,1,0);
cout << ans << '\n';
}