考虑最小字典序,显然可以贪心求。对于每次的点来说,看是否可以删边使得更小的点入队。
然后我们可以发现,对于拓扑排序来说,删边的实质就是使得某个点的入度减1。然后我们可以用线段树维护每个点的入度,然后每次可以取出一个最小的,入度满足小于等于k的点,看是否通过操作使其入队。
维护最小字典序的队列,我们换成优先队列即可。注意细节,不要让一个点重复入队。
AC代码:
#pragma GCC optimize("-Ofast","-funroll-all-loops")
#include
//#define int long long
using namespace std;
const int N=1e5+10,inf=0x3f3f3f3f,mod=1e9+7;
int n,deg[N],m,k,res,pos,mi[N<<2],cnt,vis[N];
vector<int> g[N];
#define mid (l+r>>1)
void change(int p,int l,int r,int x,int v){
if(l==r){mi[p]=v; return ;}
if(x<=mid) change(p<<1,l,mid,x,v);
else change(p<<1|1,mid+1,r,x,v);
mi[p]=min(mi[p<<1],mi[p<<1|1]);
}
int ask(int p,int l,int r,int v){
if(l==r) return l;
if(mi[p<<1]<=v) return ask(p<<1,l,mid,v);
else return ask(p<<1|1,mid+1,r,v);
}
void Top(){
priority_queue<int,vector<int>,greater<int> > q; cnt=0;
for(int i=1;i<=n;i++){
if(!deg[i]) q.push(i),change(1,1,n,i,inf),vis[i]=1;
else change(1,1,n,i,deg[i]);
}
while(q.size()){
if(mi[1]>k) pos=inf;
else pos=ask(1,1,n,k);
if(q.top()>pos) q.push(pos),k-=deg[pos],change(1,1,n,pos,inf),vis[pos]=1;
int u=q.top(); q.pop(); res=(res+1LL*(++cnt)*u%mod)%mod;
for(int to:g[u]){
if(--deg[to]==0&&!vis[to]) change(1,1,n,to,inf),q.push(to),vis[to]=1;
if(deg[to]>0&&!vis[to]) change(1,1,n,to,deg[to]);
}
}
printf("%d\n",res);
}
void solve(){
scanf("%d %d %d",&n,&m,&k); res=0;
for(int i=1;i<=n;i++) g[i].clear(),deg[i]=vis[i]=0;
for(int i=1,a,b;i<=m;i++) scanf("%d %d",&a,&b),g[a].push_back(b),deg[b]++;
Top();
}
signed main(){
int T; cin>>T; while(T--) solve();
return 0;
}