联合省选2022 题解

D1T1 预处理器

大模拟题。使用哈希+\(map\) 维护每个串会变成什么串,以及是否在被展开状态,然后暴力展开即可。由于保证输出的每行字符数都不超过 \(1000\),因此极限情况下也只能卡到调用 \(50^3\times 10^2\) 次 \(map\)。实测还是跑的很快的。

代码里加了一些(没有实际用处)神秘的记忆化。

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const pii base=make_pair(131,233);
const int mod1=998244353,mod2=1e9+7,N=210;
inline int add(int x,int y,int mod){
	return (x+y>=mod)?x+y-mod:x+y;
}
inline int dec(int x,int y,int mod){
	return (x-y<0)?x-y+mod:x-y;
}
inline pii operator +(const pii &x,const pii &y){
	return make_pair(add(x.first,y.first,mod1),add(x.second,y.second,mod2));
}
inline pii operator -(const pii &x,const pii &y){
	return make_pair(dec(x.first,y.first,mod1),dec(x.second,y.second,mod2));
}
inline pii operator *(const pii &x,const pii &y){
	return make_pair(1ll*x.first*y.first%mod1,1ll*x.second*y.second%mod2);
}
int n,tot,pd[N],vis[N];
string s,to[N],rel[N];
map<pii,int> mp;
inline bool check(char c){
	return (c>='a'&&c<='z')||(c>='A'&&c<='Z')||(c=='_')||(c>='0'&&c<='9');
}
inline void solve(string s,int now){
	int len=s.size();
	for(int l=0,r=0;l<len;l=r+1){
		r=l;
		if(!check(s[l])){
			if(now) rel[now].push_back(s[l]);
			putchar(s[l]);
			continue;
		}
		pii hsh=make_pair(s[l],s[l]);
		while(r<len-1&&check(s[r+1])){
			++r;
			hsh=hsh*base+make_pair(s[r],s[r]);
		}
		if(!mp.count(hsh)||pd[mp[hsh]]==0){
			for(int i=l;i<=r;++i){
				if(now) rel[now].push_back(s[i]);
				putchar(s[i]);
			}
			continue;
		}
		else{
			int tmp=mp[hsh];
			if(now==0){
				if(vis[tmp]){
					for(int j=0;j<rel[tmp].size();++j) putchar(rel[tmp][j]);
					continue;
				}
				else{
					pd[tmp]=0;
					solve(to[tmp],tmp);
					vis[tmp]=1;pd[tmp]=1;
					continue;
				}
			}
			pd[tmp]=0;
			solve(to[tmp],now);
			pd[tmp]=1;
		}
	}
}
int main(){
	scanf("%d",&n);getline(cin,s);
	for(int i=1;i<=n;++i){
		getline(cin,s);
		int len=s.size();
		if(s[0]=='#'){
			if(s[1]=='d'){
				++tot;
				pii hsh=make_pair(0,0);
				int now=8;
				for(;now<len&&s[now]!=' ';++now)
					hsh=hsh*base+make_pair(s[now],s[now]);
				++now;
				mp[hsh]=++tot;pd[tot]=1;
				to[tot].resize(len-now);
				for(int j=0;now<len;++now,++j)
					to[tot][j]=s[now];
			}
			else{
				pii hsh=make_pair(0,0);
				int now=7;
				for(;now<len;++now)
					hsh=hsh*base+make_pair(s[now],s[now]);
				pd[mp[hsh]]=0;
			}
		}
		else{
			for(int j=1;j<=tot;++j) vis[j]=0,rel[j].clear();
			solve(s,0);
		}
		puts("");
	}
	return 0;
}

D1T2 填树

首先考虑暴力,可以直接枚举最大值 \(mx\),那么所有数权值的可选范围为 \([mx-k,mx]\) 且至少有一个数的权值为 \(mx\)。后面这个至少的限制可以通过所有数可选范围为 \([mx-k,mx]\) 的答案,减去可选范围为 \([mx-k,mx-1]\) 的答案来完成。由于二者本质相同,现在我们可以忽略这一限制了。

于是现在可以得到每个数的实际可选范围为 \([x_i,y_i]\),考虑 \(DP\) 计算所有情况下每条链的权值之和。记 \(f_i,g_i\) 分别表示以 \(i\) 以及 \(i\) 子树内任意一个节点为端点的所有链的两问答案之和。记 \(c_i=y_i-x_i+1,s_i=\sum_{j=x_i}^{y_i}j\),那么有转移:

\[f_u=c_u+\sum_{v\in son_u}c_uf_v\\ g_u=s_u+\sum_{v\in son_u}(c_ug_v+s_uf_v) \]

这样一遍 \(\mathcal O(n)\) \(dp\) 即可算出以任意一个节点为端点的所有链的答案之和,通过换根 \(dp\) 即可 \(\mathcal O(n)\) 得到所有链的答案之和。

但是值域太大了,暴力枚举肯定不行,考虑优化。考察随着 \(mx\) 的增加,每个点的 \(c\) 与 \(s\) 的变化,可以发现将每个点 \(u\) 的 \(l_u,r_u,l_u+k,r_u+k+1\) 取出作为关键点,可以发现当 \(mx\) 在相邻两个关键点之间移动时,所有范围与 \([mx-k,mx]\) 有交的点会一直有交,无交的点会一直无交,并且有交的点的 \(c\) 与 \(s\) 为关于 \(mx\) 的一次与二次多项式。那么将链上的所有点对应的多项式相乘,最终得到的该链的答案依然是多项式,因此全局的答案为所有链的答案相交,应当是一个不超过 \(n+1\) 次的多项式。

