牛客小白月賽24 (B、C、D、E、F、G、H、I、J)

比賽鏈接

B - 組隊

思路:在排好序的能力值數組裏面搞一個頭指針,搞一個尾指針,如果尾部的能力值-頭部的能力值在k之內,那麼就讓尾指針再往後移動,反之,記錄當前尾部與頭部的差值與答案取max,並且頭指針向後移動一位。往復搞一搞就出來了。

#include <bits/stdc++.h>
using namespace std;

int _, n, k;
int a[200005];

int main() {
	scanf("%d", &_);
	while(_--) {
		scanf("%d %d", &n, &k);
		for(int i = 1;i <= n; i++) {
			scanf("%d", &a[i]);
		}
		sort(a+1, a+1+n);
		
		
		int head = 1, tail = 1;
		int ans = 0;
		while(head <= n) {
			if(a[tail] - a[head] <= k) {
				if(tail == n) break;
				tail++;
			}
			else {
				ans = max(ans, tail-head);
				head++;
			}
		}
		printf("%d\n", ans);
	}
}

C - 十面埋伏

思路:DFS 從(0,0)開始把 ‘#’ 外面的部分全部標記起來,然後開始遍歷,如果是’#‘的話,就把旁邊的標記過的點的變成’*’,遍歷完了之後再把那些標記但是沒有改變的位置變回原來的 ‘.’

#include <bits/stdc++.h>
using namespace std;

int n, m;
char a[505][505];
int dx[] = {-1,1,0,0};
int dy[] = {0,0,-1,1};

void dfs(int x, int y) {
	a[x][y] = 'x';
	for(int i = 0;i < 4; i++) {
		int tx = x+dx[i], ty = y+dy[i];
		if(tx < 0 || ty < 0 || tx > n-1 || ty > m-1) continue;
		if(a[tx][ty] == '.') dfs(tx, ty);
	}
}

int main() {
	scanf("%d %d", &n, &m);
	for(int i = 0;i < n; i++) scanf("%s", a[i]);
	
	dfs(0,0);
	
	for(int i = 0;i < n; i++) {
		for(int j = 0;j < m; j++) {
			if(a[i][j] == '#') {
				for(int k = 0;k < 4; k++) {
					int tx = i+dx[k], ty = j+dy[k];
					if(a[tx][ty] == 'x') a[tx][ty] = '*';
				}
			}
		}
	}
	
	for(int i = 0;i < n; i++) {
		for(int j = 0;j < m; j++) {
			if(a[i][j] == 'x') a[i][j] = '.';
		}
	}
	
	for(int i = 0;i < n; i++) {
		for(int j = 0;j < m; j++) {
			printf("%c", a[i][j]);
		}
		puts("");
	}
}

D - 牛妹喫豆子

思路:因爲是統一把修改放在一起,然後把查詢放在一起,就可以通過求二維差分的方法來修改矩陣中的值,之後求一次二維前綴和就可以 O(1) 求查詢了。

二維差分求法 :

1val[x1][y1]++1、val[x1][y1]++

2val[x1][y2+1]2、val[x1][y2+1]--

3val[x2+1][y1]3、val[x2+1][y1]--

4val[x2+1][y2+1]++4、val[x2+1][y2+1]++

二維前綴和求法:

sum[i][j]=val[i][j]+sum[i1][j]+sum[i][j1]sum[i1][j1]sum[i][j] = val[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]

求指定 (x1,y1) ~(x2,y2) 矩陣範圍的和的求法:

sum[x1][y1]sum[x11][y2]sum[x2][y11]+sum[x11][y11]sum[x1][y1]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]

代碼:

#include <bits/stdc++.h>
using namespace std;

int n, m, k, q, w1, h1, w2, h2;
long long val[2002][2002];

int main() {
	scanf("%d %d %d %d", &n, &m, &k, &q);
	while(k--) {
		scanf("%d %d %d %d", &w1, &h1, &w2, &h2);
		val[w1][h1]++;
		val[w1][h2+1]--;
		val[w2+1][h1]--;
		val[w2+1][h2+1]++;
	}
	
	for(int i = 1;i <= n; i++) { // 求出真實矩陣
		for(int j = 1;j <= m; j++) {
			val[i][j] = val[i][j] + val[i-1][j] + val[i][j-1] - val[i-1][j-1];
		}
	}
	
	for(int i = 1;i <= n; i++) { // 二維前綴和
		for(int j = 1;j <= m; j++) {
			val[i][j] = val[i][j] + val[i-1][j] + val[i][j-1] - val[i-1][j-1];
		}
	}
	
	while(q--) {
		scanf("%d %d %d %d", &w1, &h1, &w2, &h2);
		printf("%lld\n", val[w2][h2]-val[w1-1][h2]-val[w2][h1-1]+val[w1-1][h1-1]);
	}
}

