2024牛客暑期多校訓練營2

Luckyblock發表於2024-07-21

目錄
  • 寫在前面
  • E
  • C
  • H
  • I
  • B
  • A
  • G
  • 寫在最後

寫在前面

比賽地址:https://ac.nowcoder.com/acm/contest/81597

以下按個人向難度排序。

比第一場好點但還是唐氏。

E

簽到。

dztlb 大神寫的,我看都沒看。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int t,n;
signed main(){
	cin>>t;
	while(t--){
		cin>>n;	
		int cnt=0;
		for(int i=60;i>=0;i--){
			if(n&(1ll<<i)){
				++cnt;
			}
		}
		if(cnt==1){
			cout<<-1<<'\n';
		}else{
			int ans=0;
			int tmp=0;
			for(int i=60;i>=0;i--){
			if(n&(1ll<<i)){
				ans+=(1ll<<i);
				++tmp;
				if(tmp==cnt-1) break;
			}
		}
			cout<<ans<<'\n';
		}
	}
	
	return 0;
}

C

貪心,模擬。

剛看完題面還以為要寫什麼牛逼圖論演算法發現就兩行那純傻逼題了。

考慮僅讓這個人從左往右走,每列僅有兩個格子,於是僅需討論從上一列先移動到哪個格子即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kN = 1e6 + 10;
//=============================================================
int n, ans, f[2][kN];
std::string s[2];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n;
  std::cin >> s[0] >> s[1];
  f[0][0] = (s[0][0] == 'R'), f[1][0] = (s[1][0] == 'R');
  if (f[0][0] && f[1][0]) f[0][0] = f[1][0] = 2;

  for (int j = 1; j < n; ++ j) {
    if (s[0][j] == 'R') f[0][j] = 1 + f[0][j - 1];
    if (s[1][j] == 'R') f[1][j] = 1 + f[1][j - 1];
    if (s[0][j] == 'R' && s[1][j] == 'R') {
      if (f[0][j] < f[1][j]) f[0][j] = f[1][j] + 1;
      else if (f[0][j] > f[1][j]) f[1][j] = f[0][j] + 1;
      else ++ f[0][j], ++ f[1][j];
    }
  }
  for (int j = 0; j < n; ++ j) ans = std::max(ans, std::max(f[0][j], f[1][j]));
  if (!ans) std::cout << 0;
  else std::cout << ans - 1 << "\n";
  return 0;
}

H

dztlb 大神寫的。

發現給定區間代表的路徑僅需經過 \((x, y)\) 即有貢獻,於是考慮列舉區間左端點 \(l\),僅需找到最靠小的右端點 \(r\) 令區間 \([l, r]\) 代表的路徑恰好停在 \((x, y)\) 則所有大於 \(r\) 的右端點均有貢獻。

考慮將區間拆成兩字尾之差,則僅需使用對每種字尾和,維護對應的最靠左的字尾位置,列舉左端點時僅需查詢最近的字尾 \([r + 1, n]\)\([l, n]\)\((x, y)\) 之差恰好為 \([r + 1, n]\) 即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+5;
int t,n,x,y;
char s[N];
int a[N],b[N],ans;
int sa[N],sb[N];
map<int,int>ma;
int id(int x,int y){
	return (x+100000)*100000+y;
}
signed main(){
	cin>>n>>x>>y;
	scanf("%s",s+1);
	if(x==0&&y==0){
		cout<<(n+1)*n/2<<'\n'; return 0;
	}
	for(int i=1;i<=n;++i){
		if(s[i]=='D'){
			a[i]=1;
		}
		if(s[i]=='A'){
			a[i]=-1;
		}
		if(s[i]=='S'){
			b[i]=-1;
		}
		if(s[i]=='W'){
			b[i]=1;
		}
	}
	ma[id(0,0)]=n+1;
	for(int i=n;i>=1;--i){
		sa[i]=sa[i+1]+a[i];
		sb[i]=sb[i+1]+b[i];
	}
	for(int i=n;i>=1;--i){
		int wa=sa[i]-x,wb=sb[i]-y;
		if(ma[id(wa,wb)])ans+=(n+1-ma[id(wa,wb)]+1);
		ma[id(sa[i],sb[i])]=i;
	}
	cout<<ans;
	return 0;
}
/*
7 -3 0
AAAADAD
*/

I

線性 DP。

