CH Round #56 - 国庆节欢乐赛解题报告

时间:2021-08-25 18:07:02

最近CH上的比赛很多,在此会全部写出解题报告,与大家交流一下解题方法与技巧。

T1 魔幻森林

描述

Cortana来到了一片魔幻森林,这片森林可以被视作一个N*M的矩阵,矩阵中的每个位置上都长着一棵树,其中一些树上结有能够产生能量的魔力水果。已知每个水果的位置(Xi,Yi)以及它能提供的能量Ci。
然而,魔幻森林在某些时候会发生变化:
(1) 有两行树交换了位置。
(2) 有两列树交换了位置。
当然,树上结有的水果也跟随着树一起移动。不过,只有当两行(列)包含的魔力水果数都大于0,或者两行(列)都没有魔力水果时,上述变化才会发生。
Cortana对这些魔力水果很感兴趣,希望你帮她实时监测这片森林里魔力水果的情况。

输入格式

第一行包含3个整数N,M,K,分别表示矩阵的行数、列数和魔力水果的总数。

接下来K行每行三个整数X,Y,C,描述水果的位置(X,Y)和能量C。如果两个水果的位置相同,它们的能量累加。
第K+2行有一个整数T。
接下来T行每行三个整数Q,A,B:
若Q=1,表示A、B两行交换;
若Q=2,表示A、B两列交换;
若Q=3,表示询问位置(A,B)上产生的能量(没有水果视为0)。

1<=N,M<=2*10^9,0<=K,T<=10^5,0<=X,A<=N-1,0<=Y,B<=M-1,1<=C<=1000。

输出格式

对于每个询问,输出一个整数表示答案。

看到如此大的范围,果子又如此少,我一开始想到的是稀疏矩阵。然后思考如何交换。后来发现离散化即可。每次交换行或者交换列就将离散化后的坐标交换即可。用map实现,所有操作log(n),核心代码不超过20行

 #include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <list>
#include <vector>
#include <ctime>
#include <functional>
#define pritnf printf
#define scafn scanf
#define For(i,j,k) for(int i=(j);i<=(k);(i)++)
using namespace std;
typedef long long LL;
typedef unsigned int Uint;
const int INF=0x7ffffff;
//==============struct declaration============== //==============var declaration=================
const int MAXN=;
map <int,int> R,C;
map <pair<int,int>,int> Fruits;
int row,col,k,q;
int rcnt=,ccnt=;
//==============function declaration============ //==============main code=======================
int main()
{
scanf("%d%d%d",&row,&col,&k);
For(i,,k){
int r,c,v;
scanf("%d%d%d",&r,&c,&v);
if (R[r]==) R[r]=++rcnt;
if (C[c]==) C[c]=++ccnt;
Fruits[make_pair(R[r],C[c])]+=v;
}
scanf("%d",&q);
while (q--){
int cmd,a,b;
scanf("%d%d%d",&cmd,&a,&b);
if (cmd==){
if (R[a]==||R[b]==) continue;
swap(R[a],R[b]);
}
else if (cmd==){
if (C[a]==||C[b]==) continue;
swap(C[a],C[b]);
}
else if (cmd==){
printf("%d\n",Fruits[make_pair(R[a],C[b])]);
}
} return ;
}
//================fuction code====================

T1代码

T2 过河

描述

有个青蛙要过河,河的横截面被看做一个数轴,河岸在0和M处。河里有N块石头,可以用数轴上(0,M)中的N个点表示。青蛙要从0跳到M,每步跳的距离不能超过L,并且必须落在石头上(不能跳进河里)。
以目前的石头数,青蛙有可能过不了河。Cortana想帮帮这只青蛙,但是又不想让它过河过得太容易(以免养成青蛙好吃懒做的坏习惯),于是决定往河里再添加一些石头,使得青蛙在能过河的前提下,过河需要跳的步数尽可能多。
由于这只青蛙也很机智,在Cortana放好石头以后,它总会采取最优策略(跳尽可能少的步数)过河。因此她Cortana要采取适当的方式放置石头,使青蛙的最优策略尽可能差。

输入格式

