正睿2018暑假集训 比赛题选做

题解 ZR246 数对子

题目大意

我们定义一个数对\((x,y)\)是好的,当且仅当\(x≤y\),且\(x\operatorname{xor} y\)的二进制表示下有奇数个\(1\)

现在给定\(n\)个区间\([l_i,r_i]\),你需要对于每个\(i\in[1,n]\),输出有几对好的数\((x,y)\)满足\(x\)\(y\)都在 \([l_1,r_1]\cup[l_2,r_2]\dots\cup[l_i,r_i]\),即两个数都在前\(i\)个区间的并里。

数据范围:\(1\leq n\leq 10^5\), \(1\leq l_i\leq r_i\leq 2^{32}-1\)

本题题解

异或有一个特性:两个数异或,带来的(二进制下)\(1\)的数量的变化,永远是偶数个:要么不变、要么减少\(2\)

由此可知:\(x\operatorname{XOR} y\)有奇数个\(1\),当且仅当\(x\), \(y\)中一个是奇数,一个是偶数。

设所有数中\(1\)的数量为偶数的数有\(\text{cnt0}\)个,\(1\)的数量为奇数的数有\(\text{cnt1}\)个。则答案就是\(\text{cnt0}\times\text{cnt1}\)

我们要支持插入线段、维护\(\text{cnt0}\)\(\text{cnt1}\)。可以用线段树实现。

建一个动态开点线段树,值域为\([0,2^{32}-1]\)。则线段树上每个节点,都表示了一段形如\([a2^b,(a+1)2^b-1]\)的区间,这样的区间(长度等于\(1\)的除外)里,\(\text{cnt0}\)\(\text{cnt1}\)永远是相等的:都等于区间长度的一半。这就很方便我们用线段树维护我们需要的信息。

时间复杂度\(O(n\log n)\)

参考代码:

//problem:ZR246
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;

const int MAXN=1e5;
const ll MAXM=(1LL<<32)-1;

int bitcnt(ll x){
	int res=0;
	while(x){
		res+=(x&1LL);
		x>>=1;
	}
	return res;
}
pll operator+(pll x,pll y){
	return mk(x.fi+y.fi,x.se+y.se);
}
void operator+=(pll& x,pll y){
	x=x+y;
}

int rt;
struct SegmentTree{
	static const int SIZE=MAXN*80;
	int ls[SIZE+5],rs[SIZE+5],cov[SIZE+5],tot;
	ll cnt0[SIZE+5],cnt1[SIZE+5];
	void upd(int p,ll l,ll r){
		if(cov[p]){
			if(l==r){
				cnt0[p]=cnt1[p]=0;
				if(bitcnt(l)&1)cnt1[p]=1;
				else cnt0[p]=1;
			}
			else{
				cnt0[p]=cnt1[p]=(r-l+1)/2;
			}
		}
		else{
			if(l==r){
				cnt0[p]=cnt1[p]=0;
			}
			else{
				cnt0[p]=cnt0[ls[p]]+cnt0[rs[p]];
				cnt1[p]=cnt1[ls[p]]+cnt1[rs[p]];
			}
		}
	}
	void modify(int& p,ll l,ll r,ll ql,ll qr,int x){
		if(!p)p=++tot;
		if(ql<=l&&qr>=r){
			cov[p]+=x;
			upd(p,l,r);
			return;
		}
		ll mid=(l+r)>>1;
		if(ql<=mid)modify(ls[p],l,mid,ql,qr,x);
		if(qr>mid)modify(rs[p],mid+1,r,ql,qr,x);
		upd(p,l,r);
	}
	SegmentTree(){}
}T;

int main() {
	int n;cin>>n;while(n--){
		ll l,r;cin>>l>>r;
		T.modify(rt,0,MAXM,l,r,1);
		cout<<T.cnt0[rt]*T.cnt1[rt]<<endl;
	}
	return 0;
}

题解 ZR247 逆序对

题目大意

给定一个偶数\(N\),现在蔡老板得到了一个由\([1,N]\)内的所有偶数构成的排列\(b[1..N/2]\)

现在蔡老板又得到了一个数组\(a[1..N/2]\),其中\(a[i]=i\cdot 2-1\)

蔡老板想知道,对于所有满足\(a\)\(b\)都是它的子序列的\([1,N]\)的排列\(p\)\(p\)的逆序对数的最小值。

数据范围:\(1\leq N\leq 2\times 10^5\)

本题题解

逆序对数=偶数序列自身的逆序对数+奇数序列中每个数插入在偶数序列某个位置时产生的贡献。具体来说,插入在偶数序列某个位置时,产生的贡献就是偶数序列中该位置前面大于该奇数的数的数量+后面小于该奇数的数的数量。

考虑从小到大依次把每个奇数插入。每次都从对于该奇数最优的位置插入。这样的逆序对数一定是最少的。但是会不会出现:较小的奇数插在了较后面的位置,较大的奇数插在了较前面的位置,从而导致不满足“奇数序列是结果序列的一个子序列”这一要求呢?并不会。因为如果每次都选择最优的位置,会发现最优的插入位置是单调递增(可能非严格,但是没关系)的。

具体来说,共有\(\frac{n}{2}+1\)个可能的插入位置。我们用线段树维护每个插入位置的代价。从小到大考虑每个奇数。插入完一个奇数后,变成下一个奇数时,需要对线段树做的修改是:让一段后缀\(-1\),让一段前缀\(+1\):这样显然最优的位置单调递增。

时间复杂度\(O(n\log n)\)

参考代码:

//problem:ZR247
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=2e5;
int n,a[MAXN+5],pos[MAXN+5];
struct SegmentTree{
	int mn[MAXN*4+5],tag[MAXN*4+5];
	void push_up(int p){
		mn[p]=min(mn[p<<1],mn[p<<1|1]);
	}
	void build(int p,int l,int r){
		if(l==r){
			mn[p]=l-1;
			return;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		push_up(p);
	}
	void upd(int p,int x){
		mn[p]+=x;
		tag[p]+=x;
	}
	void push_down(int p){
		if(tag[p]!=0){
			upd(p<<1,tag[p]);
			upd(p<<1|1,tag[p]);
			tag[p]=0;
		}
	}
	void range_add(int p,int l,int r,int ql,int qr,int x){
		if(ql<=l&&qr>=r){
			upd(p,x);return;
		}
		push_down(p);
		int mid=(l+r)>>1;
		if(ql<=mid)range_add(p<<1,l,mid,ql,qr,x);
		if(qr>mid)range_add(p<<1|1,mid+1,r,ql,qr,x);
		push_up(p);
	}
	SegmentTree(){}
}T;
struct FenwickTree{
	//单点加,后缀求和
	int c[MAXN+5];
	void modify(int p,int x){
		for(;p;p-=(p&(-p)))c[p]+=x;
	}
	int query(int p){
		int res=0;
		for(;p<=n*2;p+=(p&(-p)))res+=c[p];
		return res;
	}
	FenwickTree(){}
}F;
int main() {
	cin>>n;n/=2;
	ll ans=0;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		pos[a[i]]=i;
		ans+=F.query(a[i]);
		F.modify(a[i],1);
	}
	T.build(1,1,n+1);
	for(int i=1;i<=n;++i){
		ans+=T.mn[1];
		T.range_add(1,1,n+1,pos[i*2]+1,n+1,-1);
		T.range_add(1,1,n+1,1,pos[i*2],1);
	}
	cout<<ans<<endl;
	return 0;
}

题解 ZR250 18AB-day2 配对

题目大意

给定一棵\(n\)个点的边有长度的无根树,小 A 的班里一共有\(m\)个男生和\(m\)个女生,他们各自会等概率出现在树上\(n\)个点中的某一个,(注意同一个点上可能会出现多个人)。

然后小 A 会将他们配对成\(m\)对男女,设\(dis_i\)是第\(i\)对男女在树上的最短距离,小 A 会选择使得\(\sum_{i=1}^{m}dis_i\)最大的配对方案。

现在小 A 想知道,他选择的配对方案中\(\sum_{i=1}^{m}dis_i\)的期望,由于答案可能过大,你只需要输出答案\(*n^{2m}\)后对\(10^9+7\)取模的值,这个值显然是一个整数。

数据范围:\(n,m\leq 2500\)

本题题解

考虑每条边对答案的贡献。设这条边两侧的节点数分别为\(x,y\) (\(x+y=n\)),边权为\(w\)。考虑它对答案的贡献,我们可以枚举在\(x\)里有多少男生,多少女生,则贡献为:

\[\sum_{i=0}^{m}\sum_{j=0}^{m}{m\choose i}{m\choose j}x^ix^jy^{m-i}y^{m-j}\cdot(\min(i,m-j)+\min(j,m-i))\cdot w \]

如果对所有边暴力计算,总时间复杂度\(O(nm^2)\)。特判\(w=0\)的情况,可得\(70\)分。

考虑用前缀和来优化这个式子。具体来说,对每条边,我们先预处理出\(f,f_1,f_2\)三个数组:

\[f[j]={m\choose j}x^{j}y^{m-j}\\ f_1[j]=f[j]\cdot j\\ f_2[j]=f[j]\cdot(m-j) \]

对这三个数组做前缀和。然后枚举\(i\),通过分类讨论,把\(\min\)拆开。用预处理好的\(f,f_1,f_2\)数组,就可以\(O(1)\)计算所有\(j\)的情况。

时间复杂度\(O(nm)\)

参考代码:

//problem:B
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
const int MAXN=2500;
int fac[MAXN+5],ifac[MAXN+5];
inline int comb(int n,int k){
	if(n<k)return 0;
	return (ll)fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;
}
void facinit(int lim=MAXN){
	fac[0]=1;
	for(int i=1;i<=lim;++i)fac[i]=(ll)fac[i-1]*i%MOD;
	ifac[lim]=pow_mod(fac[lim],MOD-2);
	for(int i=lim-1;i>=0;--i)ifac[i]=(ll)ifac[i+1]*(i+1)%MOD;
}

int n,m;
vector<pii>G[MAXN+5];

int sz[MAXN+5],ans,pw[MAXN+5][MAXN+5],f[MAXN+5],f1[MAXN+5],f2[MAXN+5];
void dfs(int u,int fa){
	sz[u]=1;
	for(int i=0;i<SZ(G[u]);++i){
		int v=G[u][i].fi;
		if(v==fa)continue;
		dfs(v,u);
		if(G[u][i].se){
			for(int y=0;y<=m;++y){
				f[y]=(ll)comb(m,y)*pw[sz[v]][y]%MOD*pw[n-sz[v]][m-y]%MOD;
				f1[y]=(ll)f[y]*y%MOD;
				f2[y]=(ll)f[y]*(m-y)%MOD;
				if(y)add(f[y],f[y-1]),add(f1[y],f1[y-1]),add(f2[y],f2[y-1]);
			}
			for(int x=0;x<=m;++x){
				int fx=(ll)comb(m,x)*pw[sz[v]][x]%MOD*pw[n-sz[v]][m-x]%MOD;
				add(ans,(ll)G[u][i].se*fx%MOD*x%MOD*f[m-x]%MOD);
				add(ans,(ll)G[u][i].se*fx%MOD*mod2(f2[m]-f2[m-x])%MOD);
				add(ans,(ll)G[u][i].se*fx%MOD*(m-x)%MOD*mod2(f[m]-f[m-x])%MOD);
				add(ans,(ll)G[u][i].se*fx%MOD*f1[m-x]%MOD);
			}
		}
		sz[u]+=sz[v];
	}
}

int main() {
	facinit();
	for(int i=1;i<=MAXN;++i){
		pw[i][0]=1;
		for(int j=1;j<=MAXN;++j){
			pw[i][j]=(ll)pw[i][j-1]*i%MOD;
		}
	}
	cin>>n>>m;
	for(int i=1,u,v,w;i<n;++i)cin>>u>>v>>w,G[u].pb(mk(v,w)),G[v].pb(mk(u,w));
	dfs(1,0);
	cout<<ans<<endl;
	return 0;
}

题解 ZR252 18ABday3-亵渎

题目大意

YJC最近在研究炉石。今天他在研究关于其中三张卡牌的一个问题:

这三张卡牌描述如下:

  • 香蕉:使一个单位+1生命。
  • 月火术:使一个单位-1生命。
  • 亵渎:使所有单位-1生命,若有单位死亡,则释放亵渎

现在,你的手牌中有足够多的香蕉月火术以及一张亵渎。你正面临着你对手的一个巨大的场面,你需要清空这个场面。 这个场面上有\(n\)个随从,每个随从的生命值由输入给出。由于种种原因,你所在的这局游戏这三张卡牌的费用不一定是原来的费用,他们将在输入中被给定。 现在你想知道,为了清空这个场面,至少需要多少费用。

如果你完全没有看懂上面讲了什么,可以看下面的简要题意: 你有一个可重集合\(A\)\(A\)中的元素是正整数,当一个元素变为\(0\)时从集合中删除。 你可以花费\(p\)使一个元素\(+1\),花费\(q\)使一个元素\(?1\)。 你有一次机会花费\(r\)使得集合中所有元素\(?1\),若有元素因此被删除,则重复这个操作(不需要额外的花费)。 你的目标是最小化将集合变为空集的花费。

数据范围:\(n\leq 5000\)\(1\leq a[i]\leq 10^6\)\(0\leq p,q,r\leq 10^6\)

本题题解

我们最后使用亵渎。

考虑什么情况下,能用一次亵渎就清空整个场面。发现,要求所有剩余随从的生命值(排好序后)为从\(1\)开始的连续若干个值(可以有重复),例如:$1,1,1,2,2,3,4,4,\dots $。

假设,我们一开始就选出了一个子集,作为使用亵渎前,还活着的这些随从。在使用亵渎前,子集里的随从的生命值,一定是上述的那种序列。考虑子集里每个随从,如何与序列里的元素对应。显然,一定让初始生命值最小的那个随从,对应序列里最小的元素;初始生命值第二小的,对应序列里第二小的,......,以此类推。

因此,我们先把所有随从,按初始生命值排序。然后做一个DP。设\(dp[i][j]\)表示考虑了前\(i\)个随从,使用亵渎前的序列,当前最大元素为\(j\)时,的最小花费。转移时就考虑是否把当前随从加入使用亵渎的序列即可。

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

参考代码:

#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=5000;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,a[MAXN+5],p,q,r;
ll dp[MAXN+5][MAXN+5];

int main() {
	cin>>n;
	for(int i=1;i<=n;++i)cin>>a[i];
	cin>>p>>q>>r;
	sort(a+1,a+n+1);
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	for(int i=1;i<=n;++i){
		for(int j=0;j<i;++j)if(dp[i-1][j]!=INF){
			dp[i][j]=min(dp[i][j],dp[i-1][j]+(ll)a[i]*q);
			ll cost=0;
			if(a[i]<j)cost=(ll)p*(j-a[i]);
			else if(a[i]>j)cost=(ll)q*(a[i]-j);
			dp[i][j]=min(dp[i][j],dp[i-1][j]+cost);
			cost=0;
			if(a[i]<j+1)cost=(ll)p*(j+1-a[i]);
			else if(a[i]>j+1)cost=(ll)q*(a[i]-(j+1));
			dp[i][j+1]=min(dp[i][j+1],dp[i-1][j]+cost);
		}
	}
	ll ans=INF;
	for(int j=0;j<=n;++j)ans=min(ans,dp[n][j]+r*(j>0));
	cout<<ans<<endl;
	return 0;
}

题解 ZR255 18ABday4-世界杯

题目大意

YJC最近在研究世界杯,他拿一半财产压了德国队,另一半财产压了阿根廷队,结果可想而知。YJC表示非常angry,于是又开始研究博彩公司的盈利原理。

假设在世界杯决赛前,有\(n\)个人参与了赌博。第\(i\)个人认为法国队赢的概率是\(p[i]\),克罗地亚队赢的概率是\(1?p[i]\)。对于每一只球队,如果根据博彩公司给出的赔率第\(i\)个人的期望收益非负,则他会给这只球队下注\(a[i]\)元(设赔率为\(x\),某人下注了\(y\)元,如果他赢了可以返还\(x?y\)元,他输了则会返还\(0\)元。不论输赢,下注的钱均不返还)。如果两只球队期望收益均非负,则他会给两只球队各下注\(a[i]\)元。

现在博彩公司要决定法国队和克罗地亚队的赔率,使得在自身收益最小的胜负情况下的收益最大。

数据范围:\(1\leq n\leq 10^6\), \(0\leq a[i]\leq 100\), \(0\leq p[i]\leq 1\)。输入、输出精度均为\(6\)位小数。

本题题解

考虑第\(i\)个人。设\(p_1(i)\)表示他认为法国队获胜的概率,\(p_2(i)\)表示他认为克罗地亚队获胜的概率,即:\(p_1(i)=p[i],p_2(i)=1-p[i]\)。那么,这个人会下注球队\(t\) (\(t\in\{1,2\}\)),当且仅当\(a[i]\cdot p_t(i)\cdot x\geq a[i]\),其中左边是他期望赢得的金额。把这个不等式化简,得:\(x\geq \frac{1}{p_t(i)}\)

\(f_t(i)=\frac{1}{p_t(i)}\),那么,第\(i\)个人会下注球队\(t\),当且仅当:\(x\geq f_t(i)\)。所以,对每个球队,有效的赔率取值只有\(n+1\)(即小于所有\(f_t(i)\),或者刚好等于\(n\)个中的某个\(f_t(i)\))。

暴力的做法是枚举两个球队的赔率分别为多少,再计算收益。时间复杂度\(O(n^3)\)

