Loading

2.13 sdwc 补题日记

20220301

T1

没什么好讲的,用 Hash 可以在 \(n\log n\) 内的复杂度内预处理,然后二分即可得到答案。
考场上因为边界处理错误以及对情况考虑的不周全,爆零了。

T2

我们考虑用树形 dp 来做这个事情,首先得到最优值其实是很好的得到的,而判断某个点是否在方案中出现过就比较难办,我们需要根据我们的 dp 值去判断有哪些点可以在什么时候被选上。

至于算方案数,这个 dp 考场上没有想出来,看完标称后会了。设 maxx[i] 表示以 \(i\) 为根的子树,最上面一层选的点中最大的那个,我们考虑整个题目可以被我们简化成从一棵树上选一些关键点,每个叶子都能找到一个关键点,且之间路径两两无交。

然后我们考虑设 f[x] 表示没有限制情况下的方案数,g[x] 表示一定选根情况下的方案数。然后我们考虑如何进行 dp。

首先 f[x] 还是很好做的,我们先考虑一定不选根的方案数,那么这个时候就有 f[x]=f[s1]*f[s2]...

如果有 maxx[k]>c[k],那么这个就是答案。

否则,如果 maxx[k]==c[k]f[k] 需要加上 g[k]

否则,如果 maxx[k]<c[k],答案应该一定要选根。

所以我们需要考虑 g[k] 怎么做。考虑如果一定选根的话,某棵子树的 maxx 一定是不用选了,所以我们这样考虑,我们选出那些 maxx 最大的子树,然后枚举去掉哪一个的,乘上其余子树的 'f',然后我们考虑选的那个子树,它的方案数一定是 g[k] 对吧?一个更妙的实现方法参考程序中的实现方法。

考场上写出来了 \(k=1,k=2\),显然写麻烦了。

#include<bits/stdc++.h>
#define ll long long
#define dd double
#define ld long double
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 1000100
#define m number
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=998244353;

template<typename T> inline void read(T &x){
	x=0;int f=1;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') f*=-1;
	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
	x*=f;
}

struct edge{
	int to,next;
	inline void Init(int to_,int ne_){
		to=to_;next=ne_;
	}
}li[N<<1];
int head[N],tail;

inline void Add(int from,int to){
	li[++tail].Init(to,head[from]);
	head[from]=tail;
}

