The 2021 ICPC Asia Regionals Online Contest (I) 部分题解
终究还是太菜,后面慢慢更新,只做了4题,如果早点尝试应该可以5题的。反正还是菜!!!
2021/9/22 23:44 第二更,写得有点匆忙,明日把D补了,再完善了部分解释。
A
比赛的时候我们队是用优先队列加优化过去的。正解是线段树加二分,线段树维护区间最小值+单点修改。就是对于每一个请求,先二分查找区间[i%k, k-1]满足题目条件的最近的点,如果没有就去区间[0, i%k-1]找,如果还没有这个请求就死掉了。
说说一这道题的二分,对于区间[i%k, k-1], 不能每次都去判断[x,mid]是否有小于a的点,这样会超时,因为线段树已经维护了区间最小值,那么直接判断[l,mid]是否有合法点,否则去[mid+1,r]去找。对于区间[0,i%k-1]同理。
(我的线段树写得是真的长,还是师兄厉害。)
AC代码
#include<bits/stdc++.h>
#define ll long long
#define lp p<<1
#define rp p<<1|1
using namespace std;
const int N = 1e5+9;
int ans[N];
struct SegTree
{
int l,r;
int mi;
}tr[N*4];
//建树
void build(int p,int l,int r)
{
tr[p].l=l,tr[p].r=r;
if(l==r)
{
tr[p].mi=0;
return ;
}
int mid=(tr[p].l + tr[p].r)>>1;
build(lp,l,mid);
build(rp,mid+1,r);
tr[p].mi=min(tr[lp].mi,tr[rp].mi);
}
//查询区间最小值
int ask(int p,int l,int r)
{
if(l<=tr[p].l && r>=tr[p].r) return tr[p].mi;
//spread(p);
int mid=(tr[p].l+tr[p].r)>>1;
int res=1e9;
if(l<=mid) res=min(res,ask(lp,l,r));
if(r>mid) res=min(res,ask(rp,l,r));
return res;
}
//单点修改
void change(int p,int l,int r,int d)
{
if(l<=tr[p].l && r>=tr[p].r)
{
tr[p].mi=d;
return ;
}
//spread(p);
int mid = (tr[p].l + tr[p].r )>>1;
if(l<=mid) change(lp,l,r,d);
if(r>mid) change(rp,l,r,d);
tr[p].mi= min(tr[lp].mi, tr[rp].mi);
}
int main()
{
int k,n;
scanf("%d %d",&k,&n);
build(1,1,k);
for(int i=0; i<n; i++)
{
int a,b;
scanf("%d %d",&a,&b);
int x=i%k+1;
int l=x,r=k,p=-1,ok=0;
//找到右边第一个比a小的数的位置
if(ask(1,x,k)<=a)
{
while(l<r)
{
int mid=l+r>>1;
if(ask(1,l,mid)<=a)
{
r=mid;
p=mid,ok=1;
}
else l=mid+1;
}
change(1,l,l,a+b),ans[l-1]++;
}//右边没有就去找左边
else if(ask(1,1,x-1)<=a)
{
l=1,r=x-1,ok=0;
while(l<r)
{
int mid=l+r>>1;
if(ask(1,l,mid)<=a)
{
r=mid;
p=mid,ok=1;
}
else l=mid+1;
}
change(1,l,l,a+b),ans[l-1]++;
}
}
//处理下答案再按照题意输出
int mx=0;
for(int i=0; i<k; i++) mx=max(mx,ans[i]);
int cnt=0;
for(int i=0; i<k; i++) if(mx==ans[i]) cnt++;
for(int i=0; i<k; i++)
if(mx==ans[i])
{
cnt--;
printf("%d",i);
if(cnt) printf(" ");
}
return 0;
}
优先队列做法
简单说下思路,用优先队列维护每一个点处理的最后一个请求的结束时间。每次找出,“空闲”的点,找空闲的点的过程中注意优化下,如果当前已经出现了第i%k位置,那么就直接记录了,不用继续找下去了。否则再所有空闲的点里面找离i%k距离最短的那个点,这个说的距离最短是按照题目中说的找的方法的最短
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e5+9;
struct node
{
int id;
ll tim;
bool operator<(const node & t) const
{
if(tim!=t.tim) return tim>t.tim;
else return id>t.id;
}
};
priority_queue<node> q;
node v[N];
int p=0;
int ans[N];
int main()
{
int mx=0;
int k,n;
scanf("%d %d",&k,&n);
for(int i=0; i<k; ++i) q.push({i,0});
//cout<<q.top().id<<endl;
for(int i=0; i<n; ++i)
{
ll a,b;
scanf("%lld %lld",&a,&b);
node tmp=q.top();
q.pop();
if(tmp.tim>a)
{
q.push(tmp);
continue;
}
else
{
int t=i%k;
v[p++]=tmp;
if(tmp.id==t)
{
ans[tmp.id]++;
tmp.tim=a+b;
mx=max(ans[tmp.id],mx);
q.push(tmp);
p=0;
continue;
}
int flag=0;
while(q.top().tim<=a&&q.size())
{
tmp=q.top();
q.pop();
v[p++]=tmp;
if(tmp.id==t)
{
ans[tmp.id]++;
mx=max(ans[tmp.id],mx);
tmp.tim=a+b;
flag=1;
break;
}
}
if(flag)
{
for(int j=0;j<p; ++j) q.push(v[j]);
p=0;
continue;
}
int mi=1e9;
for(int j=0; j<p; ++j)
{
int dis= ((v[j].id-t)%k+k)%k;
mi=min(mi,dis);
}
// cout<<mi<<"debug1 "<<endl;
for(int j=0; j<p; ++j)
{
int dis = ((v[j].id-t)%k+k)%k;
if(dis==mi)
{
//cout<<v[j].id<<"debug2 "<<endl;
v[j].tim=a+b;
ans[v[j].id]++;
mx=max(ans[v[j].id],mx);
}
q.push(v[j]);
}
p=0;
}
}
int cnt=0;
for(int i=0; i<k; i++)
{
// cout<<ans[i]<<endl;
if(ans[i]==mx) cnt++;
}
for(int i=0; i<k; i++)
{
if(ans[i]==mx)
{
printf("%d",i);
cnt--;
if(cnt) printf(" ");
}
}
return 0;
}
xdl的代码
#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=1e5+10;
int n,m,a[N];
struct SegTree{
#define mid (l+r>>1)
int minv[N*4];
void upd(int id,int l,int r,int pos,int x) {
if(l==r) {minv[id]=x;return;}
if(pos<=mid) upd(id<<1,l,mid,pos,x);
else upd(id<<1|1,mid+1,r,pos,x);
minv[id]=min(minv[id<<1],minv[id<<1|1]);
}
int ask(int id,int l,int r,int L,int R) {
if(L<=l&&r<=R) return minv[id];
int res=inf;
if(L<=mid) res=min(res,ask(id<<1,l,mid,L,R));
if(R>mid) res=min(res,ask(id<<1|1,mid+1,r,L,R));
return res;
}
#undef mid
}tr;
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) {
int s,e,id;
scanf("%d%d",&s,&e);
e+=s;
id=(i-1)%n;
if(tr.ask(1,0,n-1,id,n-1)<=s) {
int l=id,r=n-1;
while(l<r) {
int mid=(l+r)/2;
if(tr.ask(1,0,n-1,l,mid)<=s) r=mid;
else l=mid+1;
}
a[l]++;
tr.upd(1,0,n-1,l,e);
}
else if(id>0&&tr.ask(1,0,n-1,0,id-1)<=s) {
int l=0,r=id-1;
while(l<r) {
int mid=(l+r)/2;
if(tr.ask(1,0,n-1,l,mid)<=s) r=mid;
else l=mid+1;
}
a[l]++;
tr.upd(1,0,n-1,l,e);
}
}
int maxv=0;
for(int i=0;i<n;i++) maxv=max(a[i],maxv);
vector<int> ans;
for(int i=0;i<n;i++) if(maxv==a[i]) ans.push_back(i);
for(int i=0;i<ans.size();i++) {
if(i>0) printf(" ");
printf("%d",ans[i]);
}
return 0;
}
I
I题没什么好说的,好像许多人理解错题意了。我们队一发过的,看看代码即可
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+9;
int a[N];
int main()
{
int t=0;
int x;
while(~scanf("%d",&x))
{
a[t++]=x;
}
int A=a[t-2];
int r=a[t-1];
t-=2;
sort(a,a+t,greater<int>());
for(int i=0; i<t; i++)
{
if(abs(a[i]-A)<=r) printf("%d ",a[i]);
}
return 0;
}
H Mesh Analysis
这道题真是坑,我们当时想到了可能是这样,但是到最后10多分钟才敢尝试,结果忘记排序就wa了。
说说思路,就是对于每一个询问给出的点x,去他给出的图型里面的点去找,如果存在这个点x,那么这个图型就是它参与构造的 ,这些点也是它相邻的。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e5+9;
bool vis[N];
int ans[N],ans2[N];
struct tx
{
int id;
int f;
int a,b,c;
}tx[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1; i<=n; i++)
{
int id;
double x,y,z;
cin>>id>>x>>y>>z;
//node[i]={i,x,y,z};
}
for(int i=1; i<=m; i++)
{
int id,f,a,b,c;
cin>>id>>f;
if(f==102)
{
cin>>a>>b;
tx[i]={id,f,a,b,0};
}else
{
cin>>a>>b>>c;
tx[i]={id,f,a,b,c};
}
}
int q;
scanf("%d",&q);
while(q--)
{
memset(vis,false,sizeof vis);
int x;
scanf("%d",&x);
int p=0,p2=0;
vis[x]=true;
for(int i=1; i<=m; i++)
{
if(tx[i].f==102)
{
if(tx[i].a==x||tx[i].b==x)
{
if(!vis[tx[i].a]) ans[p++]=tx[i].a,vis[tx[i].a]=true;
if(!vis[tx[i].b]) ans[p++]=tx[i].b,vis[tx[i].b]=true;
ans2[p2++]=tx[i].id;
}
}
else
{
if(tx[i].a==x||tx[i].b==x||tx[i].c==x)
{
if(!vis[tx[i].a]) ans[p++]=tx[i].a,vis[tx[i].a]=true;
if(!vis[tx[i].b]) ans[p++]=tx[i].b,vis[tx[i].b]=true;
if(!vis[tx[i].c]) ans[p++]=tx[i].c,vis[tx[i].c]=true;
ans2[p2++]=tx[i].id;
}
}
}
printf("%d\n",x);
printf("[");
sort(ans,ans+p);
sort(ans2,ans2+p2);
for(int i=0; i<p; ++i)
{
printf("%d",ans[i]);
if(i<p-1) printf(",");
}
printf("]\n");
printf("[");
for(int i=0; i<p2; ++i)
{
printf("%d",ans2[i]);
if(i<p2-1) printf(",");
}
printf("]");
if(q) puts("");
}
return 0;
}
F
队友的思路,先到达A(a,b)点,再在到达B(2a,0)点。求最短记录,那么答案就是先到达(a,b-r),再加上此点到(2a,0)的距离减去r
AC代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main()
{
int t;
cin>>t;
int ca=0;
while(t--)
{
ll a,b,r;
scanf("%lld %lld %lld",&a,&b,&r);
ll b2=b-r;
if(b2<0) b2=0;
double ans=sqrt(1.0*a*a+1.0*b2*b2);
double c=max(0.0,sqrt(1.0*a*a+1.0*b2*b2)-r);
printf("Case #%d: %.2f",++ca,ans+c);
if(t) puts("");
}
return 0;
}
K
题目真的难读懂,队友读懂的,然后我大胆的猜了下就是个模拟,结果果然是模拟。建个邻接表,按照题目说的步骤模拟就好了,直接用vector建邻接表。
说说题意吧,第一行给出测试数据组数,第二行是n,m分别表示点的个数个,询问的个数。然后n行,每一行,对于第i个点,给出点i的邻接的点个数x,然后是x个点(有向图),并且给出点的顺序就是边的序号。比如对于点1给出2 2 3,那么代表点1第一条出边走向点2,第二条出边走向点3。下面的m行,是m个询问,对于每一个询问,第一个数代表出发点,第二个数x,代表走x条边,后面x个数xi,代表走当前点的第xi条边。最后输出到达点,如果边不合法,就输出"Packet Loss"
AC代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
vector<int> g[N];
int n, m;
int main()
{
int T;
scanf("%d", &T);
int cas = 0;
while(T --)
{
cas ++;
printf("Case \#%d: \n", cas);
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i ++)
if(g[i].size()) g[i].clear();
for(int i = 1; i <= n; ++i)
{
int c;
scanf("%d", &c);
while(c --)
{
int a;
scanf("%d", &a);
g[i].push_back(a);
}
}
while(m --)
{
int s, c;
scanf("%d %d",&s, &c);
bool ok = 1;
while(c --)
{
int x;
scanf("%d", &x);
if(x > g[s].size())
{
ok = 0;
// break;
}
if(ok) s = g[s][x-1];
}
if(ok) printf("%d",s);
else printf("Packet Loss");
if(m) puts("");
}
if(T) puts("");
}
return 0;
}
D
线段树,维护每个点的最小值,最后答案就是总权值减去每个点除了第n个点的最小值
#include<bits/stdc++.h>
#define ll long long
#define lp p<<1
#define rp p<<1|1
#define INF 0x3f3f3f3f
using namespace std;
const int N = 1e5+9;
int tr[N<<2],mi[N<<2];
struct node
{
int l,r,w;
bool operator<(const node &t) const
{
return w>t.w;
}
}e[N];
void spread(int p)
{
if(mi[p])
{
tr[lp]=tr[p];
tr[rp]=tr[p];
mi[lp]=mi[p];
mi[rp]=mi[p];
mi[p]=0;
}
}
//查询单点最小值
int ask(int p,int l,int r,int x)
{
if(l==r) return tr[p];
spread(p);
int mid=(l+r)>>1;
int res=INF;
if(x<=mid) return ask(lp,l,mid,x);
else return ask(rp,mid+1,r,x);
}
//区间修改
void change(int p,int l,int r,int x,int y,int d)
{
if(x<=l && y>=r)
{
tr[p]=d;
mi[p]=d;
return ;
}
spread(p);
int mid = (l+r )>>1;
if(x<=mid) change(lp,l,mid,x,y,d);
if(y>mid) change(rp,mid+1,r,x,y,d);
tr[p]=min(tr[lp],tr[rp]);
}
int main()
{
//build(1,1,1e5);
int t;
scanf("%d",&t);
int ca=0;
while(t--)
{
int n,m;
scanf("%d %d",&n,&m);
memset(tr,0x3f,sizeof tr);
memset(mi,0,sizeof 0);
//build(1,1,n);
ll sum=0;
for(int i=1; i<=m; ++i) scanf("%d %d %d",&e[i].l,&e[i].r,&e[i].w);
sort(e+1,e+m+1);
for(int i=1; i<=m; ++i)
{
int l,r,w;
l=e[i].l,r=e[i].r,w=e[i].w;
change(1,1,n,l,r-1,w);
int t=r-l;
sum+=1ll*w*(1+t)*t/2;
}
//cout<<ask(1,1,n,1)<<endl;
int ok=1;
for(int i=1; i<n; i++)
{
int tmp=ask(1,1,n,i);
if(tmp==INF) ok=0;
else
{
sum-=tmp;
}
}
printf("Case #%d: ",++ca);
if(ok==1) printf("%lld\n",sum);
else printf("Gotta prepare a lesson\n");
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)