#1 CF512D & CF516D & CF521D

CF512D Fox And Travelling

题目描述

点此看题

给出一张无向图,每次你可以选择一个度数 \(\leq 1\) 的点并将其删除。

问对于 \(k=0,1,2...n\) 有多少个删除 \(k\) 个点的序列,答案模 \(10^9+9\)

\(n\leq 100,m\leq \frac{n(n-1)}{2}\)

解法

因为只能删除度数 \(\leq 1\) 的点,所以环是和删除序列没关系的,那么我们把环去掉之后就得到了一个森林,这个过程可以用拓扑排序实现,没有拓扑掉的点就不需要考虑了。

但是这个森林的树是不相同的,因为如果有一棵树连接着环,那么必须要把链接的点当成根,最后选取它。但是如果树没有连接着环,就没有根这一说。所以这个森林混合了有根树无根树

首先考虑有根树怎么做,树形 \(dp\) 是显然的,\(dp[u][i]\) 表示点 \(u\) 内选取了 \(i\) 个点,然后树背包板。

无根树怎么做才是重点,根据我以前总结的套路我们可以枚举根,按照有根树的方法做树形 \(dp\),再把得到的结果求和。这样做显然会算重,本题的点睛之笔就是考虑一种方案被算重了多少次(直接但实用方法),对于选取了 \(i\) 个点的方案,由于这 \(i\) 个点一定处在树的边缘,所以选取其他的 \(size-i\) 个点为根都会算到这个方案,那么我们除以 \(size-i\) 即可。

时间复杂度 \(O(n^3)\)由于我不学多项式所以更优的做法将不再讲解

细节:无向图的拓扑排序还是增设一个访问标记吧,然后在 \(deg\leq 1\) 的时候塞到队列里面。

#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 105;
const int MOD = 1e9+9;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,tot,f[M],d[M],b[M],inv[M],fac[M],vis[M];
int dp[M][M],g[M],h[M],s[M],zinv[M],siz[M];
struct edge {int v,next;}e[M*M];
void add(int &x,int y) {x=(x+y)%MOD;}
void init(int n)
{
	fac[0]=inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=0;i<=n;i++) zinv[i]=inv[i];
	for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
}
int C(int n,int m)
{
	if(n<m || m<0) return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
void tpsort()
{
	queue<int> q;
	for(int i=1;i<=n;i++)
		if(d[i]<=1) vis[i]=1,q.push(i);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v;d[v]--;
			if(d[v]<=1 && !vis[v]) vis[v]=1,q.push(v);
		}
	}
}
void dfs(int u,int rt)
{
	b[u]=rt;s[rt]++;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(!b[v] && !d[v]) dfs(v,rt);
	}
}
void get(int u,int fa)
{
	for(int i=0;i<=n;i++) dp[u][i]=0;
	dp[u][0]=1;siz[u]=1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa || b[u]!=b[v]) continue;
		get(v,u);
		for(int j=siz[u];j>=0;j--)
			for(int k=1;k<=siz[v];k++)
				add(dp[u][j+k],dp[u][j]*dp[v][k]%MOD*C(j+k,j));
		siz[u]+=siz[v];
	}
	dp[u][siz[u]]=dp[u][siz[u]-1];
}
signed main()
{
	n=read();m=read();init(n);
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();d[u]++;d[v]++;
		e[++tot]=edge{u,f[v]},f[v]=tot;
		e[++tot]=edge{v,f[u]},f[u]=tot;
	}
	tpsort();
	for(int i=1;i<=n;i++)
		if(d[i]==1) dfs(i,i);
	for(int i=1;i<=n;i++)
		if(!d[i] && !b[i]) dfs(i,i);
	g[0]=1;
	for(int i=1;i<=n;i++) if(i==b[i])
	{
		for(int j=0;j<=n;j++) h[j]=0;
		if(d[i]==1)
		{
			get(i,0);
			for(int j=0;j<=s[i];j++) h[j]=dp[i][j];
		}
		else
		{
			for(int o=1;o<=n;o++) if(b[o]==i)
			{
				get(o,0);
				for(int j=0;j<=s[i];j++)
					add(h[j],dp[o][j]);
			}
			for(int j=0;j<=s[i];j++)
				h[j]=h[j]*zinv[s[i]-j]%MOD;
		}
		for(int j=n;j>=0;j--)
			for(int k=1;k<=s[i] && j+k<=n;k++)
				add(g[j+k],g[j]*h[k]%MOD*C(j+k,j));
	}
	for(int i=0;i<=n;i++)
		printf("%lld\n",g[i]);
}

CF516D Drazil and Morning Exercise

题目描述

点此看题

解法

首先考虑本题最基本的量 \(f(x)\) 怎么计算,连我都知道的套路是转化成树的直径问题,可以用反证法说明 \(f(x)=dis(x,y)\)\(y\) 一定是直径的某一个端点。