于是用关键点将 \(mx\) 的值域分为若干段,对于每一段暴力算出前 \(n+2\) 个值对应的答案,即可拉格朗日插值出这一段答案前缀和的多项式,由于段数是 \(\mathcal O(n)\) 的,最终复杂度 \(\mathcal O(n^3)\)。

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7,N=810,iv=(mod+1)/2;
vector<int> to[N];
int n,k,l[N],r[N],d[N],tot,mx,mn=1e9,c[N],s[N],ans0,ans1,ret0,ret1,tim[N],cnt,all=0;
int val0[N],val1[N],x[N],fac[N],inv[N],pre[N],suf[N];
int f[N],g[N],ffa[N],gfa[N];
inline int add(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
inline int dec(int x,int y){return (x-y<0)?x-y+mod:x-y;}
inline void inc(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
inline void rec(int &x,int y){x=(x-y<0)?x-y+mod:x-y;}
inline int S(int x,int y){
	return (1ll*(x+y)*(y-x+1)/2)%mod;
}
inline int ksm(int x,int y){
	int ret=1;
	for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) ret=1ll*ret*x%mod;
	return ret;
}
inline void dfs(int u,int fa){
	f[u]=c[u];g[u]=s[u];
	for(int v:to[u])
		if(v!=fa){
			dfs(v,u);
			f[u]=(f[u]+1ll*c[u]*f[v])%mod;
			g[u]=(g[u]+1ll*c[u]*g[v]+1ll*s[u]*f[v])%mod;
		}
}
inline void redfs(int u,int fa){
	if(fa){
		f[u]=(f[u]+1ll*c[u]*ffa[u])%mod;
		g[u]=(g[u]+1ll*c[u]*gfa[u]+1ll*s[u]*ffa[u])%mod;
	}
	inc(ret0,f[u]);inc(ret1,g[u]);
	int sf=ffa[u],sg=gfa[u];
	for(int v:to[u])
		if(v!=fa) inc(sf,f[v]),inc(sg,g[v]);
	for(int v:to[u]){
		if(v==fa) continue;
		int rf=dec(sf,f[v]),rg=dec(sg,g[v]);
		ffa[v]=(c[u]+1ll*c[u]*rf)%mod;
		gfa[v]=(s[u]+1ll*c[u]*rg+1ll*s[u]*rf)%mod;
	}
	for(int v:to[u]) if(v!=fa) redfs(v,u);
}
inline pair<int,int> check(int L,int R){
	for(int j=1;j<=n;++j){
		c[j]=min(R,r[j])-max(L,l[j])+1;c[j]=max(c[j],0);
		s[j]=S(max(L,l[j]),min(R,r[j]));
		if(!c[j]) s[j]=0;
	}
	ret0=ret1=0;
	dfs(1,0);
	redfs(1,0);
//	for(int j=1;j<=n;++j){
//		dfs(j,0);
//		inc(ret0,f[j]),inc(ret1,g[j]);
//	}
	return make_pair(ret0,ret1);
}
inline void init(int n){
	fac[0]=1;
	for(int i=1;i<=n;++i) fac[i]=1ll*fac[i-1]*i%mod;
	inv[n]=ksm(fac[n],mod-2);
	for(int i=n-1;i>=0;--i) inv[i]=1ll*inv[i+1]*(i+1)%mod;
}
inline int sgn(int x){return (x&1)?mod-1:1;}
inline int lagrange(int *y,int k,int len=n+1){
	if(k<=len) return y[k];
	k--;
	int ans=0;pre[0]=1;
	for(int i=1;i<=len;++i) pre[i]=1ll*pre[i-1]*dec(k,x[i])%mod;
	suf[len+1]=1;
	for(int i=len;i>=1;--i) suf[i]=1ll*suf[i+1]*dec(k,x[i])%mod;
	for(int i=1;i<=len;++i){
		int xs=1ll*y[i]*pre[i-1]%mod*suf[i+1]%mod;
		xs=1ll*xs*inv[x[i]]%mod*inv[x[len]-x[i]]%mod*sgn(x[len]-x[i])%mod;
		inc(ans,xs);
	}
	return ans;
}
inline void work(int k,int tp){
	cnt=0;
	int L=(tp==1)?mn:mn-1,R=(tp==1)?mx+1:mx;
	tim[++cnt]=L;tim[++cnt]=R;
	for(int i=1;i<=tot;++i){
		if(L<=d[i]&&d[i]<=R) tim[++cnt]=d[i];
		if(L<=d[i]+k&&d[i]+k<=R) tim[++cnt]=d[i]+k;
	}
	sort(tim+1,tim+cnt+1);
	cnt=unique(tim+1,tim+cnt+1)-tim-1;
	for(int i=1;i<cnt;++i){
		int lef=tim[i],rig=tim[i+1]-1;
		int now0=0,now1=0;
		for(int j=0;j<=n&&j<=rig-lef;++j){
			pair<int,int> ans=check(lef-k+j,lef+j);
			inc(now0,ans.first);inc(now1,ans.second);
			x[j+1]=j;val0[j+1]=now0;val1[j+1]=now1;
		}
		if(tp==1) inc(ans0,lagrange(val0,rig-lef+1)),inc(ans1,lagrange(val1,rig-lef+1));
		else rec(ans0,lagrange(val0,rig-lef+1)),rec(ans1,lagrange(val1,rig-lef+1));
	}
}
int main(){
	scanf("%d%d",&n,&k);
	init(n);
	for(int i=1;i<=n;++i){
		scanf("%d%d",&l[i],&r[i]),mx=max(mx,r[i]),mn=min(mn,l[i]);
		d[++tot]=l[i];d[++tot]=r[i];
		inc(ans0,r[i]-l[i]+1);inc(ans1,S(l[i],r[i]));
	}
	for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),to[u].push_back(v),to[v].push_back(u);
	sort(d+1,d+tot+1);
	tot=unique(d+1,d+tot+1)-d-1;
	work(k,1);
	work(k-1,-1);
	printf("%d\n%d\n",1ll*ans0*iv%mod,1ll*ans1*iv%mod);
	return 0;
}

//tqx

D1T3 学术社区

首先考虑满足性质 \(C\) 的情况。

