• 【YbtOJ#573】后缀表达


    题目

    题目链接:https://www.ybtoj.com.cn/contest/115/problem/2



    (nleq 2500)

    思路

    建出表达式树,如果一个非叶子节点有多个相同字母儿子显然可以把相同字母合并。
    然后由于满足结合律,如果一个符号存在一个相同符号儿子,那么就可以把这个儿子的所有儿子合并到上面。注意字母重复依然要合并。
    然后考虑在树上 dp。
    (f_x) 表示节点 (x) 的子树合并后最少占用单元。
    如果直接把所有儿子连起来,那么

    [f_x=1+sum_{yin ext{son}(x)}f_y ]

    因为我们把所有字母合并了,现在可能减小占用单元当且仅当一个叶子节点排在一个非叶子节点前面,并且这个非叶子节点存在一种排序使得叶子节点所表示字母可以排在最前面的同时,所需单元不增加。因为如果所需单元增加,而我们只能减小 (1) 个单元,所以显然不优。
    由于一个叶子只能和一个非叶子结合,考虑二分图匹配。左边是 (26) 个字母,右边是儿子节点。记 (avl[x][i]) 表示节点 (x) 能否在不增加最少需要单元的前提下把字母 (i) 移到最前面,那么如果 (avl[v][i]=1),就从 (i)(v) 连边。然后最大匹配就是最多能减小的单元。
    接下来考虑如何合并 (avl[x])(avl[x][i]=1) 当且仅当 (x)(i) 字母这个儿子;或者存在儿子 (v) 满足 (avl[v][i]=1) 并且最大匹配可以不匹配 (v)
    那么如果本来就没有匹配显然可行,如果本来匹配了,我们就从它的匹配点再尝试找一条增广路,如果找得到那么说明可以不用匹配 (v)
    答案就是 (f[n])
    时间复杂度 (O(mn^2)),其中 (m=26)

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=1000050;
    int n;
    char s[N];
    
    struct edge
    {
    	int next,to;
    	bool del;
    };
    
    struct Graph
    {
    	int tot,head[N],vis[N],pos[N];
    	edge e[N];
    	
    	void clr(int k)
    	{
    		for (int i=N-1;i>=N-30;i--)
    			pos[i]=0,vis[i]=0,head[i]=-1;
    		for (int i=0;i<=k;i++)
    			pos[i]=0,vis[i]=0,head[i]=-1;
    		tot=0;
    	}
    	
    	void add(int from,int to)
    	{
    		e[++tot]=(edge){head[from],to,0};
    		head[from]=tot;
    	}
    	
    	bool dfs(int x,int t)
    	{
    		if (vis[x]==t) return 0;
    		vis[x]=t;
    		for (int i=head[x];~i;i=e[i].next)
    		{
    			int v=e[i].to;
    			if (!pos[v] || dfs(pos[v],t))
    			{
    				pos[v]=x;
    				return 1;
    			}
    		}
    		return 0;
    	}
    	
    	bool find(int i)
    	{
    		if (!pos[i]) return 1;
    		return dfs(pos[i],i+19260817);
    	}
    }G;
    
    struct Tree
    {
    	int tot,head[N],f[N],rk[N];
    	bool typ[N][30],avl[N][30];
    	edge e[N];
    	
    	void add(int from,int to)
    	{
    		if (s[to]>='a' && s[to]<='z')
    		{
    			if (typ[from][s[to]-'a']) return;
    			typ[from][s[to]-'a']=1;
    		}
    		e[++tot]=(edge){head[from],to,0};
    		head[from]=tot;
    	}
    	
    	void dfs1(int x)
    	{
    		for (int i=head[x];~i;i=e[i].next)
    		{
    			int v=e[i].to;
    			dfs1(v);
    			if (s[v]==s[x])
    			{
    				for (int j=head[v];~j;j=e[j].next)
    					if (!e[j].del) add(x,e[j].to),e[j].del=1;
    				e[i].del=1;
    			}
    		}
    	}
    	
    	void dfs2(int x)
    	{
    		f[x]=1;
    		if (head[x]==-1) return;
    		for (int i=head[x];~i;i=e[i].next)
    		{
    			int v=e[i].to;
    			if (!e[i].del) dfs2(v);
    		}
    		tot=0;
    		for (int i=head[x];~i;i=e[i].next)
    			if (!e[i].del)
    			{
    				int v=e[i].to;
    				f[x]+=f[v]; rk[++tot]=v;
    				for (int j=0;j<26;j++)
    					if (typ[x][j] && avl[v][j])
    						G.add(N-1-j,tot);
    			}
    		for (int i=0;i<26;i++)
    			if (typ[x][i] && G.dfs(N-1-i,i+1)) f[x]--;
    		for (int i=1;i<=tot;i++)
    			if (G.find(i))
    				for (int j=0;j<26;j++)
    					avl[x][j]|=avl[rk[i]][j];
    		G.clr(tot);
    		for (int i=0;i<26;i++)
    			avl[x][i]|=typ[x][i];
    	}
    }T;
    
    void build()
    {
    	stack<int> st;
    	for (int i=1;i<=n;i++)
    	{
    		if (s[i]<'a' || s[i]>'z')
    		{
    			int x=st.top(); st.pop();
    			int y=st.top(); st.pop();
    			T.add(i,x); T.add(i,y);
    		}
    		st.push(i);
    	}
    }
    
    int main()
    {
    	freopen("expr.in","r",stdin);
    	freopen("expr.out","w",stdout);
    	memset(T.head,-1,sizeof(T.head));
    	memset(G.head,-1,sizeof(G.head));
    	scanf("%s",s+1);
    	n=strlen(s+1);
    	build();
    	T.dfs1(n); T.dfs2(n);
    	printf("%d",T.f[n]);
    	return 0;
    }
    
  • 相关阅读:
    僵尸进程与孤儿进程/守护进程/互斥锁
    基于udp协议支持并发的套接字/并发相关的概念/开启进程的两种方式/进程对象的方法
    python3和python2的区别
    安装python
    浅谈aiohttp和middlewares
    IO多路复用之select,poll,epoll个人理解
    python之进程,线程,协程简单理解
    Python类__call__()方法
    python中str与bytes
    测试理论基础三
  • 原文地址:https://www.cnblogs.com/stoorz/p/14409556.html
Copyright © 2020-2023  润新知