发现,收益对于两个球队的赔率是可以分别计算的。具体来说,对于球队\(t\),假设赔率为\(x\),那么:

  • 如果\(t\)获胜,博彩公司亏损的金额为:\(K_t(x)=\sum_{i=1}^{n}[x\geq f_t(i)]\cdot (x-1)\cdot a[i]\)
  • 如果\(t\)失败,博彩公司赚到的金额为:\(Z_t(x)=\sum_{i=1}^{n}[x\geq f_t(i)]\cdot a[i]\)

求出\(K_t\)\(Z_t\)后,我们枚举两个球队分别的赔率:\(x_i\), \(x_j\),则盈利为:\(\min\big(Z_1(x_i)-K_2(x_j),Z_2(x_j)-K_1(x_i)\big)\),分别代表了球队\(1\)获胜和球队\(2\)获胜的情况。暴力求\(Z,K\),再暴力枚举\(x_i,x_j\),时间复杂度\(O(n^2)\)

注意到,如果把\(f\)数组排好序,再从大到小枚举所有\(x\),则\(Z,K\)都可以用 two pointers \(O(n)\)求出。

现在,瓶颈在于枚举\(x_i,x_j\)。后面的\(\min(\dots )\)这个式子比较丑。我们把问题写成更简洁的形式:

给定四个数组\(A_1[1\dots n],A_2[1\dots n],B_1[1\dots n],B_2[1\dots n]\)。求:\(\max_{i,j\leq n}\{\min(A_1[i]+B_1[j],A_2[i]+B_2[j])\}\)

这里的\((A_1,A_2)\),可以看做一个\(\texttt{pair}\)型的数组\(A\),因为\(A_1,A_2\)出现时下标一样。\((B_1,B_2)\)同理。对\(A\)数组,按\(A_1\)从小到大排序。对\(B\)数组,按\(B_1\)从小到大排序。(也就是\(\texttt{pair}\)默认的比较方式)。

考虑二分答案\(\text{mid}\)。我们要判断是否存在一对\(i,j\),满足\(A_1[i]+B_1[j]\)\(A_2[i]+B_2[j]\)均大于等于\(\text{mid}\)

枚举\(A\)的下标\(i\)。那么,能够满足\(A_1[i]+B_1[j]\geq \text{mid}\)\(j\),一定是\(B\)序列的一段后缀。我们只需要拿这个后缀里,\(B_2[j]\)最大的\(j\),看\(B_2[j]+A_2[i]\)是否\(\text{mid}\)即可。这段后缀的长度可以在枚举\(i\)时用 two pointers 维护。后缀最大的\(B_2\),可以预处理出来。

时间复杂度\(O(n\log n+n\log(\text{eps}^{-1}))\)

参考代码:

//problem:A
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef double db;

const int MAXN=1e6;
const db INF=1e18;
const db EPS=1e-8;
int n,cntx1,cntx2;
struct People_t{
	db a,p1,p2,needx1,needx2;
}a[MAXN+5];
bool cmp_needx1(People_t a,People_t b){
	return a.needx1<b.needx1;
}
bool cmp_needx2(People_t a,People_t b){
	return a.needx2<b.needx2;
}
db x1[MAXN+5],x2[MAXN+5];
pair<db,db>A[MAXN+5],B[MAXN+5];

int main() {
	//freopen("ex_a2.in","r",stdin);
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>a[i].a>>a[i].p1;
		a[i].p2=(db)1-a[i].p1;
		if(a[i].p1!=0)a[i].needx1=(db)1/a[i].p1,x1[++cntx1]=a[i].needx1;else a[i].needx1=INF;
		if(a[i].p2!=0)a[i].needx2=(db)1/a[i].p2,x2[++cntx2]=a[i].needx2;else a[i].needx2=INF;
	}
	x1[++cntx1]=0;x2[++cntx2]=0;
	
	sort(a+1,a+n+1,cmp_needx1);
	sort(x1+1,x1+cntx1+1);
	for(int i=1,j=0;i<=cntx1;++i){
		A[i].fi=-A[i-1].se*(x1[i]-1);
		A[i].se=A[i-1].se;
		while(j+1<=n && a[j+1].needx1<=x1[i]){
			++j;
			A[i].fi-=(x1[i]-1)*a[j].a;
			A[i].se+=a[j].a;
		}
	}
	
	sort(a+1,a+n+1,cmp_needx2);
	sort(x2+1,x2+cntx2+1);
	for(int i=1,j=0;i<=cntx2;++i){
		B[i].fi=B[i-1].fi;
		B[i].se=-B[i-1].fi*(x2[i]-1);
		while(j+1<=n && a[j+1].needx2<=x2[i]){
			++j;
			B[i].se-=(x2[i]-1)*a[j].a;
			B[i].fi+=a[j].a;
		}
	}
	
	sort(A+1,A+cntx1+1);
	sort(B+1,B+cntx2+1);
	B[cntx2+1].se=-INF;
	for(int i=cntx2;i>=1;--i){
		if(B[i+1].se>B[i].se)B[i].se=B[i+1].se;//后缀max
	}
	double l=0,r=INF;
	while(r-l>EPS){
		double mid=(l+r)/((db)2);
		bool ok=false;
		for(int i=1,j=cntx2+1;i<=cntx1;++i){
			while(j>1 && A[i].fi+B[j-1].fi>=mid)--j;
			if(A[i].se+B[j].se>=mid){
				ok=true;break;
			}
		}
		if(ok)l=mid;
		else r=mid;
	}
	cout<<setiosflags(ios::fixed)<<setprecision(6)<<l<<endl;
	return 0;
}

题解 ZR256 18ABday4-数组

题目大意

YJC最近在研究数组,他认为如果数组的一个区间内不包含重复的元素,那么这个区间是一个优美的区间。

现在YJC弄到了一个interesting的数组,他想知道这个数组有多少个优美的区间。当然,他有时候也会觉得这个数组不够interesting,此时他会修改数组中的一个元素。

YJC发现在修改之后他不会算有多少个优美的区间了,于是他来向你求助。

数据范围:\(1\leq n\leq 10^5\), \(q\leq 2n\), \(1\leq a_i\leq n\)。(\(a_i\)是数组中的元素)

本题题解

\(\text{pre}[i]\)表示位置\(i\)上的数,在序列里上一次出现的位置;记\(\text{nxt}[i]\)表示位置\(i\)上的数,在序列里下一次出现的位置。像本题这种限制数在区间里出现次数的问题,往往可以转化为\(\text{pre}[i]\)\(\text{nxt}[i]\)之间的关系。这是一个小套路。

考虑枚举区间的右端点\(i\),看最靠前的、合法的左端点在哪里,记这个位置为\(f_i\)。那么,左端点在\([f_i,i]\)之间都是合法的区间。故答案为\(\sum_{i=1}^{n}(i-f_i+1)\)。容易发现,以\(i\)为右端点的\(f_i\),一定至少大于等于\(f_{i-1}\)。同时,为了保证\(i\)这个数在区间里只出现一次,\(f_i\)又需要大于\(\text{pre}[i]\)。所以,\(f_{i}=\max(f_{i-1},\text{pre}[i]+1)\)。有了这个递推式,再看求答案的式子,发现答案就等于\(\sum_{i=1}^{n}(i-\max_{j=1}^{j}\text{pre}[j])\),于是问题转化为维护\(\text{pre}\)数组的“前缀\(\max\)”之和。

一次修改操作,只会对不超过\(3\)个点的\(\text{pre}\)值产生影响,分别是:\(x\),原来的\(\text{nxt}[x]\),新的\(\text{nxt}[x]\)这三个位置。这几个位置是很容易求出的。我们可以开\(n\)\(\texttt{set}\),记录每个值在哪些位置出现过。然后在\(\texttt{set}\)里二分,就能找到这几个要修改的位置。

于是问题转化为,维护一个序列,支持单点修改,询问“前缀最大值”之和

考虑用线段树维护。对线段树上,每个节点\(p=[l,r]\),我们记录两个值:

  • \(\text{mx}[p]\),表示区间\([l,r]\)\(\text{pre}\)数组的最大值。
  • \(\text{sum}[p]\),表示区间\([l,r]\)里,每个位置\(i\),从\(l\)\(i\)的“前缀最大值”之和(注意,并不是从\(1\)\(i\),因为不好维护)。

难点在于如何\(\text{pushup}\),也就是合并左、右两个儿子。合并时,\(\text{mx}\)直接取两个儿子的较大值即可。左儿子的\(\text{sum}\),可以直接贡献到当前节点上。而右儿子的里,需要先对左儿子的最大值\(\text{mx}[\text{left_son}]\)\(\max\),再累加到\(\text{sum}[p]\)中。怎么求线段树里某个区间对某个值取\(\max\)后的和呢?我们可以递归下去,在\(O(\log n)\)的时间里求出。具体代码如下:

ll cmax_sum(int p,int l,int r,int x){
	if(l==r)return max(x,mx[p]);
	int mid=(l+r)>>1;
	if(mx[p<<1]<=x)return (ll)x*(mid-l+1)+cmax_sum(p<<1|1,mid+1,r,x);
	else return cmax_sum(p<<1,l,mid,x)+sum[p]-sum[p<<1];
}
void push_up(int p){
	int ls=p<<1,rs=p<<1|1;
	mx[p]=max(mx[ls],mx[rs]);
	sum[p]=sum[ls]+cmax_sum(rs,tl[rs],tr[rs],mx[ls]);
}

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

