[2021-06-19] 提高组新手副本Ⅱ(联网,欧几里得,分解树,开关灯)
文章目錄
- 考試心路歷程
- 聯網
- title
- solution
- code
- 歐幾里得
- title
- solution
- code
- 分解樹
- title
- solution
- code
- 開關燈
- title
- solution
- code
考試心路歷程
佛了佛了,caocaocaocaocaocao 人直接炸嗨升天
并查集直接送走200200200分!!!我屮艸芔茻
T1二分檢查直接離開——好家伙沒想到沒死在精度上,直接鞭尸并查集
之前還嘲笑香香mm,原來SB竟是我自己
T2構造不出來,拿了基礎的部分分
T3并查集反著做,排序方法錯了,不說了不說了,懂的都懂
T4敲了鏈的部分分但是沒過,誰知道呢??
害!
總之這一場發揮非常離譜,從來沒有這么炸過,可以說,題目又不難
聯網
title
題目描述
有n個無線電信號站,每個無線電發射站的發射半徑都必須相等,你可以統一設置它們半徑。發射半徑越長,則花費越大。如果兩個無線電發射站能覆蓋同一個點,則它們能夠聯網。如果A與B能聯網,B與C能聯網,則A,C也能聯網。現在告訴你這n個無線電發射站的坐標,你需要讓它們連成一個網絡,請問最小的發射半徑是多少?
輸入格式
第一行包含一個整數nnn,表示無線電信號站的數量。
接下來有n行,每行包含兩個整數xi,yix_i,y_ixi?,yi?,表示發射站的坐標
輸出格式
一個實數,表示最小發射半徑,保留7位小數。
輸入樣例1
2 1 1 2 2輸出樣例1
0.7071068輸入樣例2
7 2 3 3 4 4 5 0 1 3 1 4 2 1 5輸出樣例2
1.4142135輸入樣例3
4 2020 20 20 2020 2020 2020 20 20輸出樣例3
1000.0000000solution
二分距離,暴力檢查,用并查集判斷是否聯通
最后注意并查集不要打錯了,不然骨灰揮灑
code
#include <cmath> #include <cstdio> #define eps 1e-7 #define maxn 1005 struct node {double x, y; }net[maxn]; int n; int f[maxn];void MakeSet() {for( int i = 1;i <= n;i ++ ) f[i] = i; }int FindSet( int x ) {return x == f[x] ? x : f[x] = FindSet( f[x] ); }void UnionSet( int u, int v ) {int fu = FindSet( u ), fv = FindSet( v );f[fv] = fu; }double dis( int i, int j ) {return sqrt( ( net[i].x - net[j].x ) * ( net[i].x - net[j].x ) + ( net[i].y - net[j].y ) * ( net[i].y - net[j].y ) ); }bool check( double x ) {MakeSet();for( int i = 1;i <= n;i ++ )for( int j = i + 1;j <= n;j ++ )if( dis( i, j ) <= x * 2 )UnionSet( i, j );for( int i = 1;i < n;i ++ )if( FindSet( i ) != FindSet( i + 1 ) ) return 0;return 1; }int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ )scanf( "%lf %lf", &net[i].x, &net[i].y );double l = 0, r = 1e9, ans;while( r - l > eps ) {double mid = ( l + r ) / 2;if( check( mid ) ) ans = mid, r = mid;else l = mid;}printf( "%.10f\n", ans );return 0; }歐幾里得
title
我們知道輾轉相除法,現在給一個類似與輾轉相除的函數R(a,b)R(a,b)R(a,b),定義如下
R(a,b)={R(b,a)a<bR(a,b)=\{ R(b,a)a<b R(a,b)={R(b,a)a<b
給你兩個整數g,hg,hg,h,請你找兩個整數a,ba,ba,b,使得它們滿足gcd?(a,b)=g,R(a,b)=h\gcd(a,b)=g,R(a,b)=hgcd(a,b)=g,R(a,b)=h
輸入格式
第一行包含一個整數t(1≤t≤40)t(1\le t\le 40)t(1≤t≤40),表示測試數據的組數。
接下來有ttt行,每行包含兩個正整數g,hg,hg,h
輸出格式
包含ttt行,每行包含兩個整數a,ba,ba,b,表示符合上述條件的整數。
保證答案不超過1e181e181e18,可以證明一定有解。
1≤g≤200000,2≤h≤2000001\le g\le 200000,2\le h\le 2000001≤g≤200000,2≤h≤200000
輸入樣例1
1 1 4輸出樣例1
99 23輸入樣例2
2 3 2 5 5輸出樣例2
9 39 5 5solution
最后的答案長相一定是R(h,1)R(h,1)R(h,1),也就是說之前的每一次一定滿足min(a,b)≥hmin(a,b)\ge hmin(a,b)≥h
那么每次都至少會讓max(a,b)max(a,b)max(a,b)縮成原來的1h\frac{1}{h}h1?
假設操作進行了kkk次,那么構造得a∈[hk,2?hk)a∈[h^k,2*h^k)a∈[hk,2?hk)
又滿足是ggg的倍數
有b=g??hkg?,a=b?h+gb=g*\lceil\frac{h^k}{g}\rceil,a=b*h+gb=g??ghk??,a=b?h+g
code
#include <cmath> #include <cstdio> #include <iostream> using namespace std; #define int long long int T, g, h;signed main() {scanf( "%lld", &T );while( T -- ) {scanf( "%lld %lld", &g, &h );int b = h;while( b <= g ) b *= h;if( b % g ) b += g - ( b % g );int a = b * h + g;printf( "%lld %lld\n", a, b );}return 0; }分解樹
title
題目描述
有一棵樹,有nnn個頂點,每個節點都有權值,現在你要將每條邊都斷開,你可以選擇斷開的順序,最終的代價可能是不同的。斷開一條邊的代價為該邊連接的兩個連通塊中各取一個最大權值的頂點之和。請問最后將所有邊都斷開的最小代價是多少?n≤1e5n\le 1e5n≤1e5
輸入格式
第一行包含一個整數nnn,表示有nnn個頂點。
第二行包含nnn個整數,表示頂點iii的權值
接下來有n?1n-1n?1行,每行兩個整數a,ba,ba,b,表示點aaa與點bbb相連。
輸出格式
一個整數,表示最小的代價
輸入樣例1
3 1 2 3 1 2 2 3輸出樣例1
8輸入樣例2
4 2 2 3 2 1 3 3 2 4 3輸出樣例2
15輸入樣例3
5 5 2 3 1 4 2 1 3 1 2 4 2 5輸出樣例3
26solution
法一:反其道而行
相當于是加邊,構成一棵樹,加邊操作為兩邊連通塊各自的最大值之和,求最小花費
顯然,肯定是邊花費越小越先加
邊按照連接的兩個連通塊的最大值從小到大排序(PS:不是和從小到大)
因為兩條邊先后合并順序不同,只會是最大值加的不同
法二:找結論
獨立計算每個點會產生多少次貢獻,顯然是從最大點值的邊開始斷著走
斷開一條邊時,設該邊的兩個點為xi,yix_i,y_ixi?,yi?,設txi>tyit_{x_i}>t_{y_i}txi??>tyi??,則xix_ixi?會作為當前的最大點算一次貢獻,而第二大點也會算一次貢獻
所有點(除了全局最大點之外)都有且僅有一次機會成為第二最大點被算一次貢獻
因為算了貢獻一次后,他就是他所在連通塊中的最大點了,永遠不可能再成為第二最大點
所以最終的貢獻為:∑i<n(max(txi,tyi))+∑i≤nti?maxi≤n(ti)\sum_{i<n}(max(t_{x_i},t_{y_i}))+\sum_{i \leq n}t_i-max_{i\leq n}(t_i)∑i<n?(max(txi??,tyi??))+∑i≤n?ti??maxi≤n?(ti?)
code
#include <cstdio> #include <vector> #include <iostream> #include <algorithm> using namespace std; #define maxn 100005 #define int long long struct node {int u, v, w;node(){}node( int U, int V, int W ) {u = U, v = V, w = W;}bool operator < ( node &t ) const {return w < t.w;} }; vector < node > G; int n, ans; int val[maxn], f[maxn];void MakeSet() {for( int i = 1;i <= n;i ++ ) f[i] = i; }int FindSet( int x ) {return x == f[x] ? x : f[x] = FindSet( f[x] ); }void UnionSet( int u, int v ) {u = FindSet( u ), v = FindSet( v );ans += val[u] + val[v];f[v] = u;val[u] = max( val[u], val[v] ); }signed main() {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )scanf( "%lld", &val[i] );for( int i = 1, u, v;i < n;i ++ ) {scanf( "%lld %lld", &u, &v );G.push_back( node( u, v, max( val[u], val[v] ) ) );}sort( G.begin(), G.end() );MakeSet();for( int i = 0;i < G.size();i ++ )UnionSet( G[i].u, G[i].v );printf( "%lld\n", ans );return 0; }開關燈
title
題目描述
有nnn盞燈,組成了一棵樹,一開始有些燈是開著的,有些燈是關著的。你可以從任一盞燈開始,沿著樹上的路徑游走,你經過的地方,燈都會改變狀態。你可以重復經過某一盞燈,每經過它一次,它的狀態都會發生改變。你的目標是要將所有燈都變成開的狀態。請問,你的游走軌跡上的最少的燈數是多少?如果你重復經過一盞燈,則它要算多次。
輸入格式
第一行一個整數nnn。 接下來是一個nnn位的01串,表示燈的開關狀態,000表示關,111表示開。 再接下來有 行,每行兩個整數a,ba,ba,b,表示aaa與bbb相連。
輸出格式
一個整數,表示答案。
輸入樣例1
3 010 1 2 2 3輸出樣例1
4輸入樣例2
5 00000 1 2 2 3 2 4 3 5輸出樣例2
7輸入樣例3
5 00100 1 2 2 3 2 4 3 5輸出樣例3
8solution
題解
大部分注釋在代碼里
code
#include <cstdio> #include <vector> #include <cstring> #include <iostream> using namespace std; #define maxn 500005 vector < int > G[maxn]; int n; char s[maxn]; bool g[maxn]; int t[3][2]; int f[maxn][3][2];void dfs( int u, int fa ) {g[u] = s[u] - '0';for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if( v == fa ) continue;else dfs( v, u ), g[u] &= g[v];} }void solve( int u, int fa ) { //f(u,i,j):subtree_u has i=0/1/2 endpoints of the path. at the beginning u is j=0/1 //the minicost to make nodes of subtree_u all be 1(lights all open)f[u][0][0] = 1;for( int i = 0;i < G[u].size();i ++ ) {int v = G[u][i];if( v == fa || g[v] ) continue;else solve( v, u );//subtree_v are all lighted just continueint ss = s[v] - '0';for( int j = 0;j <= 2;j ++ )for( int k = 0;k <= 1;k ++ ) {/*endpoints both belong to u_subtreev_subtree all rightu_subtree all right(except u itself)one endpoint -> u(wrong->right)cost 1*/t[j][k] = f[u][j][k ^ 1] + f[v][0][ss] + 1;/*endpoints both belong to u_subtreev_subtree all right(except v itself)u_subtree all rightone endpoint -> u(right->wrong) -> v(wrong->right) -> u(wrong->right)cost 3*/t[j][k] = min( t[j][k], f[u][j][k] + f[v][0][ss ^ 1] + 3 );if( j > 0 ) {/*one endpoint belongs to v_subtreethe other belongs to u_subtree or other subtrees(don't include u_subtree)v_subtree and u_subtree all rightjust f(u) plus f(v)*/t[j][k] = min( t[j][k], f[u][j - 1][k] + f[v][1][ss] );/*one endpoint belongs to v_subtreethe other belongs to u_subtree or other subtrees(don't include u_subtree)v_subtree and u_subtree all right(except u and v)we need to touch u and vcost another 2*/t[j][k] = min( t[j][k], f[u][j - 1][k ^ 1] + f[v][1][ss ^ 1] + 2 );}if( j > 1 ) {/*endpoints both belong to v_subtreev_subtree all right(except v itself)u_subtree all rightwe need tp touch vone endpoint -> v(wrong->right)cost 1*/t[j][k] = min( t[j][k], f[u][j - 2][k] + f[v][2][ss ^ 1] + 1 );/*endpoints both belong to v_subtreev_subtree all rightu_subtree all right(except u itself)one endpoint -> v(right->wrong) -> u(wrong->right) -> v(wrong->right)cost 3*/t[j][k] = min( t[j][k], f[u][j - 2][k ^ 1] + f[v][2][ss] + 3 );}}for( int j = 0;j <= 2;j ++ )for( int k = 0;k <= 1;k ++ )f[u][j][k] = t[j][k];}for( int j = 1;j <= 2;j ++ )for( int k = 0;k <= 1;k ++ )f[u][j][k] = min( f[u][j][k], f[u][j - 1][k] );//comparison choose a smaller one }int main() {scanf( "%d %s", &n, s + 1 );for( int i = 1, u, v;i < n;i ++ ) {scanf( "%d %d", &u, &v );G[u].push_back( v );G[v].push_back( u ); }int rt = -1;for( int i = 1;i <= n;i ++ )if( s[i] == '0' ) {rt = i;break;} else;if( ~ rt ) {dfs( rt, 0 );printf( "NONO\n" );memset( f, 0x3f, sizeof( f ) );solve( rt, 0 );printf( "%d\n", f[rt][2][0] );}else printf( "0\n" );return 0; }總結
以上是生活随笔為你收集整理的[2021-06-19] 提高组新手副本Ⅱ(联网,欧几里得,分解树,开关灯)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [CF/AT/Luogu]各大网站网赛
- 下一篇: CF1131 G. Most Dange