题解:A,C,D,G,I,J
A题:Villages: Landlines
题目大意:
转化题意后,便是求给定多个(a,b)形式(包括a,b)的区间,求(min,max)区间里没有被覆盖的区域的长度和。
解题思路:
难度不大,就是需要把题意读懂,能够转化题意。
首先将所有区间合并为不重叠的大区间,具体操作就是按a进行排序,将当前最大right > a的所有小区间与前面的区间合并,当前最大right<a,那这个(a,b)区间直接添加进去。
随后遍历所有大区间,计算所有大区间的长度,用(min,max)区间长度减去即可得到ans,输出即可
AC代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define int long long
const int N = 200010;
vector<vector<int>> merge(vector<vector<int>> &intervals)
{
if (intervals.size() == 0)
{
return {};
}
sort(intervals.begin(), intervals.end());
vector<vector<int>> merged;
for (int i = 0; i < intervals.size(); ++i)
{
int L = intervals[i][0], R = intervals[i][1];
if (!merged.size() || merged.back()[1] < L)
{
merged.push_back({L, R});
}
else
{
merged.back()[1] = max(merged.back()[1], R);
}
}
return merged;
}
signed main()
{
int n;
cin >> n;
vector<vector<int>> a;
int minx = INF, maxx = -INF;
for (int i = 0; i < n; i++)
{
int x, r;
cin >> x >> r;
minx = min(x - r, minx);
maxx = max(x + r, maxx);
a.push_back((vector<int>){x - r, x + r});
}
int ans = maxx - minx;
vector<vector<int>> merged_a = merge(a);
for (vector<int> p : merged_a)
{
ans -= p[1] - p[0];
}
cout << ans << endl;
}
C题:Grab the Seat!
题目大意:
就是在一象限,有一块n*m的区域是教室,y轴上(0,m)区域是黑板,有k个人已经坐在教室了,此外有q次更改座位,更改座位后求没有任何视野阻挡的作为还有多少个
解题思路:
可以利用ans数组存放每行,也就是1-m行每行还剩下多少个座位没有被阻挡,方便起见我们将y轴上移一位,也就是对题所给所有y有y新= y - 1,我们知道,被阻挡的区域便是(0,0)
点和(0,m)
点与所有有人的座位所形成的的射线包括的扇形类型区域。
如这后面的所有区域便是被阻挡的区域
具体来说,我们需要两次遍历。
一次从0-m-1遍历,遍历斜率为正数的那条线,记录在第i次及之前所遇到的最大斜率那条线,因为与y=i所形成的射线只会影响y=i+a(a=0,1……)之后的线,特别的,与y=0所形成的射线只会影响y=0这一行。
另一次类似,可通过镜像反转,复用代码
AC代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10, INF = 1e9;
int n, m, k, q, a[N], b[N];
int p[N], ans[N];
signed main()
{
cin >> n >> m >> k >> q;
for (int i = 1; i <= k; i++)
cin >> a[i] >> b[i]; // a表示x坐标,b表示y坐标
int x, v;
while (q--) {
cin >> x;
cin >> a[x] >> b[x];
// p[i] 表示第i行坐在最前面的人的x轴坐标 初始相当于每个人都坐在n以外的点,不会对答案产生影响
for (int i = 0; i < m; i++)
p[i] = n + 1;
for (int i = 1; i <= k; i++) // 遍历所有坐人的坐标,记录最左边的列
p[b[i] - 1] = min(p[b[i] - 1], a[i]); // 所有y轴坐标 - 1,目的是计算斜率只需要和0,0计算即可,非常方便
for (int i = 0; i < m; i++)
ans[i] = p[i] - 1; // 记录答案,记录每行最前面的没被挡住的点,是最靠前的人的坐标-1,因为坐人的地方也相当于被挡住了
int x = n + 1, y = 0; // x,y表示前i行最能挡的人的坐标(斜率最大的坐标)
for (int i = 0; i < m; i++) { // 遍历每一行,也就是遍历所有y坐标
int x1 = p[i], y1 = i; // 当前行的最靠前的人为位置 x1,y1
if (y1 * x >= y * x1)
x = x1, y = y1; // 判断斜率是否更大,更新x,y
int temp; // 防止除0
if (y != 0)
temp = ceil(x * i * 1.0 / y) - 1; // -1是为了把人坐的地方给去掉,计算当前斜率最大的射线与当前行的交点
else
temp = n;
ans[i] = min(ans[i], temp); // 更新答案
}
reverse(p, p + m);
x = n + 1, y = 0; // 翻转再更新
for (int i = 0; i < m; i++) {
int x1 = p[i], y1 = i;
if (y1 * x >= y * x1)
x = x1, y = y1;
int temp;
if (y != 0)
temp = ceil(x * i * 1.0 / y) - 1;
else
temp = n;
ans[m - i - 1] = min(ans[m - i - 1], temp); // 更新反转的答案,注意边界
}
int sum = 0;
for (int i = 0; i < m; i++)
sum += ans[i];
cout << sum << endl; // 累计答案即可
}
return 0;
}
D题:Mocha and Railgun
题目大意:
已知一个圆心在原点的圆,半径为r。给一个Q点(x,y),求解过Q点的长度为2d的线段投影到圆上所形成的弧长的最大值。
解题思路:
这道题,就一个字,猜
,我无法证明,我也没时间证明,猜最优的线段过圆心和Q点即可,然后直接利用几何知识求解即可。
AC代码
import math
t = int(input())
for _ in range(t):
r = int(input())
x, y, d = map(int, input().split())
edd = x * x + y * y
dd = math.sqrt(edd)
x1 = dd - d
x2 = dd + d
y1 = math.sqrt(r * r - x1 * x1)
y2 = math.sqrt(r * r - x2 * x2)
xyd = math.sqrt((x1 - x2)**2 + (y1 - y2)**2)
sina = xyd * 0.5 / r
ans = math.asin(sina)
print(ans * r * 2)
G题:Lexicographical Maximum
题目大意:
很简单,就是给定一个数n,求解1-n中字典序最大的数。
解题思路:
n最大为101000000,因此这个数量级排序应该不太行。只需要判断前len-1个数是否为’9’,如果除了最后一个数都是’9’或者len=1直接输出n以外,其他全部输出len-1个’9’即可。
AC代码
#include <bits/stdc++.h>
using namespace std;
signed main()
{
string n;
cin >> n;
bool flag = true;
for (int i = 0; i < n.length()-1; i++) {
if (n[i] != '9') {
flag = false;
break;
}
}
if (n.length() == 1 || flag)
cout << n << endl;
else {
for (int i = 0; i < n.length() - 1; i++)
cout << '9';
}
return 0;
}
I题:Chiitoitsu
题目大意:
有34中牌,每种牌都有4张。起手有13张牌,其中每种类型的牌最多有2张,求起手牌到胡牌期望次数(胡牌被定义为有7个不同的对子牌)。
解题思路:
起手牌不会有超过两张相同的牌,那么在这种情况下,我们将手上的牌分为pair和single两个种类,当抽到一张pair中的牌时,由于这个pair已经存在,所以抽到的牌实际上没有用,直接弃掉;当抽到single中的牌时,两个相同的single就形成了一个pair,这就离目标7个pair更近了,所以这张牌需要保留;当抽到的牌啥也不是时,实际上弃掉此牌一定是一种最优策略(本质上相同牌数的single牌都是等价的,因为以它再来凑的话就相当于把之前某个single丢弃凑另一个single)。这里就需要观察到,在最优策略下,single牌在牌堆中永远有3张,因为我们不会用pair牌换掉single牌,而一旦抽到single就会形成pair。
那么就可以考虑使用动态规划。
定义一个数组为 d p [ i ] [ j ] dp[i][j] dp[i][j]为抽取第 i i i 次牌之后剩余 j j j 张单牌的概率。
d p [ i [ j ] = d p [ i − 1 ] [ j ] ∗ 124 − i − 3 j 124 − i + d p [ i − 1 ] [ j + 2 ] ∗ 3 ∗ ( j + 2 ) 124 − i dp[i[j]=dp[i−1][j]∗\frac{124−i−3j}{124−i}+dp[i−1][j+2]∗\frac{3∗(j+2)}{124−i} dp[i[j]=dp[i−1][j]∗124−i124−i−3j+dp[i−1][j+2]∗124−i3∗(j+2)
前一个代表的是第 i i i 次抽取的牌不是手中单牌(其中一种)一样类型的牌,后一个代表的是抽到的是和手中单牌(其中一种)一样类型的牌。
初始化 d p [ 0 ] [ c n t ] = 1 dp[0][cnt] = 1 dp[0][cnt]=1,代表不抽取牌时剩余任何张单牌的概率都是1。
我们易知 i < = 121 i <= 121 i<=121,因为抽取121次后必定有每种类型的牌都抽取了至少2个。 j < = 13 j <= 13 j<=13,因为初始手牌里面的单牌数量可能有1,3,5,7,9,11,13这几种。
注意因为要取余,所以在运算过程中要使用快速幂一直保持
AC代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
map<int, int> mp;
map<string, int> book;
const ll mod = 1e9 + 7;
ll dp[150][16];
ll res[15];
ll ksm(ll a, ll b)
{
ll res = 1;
while (b) {
if (b & 1)
res = res * a % mod;
b >>= 1;
a = a * a % mod;
}
return res;
}
void init()
{
for (int k = 1; k <= 13; k += 2) {
memset(dp, 0, sizeof dp);
dp[0][k] = 1;
for (int i = 1; i <= 121; i++) {
for (int j = 1; j <= 13; j += 2) {
dp[i][j] = (dp[i - 1][j] * (124 - i - 3 * j) % mod + dp[i - 1][j + 2] * (3 * j + 6) % mod) % mod * ksm(124 - i, mod - 2) % mod;
}
}
ll ans = 0;
for (int i = 1; i <= 121; i++) {
ans = (ans + i * dp[i - 1][1] * 3 % mod * ksm(124 - i, mod - 2) % mod) % mod;
}
res[k] = ans;
}
}
void solve(int id)
{
string s;
cin >> s;
int tot = 0;
book.clear();
for (int i = 1; i <= 34; i++)
mp[i] = 0;
string tmp;
for (int i = 0; i < s.size(); i++) {
tmp += s[i];
if (i & 1) {
int k;
if (!book[tmp])
book[tmp] = ++tot;
k = book[tmp];
mp[k]++;
tmp = "";
}
}
int cnt = 0;
for (int i = 1; i <= 34; i++)
if (mp[i] == 1)
cnt++;
cout << "Case #" << id << ": " << res[cnt] << '\n';
}
int main()
{
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
init();
int t;
cin >> t;
for (int i = 1; i <= t; i++)
solve(i);
return 0;
}
J题:Serval and Essay
题目大意:
就是给了一个有向无环图,包括 n n n个点,其中只有选择一个起点,求解所能到达的所有点的最大值。
具体的,如果要达到一个点 u u u,那么需要到达指向 u u u的所有点 v i v_i vi。
解题思路:
首先这个题一转换就有点是拓扑排序的感觉,但是当时跟榜,没有写到。题目的题意也有点难以理解,不太会,贴个并查集的解法吧。
#include <bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int fa[N], Size[N]; //维护并查集
int n, m;
int k[N];
set<int> from[N]; // from[i]记录推出i点需要的点
set<int> to[N]; // to[i]记录i点可以推出的点
int find(int u)
{
//并查集寻根函数
if (u != fa[u])
fa[u] = find(fa[u]);
return fa[u];
}
void Merge(int a, int b)
{
// b依赖a
int ha = find(a), hb = find(b);
if (ha == hb)
return;
if (to[ha].size() < to[hb].size())
swap(ha, hb); //将较少的集合放入大的
fa[hb] = ha;
Size[ha] += Size[hb]; //由于此时只要确定了ha就能确定hb,所以所有需要依赖hb的点都可以改为需要ha的点,就是将两者数量合并
vector<PII> tmp; //记录所有可以入度为1的点
for (auto u : to[hb]) { //遍历所有依赖hb的点,现在这些点都是依赖a的(所有依赖b的点在之前的hb和b的合并过程中已经转为依赖hb,所以这里遍历的是from[hb]
from[u].erase(hb); //将点u依赖hb的关系消除
from[u].insert(ha); //加上点u依赖ha(可能这个关系本来就存在,set会自动去重)
to[ha].insert(u); //记录ha能推出u
if (from[u].size() == 1) { //记录入度为1的点
tmp.push_back({ ha, u });
}
}
for (int i = 0; i < tmp.size(); i++) {
Merge(tmp[i].x, tmp[i].y);
}
}
int main()
{
int T;
cin >> T;
for (int iu = 1; iu <= T; iu++) {
cin >> n;
for (int i = 1; i <= n; i++) { //清空状态记录
fa[i] = i;
Size[i] = 1;
to[i].clear();
from[i].clear();
}
for (int i = 1; i <= n; i++) {
int k;
cin >> k;
for (int j = 0; j < k; j++) {
int a;
cin >> a;
from[i].insert(a); //记录a对i的依赖关系
to[a].insert(i); //记录i对a的被依赖关系
}
}
for (int i = 1; i <= n; i++) {
if (from[i].size() == 1) {
Merge(i, *from[i].begin());
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = max(ans, Size[i]);
}
cout << "Case #" << iu << ": " << ans << endl;
}
return 0;
}
记录a对i的依赖关系
to[a].insert(i); //记录i对a的被依赖关系
}
}
for (int i = 1; i <= n; i++) {
if (from[i].size() == 1) {
Merge(i, *from[i].begin());
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = max(ans, Size[i]);
}
cout << "Case #" << iu << ": " << ans << endl;
}
return 0;
}