並非區間 DP!雖然確實是維護區間的貢獻,但並非區間 DP!

如果你和我一開始一樣設了狀態 \(f_{l, r}\) 表示區間 \([l, r]\) 的貢獻之和並嘗試使用轉移 \(f_{l, r} = \max(f_{l + 1, r}, f_{l, r - 1})\),你將發現這種轉移將無法抉擇同時有兩個區間 \([l, r - a], [l + b, r]\) 產生貢獻時哪種更優。因為不僅需要考慮子區間獲得的價值之和,還要考慮操作之後還剩多少牌以便之後操作。這很難辦!!!於是換個思路:

發現每種權值 \(i\) 唯一地對應一個區間 \([l_i, r_i]\),且最終答案中選出的有貢獻的若干區間,要麼互不相交,要麼被另一個區間完整包含,不可能出現區間的部分交叉。

考慮在原數列兩側新增 0,則僅需考慮完整地對某個區間進行操作將其全部刪去可獲得的最大價值即可。於是記 \(f_{i} (0\le i\le n)\) 表示最後進行的一次操作為 \([l_i, r_i]\) 使,使用區間 \([l_i, r_i]\) 可獲得的最大價值之和,答案即為 \(f_0\)

發現操作順序按照權值遞減是最優的:不相交的區間操作順序無影響,被層層相互包含的區間先對內層的權值較大的區間操作可獲得更多的價值。於是考慮按照權值 \(i\) 遞減轉移 \(f_i\)。考慮當前轉移 \(f_i\),初始化 \(f_{i} = i\times (r_i - l_i + 1)\),考慮在此之前進行了哪些操作:相當於從區間 \([l_i + 1, r_i - 1]\) 中選出若干互不相交的區間 \([l_j, r_j]\),每個區間的貢獻為 \(f_{j} - (r_j - l_j + 1)\times i\),表示在對 \([l_i + 1, r_i - 1]\) 進行操作前對 \([l_j, r_j]\) 進行操作可獲得的價值減去對此次操作的影響(減少的卡牌數)。

這是一個經典的帶權線段覆蓋問題,即從數條帶權線段中選擇若干互不相交的線段使他們的權值之和最大。設 \(g_{k}(l_i + 1\le k\le r_i - 1)\) 表示使用右端點不大於 \(k\) 的線段可獲得的最大權值之和,初始化 \(g_{l_i} = 0\),則有轉移:

\[\begin{aligned} g_{k - 1}&\rightarrow g_{i}\\ g_{l_{a_k} - 1} + f_{a_k} - (r_{a_k} - l_{a_k} + 1)\times i &\rightarrow g_{k} &(k = r_{a_k}\land a_k > i \land l_{i} < l_{a_k}) \end{aligned}\]

則有:

\[f_{i} = 2\times i + g_{r_i - 1} \]

答案即為 \(f_{0}\)

上述過程中需要列舉 \(n\) 種權值,每種權值的轉移過程均為 \(O(n)\) 級別,則總時間複雜度 \(O(n^2)\) 級別。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 3e3 + 10;
//=============================================================
int n, a[kN << 1], rval[kN << 1], lpos[kN << 1], rpos[kN << 1];
LL f[kN << 1], g[kN << 1];
//=============================================================
//=============================================================
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 <= 2 * n; ++ i) {
    std::cin >> a[i];
    if (lpos[a[i]] == 0) lpos[a[i]] = i;
    else rpos[a[i]] = i, rval[i] = a[i];
  }
  lpos[0] = 0, rpos[0] = 2 * n + 1;

  for (int i = n; i >= 0; -- i) {
    int l = lpos[i], r = rpos[i];
    f[i] = 1ll * i * (r - l + 1);
    g[l] = f[i] - 2ll * i;

    for (int j = l + 1; j < r; ++ j) {
      g[j] = g[j - 1];

      int v = rval[j], l1 = lpos[v];
      LL d = f[v] - 1ll * i * (j - l1 + 1);
      if (v > i && l1 > l && g[j] < g[l1 - 1] + d) {
        g[j] = g[l1 - 1] + d;
      }
      f[i] = std::max(f[i], g[j] + 1ll * i * 2ll);
    }
  }
  std::cout << f[0];
  return 0;
}
/*
5
1 2 4 5 4 3 5 3 2 1
*/

B

根號分治,啟發式,亂搞