可以发现题目中的限制相当于若干条边 \((u,v)\) 表示如果在最终排列中 \(v\) 恰好为 \(u\) 后一个数字时可以获得 \(1\) 的贡献。那么可以想到建立一个二分图,将每个点拆为入点与出点,入点 \(u\) 与出点 \(v\) 匹配意味着钦定 \(u\) 在 \(v\) 前一个位置,那么可以暴力对于所有边 \((u,v)\) 从 \(u\) 的入点向 \(v\) 的出点连边跑二分图匹配。假设最终得到的答案 \(ans\),那么真正的答案一定 \(\le ans\)。

但目前看来真正的答案很可能 \(<ans\),因为很可能最终跑出的匹配会形成环,如 \(A\rightarrow B\rightarrow C\rightarrow A\),其中 \(A,B,C\) 是三条信息而不是三个人,这样的构造一定不合法。

但是题目中还有一条重要的我们还没有用到的性质:每一个在帖子中发过言的人都一定会在帖子中发出至少一条学术消息,考虑先用二分图匹配跑出一个构造,再利用这一点进行拆环,想到这一点后不难给出接下来的构造:

对于任何一个环,显然有环中的所有信息要么同时为楼上型信息,要么同时为楼下型信息,我们分别进行讨论:

  • 同为楼下型信息:任取环上的一个信息 \(A\) ,取出与 \(A\) 发言人相同的学术信息 \(X\),将环中 \(A\) 的出边 \(A\rightarrow B\) 改为 \(X\rightarrow B\),如果 \(X\) 有出边 \(X\rightarrow D\),那么将 \(D\) 接到 \(A\) 上即可,这样一来与 \(B,D\) 相邻的信息的发言人都没有发生变化,\(A\) 的入边没有改变因此它仍然正常做出贡献,环却被断掉了:

    例:
    E->X->D A->B->C->A
    改为
    E->X->B->C->A->D
    
  • 同为楼上型消息:此时同样任取环上的一个信息 \(A\),取出与 \(A\) 发言人相同的学术信息 \(X\),将环中 \(A\) 的入边 \(C\rightarrow A\) 改为 \(C\rightarrow X\),如果 \(X\) 有入边 \(E\rightarrow X\),将 \(E\) 接到 \(A\) 上即可:

    例:
    E->X->D A->B->C->A
    改为
    E->A->B->C->X->D
    

由此可以得到最终的答案就等于 \(ans\),并且可以通过如上的方式构造出一组解。复杂度的瓶颈在于一开始的网络流匹配,可以通过对每个发言人建立一个虚点连向他的所有发言的出点,再建一个出点,从它的所有发言的入点连向它,即可优化到点数边数均为 \(\mathcal O(m)\)。最终复杂度分析类似二分图匹配,复杂度为 \(\mathcal O(m\sqrt{m})\)。

对于没有特殊性质 \(C\) 的情况。此时的问题在于可能会存在将 \(u\) 与 \(v\) 匹配会得到 \(2\) 的贡献的情况,此时我们称 \(u\) 与 \(v\) 双向匹配。事实上出现这种情况时,直接贪心地将 \(u\) 放在 \(v\) 前面一定是不劣的。如果存在另一种方案使得将 \(u\) 放在 \(x\) 前,\(y\) 放在 \(v\) 前答案更优了,那么 \(u\) 与 \(x\),\(y\) 与 \(v\) 中一定有一组也是双向匹配的,不妨设为前者,那么 \(x\) 与 \(v\) 一定完全一致,直接交换 \(x\) ,\(v\) 即可,则 \(u\) 与 \(v\) 匹配了且答案不会更劣。

于是就做完了,复杂度 \(\mathcal O(m\sqrt{m})\)。

