【YbtOJ#608】前缀编码

题目

题目链接:https://www.ybtoj.com.cn/contest/127/problem/1
【YbtOJ#608】前缀编码

\(n,\sum|S|\leq 5\times 10^5\)。

思路

由于每一个 ? 只能填 \(0\) 或 \(1\),不难想到 2-sat。
将串安装长度排序,把每一个 ? 分别当作 \(0\) 和 \(1\) 一次扔进一棵 Trie 中,Trie 上每一个节点维护以该节点结尾的字符串集合。走到一个点时将现在的字符串与该节点的集合中的字符串连边,注意一个字符串需要区分 ? 填 \(0\) 或 \(1\)。
然后跑 2-sat 即可。
但是这样的空间复杂度是 \(O(n^2)\) 的。不可以接受。我们发现如果空间需要卡满 \(O(n^2)\),那么短串一定多,这会导致答案为 NO。所以我们每插入 lim 个串就跑一次 tarjan,如果已经无解了就输出 NO 即可。我取 \(lim=1000\)。
说白了就是一个乱搞,实际上有时空严格 \(O(n+m)\) 的做法。

代码

#include <bits/stdc++.h>
#define mp make_pair
#define NOT(x) ((x>m)?(x-m):(x+m))
using namespace std;

const int N=1000010,M=20000010;
int n,m,tot,cnt,head[N],dfn[N],low[N],col[N];
char s[N],t[N];
bool vis[N];
stack<int> st;

struct node
{
	int pos,len,p;
}b[N];

struct edge
{
	int next,to;
}e[M];

void add(int from,int to)
{
	e[++tot]=(edge){head[from],to};
	head[from]=tot;
}

bool cmp(node x,node y)
{
	return x.len<y.len;
}

struct Trie
{
	int tot,ch[N][2];
	vector<int> end[N];
	Trie() { tot=1; }
	
	bool ins(int l,int r,int k)
	{
		int p=1;
		for (int i=l;i<=r;i++)
		{
			int id=s[i]-48;
			if (!ch[p][id]) ch[p][id]=++tot;
			p=ch[p][id];
			for (int j=0;j<end[p].size();j++)
				add(end[p][j],NOT(k)),add(k,NOT(end[p][j]));
		}
		end[p].push_back(k);
		return 0;
	}
}trie;

void tarjan(int x)
{
	dfn[x]=low[x]=++cnt;
	st.push(x); vis[x]=1;
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (!dfn[v])
		{
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if (vis[v])
			low[x]=min(low[x],dfn[v]);
	}
	if (low[x]>=dfn[x])
	{
		int y; n++;
		do {
			y=st.top(); st.pop();
			col[y]=n; vis[y]=0;
		} while (x!=y);
	}
}

int main()
{
	freopen("code.in","r",stdin);
	freopen("code.out","w",stdout);
//	return printf("%d\n",sizeof(e)/1024/1024),0;
	memset(head,-1,sizeof(head));
	scanf("%d",&m);
	for (int i=1;i<=m;i++)
	{
		scanf("%s",t);
		b[i]=(node){n+1,(int)strlen(t),0};
		for (int j=0;j<b[i].len;j++)
		{
			s[++n]=t[j];
			if (s[n]=='?') b[i].p=n;
		}
	}
	sort(b+1,b+1+m,cmp);
	for (int i=1;i<=m;i++)
	{
		if (b[i].p)
		{
			s[b[i].p]='0';
			if (trie.ins(b[i].pos,b[i].pos+b[i].len-1,i))
				return printf("NO"),0;
			s[b[i].p]='1';
			if (trie.ins(b[i].pos,b[i].pos+b[i].len-1,i+m))
				return printf("NO"),0;
		}
		else
		{
			int id=i+(s[b[i].pos]-48)*m;
			if (trie.ins(b[i].pos,b[i].pos+b[i].len-1,id))
				return printf("NO"),0;
			if (s[b[i].pos]==48) add(i+m,i);
			if (s[b[i].pos]==49) add(i,i+m);
		}
		if (i%1000==0)
		{
			cnt=n=0;
			for (int j=1;j<=i;j++)
				if (!dfn[j]) tarjan(j);
			for (int j=1;j<=i;j++)
				if (col[j]==col[j+m])
					return printf("NO"),0;
			for (int j=1;j<=i;j++)
				col[j]=dfn[j]=col[j+m]=dfn[j+m]=0;
		}
	}
	cnt=n=0;
	for (int i=1;i<=m;i++)
		if (!dfn[i]) tarjan(i);
	for (int i=1;i<=m;i++)
		if (col[i]==col[i+m])
			return printf("NO"),0;
	printf("YES");
	return 0;
}

上一篇:Trie树


下一篇:R与金钱游戏:均线黄金交叉1