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;
 } 
posted @ 2022-08-28 08:43  翔村亲亲鸟  阅读(120)  评论(0编辑  收藏  举报