#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){
		#ifdef TQX
		return getchar();
		#endif
		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;
	}
	template<typename T> inline void putint(T x,char div){
		static char s[15];
		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 namespace iobuff;
typedef pair<int,int> pii;
const pii base=make_pair(131,233);
const int mod1=998244353,mod2=1e9+7,N=2.5e5+10;
inline int add(int x,int y,int mod){
	return (x+y>=mod)?x+y-mod:x+y;
}
inline int dec(int x,int y,int mod){
	return (x-y<0)?x-y+mod:x-y;
}
inline pii operator +(const pii &x,const pii &y){
	return make_pair(add(x.first,y.first,mod1),add(x.second,y.second,mod2));
}
inline pii operator -(const pii &x,const pii &y){
	return make_pair(dec(x.first,y.first,mod1),dec(x.second,y.second,mod2));
}
inline pii operator *(const pii &x,const pii &y){
	return make_pair(1ll*x.first*y.first%mod1,1ll*x.second*y.second%mod2);
}
map<pii,int> mp;

int T,n,m;
char s[3][N],t[N];
struct edge{
	int u,v,w;
}e[N];
inline bool louxia(char *s){
	int len=strlen(s);
	if(len!=6) return false;
	return s[0]=='l'&&s[1]=='o'&&s[2]=='u'&&s[3]=='x'&&s[4]=='i'&&s[5]=='a';
}
inline bool loushang(char *s){
	int len=strlen(s);
	if(len!=8) return false;
	return s[0]=='l'&&s[1]=='o'&&s[2]=='u'&&s[3]=='s'&&s[4]=='h'&&s[5]=='a'&&s[6]=='n'&&s[7]=='g';
}
int p[N],ans=0,ret[N],bel[N];
namespace sub{
	const int M=2e7+10;
	struct node{
		int v,f,nxt;
	}d[M];
	int s,t,tot,first[N],cur[N],cnt=1,dis[N],vis[N],q[N],l,r;
	inline void add(int u,int v,int f){
		d[++cnt].v=v;d[cnt].f=f;d[cnt].nxt=first[u];first[u]=cnt;
		d[++cnt].v=u;d[cnt].f=0;d[cnt].nxt=first[v];first[v]=cnt;
	}
	inline bool bfs(){
		memset(vis+1,0,sizeof(int)*(tot));
		l=1;r=0;q[++r]=s;vis[s]=1;dis[s]=0;
		while(l<=r){
			int u=q[l++];
			for(int i=first[u];i;i=d[i].nxt){
				int v=d[i].v;
				if(d[i].f>0&&!vis[v]){
					vis[v]=1;dis[v]=dis[u]+1;
					q[++r]=v;
				}
			}
		}
		return vis[t];
	}
	inline int dfs(int u,int f){
		if(u==t||!f) return f;
		int used=0;
		for(int& i=cur[u];i;i=d[i].nxt){
			int v=d[i].v;
			if(d[i].f>0&&dis[v]==dis[u]+1){
				int fl=dfs(v,min(f,d[i].f));
				d[i].f-=fl;d[i^1].f+=fl;
				used+=fl;f-=fl;
				if(!f) break;
			}
		}
		if(f) dis[u]=0x3f3f3f3f;
		return used;
	}
	inline int dinic(){
		int ans=0;
		while(bfs()){
			memcpy(cur,first,sizeof(int)*(tot+1));
			while(int tmp=dfs(s,0x3f3f3f3f)) ans+=tmp;
//			ans+=dfs(s,0x3f3f3f3f);
		}
		return ans;
	}
	int to[N],pd[N],fr[N],p1[N],p2[N],pos1[N],pos2[N],ansp[N],used[N];
	vector<int> v1[N],v2[N];
	int sum;
	vector<int> edg[N];
	map<int,int> mp[N];
	inline void Add(int u,int v){
		fr[v]=u;to[u]=v;
	}
	inline int init(){
		int ans=0;
		for(int i=1;i<=sum;++i) edg[i].clear();sum=0;
		for(int i=1;i<=m;++i) mp[i].clear();
		for(int i=1;i<=m;++i){
			if(e[i].w==1){
				if(!mp[e[i].u].count(e[i].v)) mp[e[i].u][e[i].v]=++sum;
				int tmp=mp[e[i].u][e[i].v];
				edg[tmp].push_back(i);
			}
		}
		for(int i=1;i<=m;++i){
			if(e[i].w==2){
				if(mp[e[i].v].count(e[i].u)){
					int tmp=mp[e[i].v][e[i].u];
					if(edg[tmp].size()){
						Add(i,edg[tmp].back());
						used[i]=used[edg[tmp].back()]=1;ans+=2;
						edg[tmp].pop_back();
					}
				}
			}
		}
		return ans;
	}
	void main(){
		for(int i=1;i<=m;++i) fr[i]=pd[i]=to[i]=0;
		ans=init();
		memset(first+1,0,sizeof(int)*(tot));cnt=1;
		tot=m<<1;
		s=++tot,t=++tot;
		for(int i=1;i<=n;++i) p1[i]=++tot,p2[i]=++tot;
		for(int i=1;i<=m;++i){
			if(!to[i]) add(s,i,1);
			if(!fr[i]) add(i+m,t,1);
			if(e[i].w==1&&!fr[i]) add(p1[e[i].v],i+m,1),ansp[i]=cnt;
			else if(e[i].w==2&&!to[i]) add(i,p2[e[i].v],1),ansp[i]=cnt;
			else ansp[i]=0;
			if(!to[i]) add(i,p1[e[i].u],1),pos1[i]=cnt;else pos1[i]=0;
			if(!fr[i]) add(p2[e[i].u],i+m,1),pos2[i]=cnt;else pos2[i]=0;
		}
		ans+=dinic();
		for(int i=1;i<=m;++i){
			if(d[pos2[i]].f) v2[e[i].u].push_back(i);
			if(d[pos1[i]].f) v1[e[i].u].push_back(i);
		}
		for(int i=1;i<=m;++i){
			if(e[i].w!=0&&d[ansp[i]].f){
				if(e[i].w==1){
					int ano=v1[e[i].v].back();
					Add(ano,i);
					v1[e[i].v].pop_back();
				}
				if(e[i].w==2){
					int ano=v2[e[i].v].back();
					Add(i,ano);
					v2[e[i].v].pop_back();
				}
			}
		}
		for(int i=1;i<=m;++i) pd[i]=0;
		int nw=0;
		for(int i=1;i<=m;++i){
			if(!pd[i]&&!fr[i]){
				int u=i;
				while(u&&!pd[u]){
					pd[u]=++nw;p[nw]=u;
					u=to[u];
				}
			}
		}
		for(int i=1;i<=m;++i){
			if(pd[i]) continue;
			int now=i;
			while(now&&!pd[now]){
				pd[now]=1;
				now=to[now];
			}
			if(e[now].w==1){
				int x=bel[e[now].u],nxt=to[now];
				to[now]=to[x];if(to[x]) fr[to[x]]=now;
				to[x]=nxt;fr[nxt]=x;
			}
			else{
				int x=bel[e[now].u],nxt=fr[now];
				if(fr[x]) to[fr[x]]=now;fr[now]=fr[x];
				to[nxt]=x;fr[x]=nxt;
			}
		}
		for(int i=1;i<=m;++i) pd[i]=0;
		nw=0;
		for(int i=1;i<=m;++i){
			if(!pd[i]&&!fr[i]){
				int u=i;
				while(u&&!pd[u]){
					pd[u]=++nw;p[nw]=u;
					u=to[u];
				}
			}
		}
		putint(ans,'\n');
		for(int i=1;i<=m;++i) putint(p[i],' ');
		pc('\n');
	}
}
int main(){
//	freopen("community21.in","r",stdin);
//	freopen("my.out","w",stdout);
	scanf("%d",&T);
	while(T--){
		mp.clear();
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i){
			scanf("%s",t+1);
			pii hsh=make_pair(0,0);
			int len=strlen(t+1);
			for(int j=1;j<=len;++j) hsh=hsh*base+make_pair(t[j],t[j]);
			mp[hsh]=i;
		}
		int all=0;
		for(int i=1;i<=m;++i){
			scanf("%s%s%s",s[0]+1,s[1]+1,s[2]+1);
			pii hsh=make_pair(0,0);
			int len=strlen(s[0]+1);
			for(int j=1;j<=len;++j) hsh=hsh*base+make_pair(s[0][j],s[0][j]);
			e[i].u=mp[hsh];
			
			hsh=make_pair(0,0);
			len=strlen(s[1]+1);
			for(int j=1;j<=len;++j) hsh=hsh*base+make_pair(s[1][j],s[1][j]);
			if(mp.count(hsh)){
				if(louxia(s[2]+1)) e[i].v=mp[hsh],e[i].w=1,++all;
				else if(loushang(s[2]+1)) e[i].v=mp[hsh],e[i].w=2,++all;
				else e[i].v=e[i].w=0;
			}
			else e[i].v=e[i].w=0;
			if(!e[i].w) bel[e[i].u]=i;
		}
		sub::main();
	}
	flush();
	return 0;
}