参考代码:

//problem:ZR256
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=1e5;
int n,a[MAXN+5],pos[MAXN+5],pre[MAXN+5],nxt[MAXN+5];
set<int>s[MAXN+5];

struct SegmentTree{
	ll sum[MAXN*4+5];
	int mx[MAXN*4+5];
	int tl[MAXN*4+5],tr[MAXN*4+5];
	ll cmax_sum(int p,int l,int r,int x){
		if(l==r)return max(x,mx[p]);
		int mid=(l+r)>>1;
		if(mx[p<<1]<=x)return (ll)x*(mid-l+1)+cmax_sum(p<<1|1,mid+1,r,x);
		else return cmax_sum(p<<1,l,mid,x)+sum[p]-sum[p<<1];
	}
	void push_up(int p){
		int ls=p<<1,rs=p<<1|1;
		mx[p]=max(mx[ls],mx[rs]);
		sum[p]=sum[ls]+cmax_sum(rs,tl[rs],tr[rs],mx[ls]);
	}
	void build(int p,int l,int r){
		tl[p]=l;tr[p]=r;
		if(l==r){
			sum[p]=mx[p]=pre[l];
			return;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		push_up(p);
	}
	void modify(int p,int l,int r,int pos,int x){
		if(l==r){
			sum[p]=mx[p]=x;
			return;
		}
		int mid=(l+r)>>1;
		if(pos<=mid)modify(p<<1,l,mid,pos,x);
		else modify(p<<1|1,mid+1,r,pos,x);
		push_up(p);
	}
	SegmentTree(){}
}T;

int main() {
	cin>>n;
	for(int i=1;i<=n;++i)cin>>a[i],s[a[i]].insert(i);
	for(int i=1;i<=n;++i)pre[i]=pos[a[i]],pos[a[i]]=i;
	for(int i=1;i<=n;++i)pos[i]=n+1;
	for(int i=n;i>=1;--i)nxt[i]=pos[a[i]],pos[a[i]]=i;
	T.build(1,1,n);
	ll sum=(ll)n*(n+1)/2;
	int q;cin>>q;while(q--){
		int op;cin>>op;
		if(!op){
			cout<<sum-T.sum[1]<<endl;
		}
		else{
			int p,v;cin>>p>>v;
			if(v==a[p])continue;
			if(nxt[p]!=n+1){
				pre[nxt[p]]=pre[p];
				T.modify(1,1,n,nxt[p],pre[nxt[p]]);
			}
			if(pre[p]!=0)nxt[pre[p]]=nxt[p];
			s[a[p]].erase(p);
			set<int>::iterator it=s[v].lob(p);
			if(it!=s[v].end()){
				nxt[p]=*it;
				pre[nxt[p]]=p;
				T.modify(1,1,n,nxt[p],pre[nxt[p]]);
			}
			else nxt[p]=n+1;
			if(it!=s[v].begin()){
				--it;
				pre[p]=*it;
				nxt[pre[p]]=p;
			}
			else pre[p]=0;
			T.modify(1,1,n,p,pre[p]);
			s[v].insert(p);
			a[p]=v;
			//cout<<"pre ";for(int i=1;i<=n;++i)cout<<pre[i]<<" ";cout<<endl;
		}
	}
	return 0;
}

题解 ZR258 18ABday5-友谊巨轮

题面下载

本题题解

先不管问题。我们尝试维护出每个时刻每个人的巨轮是谁。直观的做法是用一个二维数组,存pair<long long,int>类型,分别表示两人之间的消息数量和最近一次发消息的时间。我们需要支持单点修改,并维护这个数组每一行的最大值。直观的做法是每行开一个线段树,但这样空间会超限。改成动态开点线段树就好了:因为修改次数是\(O(n)\)的,每次修改只会在线段树上新增\(O(\log n)\)个节点,所以空间复杂度是\(O(n\log n)\),可以接受。

维护出每个人的巨轮后,考虑回答询问。发现每个时刻,只有不超过\(4\)个人的巨轮会变化(新加入的一对人,和刚刚过期的一对人)。我们说一个人的“状态”是指,他是否是单向巨轮。那么,如果\(x\)这个人的巨轮变化了,那么我们只需要更新:(1) 原来\(x\)的巨轮;(2) \(x\);(3) \(x\)的新巨轮;这三个点的状态。容易发现,其他点的状态不会改变。所以可以\(O(1)\)完成所有更新。

时间复杂度\(O(n\log n)\)

参考代码:

//problem:A
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=1e5;
int n,t,m;
map<pii,pair<ll,int> >mp;
struct Event_t{
	int a,b,c;
	Event_t(){}
	Event_t(int _a,int _b,int _c){
		a=_a;
		b=_b;
		c=_c;
	}
}q[MAXN+5];

const int SIZE=2e7;
int rt[MAXN+5],cnt,mxpos[SIZE],ls[SIZE],rs[SIZE];
pair<ll,int> mx[SIZE];
int newnode(){
	++cnt;
	ls[cnt]=rs[cnt]=mxpos[cnt]=0;
	mx[cnt]=mk(0,0);
	return cnt;
}
void push_up(int p){
	if(!ls[p] && !rs[p])return;
	if(!ls[p]){
		mx[p]=mx[rs[p]];
		mxpos[p]=mxpos[rs[p]];
		return;
	}
	if(!rs[p]){
		mx[p]=mx[ls[p]];
		mxpos[p]=mxpos[ls[p]];
		return;
	}
	if(mx[ls[p]]>mx[rs[p]]){
		mx[p]=mx[ls[p]];
		mxpos[p]=mxpos[ls[p]];
	}
	else{
		mx[p]=mx[rs[p]];
		mxpos[p]=mxpos[rs[p]];
	}
}
void modify(int& p,int l,int r,int pos,const pair<ll,int>& x){
	int tmp=p;
	p=newnode();
	if(tmp){
		ls[p]=ls[tmp];
		rs[p]=rs[tmp];
	}
	if(l==r){
		mx[p]=x;
		mxpos[p]=l;
		return;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)
		modify(ls[p],l,mid,pos,x);
	else
		modify(rs[p],mid+1,r,pos,x);
	push_up(p);
}
int ans;
bool in_ans[MAXN+5];
void update_ans(int a){
	if(in_ans[a])ans--;
	in_ans[a]=0;
	if(rt[a] && mx[rt[a]].fi!=0){
		int f=mxpos[rt[a]];
		if(mxpos[rt[f]]!=a){
			in_ans[a]=1;
			ans++;
		}
	}
}
vector<int>v;
void tree_modify(int a,int b){
	if(rt[a] && mx[rt[a]].fi!=0){
		v.pb(mxpos[rt[a]]);
	}
	if(rt[b] && mx[rt[b]].fi!=0){
		v.pb(mxpos[rt[b]]);
	}
	modify(rt[a],1,n,b,mp[mk(a,b)]);
	modify(rt[b],1,n,a,mp[mk(a,b)]);
	if(rt[a] && mx[rt[a]].fi!=0){
		v.pb(mxpos[rt[a]]);
	}
	if(rt[b] && mx[rt[b]].fi!=0){
		v.pb(mxpos[rt[b]]);
	}
}
void solve(){
	cnt=0;ans=0;
	mp.clear();
	for(int i=1;i<=n;++i){
		rt[i]=0;
		in_ans[i]=0;
	}
	for(int i=1;i<=t;++i){
		cin>>q[i].a>>q[i].b>>q[i].c;
		if(q[i].a>q[i].b)swap(q[i].a,q[i].b);
		
		mp[mk(q[i].a,q[i].b)].fi+=q[i].c;
		mp[mk(q[i].a,q[i].b)].se=i;
		tree_modify(q[i].a,q[i].b);
		
		if(i-m>=1){
			mp[mk(q[i-m].a,q[i-m].b)].fi-=q[i-m].c;
			if(mp[mk(q[i-m].a,q[i-m].b)].se==i-m){
				mp[mk(q[i-m].a,q[i-m].b)].se=0;
			}
			tree_modify(q[i-m].a,q[i-m].b);
			update_ans(q[i-m].a);
			update_ans(q[i-m].b);
		}
		//for(int j=1;j<=n;++j)update_ans(j);
		for(int i=0;i<SZ(v);++i){
			update_ans(v[i]);
		}
		v.clear();
		update_ans(q[i].a);
		update_ans(q[i].b);
		cout<<ans<<endl;
	}
}
int main() {
	int Tes;cin>>Tes;while(Tes--){
		cin>>n>>t>>m;
		solve();
	}
	return 0;
}

题解 ZR260 18ABday5-构解巨树

题面下载

本题题解

首先,给定一棵树,如何求出所有点对距离和?朴素做法是枚举两个点,求一遍距离,时间复杂度\(O(n^3)\)。如果求距离用\(O(1)\)LCA的方法求,则优化到\(O(n^2)\)。换个角度,累加“每条边的贡献”,例如,考虑\(u\)\(fa(u)\)之间的边,则答案为\(\sum_{u\neq 1}\text{size}_u\cdot (n-\text{size}_u)\),于是只需要求出每个点的\(\text{size}\)即可,时间复杂度\(O(n)\)。本题就是在这种考虑“每条边贡献”的思想的基础上完成的。

回到本题,也就是这个“树套树”的模型中。我们把贡献分为三类:

  1. 小树和小树之间的边。也就是\(m\)条边之一。这些边两侧各有若干棵完整的小树。设它子树里有\(x\)棵小树,则对答案的贡献就是\(x\cdot n\cdot (m\cdot n-x\cdot n)\)。于是直接对大树dfs,就可以\(O(m)\)求出这类边的贡献。
  2. 小树内部的边,同一小树内点对经过这条边的次数。这样的贡献,对每棵小树的同一条边来说是相同的。所以只需要对小树做一遍dfs,求出贡献和后乘以\(m\)就行。时间复杂度\(O(n)\)
  3. 小树内部的边,来自不同小树点对经过这条边的次数。

第3类贡献比较麻烦。我们来仔细讨论。

依次考虑每棵小树。对当前小树,先以\(1\)为根,把它看成一棵有根树。然后转化为,有一棵树,然后给定一些二元组\((x,y)\),表示在节点\(x\)上,挂了\(y\cdot n\)个点。

我们记小树节点\(u\)子树内的点数为\(\text{in}(u)\),子树外的点数为\(\text{out}(u)\)。初始时,只考虑了当前小树里的点(没有考虑二元组挂上去的点),也就是\(\text{in}(u)=\text{size}_u,\text{out}(u)=n-\text{size}_u\)。请注意,这里\(u\neq 1\),因为\(1\)节点和父亲之间没有边,自然也不会产生贡献。然后我们依次考虑每个二元组\((x,y)\),把它挂上去时,会新增的贡献数是:

  • 对于\(x\)的所有祖先\(u\)(不含\(1\)),答案增加\(y\cdot n\cdot \text{out}(u)\)
  • 对于其他节点\(v\)(不含\(1\)),答案增加\(y\cdot n\cdot \text{in}(v)\)

然后用二元组\((x,y)\)来更新每个节点的\(\text{in},\text{out}\)

  • 对于\(x\)的所有祖先\(u\)(不含\(1\)),\(\text{in}(u)\)增加\(y\cdot n\)
  • 对于其他节点\(v\)(不含\(1\)),\(\text{out}(v)\)增加\(y\cdot n\)

可以看出,我们要支持,(1) 给所有祖先加,(2) 给所有其他节点加。而“给其他节点加”,可以转化为先给所有节点加,然后再给祖先减掉。所以只需要支持对所有祖先加,对所有祖先求和即可。可以用树链剖分+线段树实现。

虽然每个小树上可能挂多个二元组,但二元组的本质就是大树上的边,所以总数只有\(O(m)\)个。每次树链剖分+线段树操作的复杂度是\(O(\log^2n)\)的,所以总时间复杂度\(O(m\log^2n)\)

有一个小问题是,每考虑完一个小树后,要清空线段树。准确来说不是清空,是还原成\(\text{in}(u)=\text{size}_u,\text{out}(u)=n-\text{size}_u\)的情况。一种做法是把所有操作反着做一遍:即区间加变成区间减。但是这样常数一下子翻倍。有一种更巧妙的方法是维护时间戳。在线段树上走到一个节点时,如果它的时间戳和当前时间不一样,说明它还停留在之前的某个阶段,我们直接把它还原,然后把它的时间戳改成当前时间即可。

参考代码:

//problem:ZR260
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MOD=1e9+7;
const int MAXN=1e5;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}

int n,m,ans;

namespace SmallTree{
struct EDGE{int nxt,to;}edge[MAXN*2+5];
int head[MAXN+5],tot;
inline void add_edge(int u,int v){edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;}

int fa[MAXN+5],sz[MAXN+5],son[MAXN+5],dep[MAXN+5];
void dfs1(int u){
	sz[u]=1;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==fa[u])continue;
		fa[v]=u;
		dep[v]=dep[u]+1;
		dfs1(v);
		sz[u]+=sz[v];
		if(!son[u]||sz[v]>sz[son[u]])son[u]=v;
	}
}
int top[MAXN+5],dfn[MAXN+5],ofn[MAXN+5],rev[MAXN+5],cnt_dfn;
void dfs2(int u,int t){
	top[u]=t;
	dfn[u]=++cnt_dfn;
	rev[cnt_dfn]=u;
	if(son[u])dfs2(son[u],t);
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);
	}
	ofn[u]=cnt_dfn;
}
}//namespace SmallTree

struct SegmentTree{
	int curtim;
	int sum[MAXN*4+5],tag[MAXN*4+5],initsum[MAXN*4+5],tim[MAXN*4+5];
	void push_up(int p){
		sum[p]=mod1(sum[p<<1]+sum[p<<1|1]);
	}
	void build(int p,int l,int r,bool flag){
		// flag:
		// in 0; out 1
		if(l==r){
			using SmallTree::sz;
			using SmallTree::rev;
			if(!flag)
				initsum[p]=sum[p]=sz[rev[l]];
			else
				initsum[p]=sum[p]=n-sz[rev[l]];
			return;
		}
		int mid=(l+r)>>1;
		build(p<<1,l,mid,flag);
		build(p<<1|1,mid+1,r,flag);
		push_up(p);
		initsum[p]=mod1(initsum[p<<1]+initsum[p<<1|1]);
	}
	void clr(int p){
		if(tim[p]!=curtim){
			tim[p]=curtim;
			sum[p]=initsum[p];
			tag[p]=0;
		}
	}
	void upd(int p,int l,int r,int x){
		add(sum[p],(ll)x*(r-l+1)%MOD);
		add(tag[p],x);
	}
	void push_down(int p,int l,int mid,int r){
		clr(p<<1);
		clr(p<<1|1);
		if(tag[p]){
			upd(p<<1,l,mid,tag[p]);
			upd(p<<1|1,mid+1,r,tag[p]);
			tag[p]=0;
		}
	}
	void range_add(int p,int l,int r,int ql,int qr,int x){
		clr(p);
		if(ql<=l && qr>=r){
			upd(p,l,r,x);
			return;
		}
		int mid=(l+r)>>1;
		push_down(p,l,mid,r);
		if(ql<=mid)
			range_add(p<<1,l,mid,ql,qr,x);
		if(qr>mid)
			range_add(p<<1|1,mid+1,r,ql,qr,x);
		push_up(p);
	}
	int query(int p,int l,int r,int ql,int qr){
		clr(p);
		if(ql<=l && qr>=r){
			return sum[p];
		}
		int mid=(l+r)>>1;
		push_down(p,l,mid,r);
		int res=0;
		if(ql<=mid)
			res=query(p<<1,l,mid,ql,qr);
		if(qr>mid)
			add(res,query(p<<1|1,mid+1,r,ql,qr));
		push_up(p);
		return res;
	}
}T_in,T_out;

int jump(int _u,int x){
	using namespace SmallTree;
	int u=_u;
	int res=T_in.query(1,1,n,2,n);
	while(top[u]!=1){
		add(res,T_out.query(1,1,n,dfn[top[u]],dfn[u]));
		sub(res,T_in.query(1,1,n,dfn[top[u]],dfn[u]));
		u=fa[top[u]];
	}
	if(u!=1){
		add(res,T_out.query(1,1,n,dfn[1]+1,dfn[u]));
		sub(res,T_in.query(1,1,n,dfn[1]+1,dfn[u]));
	}
	res=(ll)res*x%MOD;
	
	u=_u;
	T_out.range_add(1,1,n,2,n,x);
	while(top[u]!=1){
		T_in.range_add(1,1,n,dfn[top[u]],dfn[u],x);
		T_out.range_add(1,1,n,dfn[top[u]],dfn[u],mod2(-x));
		u=fa[top[u]];
	}
	if(u!=1){
		T_in.range_add(1,1,n,dfn[1]+1,dfn[u],x);
		T_out.range_add(1,1,n,dfn[1]+1,dfn[u],mod2(-x));
	}
	
	return res;
}

namespace BigTree{
struct EDGE{int nxt,to,from_node,to_node;}edge[MAXN*2+5];
int head[MAXN+5],tot;
inline void add_edge(int u,int v,int a,int b){
	edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;
	edge[tot].from_node=a;
	edge[tot].to_node=b;
}

int sz[MAXN+5];
void dfs_solve(int u,int fa,int connect_node){
	vector<pii>vec;
	sz[u]=1;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==fa)continue;
		dfs_solve(v,u,edge[i].to_node);
		vec.pb(mk(edge[i].from_node,sz[v]));
		add(ans,(ll)sz[v]*n%MOD*mod2((ll)m*n%MOD-(ll)sz[v]*n%MOD)%MOD);
		sz[u]+=sz[v];
	}
	if(fa!=0){
		vec.pb(mk(connect_node,m-sz[u]));
	}
	T_in.curtim=u;
	T_out.curtim=u;//线段树清空
	for(int i=0;i<SZ(vec);++i){
		add(ans,jump(vec[i].fi,(ll)vec[i].se*n%MOD));
	}
}
}//namespace BigTree