一個超級超級好寫的純啟發式的不在題解裡的大概是 \(O(m\log m + \left(\sum_i k_i\right)^{\frac{3}{2}}\log{\sqrt{\sum_i k_i}})\) 的很爛的亂搞做法,但是能卡過去,鑑定為為了把各種根號亂搞放過去資料不敢造太強。

由於 \(\sum k_i\le 10^5\),所有詢問的點數之和不太多,反過來考慮,則所有點所在的詢問數量之和也為 \(\sum k_i\le 10^5\),於是使用 set 記錄每個點所在的詢問集合。

考慮對原圖進行 Kruscal,在此過程中考慮一條邊對所有詢問的影響:

首先有 \(\sum k_i\le 10^5\),則可以對每個詢問的點集均維護一個並查集,空間複雜度完全允許,偷懶可以直接用 map;又發現一條邊 \((u, v, w)\) 對某個詢問有影響,當且僅當 \(u, v\) 均在該詢問中,考慮對詢問集合較小的一方列舉詢問 \(q\),並檢查 \(q\) 是否在另一集合中,若在則這條邊對該詢問有影響,大力維護並查集即可。

這東西看起來是 \(O(mq\log q\log n)\) 的,但是欽定了每次列舉詢問時均列舉的較小集合(不欽定鐵 TLE),列舉集合元素並完成維護操作單次複雜度為 \(O(\log |S|)\),則在無重邊無自環的無向簡單圖上,列舉詢問的總數並不會超過 \(O\left(m\frac{\sum_i k_i}{n}\right)\le O(\left(\sum_i k_i\right)^{\frac{3}{2}})\) 級別,列舉的總複雜度不會超過:

\[O\left(m\frac{\sum_i k_i}{n}\log \left(\frac{\sum_i k_i}{n}\right)\right)\le O\left(\left(\sum_i k_i\right)^{\frac{3}{2}}\log{\sqrt{\sum_i k_i}}\right) \]

\(m = \left(\sum_i k_i\right) = n^2\),實際跑起來效率還是挺高的。

則總時間複雜度 \(O(m\log m + \left(\sum_i k_i\right)^{\frac{3}{2}}\log{\sqrt{\sum_i k_i}})\) 級別。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, m, q;
struct Edge {
  int u, v, w;
} e[kN];
int sz[kN], tot[kN];
LL ans[kN];
std::vector<int> querynode[kN];
std::set<int> inquery[kN];
std::map<int, int> idinquery[kN];
int sumsz[kN], fa[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); 
  return f * w;
}
bool cmp(Edge fir_, Edge sec_) {
  return fir_.w < sec_.w;
}
int faid(int query_, int node_) {
  return sumsz[query_ - 1] + idinquery[query_][node_];
}
int Find(int x_) {
  return fa[x_] == x_ ? x_ : fa[x_] = Find(fa[x_]);
}
void Merge(int x_, int y_) {
  int fax = Find(x_), fay = Find(y_);
  if (fax == fay) return ;
  fa[fax] = fay;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  n = read(), m = read(), q = read();
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read(), w_ = read();
    e[i] = (Edge) {u_, v_, w_};
  }
  std::sort(e + 1, e + m + 1, cmp);

  for (int i = 1; i <= q; ++ i) {
    sz[i] = read(), sumsz[i] = sumsz[i - 1] + sz[i];
    for (int j = 1; j <= sz[i]; ++ j) {
      int p = read();
      inquery[p].insert(i);
      idinquery[i][p] = j;
      fa[faid(i, p)] = faid(i, p);
    }
  }

  for (int i = 1; i <= m; ++ i) {
    int u_ = e[i].u, v_ = e[i].v, w_ = e[i].w;
    if (inquery[u_].size() > inquery[v_].size()) std::swap(u_, v_);
    for (auto qid: inquery[u_]) {
      if (!inquery[v_].count(qid)) continue;
      int faidu = faid(qid, u_), faidv = faid(qid, v_);
      if (Find(faidu) == Find(faidv)) continue;
      Merge(faidu, faidv);
      ++ tot[qid];
      ans[qid] += w_;
    }
  }
  for (int i = 1; i <= q; ++ i) {
    if (tot[i] != sz[i] - 1) printf("-1\n");
    else printf("%lld\n", ans[i]);
  }
  return 0;
}

A

構造。