E - 旅遊旅遊

思路:題目是要求 把所有是最段路需要經過的邊都給刪除掉,剩餘的邊是否任然能夠把所有的點連在一起

我們首先需要知道最短的路長度爲多少,然後我們需知道哪一條邊是構成最短路徑的邊就好辦了,因爲把這些邊過濾掉,跑一遍 kruskal 就知道他們是不是連在一起的啦。

那麼求最短路這個很好想到嘛,用Dijkstra來搞一搞,那怎麼知道哪些邊構成了最短路呢,這個辦法就有點巧妙了:

設一條路一端是城市 u,一端是城市 v,這條路長度爲 w。那麼如何判斷這條路是不是構成最短路的邊呢?是不是可以 判斷一下 城市1 -> 城市u 的最短路徑 + w + 城市v -> 城市n 的最短路徑 (or 城市1 -> 城市v 的最短路徑 + w + 城市u -> 城市n 的最短路徑)== 最短路徑 的時候就可以斷定這條路一定是構成最段路的那條邊。

那麼也由此可見,我們在求最短路的時候需要求兩個源點的,一個是城市1到所有點,一個是城市n到所有點。最後用這個條件作爲kruskal的加邊的條件,構成最短路的邊就跳過,不是就加入父子關係大家庭,之後for驗證一下大家的父親是否一樣就👌。

這題是我補出來的,構造函數在傳step的時候忘記開 long long 真的找死。

#include <bits/stdc++.h>
using namespace std;

struct node1 {
	long long num, step;
	node1(int u, long long s) {
		num = u; step = s;
	}
	bool operator < (const node1&o) const {
		return o.step < step;
	}
};
struct node2 {
	int u, v, w;
}edge[500005];
int n, m, fa[100005];
long long dist[2][100005];
vector <int> poi[100005], len[100005];
priority_queue <node1> q;

int find(int x) {
	if(fa[x] == x) return x;
	else return fa[x] = find(fa[x]);
}

void dijkstra(bool a, int sta) {
	for(int i = 1;i <= n; i++) dist[a][i] = 1e18;
	dist[a][sta] = 0;
	q.push(node1(sta, 0));
	while(!q.empty()) {
		node1 t = q.top(); q.pop();
		if(t.step > dist[a][t.num]) continue;
		for(int i = 0;i < poi[t.num].size(); i++) {
			int k = poi[t.num][i];
			if(dist[a][k] > t.step + len[t.num][i]) {
				dist[a][k] = t.step + len[t.num][i];
				q.push(node1(k, dist[a][k]));
			}
		}
	}
}

int main() {
	scanf("%d %d", &n, &m);
	for(int i = 1;i <= n; i++) fa[i] = i;
	for(int i = 1;i <= m; i++) {
		scanf("%d %d %d", &edge[i].u, &edge[i].v, &edge[i].w);
		poi[edge[i].u].push_back(edge[i].v); poi[edge[i].v].push_back(edge[i].u);
		len[edge[i].u].push_back(edge[i].w); len[edge[i].v].push_back(edge[i].w);
	}
	dijkstra(0,1); dijkstra(1, n);
	long long ans = dist[0][n];
	
	for(int i = 1;i <= m; i++) {
		if(dist[0][edge[i].u]+edge[i].w+dist[1][edge[i].v]==ans || dist[0][edge[i].v]+edge[i].w+dist[1][edge[i].u]==ans) continue;
		int u = find(edge[i].u);
		int v = find(edge[i].v);
		if(u != v) fa[u] = v;
	}
	
	bool ok = 1;
	for(int i = 2;i <= n; i++) {
		if(find(1) != find(i)) {
			ok = 0;
			break;
		}
	}
	if(ok) puts("YES");
	else puts("NO");
}

F - 鬥獸棋

思路:舔🐶水題

#include <bits/stdc++.h>
using namespace std;

string s1, s2;