第一行3个正整数N、M、L。
第二行N个正整数Ai,描述河里已经有的石头的位置。

0<=N<=2*10^5,1<=M<=10^9,1<=L<=10^9,0<Ai<M。

输出格式

输出在Cortana添加一些石头后,过河的最优策略的步数最大是多少。

由于青蛙跳的步数没有下限,我们知道过河的最优策略一定是跳到能跳到的离当前石头最远的石头上,如果青蛙能够向前跳,那么没有必要增加石头。下面主要考虑不能够跳到的情况下如何加石头。

设当前位置是pos,上一步跳的距离为Laststep,在pos+L的范围内没有石头。显然加石头加的越近越好,我们考虑加石头在pos+L-Laststep+1的位置。

为什么?因为如果再向前1格,加在pos+L-Laststep的地方,青蛙会直接从pos-Laststep的地方直接跳到这里,而不会经过pos位。再向后一格的话解不会比当前格更优。

然后Laststep在每次跳动的时候进行修改。特别地,Laststep初始应该为l,这样青蛙如果出门的时候没办法直接跳到石头上就会跳至1。

 #include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <list>
#include <vector>
#include <ctime>
#include <functional>
#define pritnf printf
#define scafn scanf
#define For(i,j,k) for(int i=(j);i<=(k);(i)++)
using namespace std;
typedef long long LL;
typedef unsigned int Uint;
const int INF=0x7fffffff;
//==============struct declaration============== //==============var declaration=================
const int MAXN=*;
int n,m,l;
int Stone[MAXN];
//==============function declaration============ //==============main code=======================
int main()
{
scanf("%d%d%d",&n,&m,&l);
For(i,,n)
scanf("%d",Stone+i);
sort(Stone+,Stone++n);
Stone[]=;Stone[n+]=m;Stone[n+]=INF;
int Laststep=l,pos=,cnt=;
while (Stone[pos]<m){
int i;
for(i=;Stone[i+pos]-Stone[pos]<=l;i++)
Laststep=Stone[i+pos]-Stone[pos];
if (i!=)
pos=pos+i-;
else{
Stone[pos]+=l-Laststep+;
Laststep=l-Laststep+;
}
cnt++;
//pritnf("%d ",Stone[pos]);
}
printf("%d\n",cnt);
return ;
}
//================fuction code====================

T2代码

T3 异象石

描述

Adera是Microsoft应用商店中的一款解谜游戏。
异象石是进入Adera中异时空的引导物,在Adera的异时空中有一张地图。这张地图上有N个点,有N-1条双向边把它们连通起来。起初地图上没有任何异象石,在接下来的M个时刻中,每个时刻会发生以下三种类型的事件之一:
1. 地图的某个点上出现了异象石(已经出现的不会再次出现);
2. 地图某个点上的异象石被摧毁(不会摧毁没有异象石的点);
3. 向玩家询问使所有异象石所在的点连通的边集的总长度最小是多少。
请你作为玩家回答这些问题。

输入格式

第一行有一个整数N,表示点的个数。
接下来N-1行每行三个整数x,y,z,表示点x和y之间有一条长度为z的双向边。
第N+1行有一个正整数M。
接下来M行每行是一个事件,事件是以下三种格式之一:
+ x    表示点x上出现了异象石
- x    表示点x上的异象石被摧毁
?     表示询问使当前所有异象石所在的点连通所需的边集的总长度最小是多少。

1 ≤ n, m ≤ 10^5, 1 ≤ x, y ≤ n, x ≠ y, 1 ≤ z ≤ 10^9

输出格式

对于每个 ? 事件,输出一个整数表示答案。

个人认为非常好的一道题。

一棵树,给你一些动态变化的点,要求求出链接这些点的最短道路是多少。

对于无根树,我们经常可以选定一个根节点化为有根树。

以1节点为根,进行dfs,得到每个点的dfs编号。

把有异象石的节点按dfs序从小到大排好序,答案就是该序列中相邻两个数的距离和。

这么说答案可能还是不容易求,那么用这个办法:求出每个点和它的前驱、后继(最小点的前驱是最大点,最大点的后继是最小点)的点的距离,这个距离之和就是答案的两倍。

