20201009校测

国庆长假终于结束了

T1

太水了,跳过

T2

description:

给出一张n个点m条边的有向图,问其中有多少边的子集使得不存在任意一个环

data range:

(Nle 17)

solution:

巨佬题解

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=17,mod=1e9+7;
int n,m,pw[N*N];
int g[N+5][1<<N],num[1<<N],lg2[1<<N],dp[1<<N],tmp[1<<N];
inline int ct(int x)
{
	int ans=0;
	for(int i=0;(1<<i)<=x;++i)
		if(x&(1<<i))++ans;
	return ans;
}
inline int sig(int x){return (x&1)?-1:1;}
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
int main()
{
	scanf("%d%d",&n,&m);int M=1<<n;
	for(int i=1;i<=m;++i)
	{
		int u,v;scanf("%d%d",&u,&v);
		for(int j=0;j<M;++j)
			if(j&(1<<(v-1)))++g[u][j];
	}
	pw[0]=1;for(int i=1;i<=m;++i)pw[i]=2ll*pw[i-1]%mod;
	for(int i=0;i<M;++i)num[i]=ct(i);
	for(int i=1;i<=n;++i)lg2[1<<(i-1)]=i;
	dp[0]=1;
	for(int zt=0;zt<M;++zt)
	{
		int bu=(M-1)^zt;
		for(int j=bu;j;j=(j-1)&bu)
		{
			int s=bu^((j-1)&bu),lst=s&(-s);
			tmp[s]=tmp[s^lst]+g[lg2[lst]][zt];
			if(sig(num[s]+1)==1)dp[zt|s]=add(dp[zt|s],1ll*pw[tmp[s]]*dp[zt]%mod);
			else dp[zt|s]=dec(dp[zt|s],1ll*pw[tmp[s]]*dp[zt]%mod);
		}
	}
	cout<<dp[M-1];
	return 0;
}

T3

description:

给出一个N个点M条边的无向带权连通图,同时给出Q个询问,询问在图的最大生成树中有多少点对满足其唯一的简单路径上最小的边权不小于给定值x

data range:

(N,Qle 3*10^5)
(M<=10^6)

solution:

