【题意描述】
给定一个有向无环图,和若干询问,每次询问从一点出发的路径上的边的边权形成的字符串的字典序第k小的长度至少为1的路径的终点。
【思路】
这道题很有意思。我们可以先预处理每个点出发的路径条数,这个可以用dp
实现。考虑暴力,我们每次在树上贪心地走。考虑倍增,我们以每个点dp值最大的那个儿子为重儿子。预处理
表示沿重边走
步的点和
表示向下走
步的所有字典序排在重边以前的轻边的方案之和。考虑找到一条答案路径的时间,由于我们走轻边会使方案数至少少一半,所以最多会经过log条轻边和log条重边。每次需要走重边时,我们可以
倍增,每次需要走轻边时,我们可以
二分走哪个分支。注意方案数可能很大,可能爆longlong,故方案数需要对一个极大值取min。时间复杂度
。
代码:
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=2e5+5;
typedef long long ll;
inline int red(){
int data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}
int n,m,a,b,q,in[N],len[N];
const ll lim=1e15;
vector<int>g[N];
vector<ll>sum[N];
ll dp[N],s[N][19];
inline void cmin(ll&x,const ll&y){(x>y)&&(x=y);}
int son[N][19];
void dfs(int u){
if(~dp[u])return;int v;dp[u]=0;
sum[u].resize(len[u]=g[u].size());
for(int re i=0;i<len[u];i++){
dfs(v=g[u][i]);
if(dp[v]>dp[son[u][0]])son[u][0]=v,s[u][0]=dp[u]+1;
cmin(dp[u]+=dp[v]+1,lim);
sum[u][i]=dp[u];
}for(int re i=1;i<18;++i)
son[u][i]=son[son[u][i-1]][i-1],s[u][i]=min(lim,s[u][i-1]+s[son[u][i-1]][i-1]);
}
int find(int u,int k){
if(!k)return u;
for(int re i=17;~i;--i)
if(s[u][i]<=k&&s[u][i]+dp[son[u][i]]>=k)
return find(son[u][i],k-s[u][i]);
int l=0,r=len[u]-1,mid;
while(l<r)sum[u][mid=(l+r)>>1]<k?l=mid+1:r=mid;
return find(g[u][l],k-(l-1<0?1:sum[u][l-1]+1));
}
int main(){
n=red();m=red();
memset(dp,-1,sizeof(dp[0])*(n+1));
for(int re i=1;i<=m;i++){
a=red();b=red();
g[a].push_back(b);
in[b]++;
}for(int re i=1;i<=n;i++)if(!in[i])dfs(i);
q=red();
while(q--){
a=red();b=red();
if(dp[a]<b){puts("-1");continue;}
cout<<find(a,b)<<"\n";
}
}