Codeforces Div2 #548

C-Edgy Trees

给定一个有红边和黑边的树,求经过至少一条黑边的点序列的总数。直接考虑它的反面就是就是一条黑边也没有走过的点序列的总数,统计树上所有被黑边断开的连通块。答案显然就是 n k − ∑ s z k n^{k}-∑sz^{k} nkszk

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define pb push_back
#define F first
#define S second
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=LONG_LONG_MAX;
const ll mod=1e9+7;
const int N=1e5+50;
bool vis[N];
vector<int> G[N];
ll fpow(ll x,ll y) {
	ll ans=1;
	while(y) {
		if(y&1) ans*=x,ans%=mod;
		x*=x,x%=mod;
		y>>=1;
	}
	return ans;
}
ll dfs(int u) {
	vis[u]=1;
	ll sz=1;
	for(auto &v:G[u]) {
		if(!vis[v]) 
		sz+=dfs(v);
	}
	return sz;
}
int main() {
	ll n,k;
	scanf("%lld%lld",&n,&k);
	for(int i=1;i<n;i++)  {
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		if(w==0) {
			G[u].push_back(v);
			G[v].push_back(u);	
		}
	}
	ll sum=fpow(n,k);
	for(int i=1;i<=n;i++) {
		if(vis[i]) continue;
		ll sz=dfs(i);
		sum=(sum+mod-fpow(sz,k))%mod;
	}
	printf("%lld\n",sum);
	return 0;
}

D-Steps to One

首先给定一个整数n,然后我每次都会从一个空集里面添加一个元素,这个元素是从1到n等概率随机添加。然后当我添加到这个集合里面所有数的最大公约数为1的时候就停下来,代表这个游戏结束了。然后要求这个集合大小的期望。
那么这里可以考虑的是如果我集合里面的gcd为x的情况的时候还要多少次才能让游戏结束,有 d p [ x ] = 1 + 1 n ∑ i = 1 n d p [ g c d ( i , x ) ] dp[x]=1+\frac{1}{n}∑_{i=1}^{n}dp[gcd(i,x)] dp[x]=1+n1i=1ndp[gcd(i,x)],然后显然 d p [ 1 ] = 0 dp[1]=0 dp[1]=0,并且有最终答案是 a n s = 1 + 1 n ∑ i = 1 n d p [ i ] ans=1+\frac{1}{n}∑_{i=1}^{n}dp[i] ans=1+n1i=1ndp[i]
然而注意到即使这里转移的时候复杂度仍然是 O ( n 2 ) O(n^2) O(n2),所以这里要对这个dp做一些优化的工作。
枚举i先转化为枚举约数:
d p [ x ] = 1 + 1 n ∑ d ∣ x d p [ d ] ∗ ( ∑ i = 1 n g c d ( i , x ) = = d ) dp[x]=1+\frac{1}{n}∑_{d|x}dp[d]*(∑_{i=1}^{n}gcd(i,x)==d) dp[x]=1+n1dxdp[d](i=1ngcd(i,x)==d) (1)
但是这当中包含了d就是x的情形,我们分离出来移项:
d p [ x ] − [ n x ] 1 n d p [ x ] = 1 + 1 n ∑ d ∣ x   d ! = x d p [ d ] ( ∑ i = 1 n g c d ( i , x ) = = d ) dp[x]-[\frac{n}{x}]\frac{1}{n}dp[x]=1+\frac{1}{n}∑_{d|x\space d!=x}dp[d](∑_{i=1}^{n}gcd(i,x)==d) dp[x][xn]n1dp[x]=1+n1dx d!=xdp[d](i=1ngcd(i,x)==d) (2)
因为在 O ( n l o g n ) O(nlogn) O(nlogn)处理出所有的数的约数是方便的,我们只要考虑括号里面的式子,注意到可以把d给约去,括号里面的式子就等价于:
∑ i = 1 [ n d ] g c d ( i , x d ) = = 1 ∑_{i=1}^{[\frac{n}{d}]}gcd(i,\frac{x}{d})==1 i=1[dn]gcd(i,dx)==1 (3)
而这个恰好就满足了莫比乌斯函数的定义:
∑ i = 1 [ n d ] ∑ t ∣ g c d ( i , x d ) μ ( t ) ∑_{i=1}^{[\frac{n}{d}]}∑_{t|gcd(i,\frac{x}{d})}\mu(t) i=1[dn]tgcd(i,dx)μ(t) (4)
再做一些化简就能够得到
∑ t ∣ x d μ ( t ) ∗ [ m d t ] ∑_{t|\frac{x}{d}}\mu(t)*[\frac{m}{dt}] tdxμ(t)[dtm] (5)
回代到原来的式子里面就能求出来了,应该说莫比乌斯反演来做这个还是相对比较方便的。而题解给了一种容斥的做法,代码比较复杂。

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define pb push_back
#define F first
#define S second
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=LONG_LONG_MAX;
const ll mod=1e9+7;
const int N=1e5+50; 
vector<ll> factor[N];
ll mu[N],pri[N],dp[N],m;
int cnt=0;
bool vis[N];
void getmu() {
	mu[1]=1;
	for(int i=2;i<=(int)1e5;i++) {
		if(!vis[i]) pri[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt&&pri[j]*i<=(int)1e5;j++) {
			vis[i*pri[j]]=true;
			if(i%pri[j]==0) {
				mu[i*pri[j]]=0;
				break;
			}
			else mu[i*pri[j]]=-mu[i];
		}
	}
}
ll getinv(ll x) {
	ll y=mod-2,ans=1;
	while(y) {
		if(y&1) ans*=x,ans%=mod;
		x*=x,x%=mod;
		y>>=1;
	}
	return ans;
}
void getfactor() {
	for(ll i=1;i<=(ll)1e5;i++)
		for(ll j=i;j<=(ll)1e5;j+=i)	
			factor[j].push_back(i);
}
ll gsum(ll x,ll d) {
	ll sum=0;
	for(auto t:factor[x/d]) sum+=mu[t]*(m/(d*t)),sum%=mod;
	return sum;
}
int main() {
	getmu();
	getfactor();
	scanf("%lld",&m);
	dp[1]=0;
	ll ans=0;
	for(ll i=2;i<=m;i++) {
		ll rhs=m;
		for(auto fac:factor[i]) {
			if(fac==i) continue;
			ll sum=gsum(i,fac);
			rhs+=dp[fac]*sum;
			rhs%=mod;
		}
		dp[i]=rhs*getinv(m-m/i),dp[i]%=mod;
		ans+=dp[i],ans%=mod;
	} 
	printf("%lld\n",1+ans*getinv(m)%mod);
	return 0;
}

E-Maximize Mex

是说有n个学生和m个社团,每个学生都有一个潜力值和一个id表示它是属于哪一个社团的。然后这里给你q次查询是说每次都会踢掉一个人,然后每个社团这个时候都会从这个社团里面最多选出来一个人,当然没有人就不会选,要求这些人的潜力值的mex,然后这个mex就是转移SG函数的时候用的那个mex,一个不属于当前集合的最小非负元素。
既然要让mex更大,其实一个贪心的想法就是要让我现在的一个集合先尽量地大,如果我能够在我现在的状态下多添加一个元素,那么mex必定是单调不劣。
然后这里又因为每个社团最多选取一个学生,所以就从潜力值往社团连边,求最大匹配。显然删边的操作并不容易做,不如离线之后加边。用一个匈牙利来维护最大匹配就好了。

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define pb push_back
#define F first
#define S second
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=LONG_LONG_MAX;
const int N=5e3+50;
int p[N],id[N],qry[N];
bool vis[N];
bool used[N];
int nxt[N];
vector<int>G[N];
int ans[N]; 
bool Find(int u) {
	if(used[u]) return false;
	used[u]=1;
	for(auto &v:G[u]) {
		if(nxt[v]==-1||Find(nxt[v])) {
			nxt[v]=u;
			return true;
		}
	}
	return false;
}
int main() {
	memset(nxt,-1,sizeof(nxt));
	int n,m,q;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&p[i]);
	for(int i=1;i<=n;i++) scanf("%d",&id[i]);
	scanf("%d",&q);
	for(int i=1;i<=q;i++) {
		scanf("%d",&qry[i]);
		vis[qry[i]]=1;
	}
	for(int i=1;i<=n;i++) {
		if(!vis[i]) {
			G[p[i]].push_back(id[i]);	
		}
	} 
	int u=0;
	for(int i=q;i>=1;i--) {
		memset(used,0,sizeof(used));
		while(Find(u)) {
			++u;
			memset(used,0,sizeof(used));
		} 
		ans[i]=u;
		G[p[qry[i]]].push_back(id[qry[i]]);
	}
	for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值