先把最大生成树求出来
考虑对于单个询问x,我们可以砍断所有边权严格小于x的边
然后在剩下的每个联通块中求答案
具体来说,如果一个联通块大小为k,那么它对答案的贡献为((_2^k))
于是我们可以将所有询问离线下来后从大到小排序,同时将树上的边也从大到小排序,每一次按顺序加入边
用带权并查集维护每一个联通块的大小
当合并两个联通块x和y时,减去两个联通块分别的贡献,再加上合在一起后的贡献
最后输出答案即可
p.s.后来经巨佬点拨发现求最大生成树的过程可以和求答案同时进行

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5,M=1e6+5;
int n,m,q,tot,p[M],qu[N];
int fr[M],to[M],w[M],fa[N];
ll anss[N],sz[N];
vector<int>e;
inline int read()
{
	int s=0,w=1; char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
	for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
	return s*w;
}
struct cmp{bool operator()(const int &x,const int &y){return w[x]>w[y];}};
struct cmp1{bool operator()(const int &x,const int &y){return qu[x]>qu[y];}};
int fd(int x){return x==fa[x]?x:fa[x]=fd(fa[x]);}
inline void kruskal()
{
	for(int i=1;i<=n;++i)fa[i]=i;
	for(int i=1;i<=tot;++i)p[i]=i;
	sort(p+1,p+tot+1,cmp());
	for(int i=1;i<=tot;++i)
	{
		int fx=fd(fr[p[i]]),fy=fd(to[p[i]]);
		if(fx==fy)continue;
		fa[fx]=fy;
		e.push_back(p[i]);
		if(e.size()==n-1)break;
	}
}
inline ll calc(int fx){return sz[fx]*(sz[fx]-1ll)/2ll;}
inline void solve()
{
	for(int i=1;i<=q;++i)p[i]=i;
	for(int i=1;i<=n;++i)fa[i]=i,sz[i]=1ll;
	sort(p+1,p+q+1,cmp1());
	int last=0;ll ans=0;
	for(int i=1;i<=q;++i)
	{
		for(int j=last;;++j)
		{
			if(j==e.size()||w[e[j]]<qu[p[i]]){last=j;break;}
			int fx=fd(fr[e[j]]),fy=fd(to[e[j]]);
			ans-=calc(fx)+calc(fy);
			fa[fx]=fy,sz[fy]+=sz[fx];
			ans+=calc(fy);
		}
		anss[p[i]]=ans;
	}
	for(int i=1;i<=q;++i)printf("%lld
",anss[i]);
}
int main()
{
	n=read(),m=read(),q=read();
	for(int i=1;i<=m;++i)
	{
		int u=read(),v=read(),s=read();
		fr[++tot]=u,to[tot]=v,w[tot]=s;
	}
	kruskal();
	for(int i=1;i<=q;++i)qu[i]=read();
	solve();
	return 0;
}

T4:

desription:

求:

[sum_{i=1}^nsum_{j=1}^n1[lcm(i,j)>n] ]

data range:

(Nle 10^{10})

solution:

大力推柿子题
原式(=)

[n^2-sum_{i=1}^nsum_{j=1}^n[lcm(i,j)le n] ]

[=n^2-sum_{i=1}^nsum_{j=1}^n[frac{i*j}{gcd(i,j)}le n] ]

不妨令(gcd(i,j)=d),那么

[=n^2-sum_{i=1}^nsum_{j=1}^n[frac{i}{d}*frac{j}{d}le frac{n}{d}] ]

[=n^2-sum_{d=1}^nsum_{d|i}sum_{d|j}[gcd(frac{i}{d},frac{j}{d})==1][frac{i}{d}*frac{j}{d}le frac{n}{d}] ]

不妨令(x=frac{i}{d},y=frac{j}{d})

[=n^2-sum_{d=1}^nsum_xsum_y[gcd(x,y)==1][xyle frac{n}{d}] ]

大力莫比乌斯反演

[=n^2-sum_{d=1}^nsum_xsum_ysum_{k|gcd(x,y)}mu(k)[xydle n] ]

[=n^2-sum_kmu(k)sum_{d=1}^nsum_{k|x}sum_{k|y}[xydle n] ]

[=n^2-sum_kmu(k)sum_{d=1}^nsum_xsum_y[xydle frac{n}{k^2}] ]

于是此题就是直接枚举k,求有序数对(d,x,y)的组数
考虑到(frac{n}{k^2}ge 1)因此k只有(sqrt{n}种取值)
不妨假设(xle yle d),然后(x,y)直接枚举就可以
(x,y)确定后,(d)只有(frac{n}{k^2xy}-y)种取值
由排列组合知当(x==y)时贡献要(*3)
(x!=y)时贡献要(*6)
时间复杂度大概是(O(n^{frac{2}{3}}))

code;

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
const ll mod=1e9+7;
ll n;
int mu[N];
bool flag[N];
vector<int>p;
inline void pre(int lim)
{
	mu[1]=1;flag[1]=1;
	for(int i=2;i<=lim;++i)
	{
		if(!flag[i])p.push_back(i),mu[i]=-1;
		for(int j=0;j<p.size();++j)
		{
			int num=p[j]*i;
			if(num>lim)break;
			flag[num]=1;
			if(i%p[j]==0)break;
			mu[num]=mu[i]*mu[p[j]];
		}
	}
}
int main()
{
	scanf("%lld",&n);
	int m=sqrt(n+0.5),ans=0;pre(m);
	for(int i=1;i<=m;++i)
	{
		ll nw=n/i/i,sum=0;
		for(ll a=1;a*a*a<=nw;++a)
			for(ll b=a;b*b*a<=nw;++b)
			{
				ll c=nw/a/b-b;
				if(a==b)(sum+=3*c%mod+1)%mod;//这里额外加一是x==y==d的情况,下面类似
				else (sum+=6*c%mod+3)%mod;
			}
		ans=(ans+mu[i]*sum+mod)%mod;
	}
	n%=mod;
	cout<<(n*n%mod-ans+mod)%mod;
	return 0;
}
原文地址:https://www.cnblogs.com/zmyzmy/p/13788651.html