洛谷 多校訓練第 4 輪 題解 (A-H)

洛谷網校報名地址:洛谷網校

A題 T125991 God J and Ancient Tree

題目類型 :tire樹 啓發式合併

題目大意 :

給你一顆樹,每個點都具有權值,要求你求解兩條到根的鏈,使它們的並上點權異或值最大。

解題思路 :

問題轉換

可以考慮枚舉每個分叉點,每次可以向兩個方向延伸,每個方向都有很多種選擇,問題就變成了詢問兩
個集合的最大異或和。(像兩個方向延伸尋找最大異或和???)

解決方法

這個問題可以建立一邊的 trie 樹,然後在另一邊挨個詢問解決。
回到原問題,如果知道分叉點其中一個方向的所有可能形成的 trie 樹,那對另一個方向挨個暴力詢問一
次就行了。
因此問題在怎麼求一棵子樹所對應的 trie 樹。

時間複雜度分析

使用啓發式合併或者 dsu on tree,都可做到 nlog2(n)

什麼是dsu on tree?

dsu on tree跟dsu(並查集)是沒啥關係,可能是借用了一波啓發式合併的思想??
它是用來解決一類樹上詢問問題,一般這種問題有兩個特徵
1、只有對子樹的詢問
2、沒有修改
一般這時候就可以強上dsu on tree了
在題目中不一定明確的問你子樹i的答案,可能是把問題轉化後需要算子樹i的答案
爲什麼說利用啓發式和並或者dsu on tree可以降低時間複雜度呢?
原因是在於dsu on tree 利用了輕重鏈剖分的方法
什麼是輕重鏈剖分?
對於樹上的一個點,與其相連的邊中,連向的節點子樹大小最大的邊叫做重邊,其他的邊叫輕邊)

void dfs(int x, int fa, int opt){
	for(all Edge){ //遍歷所有的邊
		if (to == fa) continue;
		if (to != BigSon[x]) dfs(to, x, 0); //如果合重兒子不一樣的話即是輕邊,即遞歸掃所有的輕邊
	}
	if (son[x]) dfs(BigSon[x], x, 1); //統計及重兒子的貢獻
	add(x); //統計輕兒子的貢獻
	ans[x] = NowAns;//更新答案
	if (!opt) delete(x);
}

代碼

A.代碼,會補

B題

C題

D題 T125994 God J and Decryption

題目類型 :思維

題目大意 :

