#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("");
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM