• 联合省选2022题解


    Day1

    T1 预处理器 preprocessor

    link

    题意

    模拟 C++ 的 #define 的展开。具体细节参见题面。

    数据范围:行数 \(n\le 100\),每行长度 \(\le 100\),答案的每行长度 \(len \le 1000\)

    solution

    直接大力模拟即可通过官方数据,不过如果常数写的丑的话在 hack 数据上可能会 T。

    暴力模拟的化复杂度是 \(O(n^3len)\) 的,不过常数很小。因为可能需要 \(O(n^2)\) 的复杂度来确定答案串的一位。

    如果一直不能确定答案的一位,一定是存在下面这种情况

    #define A B
    #define B C
    #define C D
    .....
    #define Z xxx
    A
    

    其中 ABC,...,Z 是一堆长度为 \(O(n)\) 的串。

    字符串 A \(\to\) B 的变化需要 \(O(n)\) 的代价。所以我们把这一过程改为哈希值的变化,每次变化就是 \(O(1)\) 的了。

    这样总复杂度是 \(O(n^2len)\)

    view code
    #include <bits/stdc++.h>
    using namespace std;
    inline int read(){
    	char ch=getchar();
    	int s=0,f=1;
    	while(ch<'0'||ch>'9'){
    		if(ch=='-')f=-1;ch=getchar();
    	}
    	while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    	while(ch!='\n')ch=getchar();
    	return s*f;
    }
    inline bool check(char x){
    	if(x>='A'&&x<='Z')return 1;
    	if(x>='a'&&x<='z')return 1;
    	if(x>='0'&&x<='9')return 1;
    	if(x=='_')return 1;
    	return 0;
    }
    #define ll long long
    const int mod1=998244353,mod2=1e9+7;
    inline ll gethsh(const string&s){
    	int ret1=0,ret2=0;
    	for(char x:s){
    		ret1=(233ll*ret1+x)%mod1;
    		ret2=(233ll*ret2+x)%mod2;
    	}
    	return 1000000000ll*ret1+ret2;
    }
    const int N=1005;
    unordered_map<ll,string> name;
    unordered_map<ll,string> to;
    unordered_map<ll,string> S[N];
    unordered_map<ll,int> d;
    unordered_map<ll,int> id;
    unordered_map<ll,ll> to1;
    bool vis[N],previs[N][N];
    inline string bfs(string x,ll hsh){
    	if(!id.count(hsh)||!d[hsh]||vis[id[hsh]])return x;
    	ll cur=hsh,nxt=to1[hsh];
    	while(id.count(nxt)){
    		if(vis[id[nxt]]||!d[nxt])
    			break;
    		vis[id[nxt]]=1;cur=nxt;nxt=to1[cur];
    	}
    	return to[cur];
    }
    inline string solve(string s,int dep){
    	string st=s;
    	memcpy(previs[dep],vis,101);
    	if(dep)s=bfs(s,gethsh(s));
    	int n=s.size(),l=0;
    	S[dep].clear();
    	s.push_back('\n');
    	string ret;
    	for(int i=0;i<=n;++i){
    		if(!check(s[i])){
    			if(l!=i){
    				string t;
    				for(int j=l;j<i;++j)
    					t.push_back(s[j]);
    				ll hsh=gethsh(t);
    				if(id.count(hsh)){
    					if(!vis[id[hsh]]&&d[hsh]){
    						vis[id[hsh]]=1;
    						if(S[dep].count(hsh)){
    							t=S[dep][hsh];
    						}else{
    							t=solve(to[hsh],dep+1);
    							S[dep][hsh]=t;
    						}
    						vis[id[hsh]]=0;
    					}
    				}
    				ret+=t;
    			}
    			if(s[i]!='\n')ret.push_back(s[i]);
    			l=i+1;
    		}
    	}
    	memcpy(vis,previs[dep],101);
    	return ret;
    }
    string s;
    int n;
    int main(){
    	n=read();
    	for(int i=1;i<=n;++i){
    		getline(cin,s);
    		if(s.size()==0){
    			puts("");
    			continue;
    		}
    		if(s[0]=='#'){
    			string t;
    			if(s[1]=='u'){
    				for(int j=7;j<s.size();++j){
    					if(check(s[j]))
    						t.push_back(s[j]);
    					else break;
    				}
    				ll hsh=gethsh(t);
    				d[hsh]=0;
    			}else{
    				int pos=0;
    				for(int j=8;j<s.size();++j){
    					if(check(s[j]))
    						t.push_back(s[j]);
    					else{
    						pos=j+1;
    						break;
    					}
    				}
    				ll hsh=gethsh(t);
    				d[hsh]=1;
    				id[hsh]=i;
    				name[hsh]=t;
    				t.clear();
    				for(int j=pos;j<s.size();++j)
    					t.push_back(s[j]);
    				to[hsh]=t;
    				ll hsh1=gethsh(t);
    				to1[hsh]=hsh1;
    			}
    			puts("");
    		}else{
    			string ans=solve(s,0);
    			cout<<ans<<endl;
    		}
    	}
    	return 0;
    }
    
    

    T2 填树 tree

    link

    题意

    给定一棵树,每个点的点权要么是 \(0\),要么是 \([l_u,r_u]\) 之内的一个整数。现在已知树上所有点权非 \(0\) 的点构成一条路径,且非 \(0\) 权值的极差 \(\le k\)

    求满足条件的给每个点分配点权的方案数 \(\bmod 10^9+7\),以及所有方案中的所有点的点权和 \(\bmod 10^9+7\)

    数据范围\(n\le 200,1\le l_i\le r_i\le 10^9,1\le k\le 10^9\)

    solution

    设值域为 \(V\),先考虑一个 \(O(nV)\) 的暴力:枚举最大值 \(mx\),求所有方案中以 \(mx\) 为最大值的方案数和所有方案中的点权和。

    \(f_{u,0/1}\) 表示以 \(u\) 为端点,路径上是否有一个点的点权 \(=mx\) 的路径数量。\(g_{u,0/1}\) 表示 \(f_{u,0/1}\) 中所有路径的权值和。

    初值设为 \(uf_{u,0/1},ug_{u,0/1}\)

    那么合并 \(u,v\) 时,对答案的贡献是

    \[ans1\gets f_{u,x}\times f_{v,y}(x=1\lor y=1)\\ ans2\gets f_{u,x}\times g_{v,y}+g_{u,x}\times f_{v,y}(x=1\lor y=1) \]

    转移有:

    \[f_{u,x|y}\gets uf_{u,x}\times f_{v,y}\\ g_{u,x|y}\gets uf_{u,x}\times g_{v,y}+ug_{u,x}\times f_{v,y} \]

    即把 \(v\) 为端点的所有路径接上一个 \(u\)

    容易发现,\(uf_{u,0/1}\) 是一个关于 \(mx\) 的分段一次函数:

    • \(mx\in [0,l_u)\)
    • \(mx\in [l_u,r_u]\)
    • \(mx\in (r_u,l_u+k]\)
    • \(mx\in (l_u+k,r_u+k]\)
    • \(mx\in (r_u+k,+\infty)\)

    先是 \(0\),然后以斜率为 \(1\) 的速度增长,然后取得最大值(\(k\)\(r_u-l_u+1\)),然后以斜率为 \(-1\) 的速度减小,然后变为 \(0\)

    同理,\(ug_{u,0/1}\) 也是一个关于 \(x\) 的分段函数,只不过是一个分段的二次函数。

    这样 \(ans1\) 是一个关于 \(mx\)\(n\) 次多项式,\(ans2\) 是一个关于 \(mx\)\(n+1\) 次多项式。

    把所有点的分段情况合并起来,就是分成 \(O(n)\) 段,每段是一个不超过 \(n+1\) 次的多项式。我们要求的是多项式的前缀和。一个 \(m\) 次多项式的前缀和是一个 \(m+1\) 次多项式。所以我们需要一个 \(n+2\) 次的多项式。求出 \(n+3\) 个点值然后拉格朗日插值即可。如果这一段值域里没有 \(n+3\) 个数就直接暴力。

    复杂度 \(O(n^3)\)

    view code
    #include <bits/stdc++.h>
    using namespace std;
    inline int read(){
    	int s=0,f=1;char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    	return s*f;
    }
    #define ll long long
    const int mod=1e9+7,N=205;
    inline int quick_pow(int a,int b){
    	int ret=1;
    	for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)ret=1ll*ret*a%mod;
    	return ret;
    }
    inline int Inv(int a){return quick_pow(a,mod-2);}
    inline int add(int a,int b){return (a+b>=mod)?a+b-mod:a+b;}
    inline int dec(int a,int b){return (a-b<0)?a-b+mod:a-b;}
    inline void Add(int &a,int b){a+=(a+b>=mod)?b-mod:b;}
    inline void Dec(int &a,int b){a-=(a-b<0)?b-mod:b;}
    int fac[N];
    namespace lagrange{
    	int x[N],y[N],tot;
    	inline void clear(){
    		tot=0;
    	}
    	inline void Set(int _x,int _y){
    		x[++tot]=_x;y[tot]=_y;
    	}
    	inline int calc(int k){
    		int ret=0,prod=1;
    		fac[0]=1;
    		for(int i=1;i<=tot;++i)prod=1ll*prod*dec(k,x[i])%mod,fac[i]=1ll*fac[i-1]*i%mod;
    		for(int i=1;i<=tot;++i){
    			int fz=1ll*y[i]*prod%mod*Inv(dec(k,x[i]))%mod;
    			int fm=1ll*fac[i-1]*fac[tot-i]%mod;
    			if((tot-i)&1)fm=mod-fm;
    			ret=(ret+1ll*fz*Inv(fm))%mod;
    		}
    		return ret;
    	}
    }
    int f[N][2],g[N][2];
    ll f1[2],g1[2];
    int s[N<<1],n,K,l[N],r[N],lim;
    vector<int> e[N];
    int buc[N*N],tot;
    inline int S1(int x){return ((1ll*x*(x+1))>>1)%mod;}
    inline int calc1(int l,int r){
    	return (l<=r)?r-l+1:0;
    }
    inline int calc2(int l,int r){
    	if(r<l)return 0;
    	return dec(S1(r),S1(l-1));
    }
    int retf,retg,uf[N][2],ug[N][2];
    void dfs(int u,int fa,int x){
    	if(l[u]<=x&&x<=r[u]){
    		f[u][1]=1;
    		g[u][1]=x;
    	}else{
    		f[u][1]=0;
    		g[u][1]=0;
    	}
    	g[u][0]=dec(calc2(max(l[u],x-K),min(r[u],x)),g[u][1]);
    	f[u][0]=dec(calc1(max(l[u],x-K),min(r[u],x)),f[u][1]);
    	memcpy(uf[u],f[u],sizeof(f[u]));
    	memcpy(ug[u],g[u],sizeof(g[u]));
    	Add(retf,f[u][1]);
    	Add(retg,g[u][1]);
    	for(int v:e[u]){
    		if(v==fa)continue;
    		dfs(v,u,x);
    		f1[0]=f1[1]=0;
    		g1[0]=g1[1]=0;
    		for(int a:{0,1})for(int b:{0,1}){
    			f1[a|b]+=1ll*f[u][a]*f[v][b];
    			g1[a|b]+=(1ll*g[u][a]*f[v][b]+1ll*f[u][a]*g[v][b]);
    		}
    		retf=(retf+f1[1])%mod;
    		retg=(retg+g1[1])%mod;
    		f1[0]=f1[1]=0;
    		g1[0]=g1[1]=0;
    		for(int a:{0,1})for(int b:{0,1}){
    			f1[a|b]+=1ll*uf[u][a]*f[v][b];
    			g1[a|b]+=(1ll*ug[u][a]*f[v][b]+1ll*uf[u][a]*g[v][b]);
    		}
    		f[u][0]=(f[u][0]+f1[0])%mod;
    		f[u][1]=(f[u][1]+f1[1])%mod;
    		g[u][0]=(g[u][0]+g1[0])%mod;
    		g[u][1]=(g[u][1]+g1[1])%mod;
    	}
    }
    int F1[N],G1[N];
    int main(){
    	n=read();K=read();
    	int mxr=0;
    	for(int i=1;i<=n;++i){
    		l[i]=read();r[i]=read();
    		buc[++tot]=l[i]-1;
    		buc[++tot]=r[i];
    		buc[++tot]=l[i]+K;
    		buc[++tot]=r[i]+K;
    		mxr=max(mxr,r[i]);
    	}
    	sort(buc+1,buc+1+tot);
    	tot=unique(buc+1,buc+1+tot)-buc-1;
    	for(int i=1,u,v;i<n;++i){
    		u=read();v=read();
    		e[u].push_back(v);
    		e[v].push_back(u);
    	}
    	int F=0,G=0;
    	for(int o=2;o<=tot;++o){
    		int L=buc[o-1]+1,R=buc[o];
    		if(L>mxr)break;
    		if(R-L+1<=n+3){
    			for(int i=L;i<=R;++i){
    				retf=retg=0;
    				dfs(1,0,i);
    				Add(F,retf);
    				Add(G,retg);
    			}
    			continue;
    		}
    		for(int i=L;i<=L+n+2;++i){
    			retf=retg=0;
    			dfs(1,0,i);
    			F1[i-L]=retf;
    			G1[i-L]=retg;
    		}
    		lagrange::clear();
    		for(int i=0;i<=n+2;++i){
    			if(i)Add(F1[i],F1[i-1]); 
    			lagrange::Set(L+i,F1[i]);
    		}
    		Add(F,lagrange::calc(R));
    		
    		lagrange::clear();
    		for(int i=0;i<=n+2;++i){
    			if(i)Add(G1[i],G1[i-1]); 
    			lagrange::Set(L+i,G1[i]);
    		}
    		Add(G,lagrange::calc(R)); 
    	}
    	printf("%d\n%d\n",F,G);
    	return 0;
    }
    

    T3 学术社区 community

    link

    题意

    \(n\) 个人,总共发出 \(m\) 条消息。每条消息是下面 \(3\) 种中的一种。

    • A:B 楼上,称为楼上型消息,\(A\) 为发送人
    • A:B 楼下,称为楼下型消息,\(A\) 为发送人
    • A: ...,称为学术消息,\(A\) 为发送人

    价值的定义如下:

    • 对于楼上型消息A:B 楼上,如果它的下一条消息的发送人是 B,那么有 \(1\) 的价值,否则没有价值。
    • 对于楼下型消息A:B 楼下,如果它的上一条消息的发送人是 B,那么有 \(1\) 的价值,否则没有价值。
    • 对于学术消息,没有价值。

    构造一个使得价值最大的重排消息的方案。保证每个人都发出了至少 \(1\) 条学术消息。\(T\) 组数据。

    数据范围\(T\le 100,n\le m\le 77777,\sum m\le 2.5\times 10^5\)

    solution

    先考虑特殊性质 \(C\) 的做法:

    把每条限制拆点,对于楼上型消息 \(x\) A:B 楼上\(x\) 的出点连向所有 \(B\) 的消息的入点 \(y\),表示如果 \(x\)\(y\) 前面就有 \(1\) 的价值。楼下型消息类似。这样变成了一个二分图匹配问题。现在边数是 \(O(m^2)\) 的,不过我们连的边只跟一条消息的发出人有关,加 \(2n\) 个虚点即可把边数优化到 \(O(m)\)。现在的图不是一个二分图,不过用与二分图最大匹配相同的复杂度分析方法可知,现在的跑 Dinic 二分图匹配的复杂度依然是 \(O(m\sqrt{m})\) 的。

    不过这样构造出来的方案可能成环,比如 \(x\to y\to z\to x\)

    注意到这个环上有如下两个性质:

    • 环上的所有消息要么都是楼上型消息,要么都是楼下型消息。
    • 环上的所有消息不包含学术消息。

    我们可以用学术消息来断环为链。

    具体的,\(x\) 的发出人 \(A\) 有一条学术消息 \(x'\)。设此时 \(x'\) 的连边关系是 \(p\to x'\to q\)(显然此时 \(p\) 要么不存在,要么是楼上型消息,\(q\) 要么不存在,要么是楼下型消息)。

    • 如果 \(x,y,z\) 均为楼上型消息,那么断环为链的方式是 \(p\to x\to y\to z\to x'\to q\)
    • 如果 \(x,y,z\) 均为楼下型消息,那么断环为链的方式是 \(p\to x'\to y\to z\to x\to q\)

    跑完 Dinic 之后找到一组匹配,把所有环断掉即可。复杂度瓶颈在 Dinic,为 \(O(m\sqrt{m})\)


    现在没有了特殊性质 \(C\),直接连边是不行的,因为A B 楼上B A 楼下 前面的化,价值是 \(2\) 而不是 \(1\),就得跑费用流了。容易发现直接把 A B 楼上B A 楼下 匹配起来一定不会更劣。然后就又变成了上面一样的问题。

    唯一的区别是这些被匹配的消息只有入点/出点了。

    总复杂度 \(O(m\sqrt{m})\)。如果 Dinic 跑不动的化不妨尝试以下改变建边顺序,能从十几秒优化到 \(1\) 秒。

    view code
    #include <bits/stdc++.h>
    using namespace std;
    namespace iobuff{
    	const int LEN=1000000;
    	char in[LEN+5],out[LEN+5];
    	char *pin=in,*pout=out,*ed=in,*eout=out+LEN;
    	inline char gc(void){
    		return pin==ed&&(ed=(pin=in)+fread(in,1,LEN,stdin),ed==in)?EOF:*pin++;
    	}
    	inline void pc(char c){
    		pout==eout&&(fwrite(out,1,LEN,stdout),pout=out);
    		(*pout++)=c;
    	}
    	inline void flush(){fwrite(out,1,pout-out,stdout),pout=out;}
    	template<typename T> inline void read(T &x){
    		static int f;
    		static char c;
    		c=gc(),f=1,x=0;
    		while(c<'0'||c>'9') f=(c=='-'?-1:1),c=gc();
    		while(c>='0'&&c<='9') x=10*x+c-'0',c=gc();
    		x*=f;
    	}
    	inline void tostring(string&s){
    		s.clear();
    		char ch=gc();
    		for(int i=1;;++i,ch=gc()){
    			if(ch==' '||ch=='\n')break;
    			else s.push_back(ch); 
    		}
    	}
    	template<typename T> inline void putint(T x,char div='\n'){
    		static char s[20];
    		static int top;
    		top=0;
    		x<0?pc('-'),x=-x:0;
    		while(x) s[top++]=x%10,x/=10;
    		!top?pc('0'),0:0;
    		while(top--) pc(s[top]+'0');
    		pc(div);
    	}
    }
    using iobuff::putint;
    using iobuff::read;
    using iobuff::tostring;
    using iobuff::pc;
    using iobuff::flush;
    namespace Flow{
    	const int N=3.2e5+5,M=1e6+5,inf=1e9;
    	struct Edge{
    		int fr,to,next,flow;
    	}e[M];
    	int head[N],ecnt=1,tot;
    	inline void adde(int u,int v,int flow){
    		e[++ecnt]=(Edge){u,v,head[u],flow};head[u]=ecnt;
    		e[++ecnt]=(Edge){v,u,head[v],0};head[v]=ecnt;
    	}
    	inline void init(){
    		memset(head+1,0,tot<<2);
    		ecnt=1;
    		tot=0;
    	}
    	int dep[N],cur[N],q[N];
    	inline bool bfs(int s,int t){
    		memset(dep+1,0x3f,tot<<2);
    		dep[s]=0;
    		int l=1,r=1;q[l]=s;
    		while(l<=r){
    			int u=q[l++];
    			cur[u]=head[u];
    			if(t==u)return 1;
    			for(int i=head[u],v;i;i=e[i].next){
    				if(e[i].flow&&dep[v=e[i].to]>dep[u]+1){
    					dep[q[++r]=v]=dep[u]+1;
    				}
    			}
    		}
    		return dep[t]<inf;
    	}
    	inline int dinic(int u,int t,int flow){
    		if(u==t)return flow;
    		int ret=0,f,v;
    		for(int &i=cur[u];i&&flow;i=e[i].next){
    			if(dep[v=e[i].to]!=dep[u]+1||!e[i].flow)continue;
    			f=dinic(v,t,min(flow,e[i].flow));
    			e[i].flow-=f;e[i^1].flow+=f;
    			ret+=f;
    			flow-=f;
    		}
    		if(flow)dep[u]=inf;
    		return ret;
    	}
    	inline int maxflow(int s,int t){
    		int ret=0;
    		while(bfs(s,t))
    			ret+=dinic(s,t,inf);
    		return ret;
    	}
    }
    const int N=3.2e5+5,M=1e6+5,inf=1e9;
    struct Edge{
    	int to,next;
    }e[M>>1];
    int head[N],ecnt,cur[N];
    inline void adde(int u,int v){
    	e[++ecnt]=(Edge){v,head[u]};head[u]=ecnt;
    }
    map<string,int> mapp;
    int fr[N],to[N],pre[N],nxt[N],n,m;
    int ans;
    map<pair<int,int>,int> mapp1;
    int inde;
    inline void init(){
    	memset(head+1,0,(2*n+2*m+2)<<2);
    	ecnt=0;
    	inde=0;mapp1.clear();
    } 
    inline int gid(int u,int v){
    	if(mapp1.count(make_pair(u,v)))return mapp1[make_pair(u,v)];
    	else return mapp1[make_pair(u,v)]=++inde;
    }
    inline int gid1(int u,int v){
    	if(mapp1.count(make_pair(u,v)))return mapp1[make_pair(u,v)];
    	else return 0;
    }
    vector<int> buc_down[N];
    bool del[N],vis[N];
    int deg[N],Nxt[N],ed[N],st[N],Pre[N];
    inline void Add(int u,int v){
    	if(!v||!u)return;
    	Nxt[u]=v;
    	Pre[v]=u;
    }
    inline void Cut(int u,int v){
    	if(!v||!u)return;
    	Nxt[u]=Pre[v]=0;
    }
    void solve_notC(){
    	for(int i=1;i<=m;++i){
    		if(nxt[i]){
    			int t=gid1(to[i],fr[i]);
    			if(t&&buc_down[t].size()){
    				del[i]=1;
    				del[*buc_down[t].rbegin()]=1;
    				Nxt[i]=*buc_down[t].rbegin();++deg[*buc_down[t].rbegin()];
    				Add(i,Nxt[i]);
    				buc_down[t].pop_back();
    				ans+=2;
    			}
    		}
    	}
    }
    int dfs(int u){
    	if(u==2*n+2*m+2)return u;
    	for(int &i=cur[u];i;i=e[i].next){
    		int ret=0;
    		if(ret=dfs(e[i].to)){
    			i=e[i].next;
    			if(ret==2*n+2*m+2)return u;
    			else return ret;
    		}
    	}
    	return 0;
    }
    int stac[N],top,fat[N],sz[N],bc[N];
    bool instac[N];
    void dfs1(int u){
    	stac[++top]=u;
    	instac[u]=1;
    	vis[u]=1;
    	int v=Nxt[u];
    	if(vis[v]){
    		if(instac[v]){
    			if(pre[v]==fr[u]){
    				int p1=Nxt[v],p2=Nxt[ed[fr[v]]];
    				Cut(v,Nxt[v]);
    				Cut(ed[fr[v]],p2);
    				Add(ed[fr[v]],p1);
    				Add(v,p2);
    			}else{
    				int p2=Pre[ed[fr[v]]];
    				Cut(u,v);
    				Cut(p2,ed[fr[v]]);
    				Add(p2,v);
    				Add(u,ed[fr[v]]);
    			}
    			
    		}
    	}else if(v){
    		dfs1(v);
    	}
    	instac[u]=0;--top;
    }
    int main(){
    	int T;read(T);
    	for(int o=1;o<=T;++o){
    		read(n);read(m);
    		mapp.clear();
    		Flow::init();
    		ans=0;
    		for(int i=1;i<=n;++i){
    			string name;
    			tostring(name);
    			mapp[name]=i;
    		}
    		init();
    		for(int i=1;i<=m;++i){
    			del[i]=0;deg[i]=0;vis[i]=0;Nxt[i]=0;Pre[i]=0;
    			buc_down[i].clear();
    			string name;
    			tostring(name);
    			fr[i]=mapp[name];
    			tostring(name);
    			if(mapp.count(name))to[i]=mapp[name];
    			else to[i]=0;
    			tostring(name);
    			pre[i]=nxt[i]=0;
    			if(to[i]&&name=="loushang"){
    				nxt[i]=to[i];
    			}else if(to[i]&&name=="louxia"){
    				pre[i]=to[i];
    				buc_down[gid(fr[i],to[i])].push_back(i);
    			}else to[i]=0,ed[fr[i]]=i;
    			++sz[fr[i]];bc[fr[i]]=i;
    		}
    		solve_notC();
    		int s=2*n+2*m+1,t=s+1;
    		Flow::tot=t;
    		for(int i=1;i<=m;++i){
    			if(!del[i]&&pre[i])
    				if(sz[pre[i]]>1)Flow::adde(2*m+pre[i],i+m,1);
    				else Flow::adde(bc[pre[i]],i+m,1);
    			else if(!del[i]&&nxt[i]){
    				if(sz[nxt[i]]>1)Flow::adde(i,2*m+nxt[i]+n,1);
    				else Flow::adde(i,m+bc[nxt[i]],1);
    			}
    			if(!del[i]||!pre[i]){
    				if(sz[fr[i]]>1)Flow::adde(2*m+fr[i]+n,i+m,1);
    				Flow::adde(i+m,t,1);
    			}
    			if(!del[i]||!nxt[i]){
    				Flow::adde(s,i,1);
    				if(sz[fr[i]]>1)Flow::adde(i,2*m+fr[i],1);
    			}
    		}
    		ans+=Flow::maxflow(s,t);
    		putint(ans);
    		for(int i=2;i<=Flow::ecnt;i+=2)
    			if(!Flow::e[i].flow)adde(Flow::e[i].fr,Flow::e[i].to);
    		for(int i=1;i<=t;++i)cur[i]=head[i];
    		for(int i=1;i<=m;++i){
    			int v=dfs(i);
    			if(v)
    				Nxt[i]=v-m,Add(i,Nxt[i]);
    		}
    		for(int i=1;i<=m;++i)
    			if(!vis[i])
    				dfs1(i);
    		for(int i=1;i<=m;++i)++deg[Nxt[i]];
    		for(int i=1;i<=m;++i){
    			if(!deg[i]){
    				int u=i;
    				for(;u;u=Nxt[u])
    					putint(u,' ');
    			}
    		}
    		pc('\n');
    	}
    	flush();
    	return 0;
    }
    

    Day 2

    T1 卡牌 card

    link

    题意

    \(n\) 个数,第 \(i\) 个数是 \(a_i\)。有 \(m\) 次询问,每次给出 \(c\) 个质数 \(s_i\),求有多少种选数的方案使得这些数的积能被每个质数整除。答案 \(\bmod 998244353\)

    数据范围\(n\le 10^6,m\le 1500,\sum c\le 18000,1\le a_i,s_i\le 2000\)

    solution

    一种暴力做法是直接装压维护当前出现过的质数集合。注意到值域很小,\(\sqrt{2000}\approx 44.7\) 以内的质数只有 \(14\) 个,而第 \(14\) 个质数 \(43\times 47=2021>2000\),我们暴力维护最小的 \(B=13\) 个质数的出现情况,剩下的大质数最多出现 \(1\) 个。把所有数按照大质数分类,每一类预处理 \(f_s\) 表示出现了 \(s\) 集合内的所有小质数的方案数。这样最终答案就是一个或卷积的形式。

    但是这样复杂度还是很高,考虑优化。首先考虑 FWT,我们要算的东西应该是若干个 FWT 之后东西对位相乘,然后最后 IFWT 回去。IFWT 的复杂度是 \(O(m2^{B}B)\) 的,可以接受。

    如果询问的质数中不包含 \(x\) 的大质数,那么 \(x\) 对答案的贡献是:卷上一个 \(f_0=1,f_x=2^{cnt}-1\)。其中 \(cnt\) 表示 \(n\) 个数中 \(x\) 的出现此次数。我们预处理出所有 \(x\)\(f\) FWT 之后的积,当作询问的质数中不包含任意一个 \(x\) 的大质数。

    如果询问的质数中包含 \(x\) 的大质数,那么 \(x\) 对它的大质数的答案的贡献是:卷上一个 \(g_0=1,g_x=2^{cnt}-1\)。同时,\(x\) 还对上面的答案有贡献,需要在原来的答案中把它的贡献除掉。我们预处理出每个大质数的 FWT 结果(即 \(FWT(\prod g)\)),同时预处理出所有 \(x\)\(f\) FWT 之后的积,要在答案中把它除掉。

    注意到要卷的东西都只有 \(2\) 项,可以 \(O(2^B)\) 求出 FWT 之后的结果,预处理复杂度 \(O(V2^B)\)\(V\) 是值域。

    询问的时候把询问的质数中不包含 \(x\) 的大质数的 \(x\) 的贡献全部算上(即全局积除掉询问中出现的大质数的贡献),然后再把大质数的贡献算尽来。复杂度 \(O(\sum c2^B)\)

    总复杂度 \(O(V2^B+\sum c2^B+m2^BB)\)

    view code
    #include <bits/stdc++.h>
    using namespace std;
    inline int read(){
    	int s=0,f=1;char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    	return s*f;
    }
    const int N=2005,M=2e4+5,mod=998244353;
    const int p1[]={2,3,5,7,11,13,17,19,23,29,31,37,41};
    inline int add(int a,int b){return (a+b>=mod)?a+b-mod:a+b;}
    inline int dec(int a,int b){return (a-b<0)?a-b+mod:a-b;}
    inline void Add(int &a,int b){a+=(a+b>=mod)?b-mod:b;}
    inline void Dec(int &a,int b){a-=(a-b<0)?b-mod:b;}
    inline int quick_pow(int a,int b){
    	int ret=1;
    	for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)ret=1ll*ret*a%mod;
    	return ret;
    }
    inline int Inv(int a){return quick_pow(a,mod-2);}
    int Pow[N*N],cnt[N],n,m,mx,a[N],s[M];
    inline void FWT_or(int *f,int len,int x){
    	for(int mid=1,k=2;k<=len;mid<<=1,k<<=1)for(int i=0;i<len;i+=k)
    		for(int j=0;j<mid;++j)
    			x>0?Add(f[i+j+mid],f[i+j]):Dec(f[i+j+mid],f[i+j]);
    }
    int prime[N],pcnt,F[N>>1][1<<14],prod[N>>1][1<<14],rk[N];
    bool vis[N];
    inline int gets(int x){
    	int ret=0;
    	for(int i=0;i<13;++i)
    		if(x%p1[i]==0)ret|=(1<<i);
    	return ret;
    }
    inline void init(){
    	for(int i=2;i<=mx;++i){
    		if(!vis[i])
    			prime[++pcnt]=i,rk[i]=pcnt;
    		for(int j=1;j<=pcnt&&prime[j]*i<=mx;++j){
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)break;
    		}
    	}
    	for(int p=(1<<13)-1;~p;--p)prod[0][p]=1;
    	for(int i=1;i<=pcnt;++i){
    		for(int p=(1<<13)-1;~p;--p)prod[i][p]=1,F[i][p]=1;
    		if(prime[i]<=41)continue;
    		for(int j=prime[i];j<=mx;j+=prime[i]){
    			int x=gets(j),v=Pow[cnt[j]];
    			for(int p=(1<<13)-1;~p;--p)
    				if((p&x)==x)F[i][p]=1ll*F[i][p]*v%mod;
    		}
    		for(int p=(1<<13)-1;~p;--p)Dec(F[i][p],1);
    	}
    	for(int i=1;i<=mx;++i){
    		int x=gets(i),v=Pow[cnt[i]];
    		int pos=0;
    		for(int j=1;j<=pcnt;++j)
    			if(i%prime[j]==0)pos=j;
    		if(prime[pos]<=41)pos=0;
    		for(int p=(1<<13)-1;~p;--p){
    			if((p&x)==x){
    				prod[0][p]=1ll*prod[0][p]*v%mod;
    				if(pos)prod[pos][p]=1ll*prod[pos][p]*v%mod;
    			}
    		}
    	}
    	for(int i=14;i<=pcnt;++i)
    		for(int p=(1<<13)-1;~p;--p)prod[i][p]=Inv(prod[i][p]);
    }
    int main(){
    	n=read();
    	Pow[0]=1;
    	for(int i=1,x;i<=n;++i){
    		x=read();++cnt[x];
    		Pow[i]=add(Pow[i-1],Pow[i-1]);
    		mx=max(mx,x);
    	}
    	init();
    	m=read();
    	while(m--){
    		int c=read();
    		int S=0;
    		bool fff=1;
    		for(int i=1;i<=c;++i){
    			s[i]=read();
    			if(s[i]<=41)S|=(1<<(rk[s[i]]-1));
    			if(s[i]>mx)fff=0;
    		}
    		if(!fff){
    			puts("0");
    			continue;
    		}
    		sort(s+1,s+1+c);
    		c=unique(s+1,s+1+c)-s-1;
    		memcpy(F[0],prod[0],sizeof(F[0]));
    		for(int i=1;i<=c;++i){
    			if(s[i]<=41)continue;
    			int x=rk[s[i]];
    			for(int j=0;j<(1<<13);++j)
    				F[0][j]=1ll*F[0][j]*prod[x][j]%mod*F[x][j]%mod;
    		}
    		FWT_or(F[0],1<<13,-1);
    		int ans=0;
    		for(int i=0;i<(1<<13);++i)if((i&S)==S)Add(ans,F[0][i]);
    		printf("%d\n",ans);
    	}
    	return 0;
    }
    

    T2 序列变换 bracket

    link

    题意

    给定一个长为 \(2n\) 的合法括号串,每个左括号有一个权值 \(val_i\),你可以进行如下两种操作:

    • 交换形如 p(A)(B)q 的串中 AB 之间的两个括号,变换为 p(A()B)q(其中 p, q 为任意串,可以为空,但不一定分别为合法括号序列,AB 必须是合法非空括号序列,下同),它的代价为 \(x\)(A) 中第一个左括号的权值加上 \(y\)(B) 中第一个左括号的权值。
    • 交换形如 pABq 的串中的 AB,变换为 pBAq ,这个操作不需要代价。

    其中 \(x,y\in\{0,1\}\),权值会随着左括号的移动而移动。

    你要求最终的括号串中不存在子串 )(,求最小代价。

    数据范围\(n\le 4\times 10^5,1\le val_i\le 10^7\)

    solution

    观察这个变化究竟是在干什么。

    我们把括号树建出来,称一个括号在第 \(i\) 层表示它的括号树上的深度为 \(i\)。那么我们的操作相当于是:

    把第 \(i\) 层的所有括号拿出来,任选这一层的两对括号 \(A,B\)(因为操作 \(2\) 不花费代价),以 \(x\cdot val_A+y\cdot val_B\) 的代价将括号 \(B\) 扔到下一层去。重复此操作直到这一层只剩下 \(1\) 个括号,把这个括号留在这一层,开始做下一层。

    更抽象一点,每一层有若干个数,每次要选择两个数 \(a_1,a_2\),以 \(x\cdot a_1+y\cdot a_2\) 的代价将 \(a_2\) 扔到下一层去。每次选择一个数留在当前层,剩下的都必须要扔到下面一层去。

    根据 \(x,y\) 的取值分类讨论,设当前层有 \(m\) 个数,最大值为 \(mx\),最小值为 \(mn\),所有数的和是 \(sum\)

    • \(x=1,y=1\)

    一共有 \(2(m-1)\) 个代价,每个数都至少对代价贡献了一次。

    那么贪心的,从次小值开始,每次以 \(a_x+mn\) 的代价把 \(a_x\) 放到下一层,最后用 \(mx+mn\) 的代价把 \(mn\) 放到下一层,\(mx\) 留在当前层。总代价是 \(sum+(m-2)mn\)

    随便使用一个能支持删除最大值和取最小值的数据结构(比如 multiset) 维护即可。

    • \(x=0,y=1\)

    代价是被放到下一层的数的大小,用最大值把所有其他数都方下去即可。代价是 \(sum-mx\)。同样可以用 multiset/priority_queue 维护。

    • \(x=1,y=0\)

    显然我们希望用最小的代价把所有数都放下去,即用 \(mn\) 的代价把想要方下去的东西方下去。设不放下去的数是 \(c\),再以 \(c\) 的代价把 \(mn\) 放下去即可。(在只剩 \(2\) 个串的时候直接把要放下去用另外一个数放下去即可)

    除了最后一层剩下的括号不需要贡献代价之外,其他所有串都会在被留在当前层时贡献 \(1\) 的代价。所以贪心的,我们把最大值一直往下放,直到放到最后一层。

    所以有贪心:如果当前的集合内有全局最大值 \(Mx\),我们要确保 \(Mx\) 被放下去,同时我们要确保 \(mn\) 被放了下去(比如在出现了全局最大值的时候用全局次大值把 \(mn\) 放进去)。

    不过现在有点问题:当前层只有 \(2\) 个括号,那么只能把 \(Mx\)\(mn\) 中的一个放下去,而我们没办法贪心地选择应该把哪个放下去。

    设初始时,每层的数的数量为: \(1,1,1,\cdots,1,2,1,1,\cdots 1,x\)\(x>1\))。

    把最开始的一段全是 \(1\) 的先删掉,变成 \(2,1,1,\cdots 1,x\)。对于 \(x\) 之前的这一段前缀,每层在处理的时候都会是 \(2\)\(\to 1\) 个,下一层又加一个进来。所以这一段前缀中,每层的数的数量都是 \(2\)。因为每层至少加进来一个数,所以每层的数量是先单调不降,后每次减 \(1\)。所以后面再出现 \(2\) 就是最后一步了,删掉一个就完了。

    我们最终留下来的数一定是这些每层处理 \(2\) 个数的层的最大值/最小值,因为只有这两个值会影响答案。

    分留下最大值/最小值讨论,然后做上面的每层都 \(\ge 3\) 个数的贪心。

    multiset 维护,复杂度 \(O(n\log n)\)

    view code
    #include <bits/stdc++.h>
    using namespace std;
    inline int read(){
    	int s=0,f=1;char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    	return s*f;
    }
    const int N=8e5+5;
    #define ll long long
    char s[N];
    int R[N],L[N],stac[N],top,n,x,y,val[N],a[N],cntl[N],cntr[N],dep[N];
    vector<int> buc[N];
    int mxdep;
    ll sl[N];
    void build(int l,int r,int depp){
    	cntl[l]=cntr[l]=1;
    	sl[l]=a[l];
        ++depp;
        dep[l]=dep[r]=depp;
        mxdep=max(mxdep,depp);
        buc[depp].push_back(a[l]);
    	if(r<=l+1)return;
    	for(int i=l+1;i<r;i=R[i]+1){
    		build(i,R[i],depp);
    		cntl[l]+=cntl[i];
    		cntr[l]+=cntr[i];
    		sl[l]+=sl[i];
    	}
    }
    #define ll long long
    inline ll work(int cur,int d){
    	int mx=max(0,cur);
    	for(int i=d;i<=mxdep;++i)
    		for(int x:buc[i])mx=max(mx,x);
    	multiset<int> s;
    	if(cur>=0)s.insert(cur);
    	bool flag=0;
    	ll ans=0;
    	for(int i=d;i<=mxdep||s.size()>1;++i){
    		for(int x:buc[i])s.insert(x);
    		if(s.size()==2){
    			ans+=*s.begin();
    			break;
    		}
    		flag|=(*s.rbegin())==mx;
    		if(flag){
    			int mm=*prev(s.end());
    			s.erase(prev(s.end()));
    			ans+=1ll*(*s.begin())*(s.size()-1)+(*s.rbegin());
    			s.erase(prev(s.end()));
    			s.insert(mm);
    		}else{
    			ans+=1ll*(*s.begin())*(s.size()-2)+(*s.rbegin());
    			s.erase(prev(s.end()));
    		}
    	}
    	return ans;
    }
    int main(){
    	n=read();x=read();y=read();
    	if(x==0&&y==0){
    		puts("0");
    		return 0;
    	}
    	scanf("%s",s+1);
    	s[0]='(';s[n+n+1]=')';
    	for(int i=1;i<=n;++i)
    		val[i]=read();
    	int cnt=-1;
    	for(int i=0;i<=n+n+1;++i){
    		if(s[i]=='('){
    			stac[++top]=i;
    			++cnt;
    			a[i]=val[cnt];
    		}else{
    			R[stac[top]]=i;
    			L[i]=stac[top];
    			--top;
    		}
    	}
    	build(0,n+n+1,-1);
        ll ans=0;
        if(x==1&&y==1){
            multiset<int> s;
            ll sum=0;
            for(int i=1;i<=mxdep||s.size()>1;++i){
    			for(int x:buc[i])s.insert(x),sum+=x;
            	if(s.size()>1)ans+=sum+1ll*(*s.begin())*(s.size()-2);
    			sum-=*s.rbegin();
    			s.erase(prev(s.end()));
    		}
    		printf("%lld\n",ans);
        }else if(y==1){
    		priority_queue<int> q;
            ll sum=0;
            for(int i=1;i<=mxdep||q.size()>1;++i){
    			for(int x:buc[i])q.push(x),sum+=x;
            	if(q.size()>1)ans+=sum-q.top();
    			sum-=q.top();
    			q.pop();
    		}
    		printf("%lld\n",ans);
    	}else{
    		int pos1=1;
    		for(int i=1;i<=mxdep;++i){
    			if(buc[i].size()==1)pos1=i+1;
    			else break;
    		}
    		int pos2=0,mn=1e9,mx=0;
    		for(int i=pos1,cnt=0;!pos2;++i){
    			cnt+=buc[i].size();
    			if(cnt!=2)pos2=i;
    			else{--cnt;for(int x:buc[i])mn=min(x,mn),mx=max(x,mx);}
    		}
    		if(pos1==pos2)
    			ans=work(-1,pos2);
    		else{
    			ans=1e18;
    			{
    				ll sum=0;
    				multiset<int> s;
    				for(int i=pos1;i<pos2;++i){
    					for(int x:buc[i])s.insert(x);
    					if(*s.begin()==mn){
    						sum+=*s.rbegin();
    						s.erase(prev(s.end()));
    					}else{
    						sum+=*s.begin();
    						s.erase(s.begin());
    					}
    				}
    				ans=min(ans,sum+work(mn,pos2));
    			}
    			{
    				ll sum=0;
    				multiset<int> s;
    				for(int i=pos1;i<pos2;++i){
    					for(int x:buc[i])s.insert(x);
    					sum+=*s.begin();
    					s.erase(s.begin());
    				}
    				ans=min(ans,sum+work(mx,pos2));
    			}
    		}
    		printf("%lld\n",ans);
    	}
    	return 0;
    }
    
    

    T3 最大权独立集问题

    link

    题意

    给定一个二叉树,点有点权,删一条边的代价是两个端点的点权和,删完之后交换两个点的点权。你需要找到一个删边的顺序,要把所有边删完,最小化代价之和。

    数据范围\(n\le 5000,1\le d_i\le 10^9\)

    solution

    显然考虑树形 DP,先考虑一个暴力的树形 DP,设 \(f_{u,x,y}\) 表示考虑以 \(u\) 为根,从子树内换进来的值是 \(d_y\),从子树外换出去的值是 \(d_x\),把子树内边都断完的最小代代价(算上把 \(d_x\) 换出去的代价)。状态数就已经是 \(O(n^3)\) 了,无法接受。

    第三维是把状态数变高的罪魁祸首,我们考虑能不能用点别的替换掉它。\(f_{u,x,i}\) 表示考虑以 \(u\) 为根,从子树外换出去的值是 \(d_x\),从子树外换进来的数还在字数内被换了 \(i\) 次,把子树内边都断完的最小代代价。如果我们知道是 \(y\) 被换进字数内,代价就是 \(d_y\times i+f_{u,x,i}\)

    状态数看起来还是 \(O(n^3)\) 的,不过对于 \(u\)\(x\),设 \(v\)\(u\) 的两个儿子中是 \(x\) 的祖先的那一个,\(v'\)\(u\)\(v\) 的儿子,\(f_{u,x,i}\) 的所有 \(i\)\(\le v'\) 子树的所有点中离 \(u\) 距离的最大值。根据树形背包的复杂度分析,现在的状态数也是 \(O(n^2)\) 的。

    然后考虑转移:

    先只考虑一个度数为 \(3\) 的点的转移(即这个点有左儿子,记为 \(ls\);右儿子,记为 \(rs\),父亲,记为 \(fa\))。

    • 断边顺序为 \(fa,ls,rs\)

    此时换出去的值一定是自己,换进 \(rs\) 的值是 \(ls\) 换上来的值,\(fa\) 的值会进 \(ls\) 的子树,那么有

    \[f_{u,u,i+1}\gets f_{ls,v_1,i}+f_{rs,v_2,j}+d_{v_1}(j+1)+d_u \]

    预处理 \(mn_j=\min_{v_2}\{f_{rs,v_2,j}\}\)。枚举 \(v_1\),然后再枚举 \(j\),求出 \(mn2=\min\{mn_j+d_{v_1}(j+1)\}\),再枚举 \(i\),转移到 \(f_{u,u,i}\gets mn2+f_{ls,v_1,i}+d_u\)。这样转移的复杂度和状态数是一样的。

    • 断边顺序为 \(ls,fa,rs\)

    此时换出去的值一定是 \(ls\) 换出来的值,换进 \(ls\) 的值是 \(u\) 的值,\(fa\) 的值会进 \(rs\) 的子树,那么有

    \[f_{u,v_1,j+1}\gets f_{lc,v_1,i}+f_{rc,v_2,j}+d_u(i+1)+d_{v_1} \]

    同样可以优化到 \(O(n^2)\)

    • 断边顺序为 \(ls,rs,fa\)

    此时换出去的值一定是 \(rs\) 换出来的值,换进 \(ls\) 的值是 \(u\) 的值,换进 \(rs\) 的值是 \(ls\) 换上来的值,同时换进来的值会留在 \(u\),那么有

    \[f_{u,v_2,0}\gets f_{lc,v_1,i}+f_{rc,v_2,j}+d_u(i+1)+d_{v_1}(j+1)+d_{v_2} \]

    同样可以优化到 \(O(n^2)\)

    • \(3!\) 中的剩下三种把 \(ls\)\(rs\) 交换再跑一遍即可。

    • 对于当前点为根 (\(1\) 号点)的情况,枚举先删 \(ls\) 还是 \(rs\),直接对答案做出贡献。

    • 对于当前点没有 \(rs\) 的情况:

      • 断边顺序为 \(fa,ls\)\(f_{u,u,i+1}\gets f_{ls,v_1,i}+d_u\)
      • 断边顺序为 \(ls,fa\)\(f_{u,v1,0}\gets f_{lc,v_1,i}+d_u(i+1)+d_{v_1}\)

    总复杂度 \(O(n^2)\)

    view code
    #include <bits/stdc++.h>
    using namespace std;
    inline int read(){
    	int s=0,f=1;char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    	return s*f;
    }
    const int N=5005;
    vector<int> e[N],son[N];
    #define ll long long
    #define pb push_back
    template<typename T>inline void Max(T&a,T b){if(a<b)a=b;}
    template<typename T>inline void Min(T&a,T b){if(a>b)a=b;}
    struct Val{
    	ll *f;
    	int sz=0;
    	inline ll& operator[](int x){return f[x];}
    	inline void resize(int x){f=new ll[x];sz=x;memset(f,0x3f,x<<3);}
    	inline ll gmn(){
    		ll ret=1e18;for(int i=0;i<sz;++i)Min(ret,f[i]);
    		return ret;
    	}
    }f[N][N];
    int mxdep[N],sz[N],ls[N],rs[N],c[N];
    int ch[N][2],gson[N][N];
    ll mn1[N],mn2[N];
    inline void solve(int u){
    	memset(mn1,0x3f,(mxdep[u]+1)<<3);
    	for(int v2:son[rs[u]])
    		for(int i=0;i<f[rs[u]][v2].sz;++i)
    			Min(mn1[i],f[rs[u]][v2][i]);
    	for(int v1:son[ls[u]]){
    		ll mn=1e18;
    		for(int i=0;i<=mxdep[rs[u]];++i)
    			Min(mn,mn1[i]+1ll*c[v1]*(i+1));
    		for(int i=0;i<f[ls[u]][v1].sz;++i)
    			Min(f[u][u][i+1],c[u]+f[ls[u]][v1][i]+mn);
    	}
    	memset(mn1,0x3f,(mxdep[u]+1)<<3);
    	for(int v2:son[rs[u]])
    		for(int i=0;i<f[rs[u]][v2].sz;++i)
    			Min(mn1[i],f[rs[u]][v2][i]);
    	for(int v1:son[ls[u]]){
    		ll mn=1e18;
    		for(int i=0;i<f[ls[u]][v1].sz;++i)
    			Min(mn,f[ls[u]][v1][i]+1ll*c[u]*(i+1));
    		for(int i=0;i<=mxdep[rs[u]];++i)
    			Min(f[u][v1][i+1],c[v1]+mn+mn1[i]);
    	}
    
    	memset(mn1,0x3f,(mxdep[u]+1)<<3);
    	for(int v1:son[ls[u]]){
    		ll mn=1e18;
    		for(int i=0;i<f[ls[u]][v1].sz;++i)
    			Min(mn,f[ls[u]][v1][i]+1ll*c[u]*(i+1));
    		for(int i=0;i<=mxdep[rs[u]];++i)
    			Min(mn1[i],mn+1ll*c[v1]*(i+1));
    	}
    	for(int v2:son[rs[u]])
    		for(int i=0;i<f[rs[u]][v2].sz;++i)
    			Min(f[u][v2][0],c[v2]+mn1[i]+f[rs[u]][v2][i]);
    }
    ll ans=1e18;
    void solve1(int u=1){
    	memset(mn1,0x3f,(mxdep[u]+1)<<3);
    	for(int v2:son[rs[u]])
    		for(int i=0;i<f[rs[u]][v2].sz;++i)
    			Min(mn1[i],f[rs[u]][v2][i]);
    	for(int v1:son[ls[u]]){
    		ll mn=1e18;
    		for(int i=0;i<=mxdep[rs[u]];++i)
    			Min(mn,mn1[i]+1ll*c[v1]*(i+1));
    		for(int i=0;i<f[ls[u]][v1].sz;++i)
    			Min(ans,mn+f[ls[u]][v1][i]+1ll*c[u]*(i+1));
    	}
    }
    void dfs(int u){
    	son[u].pb(u);
    	sz[u]=1;
    	if(!ls[u]&&!rs[u]){
    		f[u][u].resize(1);
    		f[u][u][0]=c[u];
    		return;
    	}
    	if(ls[u]){
    		int v=ls[u];
    		dfs(v);sz[u]+=sz[v];
    		mxdep[u]=max(mxdep[v]+1,mxdep[u]);
    		for(int x:son[v])son[u].pb(x),gson[u][x]=0;
    	}
    	if(rs[u]){
    		int v=rs[u];
    		dfs(v);sz[u]+=sz[v];
    		mxdep[u]=max(mxdep[v]+1,mxdep[u]);
    		for(int x:son[v])son[u].pb(x),gson[u][x]=1;
    	}
    	f[u][u].resize(mxdep[u]+1);
    	for(int v:son[u]){
    		if(v==u)continue;
    		if(v==ls[u]||v==rs[u]){f[u][v].resize(mxdep[u]+1);continue;}
    		int len=0;
    		if(gson[u][v]==0)Max(len,mxdep[rs[u]]+1);
    		else Max(len,mxdep[ls[u]]+1);
    		f[u][v].resize(len+1);
    	}
    	if(u==1){
    		if(ls[u]&&rs[u]){
    			solve1();
    			swap(ls[u],rs[u]);
    			solve1();
    		}else{
    			for(int v1:son[ls[u]])for(int i=0;i<f[ls[u]][v1].sz;++i)
    				Min(ans,f[ls[u]][v1][i]+1ll*c[u]*(i+1));
    		}
    		return;
    	}
    	if(ls[u]&&rs[u]){
    		solve(u);
    		swap(ls[u],rs[u]);
    		solve(u);
    	}else{
    		for(int v1:son[ls[u]])for(int i=0;i<f[ls[u]][v1].sz;++i)
    			Min(f[u][u][i+1],f[ls[u]][v1][i]+c[u]),
    			Min(f[u][v1][0],f[ls[u]][v1][i]+1ll*c[u]*(i+1)+c[v1]);
    	}
    }
    int n;
    int main(){
    	n=read();
    	for(int i=1;i<=n;++i)c[i]=read();
    	for(int i=2,fa;i<=n;++i){
    		fa=read();
    		if(!ls[fa])ls[fa]=i;
    		else rs[fa]=i;
    	}
    	dfs(1);
    	printf("%lld\n",ans);
    	return 0;
    }
    
    

    游记参见另一篇博客

  • 相关阅读:
    php多态
    二分查找法
    mysql联合索引详解
    PHPFastCGI进程管理器PHP-FPM详解
    IDEA Spark程序报错处理
    逻辑回归与多项逻辑回归
    特征选择--->卡方选择器
    特征变化--->特征向量中部分特征到类别索引的转换(VectorIndexer)
    特征变化--->标签到向量的转换(OneHotEncoder)
    特征变化--->索引到标签的转换(IndexToString)
  • 原文地址:https://www.cnblogs.com/harryzhr/p/16180085.html
Copyright © 2020-2023  润新知