第二周训练赛题解

Problem - 1878A - Codeforces

  这是一道签到题 我们可以发现只要数组a中存在k就行。

A Balanced Problemset?

题目大意是将 x(1<= x <= 1e8) 分成n个整数(1<=n<=x)并使这n个数的gcd最大

我们假设x分成了 a1 a2 a3.....an且 gcd(a1,a2....an)=d;可以发现 d为x的约数。这样我们可以暴力枚举x的约数看看这个约数是否能满足条件。  条件是 x/d>=n 意思是我们至少要满足a1 a2...an都为d的倍数 。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int i,j,n,m,k,l,o,p,t,dl[200001],num[200001],ans;

int main()
{
	//freopen("1.in","r",stdin);
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		ans=1;
		if(m==1) printf("%d\n",n);
		else
		{
		for(i=2;i<=sqrt(n);i++)
			if(n%i==0)
			{
				if(n/i>=m) ans=max(ans,i);
				if(n/(n/i)>=m) ans=max(ans,(n/i));
			}
		printf("%d\n",ans);	
		}
	}
} 

Multiset

这道题是权值线段树的模板题。

#include<bits/stdc++.h>
using namespace std;
int j,n,m,k,l,q,tree[6000001],x,a[1000001],i;
inline int read() {
    int x = 0; bool neg = false; char ch = getchar();
    while(!isdigit(ch)) (ch == '-') && (neg = true), ch = getchar();
    while(isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
    return neg ? -x : x;
}
void insert(int x,int l,int r,int i,int j)
{
	tree[x]+=j;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(i<=mid) insert(x<<1,l,mid,i,j);
	else insert(x<<1|1,mid+1,r,i,j);
}
void dele(int x,int l,int r,int i,int j)
{
	if(l==r)
	{
		if(j==1)tree[x]--;
		else printf("%d\n",l);
		return;
	}
	int mid=(l+r)>>1;
	if(tree[x<<1]>=i) dele(x<<1,l,mid,i,j);
	else dele(x<<1|1,mid+1,r,i-tree[x<<1],j);
	tree[x]--;
}
int main()
{
	//freopen("1.in","r",stdin);
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
	{
		x=read(); 
		++a[x];
	}
	for(i=1;i<=n;i++)
		if(a[i]>0) insert(1,1,n,i,a[i]);
	for(i=1;i<=m;i++)
	{
		x=read();
		if(x>0) insert(1,1,n,x,1);
		else dele(1,1,n,-x,1);
	}
	if(tree[1]==0) printf("0\n");
	else dele(1,1,n,1,2);
}

同余方程

这道题要求ax\equiv 1(mod b)的最小正整数解。 相当于求ax+by=1。所以我们可以用exgcd来做这道题。 但我们发现exgcd是求 ax+by=gcd(a,b)的解。这和我们要的有点出入。不过题目已经给出保证有解,即ax+by=1有解。  一个方程 ax+by=m 有解的必要条件是  m mod gcd(a,b)=0; 所以可以推出 gcd(a,b)=1; 接下来只要求ax+by=1就行。  最后注意题目要求正整数解。

E 排列计数

对于这道题我们相当于要求 \binom{n}{m}*f\left ( n-m \right ) 其中f\left ( x \right )表示1~x错排的方案数(即 a[i]!=i)

我们考虑如何推导f[x]   假设当前位置x上的数x放在位置y上(1<=y<x)如果数y放在位置x上那就相当与x,y互换位置 剩下x-2个数错排,如果数y不放在位置x上那此时相当于有x-1个数错排因为y不能放在位置x上,所以f[x]=(x-1)*(f[x-1]+f[x-2]);这样我们就可以线性求f[n]。 同时我们在求\binom{n}{m}时要注意mod 1e9+7这需要用乘法逆元来求。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
long long i,j,n,m,k,l,mod,f[1000001],qz[1000001],ans,res,res1,t;
long long ksm(long long x,long long y)
{
	long long cnt=1;
	while(y>0)
	{
		if(y%2==1) cnt*=x,cnt%=mod;
		x*=x;x%=mod;
		y/=2;
	}
	return cnt;
}
int main()
{
	f[0]=1;f[2]=1;f[1]=0;mod=1000000007;
	qz[0]=1;qz[1]=1;qz[2]=2;
	for(i=3;i<=1000000;i++) 
		f[i]=(i-1)*(f[i-1]+f[i-2])%mod,
		qz[i]=qz[i-1]*i%mod;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		ans=f[n-m];
		res=qz[m]*qz[n-m]%mod;
		res=ksm(res,mod-2);
		ans=(ans*qz[n]%mod)*res;ans%=mod;
		printf("%lld\n",ans);
	}
} 

