【map】P4889 kls与flag
可以发现这道题求的是
assume j>i
j−i=h[i]+h[j]
j−i=abs(h[i]=h[j])
的对数。
那么显然,因为高度大于0,所以一个数对不可能同时满足两条式子,所以可以分开算。
那么进行分类讨论:
- (1) j−i=hi+hj -->hi+i=j−hj,那么开一个map,存下所有值的个数,每次都更新答案
- (2) j−i=hj−hi, -->j−hj=i−hi,同(1)
- (3) j−i=hi−hj, -->hi+i=hj+j,同(1)
那么如何保证j>i呢?倒序不就好了吗!
const int N=2e5+10;
int h[N];
int n,m;
map<int,int>mp;
#define big long long
big ans;
int main(){
rd(n),rd(m);
rep(i,1,n)rd(h[i]);
//情况1
dwn(i,n,1){
ans+=(big)mp[h[i]+i];
mp[i-h[i]]++;
}
mp.clear();
//情况3
dwn(i,n,1){
ans+=(big)mp[i+h[i]];
mp[h[i]+i]++;
}
mp.clear();
//情况2
dwn(i,n,1){
ans+=(big)mp[i-h[i]];
mp[i-h[i]]++;
}
printf("%lld",ans);
return 0;
}
【倍增】P1613 跑路
先用一个数组预处理出可以一步到达的点对,再上floyd(n<=50不上floyd上什么)
BTW:部分盆友的智商堪忧,maxlongint 就是maxint 啊,你们开什么64,明明31就行了。
const int N=60;
int n,m;
int dis[N][N];
bool G[N][N][64+5];
inline void prework(){
rep(logn,1,64)
rep(k,1,n)
rep(i,1,n)
rep(j,1,n)
if(G[i][k][logn-1] && G[k][j][logn-1]){
G[i][j][logn]=1;
dis[i][j]=1;
}
}
inline void floyd(){
rep(k,1,n)
rep(i,1,n)
rep(j,1,n)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
int main(){
#ifdef WIN32
freopen("a.txt","r",stdin);
#endif
rd(n),rd(m);
mem(dis,0x3f);
while(m--){
int u,v;rd(u),rd(v);
G[u][v][0]=1;
dis[u][v]=1;
}
prework();
floyd();
printf("%d",dis[1][n]);
return 0;
}
【倍增】#137. 关联点
思考过程:
每个点网上跳val[i]步,到达的节点就是它产生贡献的节点,可是怎么判断是左边产生的贡献还是右边产生的贡献呢?很简单,我们只需条val[i]-1步,判断当前节点是它父节点的左儿子还是右儿子就好。
bebug:lson 和rson 不为0的时候才记录啊,我真是个智娃。
const int N=2e5+10;
int n,m;
int val[N],f[N][18+2],son[N][2],ansl[N],ansr[N];
struct edge{
int v,next;
}e[N<<1];
int head[N],edge_num;
inline void adde(int u,int v){
e[++edge_num].v=v;
e[edge_num].next=head[u];
head[u]=edge_num;
}
int main(){
rd(n);
rep(i,1,n){
rd(val[i]);val[i]--;//开始就自减一
}
rep(i,1,n){
int lson,rson;
rd(lson),rd(rson);
if(lson)f[lson][0]=i,son[i][0]=lson;
if(rson)f[rson][0]=i,son[i][1]=rson;
}
rep(i,0,18)
rep(u,1,n)
f[u][i+1]=f[f[u][i]][i];
rep(u,1,n){
int x=u;//因为u要一直往上跳,而u还在循环里,不能直接改变,所以多开一个变量
dwn(i,18,0)
if((1<<i) & val[u])x=f[x][i];//注意这里是&val[u] 而不是一直在跳的val[x]
int fa=f[x][0];
if(son[fa][0]==x)ansl[fa]++;
else ansr[fa]++;
}
rep(i,1,n)
printf("%d %d
",ansl[i],ansr[i]);
return 0;
}
【倍增】P1967 货车运输
SOL
首先便是想到了Floyd的暴力方法,状态转移方程也不难推出:w[i] [j]=min(w[i] [j], w[i] [k]+w[k] [j]);但是n^ 3次方时间复杂度和n^2的空间复杂度是显然不可取的。
于是我们思考,可以发现有一些权值较小的边是不会被走过的。正如样例中的第三条边,就算有其他的很多条边,这条边无论如何也是不会被走过的。于是我们想到了可以将图中这样的边去掉,按照这个思路我们便想到了构造最大生成树,将其余的边去除。
得到了这样一个树之后,我们便考虑如何求出两个节点之间最小边权的最大值(即为题中的最大载重),因为这两点之间的路径是唯一的,我们只需要找出这条路径便可以得到答案。我们可以通过LCA来做到这一点,我求LCA的方法是先从每一个根节点进行搜索,求出节点深度等信息,然后利用这些信息进行树上倍增。
于是我们可以得出大体思路:首先重新建图,构造出最大生成树,然后在最大生成树上求LCA来回答询问。
const int N=1e4+10,M=5e4+10;
int f[N][18],w[N][18],fa[N],deep[N];
int tt,n,m,q;
struct edge{
int u,v,w;
bool operator <(const edge &rhs)const{
return w>rhs.w;
}
}e[M<<1];
inline int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
struct tree{//最大生成树
int v,next,w;
}tree[N<<1];
int head[N],edge_num;
inline void adde(int u,int v,int w){
tree[++edge_num].v=v;
tree[edge_num].w=w;
tree[edge_num].next=head[u];
head[u]=edge_num;
}
inline void kruskal(){
sort(e+1,e+m+1);
rep(i,1,n)fa[i]=i;
rep(i,1,m){
int fx=find(e[i].u),fy=find(e[i].v);
if(fx!=fy){
fa[fx]=fy;
adde(e[i].u,e[i].v,e[i].w);
adde(e[i].v,e[i].u,e[i].w);
}
}
}
inline void dfs(int u,int dad,int val){
w[u][0]=val;
deep[u]=deep[dad]+1;
f[u][0]=dad;
rep(i,0,tt){
f[u][i+1]=f[f[u][i]][i];
w[u][i+1]=min(w[u][i],w[f[u][i]][i]);
}
for(int i=head[u];i;i=tree[i].next){
int v=tree[i].v;
if(v==dad)continue;
dfs(v,u,tree[i].w);
}
}
inline int lca(int u,int v){
int res=INT_MAX;
if(deep[u]<deep[v])swap(u,v);
dwn(i,tt,0){
if(deep[u]-(1<<i)>=deep[v]){
res=min(res,w[u][i]);
u=f[u][i];
}
}
if(u==v)return res;
dwn(i,tt,0){
if(f[u][i]!=f[v][i]){
res=min(res,min(w[u][i],w[v][i]));
u=f[u][i],v=f[v][i];
}
}
return min(res,min(w[u][0],w[v][0]));
}
int main(){
mem(w,INT_MAX);
rd(n),rd(m);
tt=log2(n)+1;
rep(i,1,m){
rd(e[i].u),rd(e[i].v),rd(e[i].w);
}
kruskal();
rep(i,1,n)//题上没有说是连通图,可能是森林
if(i==fa[i])
dfs(i,i,INT_MAX);
rd(q);
while(q--){
int u,v;rd(u),rd(v);
if(find(u)!=find(v))puts("-1");
else printf("%d
",lca(u,v));
}
return 0;
}