铜牌题
题意:给你一张无向图,点权代表你到达这个点时能获得的能力值,边权代表你当前必须拥有大于等于该边权的能力值你才能经过这条边,现在有q次询问,问每次从x位置以初始能力值y出发最多能获得多少能力值(可以反复经过点和边,但是点上的能力值只能获得一次)
思路:首先要考虑到如果某些边构成了一个环,那么这个环中最大的那条边是没有用处的,所以我们应该转化成一颗最小生成树去求解(vp的时候就到此为止了,呜呜呜),然后涉及到一个知识点:kruscal重构树,不知道是啥的可以百度或者看我的图论错题集1的最后一题,利用这个生成树的性质我们可以很容易维护出任意两点之间路径上最大边权的最小值,这样对于给定起点和终点的询问我们就可以O(1)的处理,但是这题要求的是最多能获得多少的能力值,换句话说就是在这棵树上它最多能跳到第几层,曾越高获得的能力值就越多,但是朴素暴力的复杂度会到O(n)(每次向父亲跳),怎么简化跳跃次数呢?倍增!这可是个好东西,不知道倍增的可以去学一学树上倍增解lca,其实就是我们利用二进制拆分维护出每个点的第1,2,4,8......轮祖先,然后每次只需要logn的次数就可以跳到我们需要的那个点(前提是你能找到一种单调性),这样对于每次询问我们只需要logn即可找到答案。但是有一个问题,我看了好多题解,写的都和我一样,但是这种写法如果说我每次正好只能跳到直接父亲,并且每次都是这样一直跳到根的话,它不会退化到暴力求解的复杂度吗(存疑)
#include <bits/stdc++.h>
using namespace std;
#define visit _visit
#define next _next
#define pb push_back
#define fi first
#define se second
#define endl '\n'
#define fast ios::sync_with_stdio(0), cin.tie(0)
#define int long long
#define ll long long
#define pint pair<int,int>
const int mod = 998244353;
const int maxn = 200001;
const int INF = 0x3f3f3f3f;
void read(int &x){
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
ll quick_pow(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll inv(ll x) {return quick_pow(x, mod-2);}
//----------------------------------------------------------------------------------------------------------------------//
int val[maxn<<1];
int w[maxn<<1];
struct e{
int a,b,val;
};
e es[maxn];
bool com(e a,e b){
return a.val<b.val;
}
int fa[maxn<<1];
int find(int x){
if(fa[x]==x){
return x;
}
return fa[x]=find(fa[x]);
}
void join(int a,int b){
int fa1=find(a);
int fa2=find(b);
fa[fa1]=fa2;
}
struct node{
int ne,to;
};
node edge[maxn<<2];
int head[maxn<<1];
int cnt=0;
void addedge(int a,int b){
edge[cnt].to=b;
edge[cnt].ne=head[a];
head[a]=cnt++;
}
int fa_st[maxn][21];
void dfs(int x,int f){
fa_st[x][0]=f;
for(int i=1;i<=20;i++){
fa_st[x][i]=fa_st[fa_st[x][i-1]][i-1];
}
for(int i=head[x];i!=-1;i=edge[i].ne){
int son=edge[i].to;
if(son==f){
continue;
}
dfs(son,x);
}
}
int query(int s,int v){
int ans=val[s]+v;
while(1){
int tem=s;
for(int i=20;i>=0;i--){
int f=fa_st[s][i];
if(ans>=w[f]){
s=fa_st[s][i];
}
ans=val[s]+v;
}
if(fa_st[s][0]==0||tem==s){
break;
}
}
return ans;
}
void solve(){
int n,m,q;
cin>>n>>m>>q;
for(int i=0;i<=n*2;i++){
fa[i]=i;
head[i]=-1;
}
for(int i=1;i<=n;i++){
cin>>val[i];
}
for(int i=1;i<=m;i++){
cin>>es[i].a>>es[i].b>>es[i].val;
}
sort(es+1,es+1+m,com);
int num=n;
for(int i=1;i<=m;i++){
int a=es[i].a;
int b=es[i].b;
int fa1=find(a);
int fa2=find(b);
if(fa1==fa2){
continue;
}
num++;
join(fa1,num);
join(fa2,num);
w[num]=es[i].val;
val[num]=val[fa1]+val[fa2];
addedge(fa1,num);
addedge(num,fa1);
addedge(fa2,num);
addedge(num,fa2);
}
dfs(num,0);
w[0]=INF*INF;
while(q--){
int s,v;
cin>>s>>v;
cout<<query(s,v)<<endl;
}
}
signed main(){
fast;
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
铜牌题
题意:给你一个全排列,你每轮可以选择任意对数将其交换(每轮中一个数只能用一次),然后求最少的轮数将所有的数都放到应该在的位置,即变成1-n
思路:首先要能想到如果以图论的角度思考的话,这张代表位置关系的图其实都是由环组成的,而环无非奇环和偶环两种,最容易想到的做法就是我每次去把当前位置需要的数换过来,这样一次换出1/2,最多只需要logn轮就可以换完,但是还有更简单的做法,我们考虑让第一轮为第二轮做铺垫,即在第一轮时将所有的环都拆成长度为2的偶环,那能不能做到呢,我们举例来看,假设我这张图上点表示的是位置,u-v的边代表u这个位置上的数需要移动到v这个位置上,那么我的交换操作在这张图上就体现为交换结点,并且入边不变,出边随点变化,所以对于奇环来说我们只需要将1~n/2分别和n-1~n/2+1交换即可全部构造成长度为2的偶环。
#include <bits/stdc++.h>
using namespace std;
#define visit _visit
#define next _next
#define pb push_back
#define fi first
#define se second
#define endl '\n'
#define fast ios::sync_with_stdio(0), cin.tie(0)
#define int long long
#define ll long long
#define pint pair<int,int>
const int mod = 998244353;
const int maxn = 200001;
const int INF = 0x3f3f3f3f;
void read(int &x){
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
ll quick_pow(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll inv(ll x) {return quick_pow(x, mod-2);}
//------------------