由于篇幅原因,在此不给出证明。<del>其实我不会证</del>

画图很容易看出。

维护这个序列set可以很容易地完成,求出距离可以用LCA。所有操作均为log(n)

 #include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <list>
#include <vector>
#include <ctime>
#include <functional>
#define pritnf printf
#define scafn scanf
#define For(i,j,k) for(int i=(j);i<=(k);(i)++)
using namespace std;
typedef long long LL;
typedef unsigned int Uint;
const int INF=0x7ffffff;
//==============struct declaration==============
struct adj{
int to,dist;
adj(int to=,int dist=):to(to),dist(dist){}
};
//==============var declaration=================
const int MAXN=;
vector <adj> Edge[MAXN];
int depth[MAXN],pa[][MAXN],n,q,maxd=-,pre[MAXN],no[MAXN],Index=;
LL dist[][MAXN],ans=;
set <int> Stone;
//==============function declaration============
void dfs(int x);
void init();
LL lca(int u,int v);
set <int>::iterator L(set <int>::iterator it);
set <int>::iterator R(set <int>::iterator it);
//==============main code=======================
int main()
{
scanf("%d",&n);
For(i,,n-){
int s,e,t;
scanf("%d%d%d",&s,&e,&t);
Edge[s].push_back(adj(e,t));
Edge[e].push_back(adj(s,t));
}
memset(pa,-,sizeof(pa));
memset(dist,,sizeof(dist));depth[]=;
dfs();init();
scanf("%d",&q);
set <int>::iterator it,itl,itr;
while (q--){
char cmd;int k;
scanf("%c",&cmd);
while (cmd!='+'&&cmd!='-'&&cmd!='?')
scanf("%c",&cmd);
if (cmd=='+'){
scanf("%d",&k);
Stone.insert(pre[k]);
it=Stone.find(pre[k]);
ans+=lca(*L(it),*it);
ans+=lca(*R(it),*it);
ans-=lca(*R(it),*L(it));
}
else if (cmd=='-'){
scanf("%d",&k);
it=Stone.find(pre[k]);
ans-=lca(*L(it),*it);
ans-=lca(*R(it),*it);
ans+=lca(*R(it),*L(it));
Stone.erase(it);
}
else if (cmd=='?')
printf("%lld\n",ans/);
}
return ;
}
//================fuction code====================
void dfs(int x)
{
pre[x]=++Index;
no[Index]=x;
int siz=Edge[x].size()-;
For(i,,siz){
adj &e=Edge[x][i];
if (depth[e.to]==){
depth[e.to]=depth[x]+;
pa[][e.to]=x;
dist[][e.to]=e.dist;
dfs(e.to);
}
}
maxd=max(maxd,depth[x]);
}
void init()
{
for(int i=;(<<i)<=maxd;i++)
For(x,,n){
int r=pa[i][x];
if (r<)
pa[i][x]=-;
else{
pa[i+][x]=pa[i][r];
dist[i+][x]=dist[i][x]+dist[i][r];
}
}
}
set <int>::iterator L(set <int>::iterator it)
{
if (it==Stone.begin())
return --Stone.end();
return --it;
}
set <int>::iterator R(set <int>::iterator it)
{
if (it==--Stone.end())
return Stone.begin();
return ++it;
}
LL lca(int u,int v)
{
LL res=;
u=no[u];v=no[v];
if (depth[u]<depth[v])//保证u在下面
u^=v^=u^=v;
for(int i=;(<<i)<=depth[u]-depth[v];i++)
if ((depth[u]-depth[v])&(<<i)){
res+=dist[i][u];
u=pa[i][u];
}
if (u==v) return res;
for(int i=;i>=;i--)
if (pa[i][v]!=pa[i][u]){
res+=dist[i][u]+dist[i][v];
u=pa[i][u];v=pa[i][v];
}
return res+dist[][v]+dist[][u];
}

T3代码

比赛网址:http://ch.ezoj.tk/contest/CH%20Round%20%2356%20-%20%E5%9B%BD%E5%BA%86%E8%8A%82%E6%AC%A2%E4%B9%90%E8%B5%9B