int main() {
	cin >> s1 >> s2;
	if(s1 == "elephant") {
		if(s2 == "mouse") puts("tiangou txdy");
		else puts("tiangou yiwusuoyou");
	}
	else if(s1 == "tiger") {
		if(s2 == "elephant") puts("tiangou txdy");
		else puts("tiangou yiwusuoyou");
	}
	else if(s1 == "cat") {
		if(s2 == "tiger") puts("tiangou txdy");
		else puts("tiangou yiwusuoyou");
	}
	else if(s1 == "mouse") {
		if(s2 == "cat") puts("tiangou txdy");
		else puts("tiangou yiwusuoyou");
	}
}

G - 做題

思路:排好序一直加到不能加的位置,記錄當前在第幾個題目。

#include <bits/stdc++.h>
using namespace std;

long long n, m;
long long a[500005];

int main() {
	scanf("%lld %lld", &n, &m);
	for(int i = 1;i <= n; i++) {
		scanf("%lld", &a[i]);
	}
	sort(a+1, a+1+n);
	
	long long sum = 0;
	int ans = n;
	for(int i = 1;i <= n; i++) {
		if(sum + a[i] > m) {
			ans = i-1;
			break;
		}
		sum += a[i];
	}
	
	printf("%d\n", ans);
}

H - 人人都是好朋友

思路:離散化+並查集:已經成爲朋友的人可以記錄一個共同的朋友,這樣在查詢的時候就好處理一些,這樣就需要並查集來幫忙,暫時讓你的好朋友當你的👨。但是呢這個題目,人們的編號有點大,如果直接上map來存儲人的編號的話那肯定會超時的,但是他的總人數沒有那麼多,我們就可以把他們的編號離散化一下,整成在1e7範圍內的連續的數,就可以在數組裏面整了。

直接上map的慘案:

// 超時代碼 沒有離散化直接用map並查集
#include <bits/stdc++.h>
using namespace std;

int _, n, c;
int a, b;
map <int, int> fa;
map < pair<int, int>, bool> bad;
bool ok;

int find(int x) {
	if(fa[x] == x) return x;
	else return fa[x] = find(fa[x]);
}

int main() {
	scanf("%d", &_);
	while(_--) {
		fa.clear(); bad.clear();
		scanf("%d", &n);
		while(n--) {
			ok = 1;
			scanf("%d %d %d", &a, &b, &c);
			if(fa[a] == 0) fa[a] = a;
			if(fa[b] == 0) fa[b] = b;
			long long u = find(a);
			long long v = find(b);
			
			if(c == 1) {
				if(bad[make_pair(u, v)]) ok = 0;
				else {
					fa[u] = v;
				}
			}
			else {
				if(u == v) ok = 0;
				else {
					bad[make_pair(u, v)] = 1;
					bad[make_pair(u, v)] = 1;
				}
			}
		}
		
		if(ok) puts("YES");
		else puts("NO");
	}
}

正確代碼:

#include <bits/stdc++.h>
using namespace std;

int _, n;
struct node {
	int a, b, c;
} relation[2000006];
int fa[2000006], fin[2000006];

int find(int x) {
	if(fa[x] == x) return fa[x];
	else return fa[x] = find(fa[x]);
}

int main() {
	scanf("%d", &_);
	while(_--) {
		bool ok = 1;
		scanf("%d", &n);
		int cnt = 0;
		for(int i = 1;i <= n; i++) {
			scanf("%d %d %d", &relation[i].a, &relation[i].b, &relation[i].c);
			fin[++cnt] = relation[i].a;
			fin[++cnt] = relation[i].b;
		}
		
		sort(fin+1, fin+1+cnt);
		cnt = unique(fin+1, fin+1+cnt)-fin-1;
		for(int i = 1;i <= cnt; i++) fa[i] = i;
		for(int i = 1;i <= n; i++) {
			relation[i].a = lower_bound(fin+1, fin+1+cnt, relation[i].a) - fin;
			relation[i].b = lower_bound(fin+1, fin+1+cnt, relation[i].b) - fin;
			
			int u = find(relation[i].a);
			int v = find(relation[i].b);
			
			if(relation[i].c == 1) {
				if(u != v) fa[u] = v;
			}
			else {
				if(u == v) ok = 0;
			}
		}
		if(ok) puts("YES");
		else puts("NO");
	}
}

I - 求和

思路:如果題目中給的是完全獨立的n個節點,那麼就可以直接上樹狀數組或者線段樹搞一搞就可以出答案了。