媽的場上過了 98% 的資料,又是什麼神仙資料把我們卡掉了!!!哦原來是 1 5 5 13 3 3 B 這組資料呃呃呃呃!!!看到這組資料瞬間就知道錯哪了呃呃呃呃!!!

#include<bits/stdc++.h>
using namespace std;
#define int long long
int t;
int n,m,k,x,y;
char w;
char c[805][805];
void print(){
	cout<<"Yes\n";
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			cout<<c[i][j];
		} cout<<'\n';
	}
}
void printo(){
	cout<<"Yes\n";
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			cout<<c[n-i+1][m-j+1];
		} cout<<'\n';
	}
}
signed main(){
    // freopen("1.txt", "r", stdin);

	cin>>t;
	while(t--){
		cin>>n>>m>>k;
		cin>>x>>y>>w;
		if(k<n+m){
			cout<<"No\n";
			continue;
		}
		int cnt=0;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				c[i][j]='A';
			}
		}
		for(int i=1;i<=n;++i){
			if(i%2==1){
				for(int j=2;j<=m;j+=2){
					c[i][j]='B';
				}
			}else{
				for(int j=1;j<=m;j+=2){
					c[i][j]='B';
				}
			}
		}
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				if(i+1>n) continue;
				if(j+1>m) continue;
				if(c[i][j]=='A'&&c[i+1][j]=='B'&&c[i][j+1]=='B'&&c[i+1][j+1]=='A') ++cnt;
			}
		}
		if(k>n+m+cnt){//
			cout<<"No\n";
			continue;
		}
		int tmp=n+m+cnt;
		if(tmp==k){
			if(c[x][y]==w){
				print();
				continue;
			}
			if(c[n-x+1][m-y+1]==w){
				printo();
				continue;
			}
		}
		bool fl=0;
		for(int i=n;i>=2;i--){
			if(fl) break;
			if(tmp<k) break;
			for(int j=m;j>=2;--j){
				if(c[i][j]=='A'){
					c[i][j]='B';
					--tmp;
					if(tmp==k){
						if(c[x][y]==w){
							print();
							fl=1; break;
						}
						if(c[n-x+1][m-y+1]==w){
							printo();
							fl=1; break;
						}
						break;
					}
				}
			}
		}
		if(fl) continue;
		
		
		
		
		
		
		
		
		
		
		
		cnt=0;
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				c[i][j]='B';
			}
		}
		for(int i=1;i<=n;++i){
			if(i%2==1){
				for(int j=2;j<=m;j+=2){
					c[i][j]='A';
				}
			}else{
				for(int j=1;j<=m;j+=2){
					c[i][j]='A';
				}
			}
		}
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				if(i+1>n) continue;
				if(j+1>m) continue;
				if(c[i][j]=='A'&&c[i+1][j]=='B'&&c[i][j+1]=='B'&&c[i+1][j+1]=='A') ++cnt;
			}
		}
		if(k>n+m+cnt){
			cout<<"No\n";
			continue;
		}
		tmp=n+m+cnt;
		if(tmp==k){
			if(c[x][y]==w){
				print();
				continue;
			}
			if(c[n-x+1][m-y+1]==w){
				printo();
				continue;
			}
		}
		fl=0;
		for(int i=n;i>=2;i--){
			if(fl) break;
			if(tmp<k) break;
			for(int j=1;j <= m-1;++j){
				if(c[i][j]=='B'){
					c[i][j]='A';
					--tmp;
					if(tmp==k){
						if(c[x][y]==w){
							print();
							fl=1; break;
						}
						if(c[n-x+1][m-y+1]==w){
							printo();
							fl=1; break;
						}
					}
				}
			}
		}
		if(fl) continue;
		cout<<"No\n";
	}
	return 0;
}
/*
1
5 5 13
3 3 B
*/

G

數學,揹包 DP。

場上兩個腦癱用 \(2\sqrt{1000} \approx 200\) 算出來 1000 內有 200 個質數之後就做不下去了,口胡了一個和 \(\sqrt{1000}\) 內質數個數有關的狀壓 DP 發現鐵過不去就跑路了,未曾想 \(\sqrt{1000}\) 內僅有 11 個質數——我草那這不做完了!!!!

寫在最後

學到了什麼:

  • B:自然根號,大力衝一發!
  • I:發現 DP 在轉移時無法從多種子問題中選到最優解,說明狀態有誤,或者不是 DP。

相關文章