2021年度训练联盟热身训练赛第一场
A-Weird Flecks, But OK
题意
三维平面中一个巨大的正方体上有 n n n 个瑕疵点,你有一个钻头,可以垂直于正方体任意一个面钻孔。问钻头的直径至少要多大才能将全部瑕疵点全部消除(变成大孔)?
思路
最小圆覆盖。分别以 x , y , z x, y, z x,y,z 轴为视角,获得这些瑕疵点在 x y , y z , z x xy, yz, zx xy,yz,zx 三个平面的投影。钻头只要能够从某一个平面钻下去并覆盖所有点,就满足题意,那么也就是求最小的能够覆盖投影中所有点的圆的直径。又因为可以从任意一个面钻下去,所以三个面的最小圆覆盖取最小。
(室友概括:最小圆覆盖,三次(bu
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define pb push_back
#define eps 1e-8
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 3e5 + 19;
const ll mod = 1e9 + 7;
const int maxn = 100000+5;
int sgn(double x)
{
if (fabs(x)<eps)
return 0;
else
return x<0? -1:1;
}
struct Point
{
double x,y;
};
double Distance(Point A, Point B)
{
return hypot(A.x-B.x,A.y-B.y);
}
//求三角形abc的外接圆圆心
Point circle_center(const Point a, const Point b, const Point c)
{
Point center;
double a1 = b.x-a.x,b1=b.y-a.y,c1=(a1*a1+b1*b1)/2;
double a2 = c.x-a.x,b2=c.y-a.y,c2=(a2*a2+b2*b2)/2;
double d=a1*b2-a2*b1;
center.x=a.x+(c1*b2-c2*b1)/d;
center.y=a.y+(a1*c2-a2*c1)/d;
return center;
}
//求最小圆覆盖,返回圆心c和半径r:
void min_cover_circle(Point *z, int n,Point &c, double &r)
{
random_shuffle(z,z+n); //打乱所有点
c = z[0];
r = 0; //第一个点
for (int i = 1; i < n; ++i) //剩下所有点
{
if (sgn(Distance(z[i],c)-r)>0) //pi在圆外部
{
c=z[i];
r=0; //将圆心设为pi半径为0
for (int j = 0; j < i; ++j) //重新检查前面的点
{
if (sgn(Distance(z[j],c)-r)>0)//两点定圆
{
c.x=(z[i].x+z[j].x)/2;
c.y=(z[i].y+z[j].y)/2;
r=Distance(z[j],c);
for (int k = 0; k < j; ++k)
{
if (sgn(Distance(z[k],c)-r)>0)
{
c=circle_center(z[i],z[j],z[k]);
r=Distance(z[i],c);
}
}
}
}
}
}
}
Point z[maxn];
Point x[maxn];
Point y[maxn];
int main()
{
int n;
Point x_c, y_c, z_c;
double z_r, y_r, x_r;
scanf("%d", &n);
for (int i = 0; i < n; ++i)
{
double a, b, c;
scanf("%lf%lf%lf", &a, &b, &c);
z[i].x = a;
z[i].y = b;
x[i].x = b;
x[i].y = c;
y[i].x = a;
y[i].y = c;
}
min_cover_circle(z,n,z_c,z_r);
min_cover_circle(x,n,x_c,x_r);
min_cover_circle(y,n,y_c,y_r);
printf("%.10f\n", 2 * min(x_r, min(y_r, z_r)));
return 0;
}
*B-Code Names
题意
思路
代码
C-New Maths
题意
定义 ⊕ ⊕ ⊕ 为不进位的加法,并定义运算 ⊗ ⊗ ⊗ ,设有数字A( a m a m − 1 … a 0 a_m a_{m - 1} \dots a_0 amam−1…a0),数字B( b n b n − 1 … b 0 b_n b_{n - 1} \dots b_0 bnbn−1…b0),则 A ⊗ B A ⊗ B A⊗B 的值C的每一位数字为: c k = ( a 0 b k ⊕ a 1 b k − 1 ⊕ ⋯ ⊕ a k − 1 b 1 ⊕ a k b 0 ) c_k = (a_0 b_k ⊕ a_1 b_{k -1} ⊕ \dots ⊕ a_{k - 1} b_1 ⊕ a_k b_0 ) ck=(a0bk⊕a1bk−1⊕⋯⊕ak−1b1⊕akb0) 。
现给定数字 n n n ,要求你求出最小的 a a a ,满足 a ⊗ a = n a ⊗ a = n a⊗a=n 。
思路
可以看出,若数字是 a m a m − 1 … a 0 a_m a_{m - 1} \dots a_0 amam−1…a0 ,则答案的第 k k k 位就有 c k = ∑ i = 0 k a m − i a i % 10 c_k = \sum_{i = 0}^k a_{m - i} a_i \ \% \ 10 ck=∑i=0kam−iai % 10 。由此,我们从 c 0 c_0 c0 开始逐位判断是否有 a m a m − 1 … a 0 a_m a_{m - 1} \dots a_0 amam−1…a0 满足每一位都能满足上式,若有,取其中最小的为答案。
WA了很多次,最后发现是INF的值不够大导致的,然后又因为判断条件忘记改且没有先编译就交了还T了几次,实在太不细心了。幸亏现场不是我做的
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 50 + 19;
const ll mod = 1e9 + 7;
const double eps = 1e-12;
ll n, ans = INF;
int a[N];
int c[N];
string s;
inline ll num(int k)
{
ll ans = 0;
for(int i = 0; i <= k; i++)
{
ans += a[i] * a[k - i];
}
return ans % 10;
}
bool dfs(int k)
{
if(k == n)
{
ll num = 0;
for(int i = 0; i < (n + 1) / 2; i++)
{
num = num * 10 + a[i];
}
ans = min(ans, num);
return 1;
}
bool f = 0;
for(int i = 0; i < 10; i++)
{
a[k] = i;
if(num(k) == c[k])
if(dfs(k + 1))
f = 1;
}
return f;
}
int main()
{
cin >> s;
n = s.length();
for(int i = 0; i < n; i++)
c[i] = s[i] - '0';
if(dfs(0))
cout << ans;
else
cout << -1;
return 0;
}
D-Some Sum
题意
1到100取 n n n 个数,和是奇数,偶数,还是都行?
思路
n n n 为4的倍数,偶数; n n n 为2的倍数不是4的倍数,奇数; n n n 为奇数,都可以。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 3e5 + 19;
const ll MOD = 1e18 + 7;
int main()
{
int n;
cin >> n;
if(n % 2)
cout << "Either" << endl;
else
if(n % 4)
cout << "Odd" << endl;
else
cout << "Even" << endl;
return 0;
E-Early Orders
题意
n n n 个大小在 [ 1 , k ] [1, k] [1,k] 之间的整数,保证 [ 1 , k ] [1, k] [1,k] 中每个整数出现至少一次,现要你选择一个子序列,满足子序列是 k k k 的一个排列,且字典序最小。
思路
单调栈模拟,这一类题都可以用单调栈做,比赛的时候搜到了一道很像的题目:单调栈–最小字典序
赛后自己重新理解并写了一遍。思路如下:
如果当前栈为空或者栈顶元素比要放入的元素小,直接放入;
否则(即栈顶元素比要放入的元素大),一直弹出栈顶元素,直至栈顶元素比要放入的元素小或者当前栈顶元素在要放入元素的后面不再出现(即如果删掉这个元素就没有能再放进去的元素了,所以不能删)。
最后将栈倒着输出即可。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 3e5 + 19;
const ll mod = 1e9 + 7;
int a[N];
int R[N];
bool vis[N];
stack<int> st;
stack<int> ans;
int main()
{
int n, k;
cin >> n >> k;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
R[a[i]] = i;
}
for(int i = 1; i <= n; i++)
{
if(vis[a[i]])
continue;
if(st.empty())
{
st.push(a[i]);
vis[a[i]] = 1;
continue;
}
if(a[i] > st.top())
{
st.push(a[i]);
vis[a[i]] = 1;
continue;
}
while(st.size() && a[i] < st.top())
{
if(R[st.top()] < i)
break;
vis[st.top()] = 0;
st.pop();
}
st.push(a[i]);
vis[a[i]] = 1;
}
while(!st.empty())
{
ans.push(st.top());
st.pop();
}
while(!ans.empty())
{
cout << ans.top();
ans.pop();
if(ans.size())
cout << ' ';
}
cout << endl;
return 0;
}
F-Pulling Their Weight
题意
n n n 个动物,第 i i i 个动物重 a i a_i ai ,要你给出所有满足题意的数字 t t t 中最小的那个,使得质量小于 t t t 的动物质量之和和大于 t t t 的动物质量之和相等。等于 t t t 的动物不用考虑(意译)。
思路
排序后两个指针一个从头往尾走,一个从尾往头走,哪个部分的质量和较小哪个指针就走,直到该部分质量大于另一部分,再换另一部分走,重复该过程直至相遇。题目保证有答案所以可以忽略相遇不相等的情况。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 3e5 + 19;
const ll mod = 1e9 + 7;
int a[N];
int pre[N];
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++)
{
cin >> a[i];
}
sort(a, a + n);
pre[0] = a[0];
int pos1 = 0, pos2 = n - 1;
int sum1 = a[0], sum2 = a[n - 1];
bool flag = 0;
while(pos1 != pos2)
{
if(sum1 == sum2)
{
if(pos1 == pos2 - 1)
{
break;
}
else if(pos1 == pos2 - 2)
{
flag = 1;
break;
}
sum1 += a[++pos1];
sum2 += a[--pos2];
}
else if(sum1 < sum2)
{
sum1 += a[++pos1];
}
else
{
sum2 += a[--pos2];
}
}
if(flag)
cout << a[pos1 + 1] << endl;
else
cout << min(a[pos1] + 1, a[pos1 + 1]) << endl;
return 0;
}
G-Birthday Paradox
题意
m m m 个人,分成 n n n 组,第 i i i 组的人数为 a i a_i ai ,组内所有人生日相同,不同组的人生日不同的概率是多少?输出 l g ( a n s ) lg(ans) lg(ans)。
思路
用分子(所有满足题意的情况个数)/ 分母(所有可能情况个数) 的方法解题。
- 分母:所有情况的总和, 36 5 m 365^m 365m
- 分子:
- 365天选择 n n n 个生日: c 365 n c_{365}^{n} c365n
- n n n 个组的 n n n 个生日全排列,但是要舍去几个小组人数相同的情况,假设有 k k k 个组出现人数重复,其中每个组重复的队伍数为 d 1 , d 2 , … , d k d_1, d_2, \dots, d_k d1,d2,…,dk ,则情况总数为: n ! ∑ i = 1 k ( d i ! ) \frac{n!}{\sum_{i = 1}^{k} (d_i!)} ∑i=1k(di!)n!
- m m m 个人生日全排列,但是要舍去在同一组的情况,因此情况总数为: m ! ∑ i = 1 n ( a i ! ) \frac{m!}{\sum_{i = 1}^{n} (a_i!)} ∑i=1n(ai!)m!
化简上述各式,得:
l
g
(
s
o
n
)
=
∑
i
=
356
−
n
+
1
365
l
g
(
i
)
+
∑
i
=
1
m
l
g
(
i
)
−
∑
i
=
1
k
(
∑
j
=
1
d
i
l
g
(
j
)
)
+
∑
i
=
1
n
(
∑
j
=
1
a
i
l
g
(
j
)
)
lg(son) = \sum_{i = 356 - n + 1}^{365} lg(i) + \sum_{i = 1}^{m} lg(i) - \sum_{i = 1}^{k}(\sum_{j = 1}^{d_i}lg(j)) + \sum_{i = 1}^{n}(\sum_{j = 1}^{a_i}lg(j))
lg(son)=i=356−n+1∑365lg(i)+i=1∑mlg(i)−i=1∑k(j=1∑dilg(j))+i=1∑n(j=1∑ailg(j))
l g ( m o m ) = m × l g ( 365 ) lg(mom) = m \times lg(365) lg(mom)=m×lg(365)
a n s = l g ( s o n ) − l g ( m o m ) ans = lg(son) - lg(mom) ans=lg(son)−lg(mom)
到这里就很容易实现了。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 500 + 19;
const ll mod = 1e9 + 7;
const double eps = 1e-12;
int a[N];
map<int, int> d;
int main()
{
int n, m = 0;
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
m += a[i];
d[a[i]]++;
}
double ans = 0;
for(int i = 365 - n + 1; i <= 365; i++)
ans += log((double)i) / log(10.0);
for(int i = 1; i <= m; i++)
ans += log((double)i) / log(10.0);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= a[i]; j++)
ans -= log((double)j) / log(10.0);
for(auto i : d)
for(int j = 1; j <= i.second; j++)
ans -= log((double)j) / log(10.0);
ans -= (double)m * log(365.0) / log(10.0);
printf("%.10f\n", ans);
return 0;
}
H-On Average They’re Purple
题意
n个点m条边的无向图,边的颜色有蓝有红,一个人要从图上的点1走到点n,并且走过的路尽可能少的发生颜色转变。现在要你来对图进行染色,让这个人必然经过的颜色转变最多。输出转变次数。
思路
最优染色方式和点1相连的所有路涂蓝,之后的所有点拓展出去的路涂红,即一层一层的颜色都涂不同,让这个人在选路的时候每次都必然面对颜色转变。因此这个人只能选择最短路,所以这题就是求最短路。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 3e5 + 19;
const ll MOD = 1e18 + 7;
struct node
{
int v, w;
node(){}
node(int a, int b){v = a, w = b;}
bool operator < (const node& obj) const
{
if(w == obj.w)
return v < obj.v;
return w > obj.w;
}
};
vector<node> es[N];
int dis[N];
int n, m;
void addEdge(int u, int v, int w)
{
es[u].pb(node(v, w));
es[v].pb(node(u, w));
}
void dij(int s)
{
fill(dis, dis + n + 1, INF);
priority_queue<node> que;
dis[s] = 0;
que.push(node(s, 0));
while(!que.empty())
{
node now = que.top();
que.pop();
for(int i = 0; i < es[now.v].size(); i++)
{
node to = es[now.v][i];
if(dis[to.v] > now.w + to.w)
{
dis[to.v] = now.w + to.w;
que.push(node(to.v, dis[to.v]));
}
}
}
}
int main()
{
cin >> n >> m;
for(int i = 0; i < m; i++)
{
int u, v;
cin >> u >> v;
addEdge(u, v, 1);
}
dij(1);
cout << dis[n] - 1 << endl;
return 0;
}
*I-Full Depth Morning Show
题意
思路
代码
J-This Ain’t Your Grandpa’s Checkerboard
题意
判断行列中黑色和白色块个数是否相同,或者行列中是否有三个连续的黑色或者白色块。若满足输出0,不满足输出1。(意译)
思路
数据范围小,直接暴力。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 3e5 + 19;
const ll MOD = 1e18 + 7;
char a[50][50];
int n;
bool ok()
{
if(n % 2)
return 0;
for(int i = 0; i < n; i++)
{
int cnt1 = 0;
int cnt2 = 0;
for(int j = 0; j < n; j++)
{
cnt1 += (int)a[i][j];
cnt2 += (int)a[j][i];
if(j >= 3 && a[i][j] == a[i][j - 1] && a[i][j] == a[i][j - 2])
return 0;
if(j >= 3 && a[j][i] == a[j - 1][i] && a[j][i] == a[j - 2][i])
return 0;
}
if(cnt1 != (int)('B' + 'W') * (n / 2))
return 0;
if(cnt2 != (int)('B' + 'W') * (n / 2))
return 0;
}
return 1;
}
int main()
{
cin >> n;
for(int i = 0; i < n; i++)
{
cin >> a[i];
}
if(ok())
cout << 1 << endl;
else
cout << 0 << endl;
return 0;
}