但是現在的節點有父子關係,導致不能簡單的使用這兩個工具來解題,那麼怎麼辦呢?

當然首先要把這些點的關係先梳理一遍:有父子關係的都綁定起來,通過父親節點就可以知道所有子節點的和。

那麼怎麼才能搞成這樣的關係呢,就dfs序跑一遍唄,通過遍歷順序給每個節點安排上新的時間戳序號,遞歸回到父節點的時候就剛好是一個連續的區間,區間中就包括了自身與所有的子節點了。 然後有了這些關係我們就可以用樹狀數組或者線段樹來求解了。

#include <bits/stdc++.h>
using namespace std;

vector <int> g[1000006];
int val[1000006], times;
int l[1000006], r[1000006];
int n, m, k;
long long sum[1000006];

int lowbit(int x) {
	return x&-x;
}

void update(int pos, int num) {
	while(pos <= n) {
		sum[pos] += num;
		pos += lowbit(pos);
	}
}

long long getsum(int pos) {
	long long res = 0;
	while(pos) {
		res += sum[pos];
		pos -= lowbit(pos);
	}
	return res;
}

void dfs(int son, int father) {
	l[son] = ++times;
	update(times, val[son]);
	for(int i = 0;i < g[son].size(); i++) {
		int grandson = g[son][i];
		if(grandson != father) {
			dfs(grandson, son);
		}
	}
	r[son] = times;
}

int main() {
	scanf("%d %d %d", &n, &m, &k);
	for(int i = 1;i <= n; i++) {
		scanf("%d", &val[i]);
	}
	int u, v;
	for(int i = 1;i < n; i++) {
		scanf("%d %d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	dfs(k, 0);
//	for(int i = 1;i <= n; i++) printf("%d ", l[i]); puts("");
	int choose, x, y;
	for(int i = 1;i <= m; i++) {
		scanf("%d", &choose);
		if(choose == 1) {
			scanf("%d %d", &x, &y);
			update(l[x], y);
		}
		else {
			scanf("%d", &x);
			printf("%lld\n", getsum(r[x])-getsum(l[x]-1));
		}
	}
}

J - 建設道路

思路:直接模擬的話 O (n^2),我們可以先把這些需要加起來的完全平方式列出來找找規律 :

(x1-x2)2 + (x1-x3)2 + (x1-x4)2 + (x1-x5)2 + … + (x1-xn)2

​----------(x2-x3)2 + (x2-x4)2 + (x2-x5)2 + … + (x2-xn)2

​-------------------- (x3-x4)2 + (x3-x5)2 + … + (x3-x5)2

​-------------------------------(x4-x5)2 + … + (x4-xn)2

​ …

展開之後:

(n-1) * a12 + a22 + a32 + a42 +a52 + … + an2 - 2 * a1 * (a2 + a3 + a4 + a5 + … + an)

​-----(n-2) * a22 + a32 + a42 +a52 + … + an2 - 2 * a2 * (a3 + a4 + a5 + … + an)

-----------(n-3) * a32 + a42 +a52 + … + an2 - 2 * a3 * (a4 + a5 + … + an)

​ …

可以發現這些式子,減號👈相同的項相加都是 (n-1) 項, 發現這個就可以集中O(n) 處理了,減號 👉 的規律也不難找,常數2不用管,後面跟的是一個有順序的a,括號裏的就可以用前綴和的方式 pre[n] - pre[i] 就可以得到了。寫的時候記得取模問題就不大。

#include <bits/stdc++.h>
using namespace std;

const int mod = 1e9+7;
int n;
long long a[500005], presum[500005], res1, res2;

int main() {
	scanf("%d", &n);
	for(int i = 1;i <= n; i++) {
		scanf("%lld", &a[i]);
		res1 = (res1+(a[i]*a[i]%mod))%mod;
		presum[i] = (presum[i-1] + a[i]) % mod;
	}
	res1 = (res1 * (n-1)) % mod;
	
	for(int i = 1;i <= n; i++) {
//		cout << presum[i] << " ";
		res2 = (res2 + ((presum[n]-presum[i])%mod*a[i])%mod)%mod;
	}
	res2 = (2*res2)%mod;
//	puts("");
	
//	cout << "res1 = " << res1 << " res2 = " << res2 << endl;
	printf("%lld\n", (res1-res2+mod)%mod);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章