//tqx

D2T1 卡牌

首先题目中的限制相当于每个给出的质数的倍数都至少有一个被选择了,此外, \(n\) 出的很大也没有用处,我们只用关心每个数字出现的次数 \(c_i\),进而得到这一个数字的卡牌中至少一张被选择的方案 \(w_i\)。

考虑 \(2000\) 以内的所有质数,注意到 \(43\times 47=2021>2000\),因此对于所有 \(\ge 43\) 的质数,不可能有任何一个数同时包含其中两个不同的质数作为因子,可以直接根据每个数 \(\ge 43\) 的质因子是哪一个(或者不存在)将所有数分为若干类。而 \(<43\) 的质数只有 \(13\) 个,可以直接状压,对每类数预处理 \(f_S\) 分别表示这一类中的所有数任意选择,最终只有 \(S\) 里的质因子的倍数出现了的方案数。\(g_S\) 为在此基础上要求这一类至少选择了一个数的方案数。

那么回答询问时直接在某些类至少要选择一个数的限制下,计算 \(dp_S\) 表示最终选出的所有数中,\(S\) 里的质因子倍数出现了当方案数。那么询问直接枚举超集即可。\(dp_S\) 则可以通过 \(FWT\) 求出,对每一类预先将 \(f,g\) 分别 \(FWT\),询问时将被钦定了的集合的 \(g\) 与其他集合的 \(f\) 对位相乘后 \(IFWT\) 回来即可。由于被钦定集合的总数 \(\le \sum c_i\),通过预处理所有 \(f\) 对位相乘的乘积,即可做到总复杂度 \(\mathcal O(2^{13}\sum c_i)\),可以通过。

