题目大意
对于一个长为$N$的序列$A$,定义它所对应的逆序图:
有$N$个点组成,标号为$1...N$的无向图,对于每一组$i,j(i<j)$若存在$A_i>A_j$则在新图中就存在一条$(A_i,A_j)$的无向边。
现在给定一个$N(N\leq 1000)$个点的图,保证它是某个序列对应的逆序图,求它有多少个点集$S$,满足$\forall x\in S,y\in S$不存在边$(x,y)$,$\forall x\notin S$至少存在一条边$(x,y)$使得$y\in S$。即求图独立覆盖集的数量。
题解
不难发现这个序列可以的转化为一个排列。
我们可以用拓扑排序整理每对数的大小关系,从而在序列上考虑这个问题。
对于第一个条件,即点集内两两没有连边,就是满足在序列上点集对应的位置恰好形成一个上升的子序列。
对于第二个条件,满足对于$\forall x\notin S$一定至少存在一个$y\in S$使得$A_x<A_y,x>y$或$A_y<A_x,x<y$。
假设$x\notin S,y\in S$,由于$\{A_y\}$为上升子序列,和$A_x$组成逆序对的是左侧最大的$A_x$和右侧最小的$A_x$,那么就是$A_y<A_x(x=max\{x\in S,x<y\})$或$A_y>A_x(x=\min\{x\in S,x>y\})$,即对于$i,j\in S,A_i$不存在$k$使得$i<k<j,A_i<A_k<A_j,k\notin S$这样就很显然了,直接$Dp$,设$F_i$表示最后一个是$i$前$i$个合法的方案数。
暴力转移即可。复杂度$O(n^2)$。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define M 1020 using namespace std; LL read(){ LL nm=0,fh=1; char cw=getchar(); for(;!isdigit(cw);cw=getchar()) if(cw==‘-‘) fh=-fh; for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-‘0‘); return nm*fh; } LL n,m,fs[M],nt[M*M],to[M*M],tmp,ind[M],p[M],cnt,tot,q[M],hd,tl; LL mp[M][M],F[M],G[M],ans; void link(LL x,LL y){nt[tmp]=fs[x],fs[x]=tmp,to[tmp++]=y,ind[y]++;} int main(){ n=read(),m=read(),memset(fs,-1,sizeof(fs)); while(m--) mp[read()+1][read()+1]=1; for(LL i=1;i<=n;i++){ for(LL j=i+1;j<=n;j++) if(mp[i][j]||mp[j][i]) link(j,i);else link(i,j); } for(LL i=1;i<=n;i++) if(!ind[i]) q[tl++]=i; while(hd<tl){ LL x=q[hd++]; p[x]=++cnt; for(LL i=fs[x];i!=-1;i=nt[i]) if(!(--ind[to[i]])) q[tl++]=to[i]; } F[0]=1; for(LL i=0;i<n;i++){ LL minn=n+2; for(LL j=i+1;j<=n;j++){ if(p[j]>minn||p[j]<p[i]) continue; F[j]+=F[i],minn=p[j]; } } for(LL i=n,maxn=0;i;i--) if(p[i]>maxn) ans+=F[i],maxn=p[i]; printf("%lld\n",ans); return 0; }
原文地址:https://www.cnblogs.com/OYJason/p/9744776.html
时间: 2024-10-13 05:30:54