The 14th Chinese Northeast Collegiate Programming Contest 补题(A.异或二进制位最小生成树 K.二维单调队列 L.二分+最大n维曼哈顿距离)

A.Micro Structure Thread(异或二进制位最小生成树)

题意比较迷惑,最后转化下来是,

确定一个树的点与父亲的排列,使得所求式总代价最小,

即求一棵最小生成树,点i和点j连接的代价是popcount(a[i]^a[j])

即ai和aj异或的值的二进制位的个数,

其中n<=2e5,0<=ai<2^18,ai两两不同

 

考虑将n个出现的值ai,把i视为祖先,多源bfs

对于每个[0,2^18)的值确定其祖先,然后考虑枚举扰动边,

枚举j从0到18,两个popcount之差1的相邻点,

如果祖先不同,就将这两个祖先连一条边,

考虑到a变到b的变化过程,

一定是从a,变到最远的离a最近的点,再变一次变到最远的离b最近的点,再变到b的

最后边的数量是(2^18)*18级别的,作Kruskal最小生成树即可

 

求出最小生成树后建树,dfs一遍即可确定点和父亲的对应关系的排列

由于根节点没有父亲,在父亲对应的排列里给父亲赋一个[1,n]的值即可

#include<bits/stdc++.h>
using namespace std;
#define pb push_back 
typedef pair<int,int> P; 
const int N=2e5+10,M=(1<<18)+5,INF=0x3f3f3f3f;
int t,n,m,a[N],bit[M],dis[M],par[M],fa[M];
int b[N],p[N],c;
bool vis[M];
vector<int>e[N];
queue<int>q;
struct edge{
	int u,v,w;
}g[M*18];
bool operator<(edge a,edge b){
	return a.w<b.w;
}
int find(int x){
	return par[x]==x?x:par[x]=find(par[x]);
}
void bfs(){
	for(int i=1;i<=n;++i){
		vis[a[i]]=1;
		q.push(a[i]);
		fa[a[i]]=i;
		dis[a[i]]=0;
	}
	while(!q.empty()){
		int v=q.front();q.pop();
		for(int i=0;i<18;++i){
			int nex=v^(1<<i);
			if(dis[nex]==INF){
				dis[nex]=dis[v]+1;
				fa[nex]=fa[v];
				q.push(nex);
			}
			else if(fa[nex]!=fa[v]){
				g[++m]={fa[nex],fa[v],bit[a[fa[nex]]^a[fa[v]]]};
			}
		}
	}
}
void dfs(int u,int f){
	b[++c]=u;p[c]=f;
	for(int i=0;i<e[u].size();++i){
		int v=e[u][i];
		if(v==f)continue;
		dfs(v,u);
	}
}
int main(){
	for(int i=1;i<M;++i){
		bit[i]=bit[i>>1]+(i&1);
	}
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		c=m=0;
		for(int i=0;i<M;++i){
			par[i]=i;dis[i]=INF;
			vis[i]=0;
		}
		for(int i=1;i<=n;++i){
			e[i].clear();
			scanf("%d",&a[i]);
		}
		bfs();
		sort(g+1,g+m+1);
		int cnt=0,ans=0;
		for(int i=1;i<=m && cnt<n-1;++i){
			int u=g[i].u,v=g[i].v,w=g[i].w;
			if(find(u)==find(v))continue;
			e[u].pb(v);e[v].pb(u);
			ans+=w;cnt++;
			par[find(v)]=find(u); 
		}
		dfs(1,-1);
		p[1]=n;
		printf("%d\n",ans);
		for(int i=1;i<=n;++i){
			printf("%d%c",b[i]," \n"[i==n]);
		}
		for(int i=1;i<=n;++i){
			printf("%d%c",p[i]," \n"[i==n]);
		}
	}
	return 0;
}

K.PepperLa's Boast(二维单调队列)

n*m(n<=1e3,m<=1e3,sum n*m<=7e6)的矩形,

点(i,j)上有一个值a(i,j)(-1e9<=a(i,j)<=1e9),表示这个点可以提供的氧气量,

如果为正,代表安全地区,

人的肺内氧气量在这里会加a(i,j),这里认为肺的容氧量是无穷的

如果为负数,则表示这个点不能提供氧气量,为毒气地区,

一个人想从(1,1)走到(n,m),保证a(1,1)>0,a(n,m)>0

每一秒只能向右或向下或向右下走一格,

即可以从(x,y)走到(x,y+1)/(x+1,y)/(x+1,y+1),

特别地,一个人可以选择在安全地区憋气,并耗掉u(u<=1e9)的代价,

憋气时间可以长达k(k<=1e9)秒,

在憋气时间内可以穿越毒气地区,并最终在安全地区松气,

求能安全到达(n,m)所剩下的最大氧气量

 

考虑到某个点的dp值需要从左上角长度为k的正方形转移而来,

于是需要用到二维单调队列,

赛中夏老师想出二维单调队列的想法的时候,

距比赛结束还有18min,于是写不完了gg

而且这个二维单调队列不是静态的,需要一边扫一边维护

静态的则可以先横着扫一遍扫完再竖着扫一遍扫完

 

