数学相关训练题解

训练链接

CF645F

题目链接

点击打开链接

题目解法

一眼莫反
推式子的步骤就不写了,反正也比较套路
这里只给出最后的式子为: A n s = ∑ i = 1 V ϕ ( i ) ∗ ( ∑ i ∣ j c n t j k ) Ans=\sum\limits_{i=1}^{V}\phi(i)*\binom{\sum\limits_{i\mid j}cnt_j}{k} Ans=i=1Vϕ(i)(kijcntj),其中 c n t j cnt_j cntj 为数字 j j j 的出现次数
修改操作的话,根据式子,不难得出只要修改 x x x 的因子即可
时间复杂度 O ( q n ) O(q\sqrt n) O(qn )

#include <bits/stdc++.h>
using namespace std;
const int V=1000000,N=200100,P=1e9+7;
int n,k,q,a[N];
int inv[V+100],fac[V+100];
int mu[V+100],pr[V+100],tot,v[V+100];
int smu[V+100],mi[V+100],sum[V+100],cnt[V+100];
vector<int> fact[V+100];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
int qmi(int a,int b){
	int res=1;
	for(;b;b>>=1){
		if(b&1) res=1ll*res*a%P;
		a=1ll*a*a%P;
	}
	return res;
}
int C(int a,int b){
    if(a<b) return 0;
    return 1ll*fac[a]*inv[b]%P*inv[a-b]%P;
}
int main(){
	mu[1]=1;
	for(int i=2;i<=V;i++){
		if(!v[i]) v[i]=i,pr[++tot]=i,mu[i]=-1;
		for(int j=1;j<=tot&&pr[j]<=V/i;j++){
			v[i*pr[j]]=pr[j];
			if(v[i]==pr[j]) break;
			mu[i*pr[j]]=-mu[i];
		}
	}
    fac[0]=1;
    for(int i=1;i<=V;i++) fac[i]=1ll*fac[i-1]*i%P;
    inv[V]=qmi(fac[V],P-2);
    for(int i=V-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%P;
	n=read(),k=read(),q=read();
	for(int i=1;i<=n+q;i++) mi[i]=C(i,k);
	for(int i=1;i<=n;i++) a[i]=read(),cnt[a[i]]++;
	for(int i=1;i<=V;i++) for(int j=i;j<=V;j+=i) sum[i]+=cnt[j],smu[j]=((smu[j]+mu[i]*(j/i))%P+P)%P,fact[j].push_back(i);
	int ans=0;
	for(int i=1;i<=V;i++) ans=((ans+1ll*smu[i]*mi[sum[i]])%P+P)%P;
	for(int i=1;i<=q;i++){
		int x=read();
		for(int d:fact[x]){
			ans=((ans-1ll*smu[d]*mi[sum[d]])%P+P)%P;
			sum[d]++;
			ans=((ans+1ll*smu[d]*mi[sum[d]])%P+P)%P;
		}
		printf("%d\n",ans);
	}
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF1034C

题目链接

点击打开链接

题目解法

翻译有误!!!
显然我们的问题是要求出这棵树分成 k k k 个部分是否可行,如果可行,令 f k = 1 f_k=1 fk=1
那么只要做一遍 d p dp dp,即可,式子为 f i = ∑ i ∣ j f j f_i=\sum\limits_{i|j}f_j fi=ijfj,注意 i , j i,j i,j 一定要是可行的

考虑如何求 f k f_k fk
我们先求一遍子树和,令权值总和为 S S S,那么需要割的边即为满足 S k ∣ s i \frac{S}{k}|s_i kSsi 的子树和
我们需要判断需要割的边的个数是否 = k =k =k
考虑如何快速求解上面的问题
s i = a S k s_i=a\frac{S}{k} si=akS
k = a S s i k=a\frac{S}{s_i} k=asiS
所以说如果 k k k S ( S , s i ) \frac{S}{(S,s_i)} (S,si)S 的倍数,那么 i i i 就会对 k k k 产生贡献,直接扫一遍 S ( S , s i ) \frac{S}{(S,s_i)} (S,si)S n n n 以内的倍数即可
时间复杂度 O ( n ln ⁡ n ) O(n\ln n) O(nlnn)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1000100,P=1e9+7;
int n,v[N],fa[N],s[N],f[N];
int cnt[N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++) v[i]=read();
	for(int i=2;i<=n;i++) fa[i]=read();
	for(int i=n;i>=1;i--) s[i]+=v[i],s[fa[i]]+=s[i];
	int tot=s[1];
    for(int i=1;i<=n;i++){
        int g=tot/__gcd(tot,s[i]);
        for(int j=g;j<=n;j+=g) cnt[j]++;
    }
	for(int i=1;i<=n;i++) if(i==cnt[i]) f[i]=1;
	for(int i=n;i>=2;i--){
		if(!f[i]) continue;
		for(int j=i+i;j<=n;j+=i) f[i]=(f[i]+f[j])%P;
	}
	int ans=1;
	for(int i=2;i<=n;i++) ans=(ans+f[i])%P;
	printf("%lld\n",ans);
	fprintf(stderr,"%d ms\n",int64_t(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF1603D

题目链接

点击打开链接

题目解法

首先可以发现当 k ≤ l o g 2 n k\le log_2^n klog2n 时,答案一定为 n n n,因为可以按照二进制的位数分组,然后每组的 c c c 都为 1 1 1
现在只需要考虑 k ≤ 16 k\le 16 k16 的情况
我们令 f i , j f_{i,j} fi,j 为到 j j j 分成 i i i 段的最小值
看题解发现 f i , j f_{i,j} fi,j 对于每一层的 i i i 满足决策单调性,所以可以直接分治解决
现在的问题是快速算出 c ( l , r ) c(l,r) c(l,r)
推式子 . . . ... ...
自己在草稿纸上推可以得出 c ( l , r ) = ∑ i = l r ∑ j = 1 ⌊ r i ⌋ ϕ ( j ) c(l,r)=\sum\limits_{i=l}^r\sum\limits_{j=1}^{\lfloor\frac{r}{i}\rfloor}\phi(j) c(l,r)=i=lrj=1irϕ(j)
发现这很分块
具体来说的话可以算出 ϕ \phi ϕ 的前缀和,然后分类讨论,和累加
感觉是道挺妙的题,但不太好讲,自己去看题解吧,讲的挺详细的
时间复杂度 O ( 16 n l o g n ) O(16nlogn) O(16nlogn)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int K=20,MAXB=330,N=100100,inf=1e18;
int cur,phi[N],s[N],f[K][N];
int B=320,g[N][MAXB],h[N][MAXB];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
int calc_bl(int l,int r){
    int res=0;
    for(int i=l;i<=r;i++) res+=s[r/i];
    return res;
}
int calc(int l,int r){
	if(r<=B) return g[r][r]-g[r][l-1];
	else{
		int res=0;
		if(l<=B) res=g[r][B]-g[r][l-1];
		l=max(l,B+1);
		int L=l,R=r/(r/l);
		res+=h[r][r/l-1]+(R-L+1)*s[r/l];
		return res;
	}
}
void solve(int l,int r,int ql,int qr){
	if(l>r) return;
	int mid=(l+r)>>1;
	int res=inf,pos;
	for(int i=ql;i<=min(mid-1,qr);i++)
        if(f[cur-1][i]!=inf&&f[cur-1][i]+calc(i+1,mid)<res)
			res=f[cur-1][i]+calc(i+1,mid),pos=i;
	f[cur][mid]=res;
	solve(l,mid-1,ql,pos),solve(mid+1,r,pos,qr);
}
void work(){
	int n=read(),k=read();
	if(k>16||n<(1<<k)) printf("%d\n",n);
	else printf("%lld\n",f[k][n]);
}
signed main(){
    for(int i=1;i<N;i++){
        phi[i]=i;int t=i;
        for(int j=2;j*j<=t;j++) if(t%j==0){
            while(t%j==0) t/=j;
            phi[i]=phi[i]/j*(j-1);
        }
        if(t>1) phi[i]=phi[i]/t*(t-1);
    }
    for(int i=1;i<N;i++) s[i]=s[i-1]+phi[i];
	for(int i=1;i<N;i++) for(int j=1;j<=B;j++) g[i][j]=g[i][j-1]+s[i/j];
	for(int i=1;i<N;i++) for(int j=1;j<=B;j++){
		int r=i/j,l=i/(j+1)+1;
		h[i][j]+=(r-l+1)*s[j];
	}
	for(int i=1;i<N;i++) for(int j=1;j<=B;j++) h[i][j]+=h[i][j-1];
    memset(f,0x3f,sizeof(f));f[0][0]=0;
    for(int i=1;i<=16;i++) cur=i,solve(1,N-1,0,N-1);
	int T=read();
	while(T--) work();
	fprintf(stderr,"%d ms\n",int64_t(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF1770F

题目链接

点击打开链接
感觉是一道很妙的题!!!反正我是完全没思路

考虑题目中的和限制与或限制有很好的对称性,那么不难发现 a 1 a_1 a1 的位置可以换到其他任何位置上,那么当 n n n 为偶数的时候答案即为 0 0 0,当 n n n 为奇数的时候答案为所有情况下 a 1 a_1 a1 的异或和(因为异或可以抵消,且 a 1 a_1 a1 可以换到任何一个位置,那么当 n n n 为奇数时一定只会剩下一个位置未被抵消)

首先一个朴素的想法是对 a 1 a_1 a1 分位计算,那么现在我们需要求当 2 i ⊆ a 1 2^i\subseteq a_1 2ia1 的满足条件的方案数的奇偶性
对于 或 操作,具有比较好的性质(只有可能从 0 0 0 变为 1 1 1),所以我们考虑容斥求解
f y ′ ( y ′ ⊆ y ) f_{y'}(y'\subseteq y) fy(yy) ∀ a i ⊆ y ′ \forall a_i\subseteq y' aiy ∑ a i = x \sum a_i=x ai=x 的方案数
所以方案数即为 ∑ y ′ ⊆ y ( − 1 ) ∣ y ∣ − ∣ y ′ ∣ f y ′ \sum\limits_{y'\subseteq y}(-1)^{|y|-|y'|}f_{y'} yy(1)yyfy
因为我们只关心奇偶性,所以方案数可化简为 ∑ y ′ ⊆ y f y ′ \sum\limits_{y'\subseteq y}f_{y'} yyfy
需要满足 a i ⊆ y ′ a_i\subseteq y' aiy 的条件不好办,所以我们考虑组合数逆用,可得 a i ⊆ y ′    ⟺    ( y ′ a i ) ( m o d    2 ) a_i\subseteq y'\iff \binom{y'}{a_i}(\mod 2) aiy(aiy)(mod2)
f y ′ f_{y'} fy 拆分开来即为 ∑ ∑ a i = x − 2 i ( y − 2 i a 1 ) ( y a 2 ) . . . ( y a n ) \sum\limits_{\sum a_i=x-2^i}\binom{y-2^i}{a_1}\binom{y}{a_2}...\binom{y}{a_n} ai=x2i(a1y2i)(a2y)...(any)
用范德蒙德卷积化简可得
( n y − 2 i x − 2 i ) ( m o d    2 ) = [ ( x − 2 i ) ⊆ ( n y − 2 i ) ] \binom{ny-2^i}{x-2^i}(\mod 2)\\=[(x-2^i)\subseteq(ny-2^i)] (x2iny2i)(mod2)=[(x2i)(ny2i)]
直接计算即可
时间复杂度 O ( y l o g y ) O(ylogy) O(ylogy)

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,x,y;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
signed main(){
	n=read(),x=read(),y=read();
	if(n&1){
		int ans=0;
		for(int i=0;i<20;i++)
			if(y>>i&1)
				for(int yy=y;;yy=(yy-1)&y){
					if(yy>>i&1){
						int p=n*yy-(1<<i),q=x-(1<<i);
						if(p>=0&&q>=0&&(p&q)==q) ans^=1<<i;
					}
					if(!yy) break;
				}
		printf("%lld\n",ans);
	}
	else puts("0");
	fprintf(stderr,"%d ms\n",int64_t(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF1656H

题目链接

点击打开链接

题目解法

还是一道很妙的题!!!可能是我太菜了
我们一开始把所有数都选入集合中,然后考虑删数使两个集合的 l c m lcm lcm 相等
这个直接暴力查找删除即可,即找到 a i ∤ ∏ b j a_i\nmid \prod b_j aibj b j b_j bj 是仍在答案集合中的
一个判断 a i ∤ ∏ b j a_i\nmid \prod b_j aibj 的直观想法是质因数分解,然后看次幂,但是 a i , b i ≤ 4 ∗ 1 0 36 a_i,b_i\le 4*10^{36} ai,bi41036,明显不够做任何的质因数分解算法( p o l l a r d − r h o pollard-rho pollardrho 也跑不了)
所以考虑换一个角度思考问题
这里有一个很妙的判断方法是 gcd ⁡ ( a gcd ⁡ ( a , b 1 ) , a gcd ⁡ ( a , b 2 ) , . . . , a gcd ⁡ ( a , b k ) ) \gcd(\frac{a}{\gcd(a,b_1)},\frac{a}{\gcd(a,b_2)},...,\frac{a}{\gcd(a,b_k)}) gcd(gcd(a,b1)a,gcd(a,b2)a,...,gcd(a,bk)a) k k k 为仍在答案集合中的数的个数,上面这个式子分类讨论然后自己分析一下不难理解
因为 gcd ⁡ \gcd gcd 是可合并的,且需要支持修改,所以考虑用线段树维护
考虑每次删除数只会修改另一个序列中的当前位置,所以线段树只会修改 O ( n 2 ) O(n^2) O(n2) 次,删除数的话直接把 gcd ⁡ \gcd gcd 赋为 0 0 0 即可,因为 gcd ⁡ ( x , 0 ) = x \gcd(x,0)=x gcd(x,0)=x
时间复杂度为 O ( n 2 l o g n l o g V ) O(n^2lognlogV) O(n2lognlogV)

#include <bits/stdc++.h>
using namespace std;
const int N=1100;
typedef __int128_t LL;
LL a[N],b[N];
set<int> A,B;
inline LL read(){
	LL FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=FF*10+ch-48;
	return FF*RR;
}
LL gcd(LL x,LL y){ return !y?x:gcd(y,x%y);}
void print(LL x){
	if(!x) return;
	print(x/10);
	putchar(x%10+48);
}
struct SegmentTree{
	LL seg[N<<2];
	void modify(int l,int r,int x,int pos,LL v){
		if(l==r){ seg[x]=v;return;}
		int mid=(l+r)>>1;
		if(mid>=pos) modify(l,mid,x<<1,pos,v);
		else modify(mid+1,r,x<<1^1,pos,v);
		seg[x]=gcd(seg[x<<1],seg[x<<1^1]);
	}
	LL ask(){ return seg[1];}
}sg[N<<1];
void work(){
	int n,m;scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=m;i++) b[i]=read();
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) sg[i].modify(1,m,1,j,a[i]/gcd(a[i],b[j]));
	for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) sg[i+n].modify(1,n,1,j,b[i]/gcd(b[i],a[j]));
	int lenA=n,lenB=m;
    A.clear(),B.clear();
	for(int i=1;i<=n;i++) A.insert(i);
	for(int i=1;i<=m;i++) B.insert(i);
	while(!A.empty()&&!B.empty()){
		set<int> del;del.clear();
		for(auto it:A)
			if(sg[it].ask()>1){
				del.insert(it);lenA--;
				for(auto itt:B) sg[n+itt].modify(1,n,1,it,0);
			}
		for(auto it:del) A.erase(it);
		bool flg=(del.size()>0);
		del.clear();
		for(auto it:B)
			if(sg[n+it].ask()>1){
				del.insert(it);lenB--;
				for(auto itt:A) sg[itt].modify(1,m,1,it,0);
			} 
		for(auto it:del) B.erase(it);
		flg|=(del.size()>0);
		del.clear();
		if(!flg) break;
	}
	if(A.empty()||B.empty()) puts("NO");
	else{
		puts("YES");
		print(lenA);putchar(' ');print(lenB);putchar('\n');
		for(auto it:A) print(a[it]),putchar(' ');puts("");
		for(auto it:B) print(b[it]),putchar(' ');puts("");
	}
}
int main(){
	int T;scanf("%d",&T);
	while(T--) work();
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

CF986F

题目链接

点击打开链接

题目解法

挺有意思的一道题(感觉十分多合一)
首先考虑到只有 k k k 的质因子是有用的,因为合数因子一定可以表示成多个质因子相加
那么现在的问题变成了询问 n n n 是否是 p 1 , . . . , p m p_1,...,p_m p1,...,pm 的线性组合,且每一项的系数需要非负
线性组合且非负,我们第一个想到的问题应该是 P3403 跳楼机,所以这道题我们可以套用那道题的做法,用同余最短路做
但我们发现 p i p_i pi 很大,最小的 p i p_i pi 甚至都不能做最短路,所以我们考虑分类讨论:

  1. m = 1 m=1 m=1,直接判断 n n n 是否是 p p p 的倍数即可
  2. m = 2 m=2 m=2,问题是需要判断是否存在 d 1 x + d 2 y = n d_1x+d_2y=n d1x+d2y=n 的非负整数解
    如果 ( d 1 , d 2 ) ∤ n (d_1,d_2)\nmid n (d1,d2)n,则无解,考虑 ( d 1 , d 2 ) ∣ n (d_1,d_2)\mid n (d1,d2)n 的情况
    问题等价于 d 2 y ≡ n ( m o d    d 1 ) d_2y\equiv n (\mod d_1) d2yn(modd1) 的最小整数解 y y y 是否满足 y d 2 ≤ n yd_2\le n yd2n
    y y y 的最小整数解显然为 n × d 2 − 1 n\times d_2^{-1} n×d21
    直接求逆元即可
  3. m ≥ 3 m\ge 3 m3,这类情况我们直接套用同余最短路的做法,因为 ∏ p i ≤ 1 0 15 \prod p_i\le 10^{15} pi1015,且 m ≥ 3 m\ge 3 m3,所以最小的 p i ≤ 1 0 5 p_i\le 10^5 pi105,可以直接做最短路

我是直接暴力质因数分解的(当然要先筛出质数),也可以过
因为是多合一做法,所以时间复杂度需要一个一个单独算,我就不算了,反正可以过

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int LIM=31700000;
const int MAXT=10100;
const LL inf=2e18;
struct QUERY{ LL n,k;int id;}q[MAXT];
LL n,k;
int pr[LIM+5],cnt;
bool del[LIM+5],ans[MAXT];
vector<LL> v;
void sieve(){
	for(int i=2;i<=LIM;i++){
		if(!del[i]) pr[++cnt]=i;
		for(int j=1;j<=cnt&&i<=LIM/pr[j];j++){
			del[pr[j]*i]=1;
			if(i%pr[j]==0) break;
		}
	}
}
void sep(LL x){
	for(int i=1;i<=cnt&&1ll*pr[i]*pr[i]<=x;i++)
		if(x%pr[i]==0){
			v.push_back(pr[i]);
			while(x%pr[i]==0) x/=pr[i];
		}
	if(x>1) v.push_back(x);
}
LL Gcd(LL x,LL y){
	if(!y) return x;
	return Gcd(y,x%y);
}
const int MNPR=100100;
typedef pair<LL,int> pli;
priority_queue<pli,vector<pli>,greater<pli> > que;
int h[MNPR],ne[MNPR*20],e[MNPR*20],idx;
LL dis[MNPR],w[MNPR*20];
bool vis[MNPR];
void add(int x,int y,LL z){ e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;}
void dij(){
	dis[0]=0,que.push(make_pair(0,0));
	while(!que.empty()){
		int u=que.top().second;que.pop();
		for(int i=h[u];~i;i=ne[i]){
			int v=e[i];
			if(dis[u]+w[i]<dis[v]) dis[v]=dis[u]+w[i],que.push(make_pair(dis[v],v));
		}
	}
}
int qmi(LL a,int b,int P){
    a%=P;
    int res=1;
    for(;b;b>>=1){
        if(b&1) res=1ll*res*a%P;
        a=a*a%P;
    }
    return res;
}
int main(){
	sieve();
	int T;scanf("%d",&T);
	for(int i=1;i<=T;i++){
		LL n,k;scanf("%lld%lld",&n,&k);
		q[i]={n,k,i};
	}
	sort(q+1,q+T+1,[](const QUERY &x,const QUERY &y){ return x.k<y.k;});
	for(int i=1,j;i<=T;i=j+1){
		j=i;
		while(q[j+1].k==q[i].k) j++;
		if(q[i].k==1) continue;
		v.clear();sep(q[i].k);
		if(v.size()==1){
			LL d=v[0];
			for(int x=i;x<=j;x++) if(q[x].n%d==0) ans[q[x].id]=1;
			continue;
		}
		if(v.size()==2){
			LL d1=v[0],d2=v[1];
			for(int x=i;x<=j;x++){
				LL t=q[x].n%d1*qmi(d2,d1-2,d1)%d1;
				if(t*d2<=q[x].n) ans[q[x].id]=1;
			}
			continue;
		}
		idx=0;
		for(int x=0;x<v[0];x++) h[x]=-1,dis[x]=inf;
		for(int x=0;x<v[0];x++) for(int t=1;t<v.size();t++) add(x,(x+v[t])%v[0],v[t]);
		dij();
		for(int x=i;x<=j;x++) if(dis[q[x].n%v[0]]<=q[x].n) ans[q[x].id]=1;
	}
	for(int i=1;i<=T;i++) puts(ans[i]?"YES":"NO");
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值