int main() {
	cin>>n>>m;
	for(int i=1;i<=n-1;++i){
		int u,v;cin>>u>>v;
		SmallTree::add_edge(u,v);
	}
	for(int i=1;i<=m-1;++i){
		int u,v,a,b;cin>>u>>v>>a>>b;
		BigTree::add_edge(u,v,a,b);
	}
	SmallTree::dfs1(1);
	SmallTree::dfs2(1,1);
	for(int i=2;i<=n;++i){
		using SmallTree::sz;
		add(ans,(ll)sz[i]*(n-sz[i])%MOD*m%MOD);
	}
	
	T_in.build(1,1,n,0);
	T_out.build(1,1,n,1);
	BigTree::dfs_solve(1,0,0);
	
	cout<<ans<<endl;
	return 0;
}

题解 ZR261 18ABday6-萌新拆塔

题面下载

本题题解

这种类似于全排列的搜索,往往都指向一个优化:状压DP

\(dp[mask]\)表示已经打败了\(mask\)里这些怪物,剩下的血量的最大值。除血量外,攻击、防御、魔防值都是确定的,可以顺便维护出来。

但是这样会遇到一个问题:什么时候取宝石。我们发现,一打完立即取走宝石并不一定是最优的,因为有“模仿怪”这种东西存在。所以这个状态要改一改。设\(dp[s_1][s_2]\)表示打败了\(s_1\)里面这些怪物,取走了\(s_2\)里这些宝石,剩余血量的最大值。显然,\(s_2\)一定是\(s_1\)的子集,所以总状态数是\(3^n\)。在具体实现时,我们不开二维数组,而是用三进制状态来表示:未被打败、被打败了但没有取走宝石、取走了宝石,这三种状态。

时间复杂度\(O(3^n\cdot n)\)

参考代码:

//problem:ZR261
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=14;
const int MAX_MASK=5e6;

int pw3[MAXN+5];
void init(){
	pw3[0]=1;
	for(int i=1;i<=MAXN;++i)pw3[i]=pw3[i-1]*3;
}
struct Ternary_t{
	int x;
	Ternary_t(int _x=0){
		x=_x;
	}
	Ternary_t operator>>(const int& rhs){
		Ternary_t res=*this;
		res.x/=pw3[rhs];
		return res;
	}
	Ternary_t& operator>>=(const int& rhs){
		*this=((*this)>>rhs);
		return *this;
	}
	Ternary_t& operator++(){
		x++;
		return *this;
	}
	int binary(){
		int res=0;
		for(int i=0;i<=13;++i)if(((*this)>>i)%3!=0)res^=(1<<i);
		return res;
	}
	operator int()const{return x;}
};

int n,K;
int h,a,d,m;
struct Monster_t{
	int H,A,D,S,a,d,m,h;
	void input(){
		cin>>H>>A>>D>>S>>a>>d>>m>>h;
	}
}mons[MAXN+5];
vector<int>G[MAXN+5];
bool good_mask[1<<MAXN];

struct State_t{
	ll h,a,d,m;
	State_t(){}
	State_t(ll _h,ll _a,ll _d,ll _m){
		h=_h;a=_a;d=_d;m=_m;
	}
	bool operator<(const State_t& rhs){
		return h<rhs.h;
	}
}dp[MAX_MASK];

const State_t FAIL_STATE=State_t(-1,0,0,0);

State_t fight(const State_t& i,int j){
	bool monster_first=(mons[j].S&1);
	bool ignore_my_def=((mons[j].S>>1)&1);
	bool two_att=((mons[j].S>>2)&1);
	bool same_with_me=((mons[j].S>>3)&1);
	
	ll A=mons[j].A;
	ll D=mons[j].D;
	if(same_with_me){A=i.a;D=i.d;}
	
	int you_to_i=A-(ignore_my_def?0:i.d);
	int i_to_you=i.a-D;
	if(i_to_you<=0)return FAIL_STATE;
	
	ll rounds=mons[j].H/i_to_you+(mons[j].H%i_to_you!=0);
	ll you_att=max(0LL,(rounds-(!monster_first))*(two_att?2:1)*you_to_i-i.m);
	
	if(you_att>=i.h)return FAIL_STATE;
	
	State_t res=i;
	res.h-=you_att;
	return res;
}
State_t pick(const State_t& i,int j){
	State_t res=i;
	res.h+=mons[j].h;
	res.a+=mons[j].a;
	res.d+=mons[j].d;
	res.m+=mons[j].m;
	return res;
}

int main() {
	init();
	int T;cin>>T;while(T--){
		cin>>h>>a>>d>>m;
		cin>>n;
		for(int i=1;i<=n;++i){
			mons[i].input();
			vector<int>().swap(G[i]);
		}
		cin>>K;
		for(int i=1;i<=K;++i){
			int u,v;cin>>u>>v;
			G[v].pb(u);
		}
		for(int i=0;i<(1<<n);++i){
			good_mask[i]=true;
			for(int j=1;j<=n;++j)if((i>>(j-1))&1){
				for(int k=0;k<SZ(G[j]);++k){
					if(!((i>>(G[j][k]-1))&1)){
						good_mask[i]=false;
						break;
					}
				}
				if(!good_mask[i])break;
			}
		}
		dp[0]=State_t(h,a,d,m);
		for(Ternary_t mask=1;mask<=pw3[n]-1;++mask){
			dp[mask]=FAIL_STATE;
		}
		for(Ternary_t mask=0;mask<=pw3[n]-2;++mask){
			if(dp[mask].h==-1)continue;
			int bi=mask.binary();
			for(int j=1;j<=n;++j){
				if((mask>>(j-1))%3==0){
					//新打一个怪
					if(good_mask[bi|(1<<(j-1))]){
						State_t tmp=fight(dp[mask],j);
						if(dp[mask+pw3[j-1]]<tmp){
							dp[mask+pw3[j-1]]=tmp;
						}
					}
				}
				else if((mask>>(j-1))%3==1){
					//捡个宝石
					State_t tmp=pick(dp[mask],j);
					if(dp[mask+pw3[j-1]]<tmp){
						dp[mask+pw3[j-1]]=tmp;
					}
				}
			}
		}
		cout<<dp[pw3[n]-1].h<<endl;
	}
	return 0;
}

题解 ZR263 18ABday6-风花雪月

题面下载

本题题解

\(s=\sum c_i\)

首先,抽卡的总次数是有限的。当总卡数达到\(4s+3n\)时,无论抽到的是什么卡,都必能兑换所有服装。这是因为,每\(4\)张同一种类的卡,就能兑换任意一种服装。而为了避免卡被模\(4\)的余数浪费掉,所以预先安排\(3n\)张卡,专门用于被余数浪费。如果你不能理解\(+3n\),可以想一想\(c=\{0,0,1\}\),抽到的卡为\(\{2,2,0\}\)的情况。

现在,考虑手上抽到的卡,把每种卡被抽到的数量的序列,称为一个“状态”。那么,如果当前手上抽到的卡,已经能够获得\(n\)件衣服,我们称这个状态为“不好”的。否则,也就是无法获得\(n\)件衣服时,我们称这样的状态是“好的”。

题目问,第一次达到“不好的”状态,所经过的期望操作次数。就相当于问,经过的“好的”状态的数量的期望。另外,显然,“好的”状态之间,是一个严格的树形结构,不会出现环(因为每个状态比它的父亲多一张卡)。所以,每个状态最多只会被经过\(1\)次。因此,根据期望的线性性,经过的“好的”状态的数量的期望,就等于经过每个“好的”状态的概率之和(其实本质是概率\(\times 1\)之和,因为最多只会经过\(1\)次)。

可以用DP求所有“好的”状态的概率之和。依次考虑每种卡。假设当前考虑了前\(i\)种卡。我们要知道前\(i\)种卡,能为后面贡献多少张卡,记为\(k\)。这里的“贡献”,就是指每四张相同类型的卡换任意一张其他卡。例如,如果前\(i\)种卡里,某一种卡需要\(c\)张,而实际抽到了\(t\)张,则会令贡献值\(k\)增加\(\lfloor\frac{t-c}{4}\rfloor\)。当然,如果前\(i\)种卡,自己都没有抽满,需要后面的卡来贡献它们,则\(k\)为负数。

于是可以设计一个DP状态:设\(dp[i][j][k]\)表示考虑了前\(i\)种卡,总共选出了\(j\)张卡,贡献值为\(k\)的方案数。转移时,枚举第\(i\)种卡选了\(t\)张。如果\(t<c_i\),则新的\(k'=k-(c_i-t)\);否则\(k'=k+\lfloor\frac{t-c_i}{4}\rfloor\)