首先由于氧气量是1e9级别的,就放在dp数组里进行维护,

dp[i][j]表示到(i,j)这个点能剩余的最大氧气量,初始化-1e18

ans[i][j]维护[dp[i][j-k],dp[i][j]]这一段长度为k的滑动窗口的最大dp值

 

分三种情况讨论,

第一种是左上角的格子,dp[1][1]=a[1][1]

第二种是大于0的格子,这个时候可以更新这个点的dp值,

dp值可以直接从dp[i-1][j-1]、dp[i-1][j]、dp[i][j-1]三个正dp值处直接转移而来

也可以左上角一个k*k的正方形处转移而来,

即横向的ans[i][j],和纵向的单调队列维护的窗口为k的ans队列

注意先用不带dp[i][j]的ans[i][j]更新dp[i][j],再用dp[i][j]更新ans[i][j]

第三种是小于0的格子,

这个时候仍然需要维护这个点左边长为k的区间的最大dp值,即ans[i][j]

样例很良心,本来一开始脑补的时候,忽略了第三种情况

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+10;
int n,m,k,u,a[N][N];
ll dp[N][N],ans[N][N];
deque<int>row[N],col[N];
int main(){
	while(~scanf("%d%d%d%d",&n,&m,&k,&u)){
		for(int i=1;i<=n;++i){
			dp[i][0]=-1e18;
			while(!row[i].empty())row[i].pop_front();
		}
		for(int j=1;j<=m;++j){
			dp[0][j]=-1e18;
			while(!col[j].empty())col[j].pop_front();
		}
		for(int i=1;i<=n;++i){
			for(int j=1;j<=m;++j){
				scanf("%d",&a[i][j]);
				dp[i][j]=-1e18;ans[i][j]=-1e18;
				while(!row[i].empty() && row[i].front()<j-k)row[i].pop_front();
				while(!col[j].empty() && col[j].front()<i-k)col[j].pop_front();
				if(i==1 && j==1){
					dp[i][j]=a[i][j];
					ans[i][j]=dp[i][j];
				}
				else if(a[i][j]>0){
					if(!row[i].empty()){
						dp[i][j]=max(dp[i][j],dp[i][row[i].front()]-u+a[i][j]);
						ans[i][j]=max(ans[i][j],dp[i][row[i].front()]);
					}
					if(!col[j].empty()){
						dp[i][j]=max(dp[i][j],ans[col[j].front()][j]-u+a[i][j]);
					}
					dp[i][j]=max(dp[i][j],dp[i-1][j-1]+a[i][j]);
					dp[i][j]=max(dp[i][j],dp[i-1][j]+a[i][j]);
					dp[i][j]=max(dp[i][j],dp[i][j-1]+a[i][j]);
					ans[i][j]=max(ans[i][j],dp[i][j]);
				}
				else{
					if(!row[i].empty()){
						ans[i][j]=max(ans[i][j],dp[i][row[i].front()]);
					}
				}
				if(dp[i][j]>=u){
					while(!row[i].empty() && dp[i][j]>=dp[i][row[i].back()])row[i].pop_back();
					row[i].push_back(j);
				}
				if(ans[i][j]>=u){
					while(!col[j].empty() && ans[i][j]>=ans[col[j].back()][j])col[j].pop_back();
					col[j].push_back(i);
				}
				//printf("i:%d j:%d dp:%lld\n",i,j,dp[i][j]);
			}
		}
		printf("%lld\n",dp[n][m]>0?dp[n][m]:-1);
	}
	return 0;
} 

L. PepperLa's Express(二分+最大三维曼哈顿距离)

z*x*y(z<=100,x<=100,y<=100),多组样例,sum z*x*y<=6e6,

一个三维的结构,每一层是一个矩阵,矩阵里有三种字符,@.*,

@代表邮局,.代表空地,*代表用户,每个用户会选择到自己家最近的邮局

距离定义为曼哈顿距离,

现在需要确定一个新邮局,建在空地上,

使得每个用户到最近的邮局的最大距离最小,输出最大距离

保证输入里至少有一个邮局和一个空地,

 

最大距离最小,考虑二分答案距离x,然后对邮局进行多源bfs,

找到所有用户,忽略到邮局距离不超过x的用户,然后考虑剩下的用户,

问题转化成,是否存在一个空地,对给定的用户的曼哈顿距离均不超过x

即最大n维曼哈顿距离问题

 

最大n维曼哈顿距离是一个经典问题,可以参考poj2926

利用了dp的松弛思想,即abs(x0-x1)=max(x0-x1,x1-x0)

abs(x0-x1)+abs(y0-y1)=max(x0-x1,x1-x0)+max(y0-y1,y1-y0)

=max(x0-x1+y0-y1,x0-x1+y1-y0,x1-x0+y0-y1,x1-x0-y1-y0)