F Partitioning the Array

题目大意: 给定一个序列a1 a2 a3....an 对于一个正整数k(n%k==0),将数组a分成k个子数组{a1,a2..ak}{ak+1.....a2k}..{an-k+1...an},如果存在一个正整数m使得他将数组中的每个元素除以 m后的余数都替换为该元素,若所有子数组都相同则答案加一。求1<=k<=n有多少个满足的k。

根据题意我们可以得到若干个式子a1\equiv x(mod m)   a\left ( k+1 \right )\equiv x(mod m).......a2\equiv x1(mod m)    a\left ( k+2 \right )\equiv x1(mod m)......  

对于a1=x+k1*m,ak+1=x+k2*m  所以a1-ak+1=(k1-k2)*m; 所以 m为a1-ak+1的约数。那么我们相当于求  abs(ai-ai+k) (1<=i<=n-k)的公共约数。判断这公共约数是否存在就行用gcd来做。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int i,j,n,m,k,l,o,p,x,y,dl[300001],t,a[300001],ans;
int gcd(int a,int b)
{
	if(b==0) return a;
	else return gcd(b,a%b);
}
int main()
{
	//freopen("1.in","r",stdin);
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(i=1;i<=n;i++) scanf("%d",&a[i]);
		dl[0]=0;
		for(i=1;i<=sqrt(n);i++)
			if(n%i==0)
			{
				dl[++dl[0]]=i;
				if(i*i!=n) dl[++dl[0]]=n/i;
			}
		ans=0;
		for(i=1;i<=dl[0];i++)
		{
			x=0;
			for(j=1;j<=n-dl[i];j++)
			{
				if(x==0) x=abs(a[j]-a[j+dl[i]]);
				else
				{
					x=gcd(x,abs(a[j]-a[j+dl[i]]));
				}
				if(x==1) break;
			}
			if(x!=1) ans++;
		}
		printf("%d\n",ans);
	}
}

Small GCD​​​​

题目大意: 求\sum_{i=1}^{n}(n-i)\sum_{j=1}^{j=i-1}gcd(a[i],a[j])  (这里a已经从小到大排好序)

这道题我们可以用欧拉反演来做 即运用定理 n= \sum_{d|n} \varphi (d)。(d|n表示d为n的约数[...]表示里面的条件成立则为1反之为0)

对于两个数a[i],a[j]我们设res=gcd(a[i],a[j]),那么 res=\sum_{d|res} \varphi (d)

\sum_{i=1}^{n}(n-i)\sum_{j=1}^{i-1}\sum_{d|gcd(a[i],a[j])}\varphi (d)

\sum_{i=1}^{n}(n-i)\sum_{j=1}^{i-1}\sum_{d=1}^{a[i]}\varphi (d)[d|a[i]][d|a[j]]]

\sum_{i=1}^{n}(n-i)\sum_{d=1}^{a[i]}[d|a[i]]\varphi (d)\sum_{j=1}^{i-1}[d|a[j]]]

其中\sum_{j=1}^{i-1}[d|a[j]]]我们可以用个桶来优化 设cnt[x]表示从1~i-1中满足a[i]%x==0 的个数。

\sum_{j=1}^{i-1}[d|a[j]]]=cnt[d] 

这样我们就可以先用vector预处理存储每个数的约数然后线性做了

\sum_{i=1}^{n}(n-i)\sum_{d|a[i]}\varphi (d)cnt[d]

对于\varphi (i)用欧拉筛预处理就行

换句话说就是对于a[i],a[j]他们公共约数乘对应欧拉函数得到的值就是他们两的gcd。

