拓扑闲话
拓扑排序是图论的经典了吧?拓扑排序的模板是很简单的。
众所周知,在一张 DAG 上,我们如果每一次选择一个入度为 0 的点并删除,那么我们是完全可以按照一定顺序跑完这一整张图。
那么这么一个简单的算法,能有什么花样呢?
P3244 [HNOI2015] 落忆枫音
这是一道比较好玩的图论题。
首先我们发现,如果给定一张图,我们这张图的生成树,一共有 \(\prod\limits_{i=2}^n d_i\)。
我们考虑数学归纳法证明。假设我们有一个点 \(v\),使得他所有的前驱都已经在一个生成树上了,那么这个显然是会成立的。
那么我们发现,如果把他加到这个生成树上,一共会有几种可能呢?肯定就是他的入度种情况了。\(Q.E.D\)。
那么我们现在往里加一条边 \((s,t)\),那么就可能出现 \(s\rightarrow t\) 以及 \(t\rightarrow s\) 的环。
不过没有关系,我们可以用正难则反的思想,我们假设我们的一个图他包含了一个环,那么我们发现对于在 \(s,t\) 的这个环上的点集 \(A\),他们就必须组成一个环,换句话说,对于 \(A\) 中每一个点,他们的入度都为 \(1\)。所以这种情况一共有 \(\frac{\prod\limits_{i=2}^n d_i}{\prod\limits_{i\in A} d_i}\)。
那么现在的问题是,我们如何去算如上的式子呢?
我们考虑 dp。我们假设 \(f_i\) 表示从 \(t\) 出发到 \(i\) 的时候如上式子的值。那么我们知道转移为 \(f_i=\frac{\sum f_j}{d_i}\),然后最后的答案就是 \(\prod\limits_{i=2}^n d_i-f_s\) 啦。
注意拓扑的时候要给 \(t\) 赋值,而不是 \(s\),因为我们跑拓扑时还没有加上 \(s\) 到 \(t\) 的边。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=400005,MOD=1e9+7;
struct node
{
int ne,to;
}E[N];
int h[N],n,m,s,t,cnt,sum,in[N],inv[N],f[N],d[N];
void add(int u,int v)
{
E[cnt]={h[u],v};
h[u]=cnt++;
}
signed main()
{
memset(h,-1,sizeof(h));
cin>>n>>m>>s>>t;in[t]++;
inv[0]=inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
for(int i=1;i<=m;i++)
{
int u,v;cin>>u>>v;
add(u,v),in[v]++,d[v]++;
}
sum=1;
for(int i=2;i<=n;i++)
sum=sum*in[i]%MOD;
if(t==1) cout<<sum;
else
{
f[t]=sum;
queue<int> q;
for(int i=1;i<=n;i++)
if(!d[i])
q.push(i);
while(!q.empty())
{
int x=q.front();q.pop();
f[x]=f[x]*inv[in[x]]%MOD;
for(int i=h[x];~i;i=E[i].ne)
{
int v=E[i].to;
f[v]=(f[v]+f[x])%MOD;
if(!--d[v])
q.push(v);
}
}
cout<<(sum-f[s]+MOD)%MOD;
}
return 0;
}
P4934 礼物
这道题是个好玩的题,首先关注一下 \(a_i\wedge a_j\ge \min(a_i,a_j)\)。这也就是说,当且仅当 \(a_i\) 是 \(a_j\) 二进制下的超集。
那么也就是说,我们可以把二进制下互不包含的放到一个集合里,把一个是另一个超集的放到不同的集合。
换句话说,假设对于\(a_i\) 是 \(a_j\) 二进制下的超集的情况,我们对 \(i\) 和 \(j\) 连边,那么这道题就成了最长路问题。
我们用 DP 求解。我们令 \(f_i\) 表示以 \(i\) 为这些值的结尾的时候,最长路是多少,那么我们很容易得到转移 \(f_i=\max f_{i\vee(j\&-j)}+1\)。那么这道题就做完了。
至于最后输出,我们将 \(f_i\) 相同的 \(i\) 放到一个箱子里,因为这个时候他们互不为超集。然后这道题就做完了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+5;
int n,k,v[N],f[N],mx,MX;
vector<int> G[N];
signed main()
{
cin>>n>>k;
for(int i=1,x;i<=n;i++)
cin>>x,v[x]=1,mx=max(mx,x);
for(int i=0;i<=mx;i++)
{
for(int j=i;j;j^=(j&-j))
f[i]=max(f[i],f[i^(j&-j)]);
f[i]=f[i]+v[i];
if(v[i])
{
G[f[i]].push_back(i);
MX=max(MX,f[i]);
}
}
cout<<"1\n"<<MX<<"\n";
for(int i=1;i<=MX;i++)
{
cout<<G[i].size()<<" ";
for(int v:G[i]) cout<<v<<" ";
cout<<"\n";
}
return 0;
}
P6381 『MdOI R2』Odyssey
这道题很简单,就是先对于每个边的权值进行分解质因数,然后我们知道真正有用的部分是每个质因子个数 \(\pmod k\) 的部分,然后我们再记录一个 \(h_w\),表示每一个 \(w\) 我们每个质因子 \(k-\mod\) 的值,最后跑一边拓扑排序即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
map<int,int> f[N],H;
int n,m,k,to[N],ne[N],L[N],W[N],h[N],cnt,in[N],F[N];
void add(int u,int v,int w,int l)
{
to[cnt]=v,ne[cnt]=h[u];
L[cnt]=l,W[cnt]=w;
h[u]=cnt++;
}
int qsm(int x,int a)
{
int res=1;
for(;a;a>>=1,x=x*x)
if(a&1) res=res*x;
return res;
}
pair<int,int> solve(int x)
{
int ans=1,res=1;
for(int i=2;i*i<=x;i++)
{
if(x%i) continue;
int num=0;
for(;x%i==0;x/=i,num++);
ans=ans*qsm(i,num%k);
res=res*qsm(i,(k-num%k)%k);
}
if(x>1) ans=ans*x,res=res*qsm(x,k-1);
return {ans,res};
}
queue<int> q;
signed main()
{
memset(h,-1,sizeof(h));
cin>>n>>m>>k;
for(int i=1;i<=m;i++)
{
int u,v,w,l;
cin>>u>>v>>w>>l;in[v]++;
pair<int,int> tmp=solve(w);
w=tmp.first,H[w]=tmp.second;
add(u,v,w,l);
}
for(int i=1;i<=n;i++)
if(!in[i])
q.push(i);
if(k==1)
{
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=h[x];~i;i=ne[i])
{
int v=to[i],l=L[i];
F[v]=max(F[v],F[x]+l);
if(!--in[v]) q.push(v);
}
}
goto T;
}
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=h[x];~i;i=ne[i])
{
int v=to[i],w=W[i],l=L[i];
f[v][w]=max(f[v][w],f[x][H[w]]+l);
F[v]=max(F[v],f[v][w]);
if(!--in[v]) q.push(v);
}
}
T:
int mx=0;
for(int i=1;i<=n;i++)
mx=max(mx,F[i]);
cout<<mx;
return 0;
}

浙公网安备 33010602011771号