=max(x0+y0-(x1+y1),x0-y0-(x1-y1),-x0+y0-(-x1+y1),-x0-y0-(-x1-y1)

三维同理,是八个值的最大值,

且如果枚举的(x0,y0)的当前符号固定,减去的值的符号(x1,y1)也与其相同

所以考虑所有可能的(x1,y1),用数组维护八维(x1,y1)的最小值,

对于所有的(x0,y0)插进去询问即可

 

这里,(x1,y1)先插入所有当前剩下的邮局,(x0,y0)则枚举所有的空地,

可以将sum=6e6认为是1e6的样例6组,

复杂度O(6*1e6*log300*8),实际跑不满,2s跑了1.3s

 

plus:如果是最小曼哈顿距离的话,则需用到三维树状数组,

这个时候偏序关系就必须发挥作用了,维护八棵树,分别询问,

可以参考2019年牛客多校第八场D.distance

#include<bits/stdc++.h>
using namespace std;
const int N=105,INF=0x3f3f3f3f;
int z,x,y,dis[N][N][N],mn[8];
struct node{
	int a,b,c;
};
vector<node>sta,usr,spa;
char s[N][N][N];
queue<node>q;
bool leg(int a,int b,int c){
	return a>=0 && a<z && b>=0 && b<x && c>=0 && c<y && dis[a][b][c]==INF;
}
bool ok(int mid){
	//printf("mid:%d\n",mid);
	for(int i=0;i<z;++i){
		for(int j=0;j<x;++j){
			for(int k=0;k<y;++k){
				dis[i][j][k]=INF;
			}
		}
	}
	for(int i=0;i<sta.size();++i){
		int a=sta[i].a,b=sta[i].b,c=sta[i].c;
		q.push(sta[i]);
		dis[a][b][c]=0;		
	}
	usr.clear();
	while(!q.empty()){
		node d=q.front();q.pop();
		int a=d.a,b=d.b,c=d.c;
		if(dis[a][b][c]>mid && s[a][b][c]=='*'){
			usr.push_back({a,b,c});
		}
		if(leg(a+1,b,c)){
			dis[a+1][b][c]=dis[a][b][c]+1;
			q.push({a+1,b,c});
		}
		if(leg(a-1,b,c)){
			dis[a-1][b][c]=dis[a][b][c]+1;
			q.push({a-1,b,c});
		}
		if(leg(a,b+1,c)){
			dis[a][b+1][c]=dis[a][b][c]+1;
			q.push({a,b+1,c});
		}
		if(leg(a,b-1,c)){
			dis[a][b-1][c]=dis[a][b][c]+1;
			q.push({a,b-1,c});
		}
		if(leg(a,b,c+1)){
			dis[a][b][c+1]=dis[a][b][c]+1;
			q.push({a,b,c+1});
		}
		if(leg(a,b,c-1)){
			dis[a][b][c-1]=dis[a][b][c]+1;
			q.push({a,b,c-1});
		}
	}
	if(!usr.size()){
		return 1;
	}
	memset(mn,INF,sizeof mn);
	for(int i=0;i<usr.size();++i){
		int a=usr[i].a,b=usr[i].b,c=usr[i].c;
		for(int p=0;p<8;++p){
			int v=0;
			if(p&1)v-=usr[i].a;
			else v+=usr[i].a;
			if(p&2)v-=usr[i].b;
			else v+=usr[i].b;
			if(p&4)v-=usr[i].c;
			else v+=usr[i].c;
			mn[p]=min(mn[p],v);
		}
	}
	for(int i=0;i<spa.size();++i){
		int a=spa[i].a,b=spa[i].b,c=spa[i].c,ans=0;
		for(int p=0;p<8;++p){
			int v=0;
			if(p&1)v-=spa[i].a;
			else v+=spa[i].a;
			if(p&2)v-=spa[i].b;
			else v+=spa[i].b;
			if(p&4)v-=spa[i].c;
			else v+=spa[i].c;
			ans=max(ans,v-mn[p]);
		}
		//printf("mid:%d (%,%d,%d):%d\n",mid,a,b,c,ans);
		if(ans<=mid)return 1;
	}
	return 0;
}
int main(){
	while(~scanf("%d%d%d",&z,&x,&y)){
		sta.clear();
		usr.clear();
		spa.clear();
		for(int i=0;i<z;++i){
			for(int j=0;j<x;++j){
				scanf("%s",s[i][j]);
				for(int k=0;k<y;++k){
					if(s[i][j][k]=='@'){
						sta.push_back({i,j,k});
					}
					else if(s[i][j][k]=='*'){
						usr.push_back({i,j,k});
					}
					else if(s[i][j][k]=='.'){
						spa.push_back({i,j,k});
					}
				}
			}
		}
		if(!usr.size()){
			puts("0");
			return 0;
		}
		int l=1,r=z+x+y;
		while(l<=r){
			int mid=(l+r)/2;
			if(ok(mid)){
				r=mid-1;
			}
			else{
				l=mid+1;
			}
		}
		printf("%d\n",l);
	}
	return 0;
} 

总结:

在队友的carry下9/13,以为很勇,

赛后被放到gym重现了,还删了一个签到题,这样就是8/12,

然而,神仙群友纷纷9/12,10/12,11/12异常神勇,radewoosh甚至单人3.5hAKorz

F题好像是一个O(n^5)的直接模拟,不想补了gg

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值