• YbtOJ#573后缀表达【二分图匹配】


    正题

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


    题目大意

    给出一个包含字母变量和若干种同级操作符的后缀表达式。求一个等价的表达式满足该表达式的连续相同段最少。

    \(1\leq |S|\leq 2500\)


    解题思路

    构建出表达树先,然后看一下什么能够化简,

    1. 两个相邻的相同运算符可以合并
    2. 一个非叶子节点下的相同叶子节点(字母节点)可以合并

    先把这些合并了,然后目前的最优解就是现在的节点数量,但是还有一种情况可以合并。

    就是兄弟节点中,非叶子节点和叶子节点可以合并。

    用类树形\(dp\)求出所有节点的子树中的所有表达式的最优答案,如果不考虑上面那种情况就有

    \[ans_i=1+\sum_{x->y}ans_y \]

    然后考虑一个非叶子节点在最优情况下能否以某个字母作为开头,定义\(avl_{x,c}\)表示\(x\)节点在\(ans\)最大的情况下能否以\(c\)作为开头。(因为上面那种情况最多剩下一个费用,如果这里牺牲了子树的最优性那么至少需要增加一点费用,显然是一定不优的)

    那么对于一个节点的所有儿子,将非叶子节点和叶子节点分成二分图,如果非叶子节点的\(x\)满足\(avl_{x,c}=1\),那么向\(c\)连边。

    然后跑二分图匹配就是\(ans\)可以减去的价值。

    如何求出\(avl\)?如果一个字母\(c\)\(x\)的儿子那么显然可以作为开头,否则如果有一个字母\(c\)满足\(x\)的一个非叶子儿子\(y\)使得\(avl_{y,c}=1\),并且在二分图上删去\(y\)节点不会影响答案时,此时将该子树作为开头即可。

    如何判断删除一个节点后最大匹配不变,如果原图中该点没有匹配显然可以直接删去。如果有匹配,那么将该节点打上禁止标记后从它的匹配点开始求一条增广路,如果有则可以删去。

    时间复杂度\(O(n^2)\)


    code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<stack>
    using namespace std;
    const int N=3100;
    struct node{
    	int to,next;
    }a[N];
    int n,cnt,tot,ls[N],ans[N];
    bool del[N],leaf[N],ch[N][27],avl[N][27];
    char s[N];stack<int> st;
    namespace M{
    	node a[N*27];bool v[27];
    	int tot,ls[27],link[N];
    	void clear(){
    		for(int i=1;i<=cnt;i++)link[i]=0;
    		for(int i=1;i<=26;i++)ls[i]=0;
    		tot=cnt=0;return;
    	}
    	void addl(int x,int y){
    		a[++tot].to=y;
    		a[tot].next=ls[x];
    		ls[x]=tot;return;
    	}
    	int find(int x){
    		if(v[x])return 0;
    		int p,q;v[x]=1;
    		for(int i=ls[x];i;i=a[i].next){
    			int y=a[i].to;
    			p=link[y];link[y]=x;
    			if(!p||find(p))return 1;
    			link[y]=p;
    		}
    		return 0;
    	}
    	int Match(int x){
    		memset(v,0,sizeof(v));
    		return find(x);
    	}
    	int Path(int x){
    		if(!link[x])return 1;
    		return Match(link[x]);
    	}
    }
    bool isabc(char c)
    {return (c>='a')&&(c<='z');}
    void addl(int x,int y){
    	if(leaf[y])ch[x][s[y]-'a'+1]=1;
    	else{
    		a[++tot].to=y;
    		a[tot].next=ls[x];
    		ls[x]=tot;
    	}
    	return;
    }
    void Merge(int x,int y){
    	for(int i=1;i<=26;i++)ch[x][i]|=ch[y][i];
    	for(int i=ls[y];i;i=a[i].next)
    		if(!del[a[i].to])addl(x,a[i].to);
    	del[y]=1;return;
    }
    void dfs(int x){
    	for(int i=ls[x];i;i=a[i].next){
    		int y=a[i].to;dfs(y);
    		if(s[x]==s[y])Merge(x,y);
    	}
    	for(int i=1;i<=26;i++)ans[x]+=ch[x][i];
    	ans[x]++;return;
    }
    void dp(int x){
    	for(int i=ls[x];i;i=a[i].next)
    		if(!del[a[i].to])dp(a[i].to);
    	M::clear();
    	for(int i=ls[x];i;i=a[i].next){
    			int y=a[i].to;
    		if(del[y])continue;
    		++cnt;ans[x]+=ans[y]; 
    		for(int j=1;j<=26;j++)
    			if(avl[y][j]&&ch[x][j])
    				M::addl(j,cnt);
    	}
    	for(int i=1;i<=26;i++)
    		if(ch[x][i])ans[x]-=M::Match(i);
    	for(int i=1;i<=26;i++)avl[x][i]=ch[x][i];
    	for(int i=ls[x],p=0;i;i=a[i].next){
    		int y=a[i].to;
    		if(del[y])continue;p++;
    		if(M::Path(p)){
    			for(int j=1;j<=26;j++)
    				avl[x][j]|=avl[y][j];
    		}
    	}
    	return;
    } 
    int main()
    {
    	freopen("expr.in","r",stdin);
    	freopen("expr.out","w",stdout);
    	scanf("%s",s+1);
    	int l=strlen(s+1);
    	for(int i=1;i<=l;i++){
    		++n;
    		if(isabc(s[i]))leaf[n]=1;
    		else{
    			addl(n,st.top());st.pop();
    			addl(n,st.top());st.pop();
    		}
    		st.push(n);
    	}
    	dfs(n);
    	dp(n);
    	printf("%d\n",ans[n]);
    	return 0;
    }
    
  • 相关阅读:
    编码以及⼩知识点补充
    基本数据类型(dict)
    基本数据类型(list,tuple)
    python基础
    Eclipse使用技巧
    条件与(&&)和逻辑与(&)以及条件或(||)和逻辑或(|)区别
    到底怎么样才叫看书?——Tony Zhao's
    Jsp servlet 值传递。。
    (06)odoo报表
    (05)odoo数据库和业务操作
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/14407412.html
Copyright © 2020-2023  润新知