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) 求查詢了。
二維差分求法 :
二維前綴和求法:
求指定 (x1,y1) ~(x2,y2) 矩陣範圍的和的求法:
代碼:
#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);
}