在转移时,根据新增的卡数\(t\),我们乘上系数\(\frac{(p_i)^t}{t!}\)。在最后统计答案时,如果选出的总卡数为\(J\),我们再乘上\(J!\)。最后就相当于乘以了\(\frac{J!}{\prod(t!)}\),这也就是含重复元素的排列计数。

分析时间复杂度。\(i\leq n,0\leq j\leq 4s+3n,-s\leq k\leq s\)。转移时,还要枚举一个和\(j\)同阶的\(t\)。总时间复杂度\(O(n(4s)^2s)\)卡卡就过了

参考代码:

//problem:ZR263
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=10,MAXM=1600,BASE=401;
const int MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int fac[MAXM+5],ifac[MAXM+5];
inline int comb(int n,int k){
	if(n<k)return 0;
	return (ll)fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;
}
void facinit(int lim){
	fac[0]=1;
	for(int i=1;i<=lim;++i)fac[i]=(ll)fac[i-1]*i%MOD;
	ifac[lim]=pow_mod(fac[lim],MOD-2);
	for(int i=lim-1;i>=0;--i)ifac[i]=(ll)ifac[i+1]*(i+1)%MOD;
}

int n,p[MAXN+5],c[MAXN+5],suf_c[MAXN+5];
int dp[MAXN+5][MAXM+MAXN*3+5][BASE*2+5];

int main() {
	facinit(MAXM);
	cin>>n;
	int sum=0;
	for(int i=1;i<=n;++i){
		cin>>p[i];
		add(sum,p[i]);
	}
	int isum=pow_mod(sum,MOD-2);
	for(int i=1;i<=n;++i){
		p[i]=(ll)p[i]*isum%MOD;
	}
	sum=0;
	for(int i=1;i<=n;++i){
		cin>>c[i];
		sum+=c[i];
	}
	for(int i=n;i>=1;--i){
		suf_c[i]=suf_c[i+1]+c[i];
	}
	int m=sum*4+n*3;
	dp[0][0][0+BASE]=1;
	for(int i=1;i<=n;++i){
		static int f[MAXM+5];
		int w=1;
		for(int j=0;j<=m;++j){
			f[j]=(ll)w*ifac[j]%MOD;
			w=(ll)w*p[i]%MOD;
		}
		for(int j=0;j<=m;++j){
			for(int k=-sum,v;k<=suf_c[i];++k)if(v=dp[i-1][j][k+BASE]){
				for(int t=0;t+j<=m;++t){
					int newk=k;
					if(t<c[i])newk-=c[i]-t;
					else newk+=(t-c[i])/4;
					if(newk<-sum)continue;
					if(newk>suf_c[i+1])break;
					add(dp[i][t+j][newk+BASE],(ll)v*f[t]%MOD);
				}
			}
		}
	}
	int ans=0;
	for(int j=0;j<=m;++j){
		int tmp=0;
		for(int k=-sum;k<0;++k){
			add(tmp,dp[n][j][k+BASE]);
		}
		add(ans,(ll)tmp*fac[j]%MOD);
	}
	cout<<ans<<endl;
	return 0;
}

题解 ZR264 18ABday7-花园

题目大意

小A和一些朋友来到了一个热带花园游玩。花园中有\(n\)个喷泉(标号\(0\sim n-1\)),以及\(m\)条双向道路。

每条道路有个互不相同的美丽程度,当一个人在节点\(x\)时,他会选择一条与\(x\)相连的最美丽的道路走过去,如果他在前一时刻是从那条道路走过来的,则他会选择美丽程度次大的道路走(如果不存在次大的,则还是走最大的那条)。保证每个点都至少有一条边与它相连。

一开始每个点上都有一个人,他们将按照上述规则行走,每单位时间所有人都走过一条道路。

\(P\)号点上有一个豪华餐厅,你需要计算在\(K_i\)时间后恰好在\(P\)号点上的人数。

\(q\)\(K_i\)需要你计算,每组的\(P\)都相同,询问互相独立。

数据范围:\(2\leq n\leq 150000\)\(1\leq m\leq 150000\)\(1\leq q\leq 2000\)\(1\leq K_i\leq 10^9\)

本题题解

从某条边走入某个花园时,会有一个固定的出边。这个出边只可能有两种:美丽度最大的,或美丽度次大的。具体来说,只有从美丽度最大的边进来,且存在美丽度次大的边时,才会从美丽度次大的边出去。

这个“两种出边”的设定,令人非常头疼。我们考虑拆点!把每个节点\(x\),拆成\(x_1=x\)\(x_2=(x+n)\)两个点。\(x_1\)表示,从一条不是\(x\)的美丽度最大的边,走向点\(x\)\(x_2\)表示,从\(x\)的美丽度最大的边,走向点\(x\)。那么,\(x_1\),一定会从美丽度最大的边走出去;\(x_2\)(取决于是否存在美丽度次大的边)会从美丽度次大的(或最大的)边走出去。于是,每个节点,都有拥有了唯一、确定的一条出边。这张\(2n\)个节点的图,就形成了基环内向树森林的结构。

问题转化为,对一个基环内向树森林,有一个关键节点\(p\)。初始时,某些节点上有\(1\)个人(\(1\dots n\)号节点初始时有人,\(n+1\dots 2n\)节点初始时无人)。每单位时间每个人会沿出边向前走一步。\(q\)次询问某一时刻\(K_i\)时关键点上的人数。当然,在本题中,关键点有两个:\(P\)\(P+n\)。但由于一个人不可能同时出现在两个关键点上,所以把\(P\)\(P+n\)的答案分别求一下加起来即可。

这个基环树上走动问题,比较经典。考虑每个节点(也就是某个人的起点),可能有三种情况:

  • 永远无法到达关键点。比如和关键点不在同一个连通块。或者在同一个连通块,但关键点不在环上,且该节点也不在关键点的子树内。
  • 仅可以到达关键点恰好一次。关键点不在环上,且该节点在关键点的子树内。
  • 在第一次到达关键点之后,每隔\(x\)秒就到一次。这种情况当且仅当关键点在环上,这个\(x\)就是环长。

求出每个节点的情况后,关键在于处理第三类情况。假设它第一次到达是在第\(y_j\)秒,之后每\(x\)秒就会到一次,那么该节点\(j\)被计入答案,当且仅当\(y_j+tx=K_i\),也就是\((K_i-y_j)=0\bmod x\)。可以把\(y_j\bmod x\)的值相同的节点放到一起,按\(y_j\)排序,询问时,用\(\texttt{lower_bound}\)查询一下\(K_i\bmod x\)\(y_j\leq K_i\)的有几个即可。

时间复杂度\(O((n+q)\log n)\)

参考代码:

//problem:A
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=3e5;
int n,m,P,fa[MAXN+5];
vector<int>G[MAXN+5];

int Root,cirlen0,cirlen1;
bool foundP0,foundP1,vis[MAXN+5],oncir[MAXN+5];
void dfs(int u){
	vis[u]=1;
	if(u==P)foundP0=true;
	if(u==P+n)foundP1=true;
	for(int i=0;i<SZ(G[u]);++i){
		int v=G[u][i];
		if(v==Root)continue;
		dfs(v);
	}
}
int dis0[MAXN+5],dis1[MAXN+5];
void dfs2(int u,int frm,int* dis){
	for(int i=0;i<SZ(G[u]);++i){
		int v=G[u][i];
		if(v==frm||v==Root)continue;
		dis[v]=dis[u]+1;
		dfs2(v,u,dis);
	}
}

