Link
题目描述
\(n\) 个点 \(m\) 条边的无向图被分成 \(k\) 个部分。每个部分包含一些点。
请选择一些关键点,使得每个部分恰有一个关键点,且每条边至少有一个端点是关键点。
解法
有一些限制条件,问你是否有合法方案,容易想到 2-SAT 。
思考如何建立新图,一共有两种限制:对于原图的每条边来说,它连接的两个点至少有一个点被选;对于一组相关联的点来说,他们之中只有一个被选择。可以看到对于每个点,它的状态只有选与不选这两种。
那么容易想到把每个点都拆成两个点,一个表示选(记为 \(u_1\)),另一个表示不选(记为 \(u_2\))。对于第一种限制,若原图中一条边连接了 \(u\) 和 \(v\) 两个点,那么连接 \(u_2\to v_1\) 和 \(v_2\to u_1\),因为若 \(u\) 不选必选 \(v\) ,后者同理。对于第二种限制,连接所有 \(u_1\to v_2\),其中 \(v\) 为除 \(u\) 外同一个部分的其它所有点,表示选了 \(u\) ,那么这个部分的其它点都不能选。
最后按套路跑一遍 \(tarjan\) 。
优化
可以看出,如果所有点都被划分在一个部分,即一个部分有 \(n\) 个点,那么总边数一定是 \(O(n^2)\) 的。
如果按最后分组时读入节点的顺序来看的话,那么我们其实是把一个点向一段连续的区间连边,容易想到线段树优化建边。然而非常毒瘤的是数据为 \(1e6\) ,那么线段树常数稍一大就会挂掉。
还可以怎么优化?
再次回到题目,我们漏掉了一个重要的条件:在一组之内,每次区间的左端点或者右端点都是固定的!
也就是说每次我们连接的其实是一个前缀和后缀,于是想到了前缀后缀优化。
如图,节点 \(u\) 只需要连接一个节点实际上就连接了整个前缀,后缀同理。而对于每个节点都只需要连接前缀后缀两个节点,故空间复杂度为 \(O(n)\)。至此可以很好地通过此题。
#include<stdio.h>
#define lid id<<1
#define rid id<<1|1
using namespace std;
#define N 5000007
template<class T>
inline void read(T &x){
T flag=1;x=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
x*=flag;
}
struct E{
int next,to;
}e[N<<1];
int n,m,K,cnt,len[N],a[N],dfn[N],c[N],low[N],tim=0,sta[N],top=0,scc_num=0,head[N];
bool vis[N];
inline void add(int id,int to){
e[++cnt]=(E){head[id],to};
head[id]=cnt;
}
inline int min(int x,int y){return x<y? x:y;}
inline void tarjan(int u){
dfn[u]=low[u]=++tim;
sta[top++]=u;vis[u]=true;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}else if(vis[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int y;
scc_num++;
do{
y=sta[--top];
vis[y]=false;
c[y]=scc_num;
}while(u!=y);
}
}
inline bool check(){
for(int i=1;i<=(n<<2);++i)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;++i)
if(c[a[i]]==c[a[i]+n]) return false;
return true;
}
int main(){
// freopen("sample2.in","r",stdin);
read(n),read(m),read(K);
for(int i=1;i<=m;i++){
int u,v;
read(u),read(v);
add(u+n,v),add(v+n,u);
}
int ret=0;
for(int i=1;i<=K;i++){
read(len[i]);
for(int j=1;j<=len[i];j++){
int pos=ret+j;
read(a[pos]);
if(j!=1){
add(a[pos]+2*n,a[pos-1]+2*n);
add(a[pos],a[pos-1]+2*n);
}
add(a[pos]+2*n,a[pos]+n);
}
ret+=len[i];
}
ret=0;
for(int i=1;i<=K;i++){
for(int j=1;j<=len[i];j++){
int pos=ret+j;
if(j!=len[i]){
add(a[pos]+3*n,a[pos+1]+3*n);
add(a[pos],a[pos+1]+3*n);
}
add(a[pos]+3*n,a[pos]+n);
}
ret+=len[i];
}
printf("%s",check()? "TAK":"NIE");
}
/*
5 4 1
1 2
1 3
3 4
4 5
5 2 3 1 5 4
*/