#include<bits/stdc++.h>
using namespace std;
const int N=2010,M=1e6+10,mod=998244353;
int mx,n,m,s[M],ct[N],p[N],pcnt,pdp[N],tot,stk[N],top;
inline void init_prime(int n){
	for(int i=2;i<=n;++i){
		if(!pdp[i]) p[++pcnt]=i;
		for(int j=1;j<=pcnt&&i*p[j]<=n;++j){
			pdp[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
}
vector<int> ve[N];
int dp[(1<<13)+20],pd[N],pos[N],in[N],w[N];
int f[310][2][(1<<13)+20],ans[(1<<13)+20],ret[(1<<13)+20];
inline int dec(int x,int y){return (x-y<0)?x-y+mod:x-y;}
inline void inc(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
inline void rec(int &x,int y){x=(x-y<0)?x-y+mod:x-y;}
inline int ksm(int x,int y){
	int ret=1;
	for(;y;y>>=1,x=1ll*x*x%mod)
		if(y&1) ret=1ll*ret*x%mod;
	return ret;
}
inline void FWT(int *f,int tp){
	int sum=1<<tot;
	for(int i=1;i<sum;i<<=1){
		int len=i<<1;
		for(int j=0;j+len-1<sum;j+=len){
			for(int k=j;k<j+i;++k){
				if(tp==1) inc(f[k+i],f[k]);
				else rec(f[k+i],f[k]);
			}
		}
	}
}
inline void work(int x){
	for(int j=0;j<(1<<tot);++j) dp[j]=0;
	dp[0]=1;
	for(int j:ve[x])
		for(int k=(1<<tot)-1;k>=0;--k) dp[k|pd[j]]=(dp[k|pd[j]]+1ll*dp[k]*w[j])%mod;
	for(int j=0;j<(1<<tot);++j)
		f[x][1][j]=f[x][0][j]=dp[j];
	rec(f[x][1][0],1);
	FWT(f[x][0],1);FWT(f[x][1],1);
	for(int j=0;j<(1<<tot);++j)
		f[x][1][j]=1ll*f[x][1][j]*ksm(f[x][0][j],mod-2)%mod;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&s[i]),mx=max(mx,s[i]),ct[s[i]]++;
//	cerr<<(double)clock()/CLOCKS_PER_SEC<<endl;
	init_prime(mx);
	for(int i=1;i<=pcnt;++i) pos[p[i]]=i;
	tot=0;
	while(tot<pcnt-1&&p[tot+1]*p[tot+2]<=mx) ++tot;
//	cerr<<"tot="<<tot<<endl;
//	tot=pcnt;
	for(int i=1;i<=tot;++i)
		for(int j=p[i];j<=mx;j+=p[i]) pd[j]|=1<<i-1;
	for(int i=1;i<=mx;++i) w[i]=dec(ksm(2,ct[i]),1);
	for(int i=tot+1;i<=pcnt;++i){
		stk[++top]=p[i];
		for(int j=p[i];j<=mx;j+=p[i])
			if(ct[j]) ve[top].push_back(j),in[j]=1;
		work(top);
	}
	stk[++top]=0;
	for(int i=1;i<=mx;++i) if(ct[i]&&!in[i]) ve[top].push_back(i);
	work(top);
	for(int i=0;i<(1<<tot);++i){
		ans[i]=1;
		for(int j=1;j<=top;++j) ans[i]=1ll*ans[i]*f[j][0][i]%mod;
	}
	scanf("%d",&m);
	while(m--){
		int c,tmp=0;scanf("%d",&c);
		for(int i=0;i<(1<<tot);++i) ret[i]=ans[i];
		for(int i=1,x;i<=c;++i){
			scanf("%d",&x);
			if(pos[x]<=tot) tmp|=1<<(pos[x]-1);
			else{
				x=pos[x]-tot;
				for(int j=0;j<(1<<tot);++j)
					ret[j]=1ll*ret[j]*f[x][1][j]%mod;
			}
		}
		FWT(ret,-1);
		int ans=0;
		for(int i=0;i<(1<<tot);++i)
			if((i&tmp)==tmp)
				inc(ans,ret[i]);
		printf("%d\n",ans);
	}
	return 0;
}

//tqx

D2T2 序列变换

又是阴间括号序列题(令人闻风丧胆的英文名称)。

和 WCT1 一样,还是考虑建立括号树。最终建出的实际上是一个森林,于为了方便用一个虚点连向所有树的根使森林变成树。那么第一类操作就是对于相邻兄弟节点 \(u,v\),将 \(v\) 的儿子全被接到 \(u\) 上,再将 \(v\) 也接到 \(u\) 上。第二类操作可以交换相邻的兄弟节点,也就是将第一类中的“相邻”去掉。

于是最终的操作,可以看作若干轮,每一轮对于当前层的若干括号,将其中一个保留,其他的都变成它的儿子,留到下一层。根据 \(x,y\) 的不同,可以给出不同的贪心策略,记某一层所有节点的权值和为 \(sum\),点数量为 \(sz\)。

  • \(x=y=0\):不多说。好像没有这样的点。

  • \(x=0,y=1\):每次合并,会付出被下放的节点的权值。因此若最终保留的节点权值为 \(w\),总代价就是这层所有节点的代价和减去 \(w\),贪心保留权值最大的点即可。

  • \(x=1,y=1\):每次合并,会付出两个节点的权值之和。记权值最小的节点为 \(mn\),显然可以贪心的将 \(mn\) 之外的节点分别与 \(mn\) 操作,如果要保留的不是 \(mn\) ,那么最后将 \(mn\) 挂到这个点的儿子上,其余节点与 \(mn\) 的操作都是将自己挂到 \(mn\) 的儿子上。于是总代价为 \(sum-mn+mn(sz-1)=sum+mn(sz-2)\) 。贪心地每层保留权值最大的点使之后层的 \(mn,sum\) 都尽量小即可。

  • \(x=1,y=0\):此时每一层的代价就是 \(mn(sz-2)+w\),其中 \(w\) 是被保留的节点的权值。注意到随着层数的增加,\(sz\) 一定一开始不减,最后单调每次减 \(1\)。最开始的一段 \(sz=1\) 的层可以忽略,那么剩下的部分中除了最后一层留下的唯一一个节点外,其他节点的权值都会被计算。于是对于每一层,我们一方面想要将最小值下放以使得后面层的 \(mn\) 尽量小,一方面也想下放最大值,使得最终可以不计算最大值的权值。

    对于 \(sz>2\) 的层,这两者可以兼得,保留任意一个既不是最大值也不是最小值的数即可。

    对于 \(sz=2\) 的层,它们一定共同组成一个前缀,在经过一系列操作后留下了某一个节点下放到接下来的层,并付出其他节点权值和的代价。不难发现最后下放的一定是这些层中所有节点权值最小值或者最大值。

    证明:考虑反证法,设最终保留的节点权值为 \(w\),满足 \(w\) 既不是最小值 \(mn\) 也不是最大值 \(mx\)。

    相比于 \(mn\),保留下 \(w\) 会导致在 \(sz=2\) 的层中付出的代价减少了 \(w-mn\),但如果在接下来层的操作中,某一时刻 \(w\) 成为了最小值,此时如果一开始保留的 \(mn\) 可以节省 \((w-mn)(sz-2)\) 的代价。因此保留 \(mn\) 一定不劣。

    如果 \(w\) 一直没有成为最小值,那么一开始保留 \(mx\) 一定也不会成为最小值,且付出的代价更少。

    于是得证。

于是只需要用 \(set\) 模拟以上过程即可。复杂度 \(\mathcal O(n\log n)\)。

#include<bits/stdc++.h>
using namespace std;
const int N=8e5+10;
typedef long long ll;
int n,x,y,w[N],dep[N],mat[N],stk[N],top,now,val[N],fa[N],tot,mstk[N],all,ap;
char s[N];
ll sum;
vector<int> de[N];
inline void build(int p,int l,int r){
	if(l>r) return ;
	if(mat[l]!=r){build(p,l,mat[l]),build(p,mat[l]+1,r);return ;}
	++tot;dep[tot]=dep[p]+1;
	de[dep[tot]].push_back(tot);
	if(p) fa[tot]=p;
	build(tot,l+1,r-1);
}
inline ll sub(){
	multiset<pair<int,int> > s;
	ll sum=0;
	for(int v:de[1]) s.insert(make_pair(w[v],v)),sum+=w[v];
	ll ans=0;int now=1;
	while(s.size()){
		ll mn=(*s.begin()).first,mx=(*s.rbegin()).first;
		++now;int pos=(*s.rbegin()).second;
		if(s.size()!=1){
			if(!de[now].size()&&s.size()==2) ans+=mn;
			else{
				if((*s.rbegin()).second==ap){
					s.erase(s.find(make_pair(mx,pos)));
					auto p=s.rbegin();
					int nmx=(*p).first,np=(*p).second; 
					s.insert(make_pair(mx,pos));
					mx=nmx;pos=np;
				}
				ans+=mn*(s.size()-2)+mx;
			}
		}
		s.erase(s.find(make_pair(mx,pos)));
		sum-=mx;
		for(int v:de[now]) s.insert(make_pair(w[v],v)),sum+=w[v]; 
	}
	return ans;
}
int main(){
//	freopen("bracket5.in","r",stdin);
	scanf("%d%d%d",&n,&x,&y);
	if(!x&&!y){puts("0");return 0;}
	scanf("%s",s+1);
	for(int i=1;i<=(n<<1);++i){
		if(s[i]=='(') stk[++top]=i;
		else{
			mat[stk[top]]=i;
			mat[i]=stk[top];
			--top;
		}
	}
	bool fl=1;
	for(int i=1;i<=n;++i)
		scanf("%d",&w[i]),fl&=w[i]==w[1];
	build(0,1,n<<1);
	ll ans=0;
	if(x==1&&y==0){
		all=0;ap=0;
		for(int i=1;i<=n;++i)
			if(w[i]>all) all=w[i],ap=i;
		ll mn=6e18,mnp=0,mx=0,ct=1,ret=0,mxp=0;
		ans=sub();
		set<pair<int,int> > s;
		for(int v:de[1]) s.insert(make_pair(w[v],v));
		while(s.size()){
			if(s.size()==2){
				if((*s.begin()).first<mn) mn=(*s.begin()).first,mnp=(*s.begin()).second;
				if((*s.rbegin()).first>mx) mx=(*s.rbegin()).first,mxp=(*s.rbegin()).second;
			}
			if(s.size()!=1) ret+=(*s.rbegin()).first;
			s.erase(*s.rbegin());ct++;
			if(s.size()+de[ct].size()>2){
				for(auto p:s) ret+=p.first;
				s.clear();s.insert(make_pair(mn,mnp));
				for(int v:de[ct]) s.insert(make_pair(w[v],v));
				ret-=mn;
				while(s.size()){
					mn=(*s.begin()).first,mx=(*s.rbegin()).first;
					int pos=(*s.rbegin()).second;
					if(s.size()!=1){
						if(!de[ct].size()&&s.size()==2) ret+=mn;
						else{
							s.erase(s.find(make_pair(mx,pos)));
							auto p=s.rbegin();
							int nmx=(*p).first,np=(*p).second; 
							s.insert(make_pair(mx,pos));
							mx=nmx;pos=np;
							ret+=mn*(s.size()-2)+mx;
						}
					}
					s.erase(s.find(make_pair(mx,pos)));ct++;
					for(int v:de[ct]) s.insert(make_pair(w[v],v));
				}
				ans=min(ans,ret);
				break;
			}
			for(int v:de[ct]) s.insert(make_pair(w[v],v)); 
		}
		ap=mxp;
		ans=min(ans,sub());
		printf("%lld\n",ans);
		return 0;
	}
	int now=1;
	ll sum=0;
	multiset<int> s;
	for(int v:de[1]) s.insert(w[v]),sum+=w[v];
	while(s.size()){
		ll mn=*s.begin(),mx=*s.rbegin();
		if(s.size()!=1){
			if(x==1&&y==1) ans+=mn*(s.size()-1)+(sum-mn);
			if(x==0&&y==1) ans+=sum-mx;
		}
		++now;
		sum-=mx;s.erase(s.find(*s.rbegin()));
		for(int v:de[now]) s.insert(w[v]),sum+=w[v]; 
	};
	printf("%lld\n",ans);
	return 0;
}

//tqx

D2T3 最大权独立集问题

容易想到 \(dp\),记 \(f_{u,v,w}\) 表示 \(u\) 子树最终传到 \(u\) 父亲去的权值为 \(v\),从 \(u\) 父亲处传入的权值为 \(w\) 时这个子树内所有操作的代价之和的最小值。转移时根据点 \(u\) 的三条边的断裂顺序大力讨论即可。

这样做状态数是 \(\mathcal O(n^3)\) 的,考虑优化。改为设 \(f_{u,v,d}\) 表示 \(u\) 子树最终传到 \(u\) 父亲去的点为 \(v\),从 \(u\) 父亲处传入的节点停留在了离 \(u\) 距离 \(d\) 的节点时,这个子树内所有操作,不考虑传入的值的贡献时,代价之和的最小值。需要注意的是为了方便,点 \(u\) 与父亲交换这一过程中 \(u\) 上的权值也被算了状态中。

注意到 \(v\) 以及传入节点停留的位置一定在 \(u\) 不同的子树中。于是根据树形背包的理论状态数就是 \(\mathcal O(n^2)\)。

接下来就是巨大多阴间的转移:

现在只考虑点 \(u\) 同时有左儿子 \(lc\)、右儿子 \(rc\)、父亲 \(fa\) 的情况。其余情况都可以效仿这一情况。

  • 断边顺序为 \(fa\rightarrow lc\rightarrow rc\):此时被换出的点就是 \(u\) 自己了,有转移:

    \[f_{u,u,d_1+1}\gets f_{lc,v_1,d_1}+f_{rc,v_2,d_2}+c_{v_1}(d_2+1)+c_u \]

    直接枚举复杂度比较高,但注意到 \(v_2\) 的唯一出现是 \(f_{rc,v_2,d_2}\),可以先枚举 \(d_2\) 计算 \(mn_{d_2}=\min_{v_2}\{f_{rc,v_2,d_2}\}\)。同理枚举 \(v_1\) 计算 \(\min_{d_2}\{mn_{d_2}+c_{v_1}(d_2+1)\}\),最后再枚举 \(v_1,d_1\)。此时第一、第三部分的复杂度就是 \(f_{lc},f_{rc}\) 的合法状态数,于是所有点 \(u\) 的转移中第一、三部分的复杂度之和也是 \(\mathcal O(n^2)\) 的。第二部分的复杂度 \(\le siz_{lc}siz_{rc}\)。于是全局总复杂度也是 \(\mathcal O(n^2)\) 的了。

  • 断边顺序为 \(lc\rightarrow fa\rightarrow rc\):

    \[f_{u,v_1,d_2+1}\gets f_{lc,v_1,d_1}+f_{rc,v_2,d_2}+c_u(d_1+1)+c_{v_1} \]

    使用类似上一条的优化方法可以同理优化到 \(\mathcal O(n^2)\)。

  • 断边顺序为 \(lc\rightarrow rc\rightarrow fa\):

    \[f_{u,v_2,0}\gets f_{lc,v_1,d_1}+f_{rc,v_2,d_2}+c_u(d_1+1)+c_{v_1}(d_2+1)+c_{v_2} \]

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

  • \(fa\rightarrow rc\rightarrow lc,rc\rightarrow fa\rightarrow lc,rc\rightarrow lc\rightarrow fa\):将 \(lc,rc\) 交换后再做一遍上面三种情况即可。

具体细节参见代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5010;
const ll inf=1e18;
int n,fa[N],ls[N],rs[N],md[N],all;
ll w[N],mn1[N],mn2[N];
vector<ll> f[N][N];
vector<int> son[N];
inline void init(int u,int v,int d){
//	if(f[u][v].size()<d){
//		int rec=f[u][v].size();
//		f[u][v].resize(d);
//		for(int i=rec;i<d;++i) f[u][v][i]=inf;
//	}
	while(f[u][v].size()<d) f[u][v].push_back(inf);
}
inline void dfs(int u){
	int lc=ls[u],rc=rs[u];
	son[u].push_back(u);
	if(lc){
		dfs(lc);md[u]=max(md[u],md[lc]);
		for(int v:son[lc]) son[u].push_back(v);
	}
	if(rc){
		dfs(rc);md[u]=max(md[u],md[rc]);
		for(int v:son[rc]) son[u].push_back(v);
	}
	if(lc) md[u]++;
	if(!fa[u]){
		if(!rc){
			init(u,0,1);
			for(int v:son[lc])
				for(int d=0;d<f[lc][v].size();++d)
					f[u][0][0]=min(f[u][0][0],f[lc][v][d]+w[u]*(d+1));
		}
		else{
			init(u,0,1);
			//lc->rc 
			for(int v1:son[lc]){
				mn1[v1]=inf;
				for(int d1=0;d1<f[lc][v1].size();++d1) mn1[v1]=min(mn1[v1],f[lc][v1][d1]+w[u]*(d1+1));
			}
			for(int d2=0;d2<=md[rc];++d2){
				mn2[d2]=inf;
				for(int v2:son[rc])
					if(f[rc][v2].size()>d2) mn2[d2]=min(mn2[d2],f[rc][v2][d2]);
				for(int v1:son[lc]) 
					f[u][0][0]=min(f[u][0][0],mn1[v1]+mn2[d2]+w[v1]*(d2+1)); 
			}
			//rc->lc
			swap(lc,rc);
			for(int v1:son[lc]){
				mn1[v1]=inf;
				for(int d1=0;d1<f[lc][v1].size();++d1) mn1[v1]=min(mn1[v1],f[lc][v1][d1]+w[u]*(d1+1));
			}
			for(int d2=0;d2<=md[rc];++d2){
				mn2[d2]=inf;
				for(int v2:son[rc])
					if(f[rc][v2].size()>d2) mn2[d2]=min(mn2[d2],f[rc][v2][d2]);
				for(int v1:son[lc]) 
					f[u][0][0]=min(f[u][0][0],mn1[v1]+mn2[d2]+w[v1]*(d2+1)); 
			}
			swap(lc,rc);
		}
	}
	else{
		if(!lc){
			init(u,u,1);
			f[u][u][0]=w[u];
		}
		else if(!rc){
			//fa->lc
			init(u,u,md[u]+1);
			for(int v:son[lc])
				for(int d=0;d<f[lc][v].size();++d)
					f[u][u][d+1]=min(f[u][u][d+1],f[lc][v][d]+w[u]);
			//lc->fa
			for(int v:son[lc]){
				init(u,v,1);
				for(int d=0;d<f[lc][v].size();++d)
					f[u][v][0]=min(f[u][v][0],f[lc][v][d]+w[u]*(d+1)+w[v]);
			}
		}
		else{
			auto work=[&](){
				init(u,u,md[u]+1);
				memset(mn2,0x3f,sizeof(ll)*(md[rc]+1));
				for(int v2:son[rc]) for(int d2=0;d2<f[rc][v2].size();++d2) mn2[d2]=min(mn2[d2],f[rc][v2][d2]);
				for(int v1:son[lc]){
					mn1[v1]=inf;
					for(int d2=0;d2<=md[rc];++d2) mn1[v1]=min(mn1[v1],mn2[d2]+w[v1]*(d2+1));
					for(int d1=0;d1<f[lc][v1].size();++d1)
						f[u][u][d1+1]=min(f[u][u][d1+1],f[lc][v1][d1]+mn1[v1]+w[u]);
				}
				//lc->fa->rc
				for(int v1:son[lc]){
					mn1[v1]=inf;
					for(int d1=0;d1<f[lc][v1].size();++d1) mn1[v1]=min(mn1[v1],f[lc][v1][d1]+w[u]*(d1+1));
					init(u,v1,md[rc]+2);
					for(int d2=0;d2<=md[rc];++d2)
						f[u][v1][d2+1]=min(f[u][v1][d2+1],mn1[v1]+mn2[d2]+w[v1]);
				}
				//lc->rc->fa
				for(int v:son[rc]) init(u,v,1);
				for(int v1:son[lc]){
					mn1[v1]=inf;
					for(int d1=0;d1<f[lc][v1].size();++d1) mn1[v1]=min(mn1[v1],f[lc][v1][d1]+w[u]*(d1+1));
				}
				for(int d2=0;d2<=md[rc];++d2){
					mn2[d2]=inf;
					for(int v1:son[lc]) mn2[d2]=min(mn2[d2],w[v1]*(d2+1)+mn1[v1]);
				}
				for(int v2:son[rc])
					for(int d2=0;d2<f[rc][v2].size();++d2) f[u][v2][0]=min(f[u][v2][0],mn2[d2]+f[rc][v2][d2]+w[v2]);
			};
			work();
			swap(lc,rc);
			work();
			swap(lc,rc);
		}
	}
	int sum=0;
	for(int v:son[u]) sum+=f[u][v].size();
	all+=sum;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%lld",&w[i]);
	if(n==1){puts("0");return 0;}
	for(int i=2;i<=n;++i){
		scanf("%d",&fa[i]);
		if(!ls[fa[i]]) ls[fa[i]]=i;
		else if(!rs[fa[i]]) rs[fa[i]]=i;
	}
	dfs(1);
	printf("%lld\n",f[1][0][0]);
	return 0;
}
posted @ 2022-04-21 15:52  cjTQX  阅读(424)  评论(1)    收藏  举报