inline int ksm(int a,int b,int mod){
	int res=1;while(b){if(b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}return res;
}

int n,k,c[N];
int f[N][2];
typedef pair<int,int> P;
bool leaf[N];

namespace Question1{
	inline void dfs(int k,int fa){
		bool op=0;
		for(int x=head[k];x;x=li[x].next){
			int to=li[x].to;
			if(to==fa) continue;
			op=1;
			dfs(to,k);
		}
		if(!op){
			f[k][0]=c[k];f[k][1]=0;
			leaf[k]=1;
//			printf("f[%lld][0]=%lld f[%lld][1]=%lld\n",k,f[k][0],k,f[k][1]);
			return;
		}
		for(int x=head[k];x;x=li[x].next){
			int to=li[x].to;
			if(to==fa) continue;
			f[k][0]+=min(f[to][0],f[to][1]+c[to]);
		}
		f[k][1]=f[k][0];
		for(int x=head[k];x;x=li[x].next){
			int to=li[x].to;
			if(to==fa) continue;
			f[k][1]=min(f[k][1],f[k][0]-min(f[to][0],f[to][1]+c[to])+f[to][1]);
		}
//		printf("f[%lld][0]=%lld f[%lld][1]=%lld\n",k,f[k][0],k,f[k][1]);
	}
	
	inline void Solve(){
		dfs(1,0);
		printf("%lld\n",min(f[1][0],f[1][1]+c[1]));
	}
}

namespace Question2{
	
	bool vis[N][2],ans[N];
	int Can[N][2];
	
	inline void dfs(int k,int x,int fa){
//		printf("k=%d x=%d fa=%d\n",k,x,fa);
		if(vis[k][x]) return;
		vis[k][x]=1;
//		if(leaf[k]&&x==0){
//			ans[k]=1;return;
//		}
		for(int x=head[k];x;x=li[x].next){
			int to=li[x].to;
			if(to==fa) continue;
			Can[to][0]=Can[to][1]=0;
		}
		if(x==0){
			for(int x=head[k];x;x=li[x].next){
				int to=li[x].to;
				if(to==fa) continue;
				if(f[to][0]<=f[to][1]+c[to]){
					dfs(to,0,k);
				}
				if(f[to][0]>=f[to][1]+c[to]){
					ans[to]=1;dfs(to,1,k);
				}
			}
		}
		else{
			if(f[k][1]==f[k][0]){
				for(int x=head[k];x;x=li[x].next){
					int to=li[x].to;
					if(to==fa) continue;
					if(f[to][0]<=f[to][1]+c[to]){
						dfs(to,0,k);
					}
					if(f[to][0]>=f[to][1]+c[to]){
						ans[to]=1;dfs(to,1,k);
					}
				}
			}
			for(int x=head[k];x;x=li[x].next){
				int to=li[x].to;
				if(to==fa) continue;
				if(f[to][0]<=f[to][1]+c[to]){
					Can[to][0]=1;
				}
				if(f[to][0]>=f[to][1]+c[to]){
					Can[to][1]=1;
				}
			}
			int cnt=0;
			for(int x=head[k];x;x=li[x].next){
				int to=li[x].to;
				if(to==fa) continue;
				if(f[k][1]==f[k][0]-min(f[to][0],f[to][1]+c[to])+f[to][1]){
//					printf("to=%d\n",to);
					cnt++;
					if(f[to][0]<=f[to][1]+c[to]) Can[to][0]=2;
					if(f[to][0]>=f[to][1]+c[to]) Can[to][1]=2; 
				}
			}
			for(int x=head[k];x;x=li[x].next){
				int to=li[x].to;
				if(to==fa) continue;
				if(Can[to][0]==1){
					dfs(to,0,k);
				}
				if(Can[to][1]==1){
					ans[to]=1;dfs(to,1,k);
				}
				if(Can[to][0]==2||Can[to][1]==2) dfs(to,1,k);
				if(cnt>1){
					if(Can[to][0]==2) dfs(to,0,k);
					if(Can[to][1]==2){ans[to]=1;dfs(to,1,k);} 
				}
			}
		}
	}
	
	inline void Solve(){
		if(f[1][0]<=f[1][1]+c[1]){
			dfs(1,0,0);
		}
		if(f[1][0]>=f[1][1]+c[1]){
			ans[1]=1;dfs(1,1,0);
		}
		// int cnt=0;
		// for(int i=1;i<=n;i++) if(ans[i]) cnt++;
		// printf("%lld\n",cnt);
		for(int i=1;i<=n;i++) if(ans[i]) printf("%lld ",i);puts("");
	}
}

namespace Question3{
	int f[N],g[N],maxx[N];

	inline void dfs(int k,int fa){
		bool op=0;
		for(int x=head[k];x;x=li[x].next){
			int to=li[x].to;if(to==fa) continue;
			dfs(to,k);maxx[k]=max(maxx[k],maxx[to]);
			op=1;
		}
		if(!op){
			f[k]=1;g[k]=1;maxx[k]=c[k];return;
		}
		f[k]=1;
		for(int x=head[k];x;x=li[x].next){
			int to=li[x].to;if(to==fa) continue;
			if(maxx[k]==maxx[to]){
				g[k]=1ll*f[k]*g[to]%mod+1ll*g[k]*f[to]%mod;
				f[k]=1ll*f[k]*f[to]%mod;
			}
			else{
				f[k]=1ll*f[k]*f[to]%mod;
				g[k]=1ll*g[k]*f[to]%mod;
			}
		}
		if(c[k]<maxx[k]){
			maxx[k]=c[k];
			f[k]=g[k];
		}
		else if(c[k]==maxx[k]){
			f[k]=(f[k]+g[k])%mod;
		}
	}

	inline void Solve(){
		dfs(1,0);
		printf("%d\n",f[1]);
	}
}

signed main(){
	// freopen("purtree.in","r",stdin);
	// freopen("purtree.out","w",stdout);
	read(n);for(int i=1;i<=n;i++) read(c[i]);
	for(int i=1;i<=n-1;i++){
		int from,to;read(from);read(to);
		Add(from,to);Add(to,from);
	}
	read(k);
	Question1::Solve();
	if(k>=2) Question2::Solve();
	if(k>=3) Question3::Solve();
	return 0;
}

T3

我们考虑这个 dp,自然的想法,\(f_{i,c}=\min(f_{1,H}+H-c,\sum_{(i,u)\in E}\frac{1}{p}f_{u,c-d_u})\),我们考虑一下,似乎不能用最短路或者其他来做,因为如果是最短路模型应该是对一些期望取 \(min\) 或者类似的操作。我们只能来分析后效性,不难发现后效性都与 \(f_{1,H}\) 有关,不如设这个量为 \(A\),我们把这个量射出来之后,不难发现,其余点就都可以表示了,且每个点都是关于 \(A\) 的一次函数,可能会有一些分段函数,但最终一定是几个一次函数分段,我们考虑可以通过最开始的节点来列方程,从而我们知道方程的解只有一个。不难发现表示方程是非常困难的,但是因为解只有一个,我们可以对 \(A\) 进行二分,相当于在一个分段的一次函数上找零点。如果无解,答案是多大都可以。

P5400

首先考虑一下一个外向树,考虑其拓扑序的个数,首先给每个点一个初始标号,然后我们我们枚举所有排列,然后把排列看做映射给树重新标号,问有多少个排列满足树的拓扑序为 1,2,...n

我们考虑一下,整颗树的根一定是在整个序列的前面,所以我们随机选择一个数,选对的概率是 \(\frac{1}{n}\)

由此考虑一下,我们可以看出如果一个子树的大小为 \(siz\),那么我们选出一个数作为子树的根,选出的概率是 \(\frac{1}{siz}\)

我们考虑一下,子树之间是相互独立的,我们选一个出来当整棵树的根,然后对每一个儿子的子树,选一个出来当它的根,整个过程是相互独立的。

所以我们可以得到方案数为 \(n!\prod \frac{1}{siz_i}\)

对于这个题,我们二项式反演之后考虑先选 \(k\) 个点,然后对这 \(k\) 个点从小到大进行填数,用二维情况来类比,对于第一个数来说,这个十字架上的数以这个极大值为父亲,对于第二个数来说,第一个数以其为父亲,并且其十字架上非第一个十字架上的点以第二个点为父亲。这样就构成了一棵树。

我们考虑钦定 \(k\) 个点之后选出来的不同的方案数是多少。总方案数是 \((nml)!\)

我们有以下式子:

\[ ((l-i)(n-i)(m-i))!\dbinom{nml}{nml-(n-i)(m-i)(l-i)}(nml-(n-i)(m-i)(l-i))! \frac{1}{\prod (nml-(n-j)(m-j)(l-j))} \]

化简一下,就会发现概率为:

\[ \frac{1}{\prod (nml-(n-j)(m-j)(l-j))} \]

然后就可以做了。

AGC028D

首先注意到我们可以把圆弄到序列上,然后拆贡献,我们考虑每一个连通块出出现的方案数。

我们考虑一个连通块一定是一个连续的区间,设 \(f_{l,r}\) 表示其区间为 \([l,r]\) 的时候,在这个区间里出现连通块的个数,注意到这里我们限制 \(l\)\(r\) 必须属于一个连通块,但这里我们可以不只有一个连通块,我们相当于计数 \(l,r\) 所在的连通块。我们考虑容斥,首先让这里面的点随便链接,然后考虑我们只需要去掉 \(l\)\(r\) 没有在一个连通块内的方案数,那我们可以枚举左端点与那个点在一个连通块(如果有多个,考虑最右边的)。

注意统计方案乘上其他点随便连的方案数,因为我们上面 dp 的是只考虑这个区间的点。

AGC036F

我们考虑如果去掉下界,很好做,我们考虑把下界的限制容斥掉,方法是钦定有多少个在下界以下。

排序之后 dp 就可以了。

CF917D

二项式反演之后利用 prufer 序列,考虑出我们现在需要计算的是一个 \(\sum\prod s_i\) 的形式,考虑组合意义,相当于每个连通块选出一个特殊点,所以我们可以设 dp \(f_{i,j,0/1}\) 表示以 \(i\) 为根的子树,选 \(j\) 个特殊点,最上面的连通块有没有选特殊点。

利用背包转移,我们就可以在 \(O(n^2)\) 的时间复杂度内解决这个问题。

20220203

P3349

这个题一开始没有任何统计的思路。其实不拿发现如果我们用图而不是树作为我们统计的主题,我们就会发现我们可以进行状压,我们只需要记录当前 dp 的树节点对应的图上哪一个节点,并且记录哪些节点我们已经选过了。

不难发现整个 dp 最后一个排列的复杂度是非常大的,我们需要枚举子集所以有一个 \(n^3\),我们不妨把其容斥掉,方法是我们不限定数字要不重不漏的填写,我们考虑限制填写的数,\(2^n\) 来枚举一个集合,然后我们用可以用 \(n\) 个数,减去可以用 \(n-1\) 个数,加上可以用 \(n-2\) 个数,一次类推,来做容斥即可。

UOJ223

我的 dp 还是有点菜,这样一个题反而为了轮廓线 dp 想了快一个上午。

我们写出来式子,然后运用 min-max 容斥,剩下的部分可以用轮廓线 dp 来解决。不过我们把一个需要枚举所有集合的问题改成枚举一个答案的容斥系数和的问题还是非常妙的。写这个轮廓线 dp 注意需要考虑我们所有的放的方案。

posted @ 2022-03-04 09:26  hyl天梦  阅读(54)  评论(0编辑  收藏  举报