因为他们的公共约数就是gcd(a[i],a[j])的所有约数。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
long long i,j,n,m,k,l,phi[200001],prime[200001],cnt[200001],t,a[200001],x;
long long ans,mod,sum;
bool bz[200001];
void getphi()
{
	phi[1]=1;
	for(int i=2;i<=100000;i++)
	{
		if(bz[i]==0)
		{
			bz[i]=1;
			prime[++prime[0]]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=prime[0];j++)
		{
			if(prime[j]*i>100000) break;
			bz[i*prime[j]]=1;
			if(i%prime[j]==0) phi[i*prime[j]]=phi[i]*prime[j];
			else phi[i*prime[j]]=phi[i]*(prime[j]-1);
		}
	}
}
int main()
{
	//freopen("1.in","r",stdin);
	scanf("%d",&t);
	getphi();
	while(t>0)
	{
		t--;
		scanf("%d",&n);
		for(i=1;i<=n;i++) scanf("%d",&a[i]);
		memset(cnt,0,sizeof cnt);
		sort(a+1,a+1+n);
		vector<vector<int> > q;
		for(i=1;i<=n;i++)
		{
			vector<int> temp;
			x=a[i];
			for(j=1;j<=sqrt(x);j++)
				if(x%j==0)
				{
					temp.push_back(j);
					if(x/j!=j) temp.push_back(x/j);
				}
			q.push_back(temp);
		}
		ans=0;
		for(i=0;i<n;i++)
		{
			sum=0;
			for(j=0;j<q[i].size();j++)
			{
				sum+=(long long)cnt[q[i][j]]*phi[q[i][j]];
				cnt[q[i][j]]++;
			}
			ans+=sum*(n-i-1);
		}
		printf("%lld\n",ans);
	}
} 

Dominant Indices

题目大意:给一颗无向树,根为1。对于每个节点,求其子树中,哪个距离下的节点数量最多。数量相同时,取较小的那个距离。

我们设f[i][x]表示对于当前点i距离为x的点有多少个,我们首先可以用O(n^2)的暴力来做,但这样会超时。但我们可以发现f[i][x]和深度有关,因此考虑长链剖分,我们考虑每次继承重儿子的dp数组和答案然后暴力更新轻儿子的贡献,我们每次暴力更新轻儿子时,轻儿子f[][x]的数组长度就是该链的长度,则时间复杂度是所有长链的长度之和,即O(n)。对于空间而言,可以通过维护一根指针来合理分配空间,使得所用空间也是线性的。

(我不太擅长处理指针所以用树上启发式合并直接压缩dp数组为一维,更新轻儿子时就更暴力罢了 仅供参考)


仅供参考
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int i,j,n,m,k,l,h[1000001],cnt,x,y,deep[1000001],md[1000001],in[1000001],out[1000001],sum,fa[1000001],son[1000001],f[1000001],wz[1000001],ans[1000001],len;
struct node{
	int to,ne;
}e[4000001];
void add(int u,int v)
{
	e[++cnt].ne=h[u];
	e[cnt].to=v;
	h[u]=cnt;
}
void dfs(int x,int y)
{
	deep[x]=y;
	in[x]=++sum;
	wz[sum]=x;
	for(int i=h[x];i!=0;i=e[i].ne)
		if(deep[e[i].to]==0)
		{
			fa[e[i].to]=x;
			dfs(e[i].to,y+1);
			if(md[e[i].to]>md[x])
			{
				md[x]=max(md[x],md[e[i].to]);
				son[x]=e[i].to;	
			}
		}
	md[x]++;
	out[x]=sum;
}
void dfs1(int x,int y)
{
	for(int i=h[x];i!=0;i=e[i].ne)
	{
		if(e[i].to==son[x]||e[i].to==fa[x]) continue;
		dfs1(e[i].to,0); 
	}
	if(son[x]!=0) dfs1(son[x],1);
	f[deep[x]]++;
	if(f[deep[x]]>f[len]||(f[deep[x]]==f[len]&&len>deep[x])) len=deep[x];
	for(int i=h[x];i!=0;i=e[i].ne)
	{
		if(e[i].to==son[x]||e[i].to==fa[x]) continue;
		for(int j=in[e[i].to];j<=out[e[i].to];j++)
		{
			f[deep[wz[j]]]++;
			if(f[deep[wz[j]]]>f[len])
			{
				len=deep[wz[j]];
			}
			else if(f[deep[wz[j]]]==f[len]&&len>deep[wz[j]])
			{
				len=deep[wz[j]];
			}
		}
	}
	ans[x]=len-deep[x];
	if(y==0)
	{
		for(int i=in[x];i<=out[x];i++)
		{
			f[deep[wz[i]]]--;
			if(f[deep[wz[j]]]>f[len])
			{
				len=deep[wz[j]];
			}
			else if(f[deep[wz[j]]]==f[len]&&len>deep[wz[j]])
			{
				len=deep[wz[j]];
			}
		}
	}
}
int main()
{
	//freopen("1.in","r",stdin);
	scanf("%d",&n);
	for(i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(1,1);
	dfs1(1,1);
	for(i=1;i<=n;i++) printf("%d\n",ans[i]);
}

ak爽!

  • 50
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值