- 寫在前面
- A
- M
- C
- E
- L
- G
- J
- 寫在最後
寫在前面
比賽地址: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。