The 2022 ICPC Asia Tehran Regional Contest 全题解
The 2022 ICPC Asia Tehran Regional Contest Solution
全网首发神秘德黑兰区域赛题解,含全代码。
A.Final Price
给物品的初始价格和后面若干天的价格变化值,求最终价格。
纯签到,直接求和即可
int n;
int main() {
ll x = 0;
scanf("%d", &n);
while (n --) {
ll y;
scanf("%lld", &y);
x += y;
}
printf("%lld", x);
return 0;
}
B.Flower Festival
给 \(n\) 辆汽车,每辆汽车有初始位置 \(x_i\) 和向正方向的速度 \(v_i\),问谁能先到 \(f\) 位置。
直接时间等于路程除以速度,然后可以避免浮点数运算即可。
const int N = 100 + 5;
int n, f;
int x[N], v[N];
int main() {
scanf("%d%d", &n, &f);
for (int i = 1; i <= n; i ++)
scanf("%d%d", &x[i], &v[i]);
int ans = 1;
for (int i = 2; i <= n; i ++) {
if ((f - x[i]) * v[ans] < (f - x[ans]) * v[i])
ans = i;
}
printf("%d", ans);
return 0;
}
E.Parking Party
\(n\times m\) 网格,有障碍
o与空地.。每行/列两端可进车,车沿直线前进,遇障碍/已停/出口则停于最后一个空位。每格至多一车。求最多停车数。
可以发现已停的车对答案不会有影响,直接模拟每行每列两端进车即可,具体实现见代码。
typedef long long ll;
const int N = 1000 + 5;
int n, m;
char s[N][N];
bool vis[N][N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++)
scanf("%s", s[i] + 1);
for (int i = 1; i <= n; i ++) {
bool f = 1;
for (int j = 1; j <= m; j ++) {
if (s[i][j] == 'o')
f = 0;
else
vis[i][j] |= f;
}
f = 1;
for (int j = m; j >= 1; j --) {
if (s[i][j] == 'o')
f = 0;
else
vis[i][j] |= f;
}
}
for (int i = 1; i <= m; i ++) {
bool f = 1;
for (int j = 1; j <= n; j ++) {
if (s[j][i] == 'o')
f = 0;
else
vis[j][i] |= f;
}
f = 1;
for (int j = n; j >= 1; j --) {
if (s[j][i] == 'o')
f = 0;
else
vis[j][i] |= f;
}
}
int ans = 0;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
ans += vis[i][j];
printf("%d", ans);
return 0;
}
J.Magic with Cards
\(2n\) 张牌,位置 \(1\) 到 \(2n\)。操作:Riffle(将前 \(n\) 张与后 \(n\) 张交替:\(c_1,c_{n+1},c_2,c_{n+2},\dots\));Scuffle(相邻对交换:\(c_2,c_1,c_4,c_3,\dots\))。给定 \(n,i,j\),求将第 \(i\) 张牌移到第 \(j\) 位的最小步数,或 \(-1\)。
广度优先搜索即可,只需求第 \(i\) 张牌到位置 \(p\) 的最小操作次数 \(dis_p\) 即可,然后对于从 \(p\) 开始能到的下一个位置 \(p'\),\(dis_{p'}\leftarrow \min \{dis_{p'},dis_p+1 \}\)。
const int N = 1e5 + 5;
int n, x, y;
std::queue<std::pair<int, int>> Q;
bool vis[2 * N];
int main() {
scanf("%d%d%d", &n, &x, &y);
Q.push({x, 0});
vis[x] = 1;
while (!Q.empty()) {
auto [t, s] = Q.front(); Q.pop();
if (t == y) {
printf("%d", s);
return 0;
}
int nx = 0;
if (t <= n)
nx = 2 * t - 1;
else
nx = 2 * (t - n);
if (!vis[nx]) {
Q.push({nx, s + 1});
vis[nx] = 1;
}
nx = ((t - 1) ^ 1) + 1;
if (!vis[nx]) {
Q.push({nx, s + 1});
vis[nx] = 1;
}
}
puts("-1");
return 0;
}
K.Iranian Hazfi Cup
\(2^k\) 支球队进行单败淘汰赛,已知去年所有比赛结果(含点球),可唯一确定对阵树。对 \(n\) 个查询(两队),输出它们可能相遇的最早轮次(\(1\) 为第一轮,\(k\) 为决赛)。
一个观察是我们不关心比分,只关心一个队伍与其他队伍交手了多少次(反映了它第几轮被淘汰)以及交手的对手有哪些。这个东西用简单的字符串模拟即可求出来。
我们的目标是找到他们的初始位置,不妨假设位置编号 \(0\sim 2^k-1\),编号第 \(i\) 位为 \(0\) 表示在对阵树中第 \(i\) 轮位于父亲中左子树(且认为它这一轮赢),为 \(1\) 表示位于父亲右子树中(且认为这一轮输)。
如果两个队伍编号的第 \(i\) 位以后完全相同,则说明他们在第 \(i\) 轮相遇。
现在考虑求这个东西,找到交手最多的队伍(两只任选一直,实际输赢不重要),认为它赢,则最高位为 \(0\),接下来它递归寻找交手次数更少的对手。对于与他交手过的与他交手次数一样的队伍,认为它输,最高位为 \(1\),递归寻找与这只队伍交手过的队伍。具体实现见代码。
贴的是队友 vp 时的代码。
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)
bool flag;
template <class T>
inline T read(const T sample) {
T x=0; bool f=0; char s;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
if(s=='(' || s==')') flag = true;
return x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <map>
# include <set>
# include <cmath>
# include <queue>
# include <vector>
# include <cstring>
# include <iostream>
# include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 2e5+5;
const int mod = 998244353;
const ll infty = 1e18;
int k, n, tot, co[MAXN];
map <string, int> mp;
vector <int> e[MAXN];
void dfs(int u, int dep, int o) {
if(dep==0) {
co[u] = o;
return;
}
dfs(u, dep-1, o<<1);
for(auto v: e[u]) {
if(e[v].size() == dep)
dfs(v, dep-1, o<<1|1);
}
}
int main() {
k=read(9), n=read(9);
for(int i=1; i<(1<<k); ++i) {
flag = false;
string x, y; cin>>x;
int a, b, c, d;
a=read(9), b=read(9);
if(flag) {
c=read(9), d=read(9);
cin >> y;
} else {
cin >> y;
}
if(mp.find(x) == mp.end()) mp[x] = ++tot;
if(mp.find(y) == mp.end()) mp[y] = ++tot;
e[mp[x]].push_back(mp[y]);
e[mp[y]].push_back(mp[x]);
}
int rt;
for(int i=1; i<=tot; ++i)
if(e[i].size() == k) rt=i;
dfs(rt, k, 1);
while(n --) {
string x, y; cin>>x>>y;
int u=mp[x], v=mp[y], ans=0;
u = co[u], v = co[v];
while(u^v) {
u>>=1, v>>=1;
++ ans;
}
print(ans, '\n');
}
return 0;
}
C.Simplification
给定 \(n\) 个点 \((t_i, p_i)\)(\(t_i\) 严格递增),定义分段线性函数 \(F\) 依次连接这些点。\(F\) 的“简化” \(F'\) 是由 \(F\) 的一个子序列(必须包含首尾点)定义的分段线性函数,且在每个选中的时间点上与原函数值相等。\(F'\) 的误差为 \(\max_{1\le k\le n} |F'(t_k)-F(t_k)|\)(即所有原时间点上的绝对差最大值)。给定误差上限 \(\delta\),求最小的简化大小(即选中的点数)。
\(2\leq n\leq 2000\)
考虑动态规划,令 \(dp_i\) 为只考虑前 \(i\) 个点(且第 \(i\) 个点必选)的最少选中点数,记 \(S(i)\) 为满足从 \(i\) 点左侧出发到该点线段之间的点到线段的误差均小于等于上限的点集,显然
问题就是求 \(S(i)\),刚才的定义有些拗口,写成形式化的语言,即
我们需要避免对每个 \(j\) 都求一下中间点到 \((t_i,p_i),(t_j,p_j)\) 线段的误差,我们发现一个中间点 \(k\) 到线段的误差可以转化为,\((t_i,p_i),(t_j,p_j)\) 这条线段在 \(t_k\) 处的值介于 \([p_k-\delta,p_k+\delta]\) 之间。进一步,可以转化为点 \((t_j,p_j)\) 在 \((t_i,p_i)\to(t_k,p_k-\delta)\) 和 \((t_i,p_i)\to (t_k,p_k+\delta)\) 这两条射线之间,也等价于 \((t_i,p_i)\to (t_j,p_j)\) 这条射线位于那两条射线之间。那么这个“射线之间”就可以用斜率描述,那么就是求
可以使用后缀最大值最小值维护 \(k>j\) 的部分判断 \(j\) 是否可以与 \(i\) 连线。具体实现见代码。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef array<ll,2>ttfa;
const int N=500005;
const ll INF=0x3f3f3f3f3f3f3f3f;
const double EPS=1e-12;
int n,dp[N];
ll r;
ttfa a[N];
double calc(ttfa x,ttfa y){
return 1.0*(x[1]-y[1])/(y[0]-x[0]);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%lld%lld",&a[i][0],&a[i][1]);
}
sort(a+1,a+1+n);
scanf("%lld",&r);
for(int i=1;i<=n;++i){
dp[i]=min(dp[i-1]+1,i);
double L=-1e18,R=1e18;
for(int j=i-1;j>=1;--j){
ttfa tmp=a[j];tmp[1]+=r;
R=min(R,calc(tmp,a[i]));
tmp=a[j];tmp[1]-=r;
L=max(L,calc(tmp,a[i]));
if(j-1>=1){
double val=calc(a[j-1],a[i]);
if(L-EPS<=val&&val<=R+EPS){
dp[i]=min(dp[i],dp[j-1]+1);
}
}
}
}
printf("%d\n",dp[n]);
return 0;
}
F.Ammunition Storage
\(n\times m\) 网格,高度互异(\(1\) 到 \(nm\))。求满足以下条件的子矩形个数:宽高均 \(\ge 2\),且四个角的高度均严格大于矩形内(含边界)所有其他单元格的高度。\(2\leq n,m\leq 750\)
队友想出来的,这里叙述一下。
从大到小枚举高度,每次只统计当前高度为矩阵四角上最小高度时的答案。设当前位置 \((x,y)\) 寻找向上下左右第一个大于这个高度的位置,分别为 \((u,y),(d,y),(x,l),(x,r)\),则答案只可能是以 \((u,l),(u,r),(d,l),(d,r)\) 为 \((x,y)\) 对角位置的矩阵(至多这四个)。以 \((u,l)\) 举例,如果 \((u,l)\) 位置高度也大于 \((x,y)\) 高度,且 \((u,l)\) 到 \((x,y)\) 的矩阵内除四角外其余元素均比 \((x,y)\) 小,则可以统计进入答案。
正确性比较显然,如果答案矩阵的 \((x,y)\) 对角位置在以 \((u,l),(d,r)\) 为对角线的矩阵的内部,则这个对角位置高度小于 \((x,y)\),不满足我们只统计当前 \((x,y)\) 位置高度为矩阵四角上最小高度的要求。如果在外部,则 \((u,y),(d,y),(x,l),(x,r)\) 这几个位置之一就会出现在矩阵内部,导致 \((x,y)\) 位置高度小于矩阵内部高度,与题目要求不满足。
对于每行,对于所有位置寻找向一个方向第一个大于该位置的元素是 \(O(m^2)\) 的,共 \(n\) 行,总复杂度为 \(O(nm^2)\),类似的对于列有复杂度 \(O(n^2m)\)。然后判断一个矩阵除四角外其余元素均比当前高度小可以使用二维树状数组求区间和:对于大于等于当前高度的位置,给它在数据结构里 \(+1\),求当前矩阵内元素和是否等于 \(4\) 即可。这部分不是复杂度瓶颈。故总时间复杂度为 \(O(nm^2+n^2m)\)。
#include <bits/stdc++.h>
typedef long long ll;
const int N = 750 + 5;
int n, m;
int a[N][N], b[N][N];
std::pair<int, int> pos[N * N];
inline int lowbit(int x){return x&-x;}
struct BIT_2di {
int c[N][N];
void update(int x,int y,int v){
for(int i=x;i<=n;i+=lowbit(i)){
for(int j=y;j<=m;j+=lowbit(j))
c[i][j]+=v;
}
}
int getsum(int x,int y,int v=0){
for(int i=x;i;i-=lowbit(i)){
for(int j=y;j;j-=lowbit(j))
v+=c[i][j];
}
return v;
}
} sij;
int sij_get(int lx, int ly, int rx, int ry) {
return sij.getsum(rx, ry) - sij.getsum(lx - 1, ry) - sij.getsum(rx, ly - 1) + sij.getsum(lx - 1, ly - 1);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
scanf("%d", &a[i][j]);
pos[a[i][j]] = std::make_pair(i, j);
}
int ans = 0;
for (int i = n * m; i >= 1; i --) {
auto [x, y] = pos[i];
b[x][y] = 1;
sij.update(x, y, 1);
// printf("%d %d:\n", x, y);
// for (int j = 1; j <= n; j ++, printf("\n"))
// for (int k = 1; k <= m; k ++)
// printf("%d ", sij.getsum(j, k));
int ux = x - 1, dx = x + 1, ly = y - 1, ry = y + 1;
while (ux > 1 and !b[ux][y])
ux --;
while (dx < n and !b[dx][y])
dx ++;
while (ly > 1 and !b[x][ly])
ly --;
while (ry < m and !b[x][ry])
ry ++;
if (ux >= 1 and b[ux][y] and ly >= 1 and b[x][ly]) {
if (b[ux][ly] and sij_get(ux, ly, x, y) == 4) {
ans ++;
}
}
if (ux >= 1 and b[ux][y] and ry <= m and b[x][ry]) {
if (b[ux][ry] and sij_get(ux, y, x, ry) == 4) {
ans ++;
}
}
if (dx <= n and b[dx][y] and ly >= 1 and b[x][ly]) {
if (b[dx][ly] and sij_get(x, ly, dx, y) == 4) {
ans ++;
}
}
if (dx <= n and b[dx][y] and ry <= m and b[x][ry]) {
if (b[dx][ry] and sij_get(x, y, dx, ry) == 4) {
ans ++;
}
}
}
printf("%d\n", ans);
return 0;
}
G.Laboratory Report
纯模拟题,题目太长了可以自己点上面的链接看
直接贴丑陋代码了,注意几个细节:
- 测量名称可能会出现测量名称 \(A\) 是测量名称 \(B\) 的一个前缀的情况。
- 不同 oj 上对行末空格的要求不同,CF上要求行末空格也要输出。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef tuple<double,double,int>ttfa;
typedef pair<int,double>links;
bool is_cho=1,flag=1;
const int N=755;
const double INF=1e9;
string AAA,BBB;
struct node{
string name;
string unit;
vector<ttfa>lis;
vector<string>state;
}a[N];int n;
struct player{
string name;
vector<links>lis;
vector<string>valve;
}b[N];int m;
vector<double>number;
vector<int> criteria;
double stodb(string s){
stringstream ss(s);
double x;ss>>x;
return x;
}
string read(int mode){
string s;
flag=(bool)getline(cin,s);
if(flag==0)return "";
if(mode==1)return s;
if(mode==2){
number.clear();
criteria.clear();
if(s==AAA||s==BBB)return s;
int len=s.length();
int p=-1,l=-1,r=-1;
for(int i=0;i<len;++i){
if(s[i]=='<'){
if(s[i+1]=='='){
criteria.push_back(2);
p=i+2;
}
else{
criteria.push_back(1);
p=i+1;
}
}else if(s[i]=='>'){
if(s[i+1]=='='){
criteria.push_back(4);
p=i+2;
}
else{
criteria.push_back(3);
p=i+1;
}
}else if(s[i]=='~'){
criteria.push_back(6);
criteria.push_back(5);
criteria.push_back(6);
p=i;
l=0,r=len;
}else if(s[i]==','){
criteria.push_back(5);
p=i;
}else if(s[i]=='['||s[i]==']'){
criteria.push_back(6);
if(s[i]=='[')l=i+1;
else r=i;
}else if(s[i]=='('||s[i]==')'){
criteria.push_back(7);
if(s[i]=='(')l=i+1;
else r=i;
}
}
if(l==-1){
number.push_back(stodb(s.substr(p,len-p)));
}else{
number.push_back(stodb(s.substr(l,p-l)));
number.push_back(stodb(s.substr(p+1,r-p-1)));
}
}
return s;
}
string ttt="";
links readval(){
string s;
flag=(bool)getline(cin,s);
if(flag==0)return {-1,-1};
int id=0;double val=0;
if(s==AAA||s==BBB)return {0,0};
int lassize=0;
for(int i=1;i<=n;++i){
string &t=a[i].name;
if(s.size()<t.size())continue;
if((int)t.size()<lassize)continue;
if(s.substr(0,t.size())==t){
id=i;
ttt=s.substr((int)t.size(),s.size()-t.size());
int loc=0;
while(ttt[loc]==' '&&loc+1<(int)ttt.size())++loc;
ttt=ttt.substr(loc,(int)ttt.size()-loc);
val=stodb(ttt);
lassize=t.size();
continue;
}
}
return {id,val};
}
void insert(vector<ttfa>&lis,int id){
if(number.size()==1){
double x=number.back();
int tp=criteria.back();
if(tp==1){
lis.push_back({-INF,x,id});
}else if(tp==2){
lis.push_back({-INF,x,id});
lis.push_back({x,x,id});
}else if(tp==3){
lis.push_back({x,INF,id});
}else if(tp==4){
lis.push_back({x,x,id});
lis.push_back({x,INF,id});
}else{
puts("WWW");exit(0);
}
}else{
double x=number[0],y=number[1];
int tl=criteria[0],tr=criteria[2];
lis.push_back({x,y,id});
if(tl==6)lis.push_back({x,x,id});
if(tr==6)lis.push_back({y,y,id});
}
}
int main(){
// freopen("data.in","r",stdin);
// freopen("data.out","w",stdout);
// ios::sync_with_stdio(0);
// cin.tie(0);
for(int i=0;i<75;++i){
AAA.push_back('-');
}
for(int i=0;i<80;++i){
BBB.push_back('=');
}
while(is_cho){
++n;
//cout<<n<<"\n";
a[n].name=read(1);
a[n].unit=read(1);
bool is_val=1;
while(is_val){
string t=read(2);
if(t==AAA||t==BBB){
is_val=0;
if(t==BBB)is_cho=0;
break;
}
string ss=read(1);
// cout<<ss<<"\n";
// cout<<AAA<<"\n";
if(ss==AAA||ss==BBB){
//puts("114514");
a[n].state.push_back("Normal");
insert(a[n].lis,0);
is_val=0;
if(ss==BBB)is_cho=0;
break;
}
ss=ss.substr(2,(int)ss.size()-2);
a[n].state.push_back(ss);
insert(a[n].lis,(int)a[n].state.size()-1);
}
sort(a[n].lis.begin(),a[n].lis.end());
}
// for(int i=1;i<=n;++i){
// for(auto s:a[i].state){
// cout<<s<<" | ";
// }cout<<"\n";
// for(auto [l,r,id]:a[i].lis){
// cout<<"{"<<l<<","<<r<<","<<id<<"} ";
// }cout<<"\n";
// }
while(is_cho==0){
++m;
b[m].name=read(1);
if(flag==0){
--m;
break;
}
//cout<<m<<" "<<flag<<"\n";
while(1){
links tmp=readval();
if(tmp.first==-1){
is_cho=1;
break;
}
if(tmp.first==0)break;
b[m].lis.push_back(tmp);
b[m].valve.push_back(ttt);
}
}
// for(int i=1;i<=m;++i){
// for(auto [id,val]:b[i].lis){
// cout<<"{"<<id<<","<<val<<"} ";
// }cout<<"\n";
// }
string hat="Test Result Unit Flag ";
for(int i=1;i<=m;++i){
cout<<b[i].name<<"\n";
cout<<AAA<<"\n";
cout<<hat<<"\n";
cout<<AAA<<"\n";
for(int j=0;j<(int)b[i].lis.size();++j){
int id=b[i].lis[j].first;double val=b[i].lis[j].second;
string num=b[i].valve[j];
string t="Abnormal";
for(auto [l,r,p]:a[id].lis){
if(l==r&&l==val){
t=a[id].state[p];
}else if(l!=r&&l<val&&val<r){
t=a[id].state[p];
}
}
if(t=="Normal")t="";
cout<<left<<setw(26)<<a[id].name;
cout<<left<<setw(8)<<num;
cout<<left<<setw(16)<<a[id].unit;
cout<<t<<"\n";
}
cout<<AAA<<"\n";
cout<<BBB<<"\n";
}
return 0;
}
D.Domniant
\(n\) 张骨牌,第 \(i\) 张位置 \(x_i\)、高度 \(l_i\)(\(x_i\) 递增)。骨牌向左倒下,覆盖半径为 \(l_i\) 的四分之一圆区域。若某骨牌倒下,其区域会推倒被覆盖的后续骨牌(连锁反应)。对每个 \(i\) 作为初始倒下的骨牌,求所有倒下骨牌的四分之一圆区域并集的面积。\(n\leq 2\times 10^5,x_i,l_i\in \Z,x_i,l_i\leq 10^9\)
使用一个栈维护每张多米诺骨牌所独立占据的横坐标范围,对于相交的部分归高度值更大的骨牌管理,如图所示

其中下方红色的线段即表示每个骨牌管理的区间。
然后考虑在左侧加入一张新的骨牌(用绿色表示),我们依次考虑栈顶骨牌,一个很自然的想法是,如果新骨牌在栈顶骨牌所管理的每个横坐标处的高度值都比栈顶骨牌本身高(由于是圆弧,等价于在管理的区间两端点的高度值比栈顶骨牌高),那么这个栈顶骨牌就可以被新的骨牌完全覆盖,直接移除栈顶骨牌。
接下来分为三种情况
情况一
加入的骨牌不与现有的栈顶骨牌相交或栈为空,则直接加入(且为满四分之一圆弧面积)

情况二
加入的骨牌在栈顶区间的面积被栈顶对应骨牌倒下的面积完全覆盖,等价于加入骨牌在栈顶右端点的高度值小于栈顶骨牌右端点的高度值,则将这个骨牌加入时的覆盖区间为栈顶右端点位置到骨牌位置。

在这个上方图示中,原先右侧红色三段红色区间被弹出栈,加入的新区间由绿色表示。
情况三
加入的骨牌在栈顶区间的圆弧与原先栈顶骨牌倒下的圆弧有一个交点,则交点左侧区间仍归栈顶骨牌管理,二交点右侧到加入骨牌位置归加入骨牌管理。此时需要调整原先栈顶骨牌覆盖的面积。

上方图示中,最右侧红色区间被弹出栈,而右侧第二个区间因新加入骨牌而调整范围。
这个交点可以 \(O(1)\) 计算
设新加入骨牌位置 \(x_1\),长度 \(l_1\),与之相交骨牌位置 \(x_2\),长度 \(l_2\),设相交位置横坐标 \(p\),则由勾股定理
\[l_1^2-(x_1-p)^2=l_2^2-(x_2-p)^2\\ p=\frac{x_1^2-x_2^2-l_1^2+l_2^2}{2(x_1-x_2)} \]数据保证了 \(x_2<x_1\)
若认为相关数学函数计算复杂度为 \(O(1)\),总时间复杂度为 \(O(n)\)。
然后就是代码实现问题了。由于本人是计算几何低手,故我使用了 long double 才通过此题,以下代码仅供参考。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int>ttfa;
#define double long double
const int N=500005;
const double INF=1e18;
const double EPS=1e-13;
const double pi=acos(-1);
int n;
double hei(double x,double R,double x2){//x2<x 处的高度
if(x-x2>R)return -INF;
return sqrtl(max(0.0L,R*R-(x-x2)*(x-x2)));
}
struct range{
double x,R,l,r,val;
range(double xx=1,double RR=1,double ll=0,double rr=1){
x=xx,R=RR,l=ll,r=rr;
double rit=acos(max(0.0L,(x-r)/R)),lef=acos(min(1.0L,(x-l)/R));
val=(rit-lef)*R*R/2.0-(x-r)*hei(x,R,r)/2.0+(x-l)*hei(x,R,l)/2.0;
}
}stk[N],a[N];int top;
double sum[N],ans[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%Lf%Lf",&a[i].x,&a[i].R);
}
for(int t=1;t<=n;++t){
while(top&&a[t].x-a[t].R<stk[top].r){
if(hei(a[t].x,a[t].R,stk[top].r)>=hei(stk[top].x,stk[top].R,stk[top].r)-EPS&&hei(a[t].x,a[t].R,stk[top].l)>=hei(stk[top].x,stk[top].R,stk[top].l)-EPS){
--top;
}else break;
}
if(!top||a[t].x-a[t].R>=stk[top].r){
stk[++top]=range(a[t].x,a[t].R,a[t].x-a[t].R,a[t].x);
ans[t]=sum[top]=stk[top].val;
continue;
}
if(hei(a[t].x,a[t].R,stk[top].r)<=hei(stk[top].x,stk[top].R,stk[top].r)+EPS){
stk[++top]=range(a[t].x,a[t].R,stk[top].r,a[t].x);
ans[t]=sum[top]=stk[top].val+sum[top-1];
}else{
double p=(a[t].x*a[t].x-stk[top].x*stk[top].x-a[t].R*a[t].R+stk[top].R*stk[top].R)/(2.0*(a[t].x-stk[top].x));
sum[top]-=stk[top].val;
stk[top]=range(stk[top].x,stk[top].R,stk[top].l,p);
sum[top]+=stk[top].val;
stk[++top]=range(a[t].x,a[t].R,stk[top].r,a[t].x);
ans[t]=sum[top]=stk[top].val+sum[top-1];
}
}
for(int i=1;i<=n;++i)
printf("%.10Lf\n",ans[i]);
return 0;
}
H.Network Topology in Hezardastan
\(m\) 个终端,\(n\) 个服务器(\(m\le n\))。\(m\times n\) 的 0/1 矩阵表示连接终端与服务器是否连接。称大小为 \(m\) 的服务器子集 \(S\) 为可管理的,则存在双射使每个终端连到不同服务器(即服务器子集可以终端完美匹配)。若所有大小为 \(m\) 的子集均可管理,输出
1;否则输出0及任意一个不可管理的大小为 \(m\) 的子集(服务器编号)。\(m\leq 150,n\leq 400,m\leq n\)
算是一道好题,请见独立博客题解。
I.Windcatchers
给定平面 \(n\) 个点(\(3\le n\le 4000\)),找一条直线作为中线,使中线上至少有两个点,且所有点到直线的距离 \(\ge\) 宽度(点在边界上允许)。最大化宽度,输出该宽度。坐标为整数且保证所有点不在一条直线上。
见独立博客题解。

浙公网安备 33010602011771号