vector<int>v0[MAXN+5],v1[MAXN+5];
map<int,int>mp0,mp1;
int main() {
	cin>>n>>m>>P;++P;
	for(int i=1;i<=m;++i){
		int x,y;
		cin>>x>>y;++x;++y;
		bool tx=fa[x],ty=fa[y];
		
		if(!fa[x])fa[x]=(ty?y:y+n);
		else if(!fa[x+n])fa[x+n]=(ty?y:y+n);
		
		if(!fa[y])fa[y]=(tx?x:x+n);
		else if(!fa[y+n])fa[y+n]=(tx?x:x+n);
	}
	for(int i=1;i<=n;++i){
		if(!fa[i+n])fa[i+n]=fa[i];
	}
	for(int i=1;i<=n*2;++i)if(fa[i])G[fa[i]].pb(i);//cout<<"* "<<i<<" "<<fa[i]<<endl;
	memset(dis0,-1,sizeof(dis0));
	memset(dis1,-1,sizeof(dis1));
	for(int i=1;i<=n*2;++i){
		if(!fa[i]||vis[i])continue;
		int x=i;
		while(!vis[x]){
			vis[x]=1;
			x=fa[x];
		}
		Root=x;
		foundP0=foundP1=false;
		dfs(x);
		int y=x,len=0;
		while(1){
			len++;
			oncir[y]=1;
			y=fa[y];
			if(y==x)break;
		}
		if(foundP0){
			if(oncir[P]){
				cirlen0=len;
				Root=P;dis0[P]=0;
				dfs2(P,0,dis0);
			}
			else{
				Root=0;dis0[P]=0;
				dfs2(P,fa[P],dis0);
			}
		}
		if(foundP1){
			if(oncir[P+n]){
				cirlen1=len;
				Root=P+n;dis1[P+n]=0;
				dfs2(P+n,0,dis1);
			}
			else{
				Root=0;dis1[P+n]=0;
				dfs2(P+n,fa[P+n],dis1);
			}
		}
	}
//	for(int i=1;i<=n;++i)cout<<dis0[i]<<" ";cout<<endl;
//	for(int i=1;i<=n;++i)cout<<dis1[i]<<" ";cout<<endl;
//	cout<<cirlen0<<" "<<cirlen1<<endl;
	
	if(cirlen0){
		for(int i=1;i<=n;++i)if(dis0[i]!=-1){
			v0[dis0[i]%cirlen0].pb(dis0[i]);
		}
		for(int i=0;i<cirlen0;++i){
			sort(v0[i].begin(),v0[i].end());
		}
	}
	else{
		for(int i=1;i<=n;++i)if(dis0[i]!=-1)mp0[dis0[i]]++;
	}
	if(cirlen1){
		for(int i=1;i<=n;++i)if(dis1[i]!=-1){
			v1[dis1[i]%cirlen1].pb(dis1[i]);
		}
		for(int i=0;i<cirlen1;++i){
			sort(v1[i].begin(),v1[i].end());
		}
	}
	else{
		for(int i=1;i<=n;++i)if(dis1[i]!=-1)mp1[dis1[i]]++;
	}
	
	
	int q;cin>>q;while(q--){
		int t;cin>>t;
		int ans=0;
		if(cirlen0){
			int w=t%cirlen0;
			ans+=upb(v0[w].begin(),v0[w].end(),t)-v0[w].begin();
		}
		else{
			if(mp0.count(t))ans+=mp0[t];
		}
		if(cirlen1){
			int w=t%cirlen1;
			ans+=upb(v1[w].begin(),v1[w].end(),t)-v1[w].begin();
		}
		else{
			if(mp1.count(t))ans+=mp1[t];
		}
		cout<<ans<<" ";
	}
	cout<<endl;
	return 0;
}

题解 ZR265 18ABday7-归来

题目大意

小A在8102年掌握了穿越时间的科技,他想要回归遥远的2018年来看一看。

我们定义“时间线”是长度恰好为\(n\),仅由 ABCD 四个大写字母组成的字符串。

设某个时间线为\(S\),小A可以用以下两种方式修改时间线:

  1. 交换\(S\)中相邻两个字符。
  2. 使用某种转移。一个转移是两个长度不超过\(n\)的等长字符串\((A_i,B_i)\),可以把\(S\)中某个子串\(A_i\)替换成\(B_i\)

小A自然能随意行走于时间线上,但他想享受这跃迁的过程。他想考考你:初始的时间线任选,任意操作,在任意一条时间线结束,途中经过的不同时间线数量最多是多少?

注意可以经过重复的时间线,但只会计入一次答案。

数据范围:\(n\leq 30\), \(m\leq 50\)

本题题解

如果把每个时间线(也就是每个长度为\(n\)的、由ABCD组成的串)看成一个节点。那么本题显然是一个求图上最短路的问题。

但是节点的数量高达\(O(s^{n})=4^{30}\)(其中\(s\)是字符集大小),暴力建图不可取。

注意到,第一个操作(交换\(S\)中相邻两个字符),意味着对于任何一个串,我们能把它的字母随意排列。因此,字母的顺序其实没有关系。我们关心的是,ABCD各在串里出现了多少次。如果这样定义状态,则状态数简化为\(O(n^s)=30^4\),建图是绰绰有余的。

枚举每个状态,再枚举每种转移,就可以建图了。时间复杂度\(O(n^s\cdot m)\)

建图后,我们先用Tarjan算法缩点,得到拓扑图。然后在拓扑图上DP,求出最长路。

总时间复杂度\(O(n^sm)\)

参考代码:

//problem:B
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int MAXN=30,MAXM=50;
const int MAXS=1e6;
struct State_t{
	int a,b,c,d;
	State_t(){
		a=b=c=d=0;
	}
	State_t(int _a,int _b,int _c,int _d){
		a=_a;b=_b;c=_c;d=_d;
	}
	int id(){
		return a*31*31*31+b*31*31+c*31+d;
	}
	State_t& operator=(int x){
		d=x%31;
		x/=31;
		c=x%31;
		x/=31;
		b=x%31;
		x/=31;
		a=x;
		return *this;
	}
	State_t& operator=(string s){
		a=b=c=d=0;
		for(int i=0;i<(int)s.length();++i){
			if(s[i]=='A')a++;
			if(s[i]=='B')b++;
			if(s[i]=='C')c++;
			if(s[i]=='D')d++;
		}
		return *this;
	}
	bool operator<=(const State_t& rhs){
		return a<=rhs.a && b<=rhs.b && c<=rhs.c && d<=rhs.d;
	}
	State_t operator-(const State_t& rhs){
		State_t res;
		res.a=a-rhs.a;
		res.b=b-rhs.b;
		res.c=c-rhs.c;
		res.d=d-rhs.d;
		return res;
	}
	State_t operator+(const State_t& rhs){
		State_t res;
		res.a=a+rhs.a;
		res.b=b+rhs.b;
		res.c=c+rhs.c;
		res.d=d+rhs.d;
		return res;
	}
};

ll comb[MAXN+5][MAXN+5];
pair<State_t,State_t> tr[MAXS+5];
int n,m;

namespace Gragh{
const int MAXN=::MAXS;
ll f[MAXN+5];
struct EDGE{int nxt,to;}edge[MAXN*(::MAXM)+5];
int head[MAXN+5],tot;
inline void add_edge(int u,int v){edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;}
int dfn[MAXN+5],low[MAXN+5],bel[MAXN+5],cnt_scc,idx;
ll sum[MAXN+5];
int sta[MAXN+5],top;
bool insta[MAXN+5];
void tarjan(int u){
	dfn[u]=low[u]=++idx;
	sta[++top]=u;insta[u]=1;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(insta[v]){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		++cnt_scc;
		while(sta[top]!=u){
			bel[sta[top]]=cnt_scc;
			sum[cnt_scc]+=f[sta[top]];
			insta[sta[top]]=0;
			--top;
		}
		bel[sta[top]]=cnt_scc;
		sum[cnt_scc]+=f[sta[top]];
		insta[sta[top]]=0;
		--top;
	}
}
vector<int>G[MAXN+5];
int ind[MAXN+5];
ll dp[MAXN+5];
ll solve(){
	for(int i=0;i<=MAXN;++i)if(!dfn[i])tarjan(i);
	for(int i=0;i<=MAXN;++i)for(int j=head[i];j;j=edge[j].nxt){
		int k=edge[j].to;
		// add edge: bel(i) -> bel(k)
		if(bel[i]==bel[k])continue;
		G[bel[i]].pb(bel[k]);
		ind[bel[k]]++;
	}
	queue<int>q;
	for(int i=1;i<=cnt_scc;++i)if(!ind[i])q.push(i),dp[i]=sum[i];
	ll ans=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		ans=max(ans,dp[u]);
		for(int i=0;i<SZ(G[u]);++i){
			int v=G[u][i];
			ind[v]--;
			dp[v]=max(dp[v],sum[v]+dp[u]);
			if(!ind[v]){
				q.push(v);
			}
		}
	}
	return ans;
}
}//namespace Gragh
int main() {
	//freopen("ex_return3.in","r",stdin);
	cin>>n>>m;
	comb[0][0]=1;
	for(int i=1;i<=n;++i){
		comb[i][0]=1;
		for(int j=1;j<=i;++j){
			comb[i][j]=comb[i-1][j-1]+comb[i-1][j];
		}
	}
	for(int i=1;i<=m;++i){
		string f,t;
		State_t fr,to;
		cin>>f>>t;
		fr=f;to=t;
		tr[i]=mk(fr,to);
	}
	for(int a=0;a<=n;++a){
		for(int b=0;a+b<=n;++b){
			for(int c=0;a+b+c<=n;++c){
				int d=n-(a+b+c);
				int x=State_t(a,b,c,d).id();
				Gragh::f[x]=comb[a+b][b]*comb[a+b+c][c]*comb[a+b+c+d][d];
				//cout<<x<<" "<<Gragh::f[x]<<endl;
			}
		}
	}
	for(int a=0;a<=n;++a){
		for(int b=0;a+b<=n;++b){
			for(int c=0;a+b+c<=n;++c){
				int d=n-(a+b+c);
				State_t cur=State_t(a,b,c,d);
				for(int i=1;i<=m;++i){
					if(tr[i].fi<=cur){
						Gragh::add_edge(cur.id(),(cur-tr[i].fi+tr[i].se).id());
					}
				}
			}
		}
	}
	cout<<Gragh::solve()<<endl;
	return 0;
}
posted @ 2020-05-26 17:30  duyiblue  阅读(623)  评论(1编辑  收藏  举报