2022 China Collegiate Programming Contest (CCPC) Guilin Site

Luckyblock發表於2024-04-09

目錄
  • 寫在前面
  • 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。

相關文章