文章目录
- 考试心路历程
- 联网
- 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.0000000
solution
二分距离,暴力检查,用并查集判断是否联通
最后注意并查集不要打错了,不然骨灰挥洒
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 5
solution
最后的答案长相一定是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
26
solution
法一:反其道而行
相当于是加边,构成一棵树,加边操作为两边连通块各自的最大值之和,求最小花费
显然,肯定是边花费越小越先加
边按照连接的两个连通块的最大值从小到大排序(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≤nti−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
8
solution
题解
大部分注释在代码里
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;
}