目录
写在前面
比赛地址:https://codeforces.com/gym/104008。
以下按个人向难度排序。
三月初 vp,vp 完就去打华为软挑了,拖到现在才补题解呃呃。
唉华为软挑打得也是一拖,感觉没有活着的价值。
A
签到。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1010;
//=============================================================
int pre[kN], next[kN];
std::string s;
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int n; std::cin >> n;
std::cin >> s;
for (int i = 0; i < n; ++ i) {
if (s[i] == 'L') pre[i] = i;
else if (i != 0) pre[i] = pre[i - 1];
else pre[i] = -100;
}
for (int i = n - 1; i >= 0; -- i) {
if (s[i] == 'L') next[i] = i;
else if (i != n - 1) next[i] = next[i + 1];
else next[i] = n + 100;
}
for (int i = 0; i < n; ++ i) {
if (s[i] == 'L') std::cout << 'L';
else if (pre[i] < i - 1 && next[i] > i + 1) std::cout << 'C';
else std::cout << '.';
}
return 0;
}
M
树状数组。
没看题,咕。
Code by dztle:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=600005;
#define lowbit(x) x&(-x)
int n,m;
int a[N],t[N],q[N*3];
char s[N];
void add(int x,int k){
while(x<=n){
t[x]+=k;
x+=lowbit(x);
}
}
int query(int x){
int ans=0;
while(x){
ans+=t[x];
x-=lowbit(x);
}
return ans;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
int Sum=0;
for(int i=n;i>=1;--i){
Sum+=query(a[i]);
add(a[i],1);
}
scanf("%s",s);
int cnt=0,fl=0,l=N,r=l+n-1;
for(int i=1;i<=n;++i){
q[l+i-1]=a[i];
}
cout<<Sum<<endl;
for(int i=0;i<m;++i){
if(s[i]=='S'){
if(fl==1){
Sum=Sum-(q[r]-1)+(n-q[r]);
q[l-1]=q[r]; --r,--l;
}else{
Sum=Sum-(q[l]-1)+(n-q[l]);
q[r+1]=q[l]; ++l,++r;
}
}else{
Sum=(n*(n-1)/2)-Sum;
fl=!fl;
}
cout<<(Sum%10+10)%10;
// cout<<Sum<<endl;
}
return 0;
}
C
手玩结论题。
发现经过一次操作二之后,操作一二的影响变为相同,于是考虑枚举第几次操作时进行操作二即可。
更进一步地,将最终的贡献式展开发现最终答案只有两种情况:要么一直进行操作一,要么第一步就进行操作二。取最大值即可。
Code by dztle:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;const int N=1e5+5,mod=1e9+7;
int a[N],b[N],sum;
signed main(){
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
sum+=a[i];
b[i]=a[i]+b[i-1];
}
int pre=0;
for(int i=1;i<=n;++i) pre+=b[i];
int A=pre,B=(2*n+1)*sum;
for(int i=1;i<=m;++i){
A=2*A%mod+n*sum%mod;
A%=mod;
B=(2*n+1)*sum%mod;
n=n*2%mod;
sum=sum*2%mod;
}
cout<<max(A,B);
return 0;
}
E
数学。
赛时猜了个假结论在线段中点处找 50 个点检查 WA 成构式耻辱下班、、、
记已知的两个点为 \(A, B\),未知的点为 \(C\),则最小化 \(S_{\triangle ABC}\) 等价于最小化 \(\frac{1}{2} \left|\overrightarrow{AB} \times \overrightarrow{AC}\right|\)。记 \(\overrightarrow{AB} = (a, b), \overrightarrow{AC} = (c, d)\),代入可得只需最小化 \(\frac{1}{2} \left| ad - bc \right|\),其中 \(a, b\) 为已知参数。
发现是一个经典的二元一次不定方程形式,由裴蜀定理 \(|ad - bc|\) 最小值为 \(\gcd(a, b)\),扩展欧几里得解出一组任意解 \((c, d)\) 后即可得到 \(C\) 的坐标。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
LL x1, y1, x2, y2;
//=============================================================
LL exgcd(LL a_, LL b_, LL &x_, LL &y_) {
if (!b_) {
x_ = 1, y_ = 0;
return a_;
}
LL d_ = exgcd(b_, a_ % b_, y_, x_);
y_ -= a_ / b_ * x_;
return d_;
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> x1 >> y1 >> x2 >> y2;
LL a = y2 - y1, b = x1 - x2, f1 = 1, f2 = 1;
if (a < 0) f1 = -1, a = -a;
if (b < 0) f2 = -1, b = -b;
LL x3, y3, d = exgcd(a, b, x3, y3);
x3 = x3 * f1, y3 = y3 * f2;
std::cout << (x2 - x3) << " " << (y2 - y3) << "\n";
}
return 0;
}
L
手玩诈骗题。
数据范围这么小是因为 checker
时间复杂度是指数级的呃呃,还以为是不可做的神题太诈骗了
样例三对最终做法是有启发性的。
一个很显然的想法是所有人应当尽量选大的,且若一个数字被选择了两次则之后再选均无贡献。则一种构造方案是:
- 玩家 1,2:\(100\%\) 概率选择 \(m\)。
- 玩家 3,4:\(100\%\) 概率选择 \(m-1\)。
- ……
- 玩家 \(n\):\(100\%\) 概率选择 \(m-\left\lfloor\frac{n - 1}{2}\right\rfloor\)。
发现此时对于任意玩家,无论调整到更大的数还是更小的数均无法成为胜者——要么大家都挂掉,要么使其他人成为了胜者,达到了纳什均衡状态。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 20;
//=============================================================
//=============================================================
double ans[kN][kN];
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int n, m; std::cin >> n >> m;
int temp = n;
for (int i = m; i; -- i) {
ans[temp --][i] = 1;
if (!temp) break;
ans[temp --][i] = 1;
if (!temp) break;
}
while (temp) ans[temp --][m] = 1;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
std::cout << std::fixed << std::setprecision(10) << ans[i][j] << " ";
}
std::cout << "\n";
}
return 0;
}
G
换根 DP。
第一次见这种换根 DP 的写法,牛逼。
大力手玩下,发现最优的情况下,两条路径至多有一个交点。则最终有贡献的节点构成的形态一定为下列两种情况之一:
- 以某个节点 \(u\) 为一端的四条链(不包含节点 \(u\),且长度可为 0)。
- 两条不相交的路径。
若不为上述两种形态,则可以通过添加某些节点调整成上述两种形态,且获得更多的贡献。
第一种情况非常简单,换根 DP 维护以每个节点为端点的所有链,并按长度排序,枚举所有节点取其中长度前 4 大的即可。
第二种情况实际上可看做断开树中的一条边,在得到的两根树分别求带权直径。这是一般的换根 DP 实现起来比较麻烦的。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define mp std::make_pair
const int kN = 2e5 + 10;
//=============================================================
int n, a[kN];
int edgenum, head[kN], v[kN << 1], ne[kN << 1];
int ans;
std::vector <std::pair <int, int> > maxl[kN];
std::map <int, int> f[kN];
//=============================================================
void Add(int u_, int v_) {
v[++ edgenum] = v_;
ne[edgenum] = head[u_];
head[u_] = edgenum;
}
void Dfs1(int u_, int fa_) {
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue;
Dfs1(v_, u_);
maxl[u_].push_back(mp(a[v_], v_));
if (!maxl[v_].empty()) maxl[u_].back().first += maxl[v_][0].first;
}
std::sort(maxl[u_].begin(), maxl[u_].end(), std::greater <std::pair <LL, int> >());
}
void Dfs2(int u_, int fa_, int dis_) {
if (fa_) {
maxl[u_].push_back(mp(dis_, fa_));
std::sort(maxl[u_].begin(), maxl[u_].end(), std::greater <std::pair <LL, int> >());
}
while (maxl[u_].size() < 4) maxl[u_].push_back(mp(0, 0));
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue;
if (v_ != maxl[u_][0].second) Dfs2(v_, u_, maxl[u_][0].first + a[u_]);
else Dfs2(v_, u_, maxl[u_][1].first + a[u_]);
}
}
int F(int u_, int fa_) {
if (f[u_].count(fa_)) return f[u_][fa_];
int p1 = 0;
while (maxl[u_][p1].second == fa_) ++ p1;
int p2 = p1 + 1;
while (maxl[u_][p2].second == fa_) ++ p2;
int ret = maxl[u_][p1].first + maxl[u_][p2].first + a[u_];
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_) continue;
ret = std::max(ret, F(v_, u_));
}
f[u_][fa_] = ret;
return ret;
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
for (int i = 1; i < n; ++ i) {
int u_, v_; std::cin >> u_ >> v_;
Add(u_, v_), Add(v_, u_);
}
Dfs1(1, 0), Dfs2(1, 0, 0);
for (int i = 1; i <= n; ++ i) {
int sum = 0;
for (int j = 0; j < 4; ++ j) sum += maxl[i][j].first;
ans = std::max(ans, sum);
}
for (int u_ = 1; u_ <= n; ++ u_) {
for (int i = head[u_]; i; i = ne[i]) {
ans = std::max(ans, F(u_, v[i]) + F(v[i], u_));
}
}
std::cout << ans << "\n";
return 0;
}
J
贪心,拓扑排序。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kN = 2e5 + 10;
//=============================================================
int n, m, p[kN], ans[kN];
std::vector <int> v[kN], u[kN];
std::vector <pii> interval[kN];
int into[kN], out[kN];
int l[kN], r[kN];
//=============================================================
void Init() {
std::cin >> n >> m;
for (int i = 1; i <= n; ++ i) {
std::cin >> p[i];
v[i].clear(), u[i].clear(), interval[i].clear();
into[i] = out[i] = 0;
}
for (int i = 1; i <= m; ++ i) {
int u_, v_; std::cin >> u_ >> v_;
v[u_].push_back(v_), u[v_].push_back(u_);
++ into[v_], ++ out[u_];
}
for (int i = 1; i <= n; ++ i) {
if (p[i] == 0) l[i] = 1, r[i] = n;
else l[i] = p[i], r[i] = p[i];
}
}
bool Topsort() {
std::queue <int> q;
int cnt = n;
for (int i = 1; i <= n; ++ i) if (!into[i]) q.push(i);
while (!q.empty()) {
int u_ = q.front(); q.pop();
-- cnt;
for (auto v_: v[u_]) {
l[v_] = std::max(l[v_], l[u_] + 1);
if (!(-- into[v_])) q.push(v_);
}
}
if (cnt) return false;
for (int i = 1; i <= n; ++ i) if (!out[i]) q.push(i);
while (!q.empty()) {
int v_ = q.front(); q.pop();
for (auto u_: u[v_]) {
r[u_] = std::min(r[u_], r[v_] - 1);
if (!(-- out[u_])) q.push(u_);
}
}
for (int i = 1; i <= n; ++ i) if (l[i] > r[i]) return false;
return true;
}
bool Solve() {
std::priority_queue <pii> q;
for (int i = 1; i <= n; ++ i) interval[l[i]].push_back(mp(-r[i], i));
for (int i = 1; i <= n; ++ i) {
for (auto x: interval[i]) q.push(x);
if (q.empty() || -q.top().first < i) return false;
ans[q.top().second] = i, q.pop();
}
return true;
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
Init();
if (!Topsort()) std::cout << "-1\n";
else if (!Solve()) std::cout << "-1\n";
else {
for (int i = 1; i <= n; ++ i) std::cout << ans[i] << " ";
std::cout << "\n";
}
}
return 0;
}
写在最后
参考:
- 2022 China Collegiate Programming Contest (CCPC) Guilin Site(持续更新) - 空気力学の詩 - 博客园
- 2022_ccpc_guilin_solution.pdf
学到了什么:
- E:平面集合问题,考虑将恶心的差值式子转化为好看的向量形式。
- L:直觉贪心,小心数据范围诈骗。
- G:带断边贡献为两棵树之和的换根 DP。