YbtOJ#573-后缀表达【二分图匹配】
正題
題目鏈接:https://www.ybtoj.com.cn/contest/115/problem/2
題目大意
給出一個包含字母變量和若干種同級操作符的后綴表達式。求一個等價的表達式滿足該表達式的連續相同段最少。
1≤∣S∣≤25001\leq |S|\leq 25001≤∣S∣≤2500
解題思路
構建出表達樹先,然后看一下什么能夠化簡,
先把這些合并了,然后目前的最優解就是現在的節點數量,但是還有一種情況可以合并。
就是兄弟節點中,非葉子節點和葉子節點可以合并。
用類樹形dpdpdp求出所有節點的子樹中的所有表達式的最優答案,如果不考慮上面那種情況就有
ansi=1+∑x?>yansyans_i=1+\sum_{x->y}ans_yansi?=1+x?>y∑?ansy?
然后考慮一個非葉子節點在最優情況下能否以某個字母作為開頭,定義avlx,cavl_{x,c}avlx,c?表示xxx節點在ansansans最大的情況下能否以ccc作為開頭。(因為上面那種情況最多剩下一個費用,如果這里犧牲了子樹的最優性那么至少需要增加一點費用,顯然是一定不優的)
那么對于一個節點的所有兒子,將非葉子節點和葉子節點分成二分圖,如果非葉子節點的xxx滿足avlx,c=1avl_{x,c}=1avlx,c?=1,那么向ccc連邊。
然后跑二分圖匹配就是ansansans可以減去的價值。
如何求出avlavlavl?如果一個字母ccc是xxx的兒子那么顯然可以作為開頭,否則如果有一個字母ccc滿足xxx的一個非葉子兒子yyy使得avly,c=1avl_{y,c}=1avly,c?=1,并且在二分圖上刪去yyy節點不會影響答案時,此時將該子樹作為開頭即可。
如何判斷刪除一個節點后最大匹配不變,如果原圖中該點沒有匹配顯然可以直接刪去。如果有匹配,那么將該節點打上禁止標記后從它的匹配點開始求一條增廣路,如果有則可以刪去。
時間復雜度O(n2)O(n^2)O(n2)
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; }總結
以上是生活随笔為你收集整理的YbtOJ#573-后缀表达【二分图匹配】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: YbtOJ#463-序列划分【二分答案,
- 下一篇: YbtOJ#903-染色方案【拉格朗日插