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