給你段序列,其實代表的是一個環,最後一個位置實際上是和第一個位置相連接的,你每次可以修改連續的k個位置,(最後一個和第一個是算連續的)(修改方式,模10+1

解題思路 :

假設原序列是 ai,差分一下得到一個新序列 bi = ai - ai-1(mod 10 ) ,b1 = a1 - an(mod 10) ,那麼對區間 l 到 r 的 +1 操作就變成了bl + 1 然後br+1 -1 了。
這樣問題就變成 b 序列被劃分成了若干個環,每個環裏的數可以自己 +1 然後使後一個數 -1。那麼可以變成目標的要求就是這個環裏所有數的總和模 10 是 0 了。

代碼 :

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
const int MAXN = 1e5 + 50;
int a[MAXN];
int main(){
    int n, k; scanf("%d%d", &n, &k);
    string s; cin >> s;
    for(int i = 0; i < s.size(); i++){
        i == 0 ? a[i] = s[0] - s[n - 1] : a[i] = s[i] - s[i - 1];
        a[i] = (a[i] + 10) % 10; // 確保a[i]的值是大於等於0的,所以先加了10
    }
    int p = __gcd(n, k);
    for(int i = 0; i < p; i++){
        int l = i, r = 0;
        do {
            r = (r + a[l]) % 10;
            l = (l + k) % n;
        } while(l != i);
        if (r) { printf("No\n"); return 0;}
    }
    printf("Yes\n");
}

E 題 T125995 God J and Eel

題目大意 :

題外話.感覺這題不太像是一個模擬題,更像是是一個閱讀理解題

解題思路 :

按要求模擬

代碼 :

#include <iostream>
#include <vector>
#include <string>
using namespace std;
const int MAXN = 1e5 + 50;
struct DDL{
	string name;
	int due;//截止時間
};
vector<DDL> a[MAXN];
int main(){
	int n, m; cin >> n >> m;
	//每一次的輸入時間也在發生改變
	for(int qr = 1; qr <= m; qr++){
		int ju, x, y; string b; cin >> ju;
		if (ju == 1){
			cin >> x >> b >> y;
			DDL cur; cur.name = b; cur.due = y;
			a[x].push_back(cur);//類似與迪傑斯特拉的,x到y存在邊權
		}
		else if (ju == 2){
			cin >> x;
			DDL ans; int cur = 1e5;
			for(int i = 0; i < a[x].size(); i++){
				if (a[x][i].due - qr > 0 && a[x][i].due - qr < cur){
					//
					ans = a[x][i]; cur = a[x][i].due - qr;
				}

			}
			if (cur != 1e5) cout << ans.name << '\n';
			else cout << "Happy\n";
		}
		else {
			cin >> x >> b;
			for(int i = 0; i < a[x].size(); i++) 
				if (a[x][i].name == b){
					if (a[x][i].due < qr) printf("GG\n");
					else printf("OK\n");
				break;
				}
		}
	}
}

F 題 T125996 God J and Firm Structure

題目類型 :圖論,數學

題目大意 :

有一個n個點的連通圖,現在要求你最少刪去k個點使得圖變成不連通。求解這n個點所構成圖的最少邊數

樣例解析 :

可能通過題目大意還是不太明白題目是什麼意思,那麼我們先來看一下第一個樣例
在這裏插入圖片描述
第一行輸入的是樣例個數先不用管它。

注意 n = 4, k = 1,
他僅僅刪去了一個點就使得圖變成了不連通的了

那麼就很容易想到生成樹

什麼是生成樹?
連通圖中的生成樹必須滿足以下 2 個條件:
包含連通圖中所有的頂點;
任意兩頂點之間有且僅有一條通路;
因此,連通圖的生成樹具有這樣的特徵,即生成樹中邊的數量 = 頂點數 - 1
(區別於最小生成樹,最小生成樹,是在圖中衆多的生成樹中,尋找總權值最小的生成樹

k=1的時候很明顯就是最小生成樹,你只需要刪去他的根就可以

所以k=1的時候答案就是 n-1

第二行樣例也很容易想到,那便是成環情況。(因爲你刪一個剩下的還是處於聯通的狀態,只有刪除兩個纔是變成不連通)
特別提示,這裏刪我們一直刪的是點而不是邊

在這裏插入圖片描述
那麼其他情況是什麼樣的呢?

我們很容易清楚一個圖是否連通,我們通常是用深搜廣搜拓撲排序去判斷。而出現不連通情況也通常是由於一個點被其他的點孤立,而使得該孤立點無法到達其他邊,我們現在要做的其實就是刪除k個點,實際上也算是刪除與較爲孤立點
連接的周圍幾個點,這樣就能確保題目中要求的

度數表示的就是一個點所連接的邊的數量(如果是橋的話,算兩邊,這題沒有不用在意),下面所設的最小度數其實就是那個比較孤立的點。
以下來源洛谷官方題解,地址:洛谷

洛谷官網題解

代碼 :

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
int main(){
	int t; cin >> t;
	for(int T = 1; T <= t; T++){
		int n, k; cin >> n >> k;
		if (k == 1) {
			//生成樹
			cout << n - 1 << '\n';
		}
		else{
			cout << ceil(n * k / 2) << '\n';
		}
	}
}

G題 T125997 God J and Giovanni’s Ticket

題目類型 :數學(解方程??)

題目大意 :解方程,解出下列式子的xi如果解的出來輸出YES和xi解不出來輸出NO

在這裏插入圖片描述

For each test case, if you can find a xi print “YES” and xi Otherwise, print a single line “NO”

解題思路 :

2Bi2^{B_i} 底數2能不能讓你思考到什麼呢?二進制?移位運算?沒錯
⊕異或符號就是表示異或了,沒什麼好想的啦~

那麼我們把公式化簡,得到

根據題目所給的數據範圍B、D都是大於0的數 [1,31][1, 31], D、B算是已經被確定的數(由輸入確定)

代碼 :

H 題 T125998 God J and Ham Sausage

題目類型 :數學

數學場無疑了QAQ

題目大意 :

每d距離對香腸來上一刀,求解每段的體積是多少?
香腸形狀被視爲由兩個半球和一個圓柱組成的形狀。

在這裏插入圖片描述
是這個樣子的(這是洛谷題目中所提供的圖片,如果需要更清晰的,請報名洛谷多校 0.0)

解題思路 :

(我們先不考慮後半部分,先考慮前半部分,因爲當你前半部分考慮清楚的時候,後半部分也便簡單了)
問題的難點主要是在於處理半球和圓柱的交接的地方,爲什麼這會是一個難點呢,請看下面的圖
在這裏插入圖片描述

  • d == r的時候,是很好解決的,第一部分的體積就是半球的體積
  • d > r 的時候也很好解決, 第一部分就是半球的體積+長度爲d-r的圓柱的體積
  • 最不好考慮的是 d < r,第一需要計算球缺的體積,第二可能需要計算既不是球缺也不是圓柱的體積
    那麼怎麼解決呢?

  • 由於球缺我都不是很瞭解,所以我先上網搜索了一下球缺這個東西要怎麼計算體積
    在這裏插入圖片描述
    找到了這麼一個公式

V=(π/3)(3Rh)h²V=(π/3)(3R-h)*h²

  • 這個公式是怎麼得來的呢?是通過二重積分求解體積積分而來的。
  • 於是我們可以想,難道不能直接通過積分去計算每一段嗎?這就是這一題的解題方法,避免了什麼時候切在半球上的判斷難點

把球的圓心視作原點,體積則爲函數 y=r2x2y = \sqrt{r^2-x^2}的某一段繞x軸旋轉得到。令VV(l,r)(l, r)的體積,可以積分得到

在這裏插入圖片描述
剩餘的圓柱部分就很好求解了。每d段算一次

代碼 :

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long double LDB;
int h, r, d; //輸入變量
const LDB PI = acosl(-1); //圓周率

LDB get1(int H){
	H = min(max(H, 0), r);
	return PI * H * H * (r - H / 3.0L);
}
LDB get2(int H){
	H = min(max(H, 0), h);
	return PI * r * r * H;
}
int main(){
	scanf("%d%d%d", &h, &r, &d);
  	int k = (h + 2 * r) / d + ((h + 2 * r) % d);
  	for (int i = 0; i < k; i++) {
    int s = i * d, t = (i + 1) * d;
    long double ans = get1(t) - get1(s) + get2(t - r) - get2(s - r) + get1(h + 2 * r - s) - get1(h + 2 * r - t);
    printf("%.10lf\n", (double) ans);
  }
}

總結

如何判斷是否是一個好的題目?
英文看不懂,chrome翻譯成中我依然看不懂,就是什麼都看不懂。

參考文獻

1 球缺推導
2 luogu std

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章