然后好像没有什么好的思路,但是考虑如果不要求 \(S\) 的点联通,可以直接求出 \(f(x)\) 之后排序,然后枚举最小值,贴着限制能选就选即可。

现在要求 \(S\) 联通,我们可以去考察 \(f(x)\) 在树上的分布情况:他的最大值在直径端点取得,最小值在直径中心取得,并且变化较为均匀。可以以直径为根建树,那么树的从上到下 \(f(x)\) 一定是递增的。

实际上没有必要以直径为根建树,以最小的 \(f(x)\) 建树即可,这样还是能保证从上到下 \(f(x)\) 递增。然后我们类似地枚举最小值,剩下的点只能在子树中选取了,好的实现方式是每个点往上打树上差分标记(其实就是切换了主体)

时间复杂度 \(O(nq)\)我个人的想法稍复杂一点,但很接近正解了

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std; 
const int M = 100005;
#define int long long
const int inf = 1e18;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,x,ans,rt,tot,f[M],da[M],db[M],d[M],c[M];
vector<int> v,p;
struct edge{int v,c,next;}e[M<<1];
void dfs1(int u,int fa,int *d)
{
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		d[v]=d[u]+e[i].c;
		dfs1(v,u,d);
	}
}
void dfs2(int u,int fa)
{
	v.push_back(d[u]);p.push_back(u);
	c[p[lower_bound(v.begin(),v.end(),d[u]-x)-
	v.begin()-1]]--;c[u]=1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dfs2(v,u);
		c[u]+=c[v];
	}
	ans=max(ans,c[u]);
	v.pop_back();p.pop_back();
}
signed main()
{
	n=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read(),c=read();
		e[++tot]=edge{u,c,f[v]},f[v]=tot;
		e[++tot]=edge{v,c,f[u]},f[u]=tot;
	}
	dfs1(1,0,da);
	for(int i=1;i<=n;i++) if(da[rt]<da[i]) rt=i;
	memset(da,0,sizeof da);
	dfs1(rt,0,da);
	for(int i=1;i<=n;i++) if(da[rt]<da[i]) rt=i;
	dfs1(rt,0,db);rt=1;
	for(int i=1;i<=n;i++)
	{
		d[i]=max(da[i],db[i]);
		if(d[rt]>d[i]) rt=i;
	}
	m=read();
	v.push_back(-inf);p.push_back(0);
	while(m--)
	{
		x=read();ans=0;
		for(int i=0;i<=n;i++) c[i]=0;
		dfs2(rt,0);
		printf("%lld\n",ans);
	}
}

CF521D Shop

题目描述

点此看题

解法

我直接手切所以简记一下,最后发现做法和 \(\tt jiangly\) 聚聚一模一样。

操作很多样,首先我们可以做出一些简单的观察:

  • 如果知道了要选哪些操作,那么操作的顺序一定是 赋值->加法->乘法
  • 赋值操作只需要保留每个位置最大的即可。
  • 因为赋值操作最先进行,所以可以看成加法。
  • 因为加法一定是从大到小进行,所以可以看成乘法,也就是在原来的基础上增加了几倍。
  • 因为答案的特殊形式,所以一定是乘上的值越大越好。

根据上面的观察,就不难设计出先把赋值转化成加法,然后把加法按照权值排序,计算转化成乘法之后的数乘。然后直接按权值从小到大选取乘法,但是注意这不是最终的顺序,还要按照种类排序,时间复杂度 \(O(n\log n)\)

总结

化归的思想,把不同操作统一成同种形式更有利于分析。

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 100005;
#define db double
#define pb push_back
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,a[M];pair<int,int> mx[M];
struct node{db v;int id,ty;};vector<node> v,ad[M];
bool cmp1(node a,node b) {return a.v>b.v;}
bool cmp2(node a,node b) {return a.ty<b.ty;}
signed main()
{
	n=read();m=read();k=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=m;i++)
	{
		int op=read(),x=read(),y=read();
		if(op==1) mx[x]=max(mx[x],make_pair(y,i));
		if(op==2) ad[x].pb(node{y,i,2});
		if(op==3) v.pb(node{y,i,3});
	}
	for(int i=1;i<=n;i++)
	{
		if(mx[i].first>a[i])//trans cover to add
			ad[i].pb
			(node{mx[i].first-a[i],mx[i].second,1});
		sort(ad[i].begin(),ad[i].end(),cmp1);
		db sum=a[i];
		for(auto x:ad[i])//trans add to mul
		{
			v.pb(node{(sum+x.v)/sum,x.id,x.ty});
			sum+=x.v;
		}
	}
	sort(v.begin(),v.end(),cmp1);
	while(v.size()>k) v.pop_back();
	sort(v.begin(),v.end(),cmp2);
	printf("%d\n",(int)v.size());
	for(auto x:v) printf("%d ",x.id);
	puts("");
}
posted @ 2022-01-26 11:10  C202044zxy  阅读(349)  评论(14编辑  收藏  举报