训练链接
点击打开链接
一眼莫反
推式子的步骤就不写了,反正也比较套路
这里只给出最后的式子为: 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=1∑Vϕ(i)∗(ki∣j∑cntj),其中 c n t j cnt_j cntj 为数字 j j j 的出现次数
修改操作的话,根据式子,不难得出只要修改 x x x 的因子即可
时间复杂度 O ( q n ) O(q\sqrt n) O(qn)
#include
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;
}
点击打开链接
翻译有误!!!
显然我们的问题是要求出这棵树分成 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=i∣j∑fj,注意 i , j i,j i,j 一定要是可行的
考虑如何求 f k f_k fk
我们先求一遍子树和,令权值总和为 S S S,那么需要割的边即为满足 S k ∣ s i \frac{S}{k}|s_i kS∣si 的子树和
我们需要判断需要割的边的个数是否 = 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
#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;
}
点击打开链接
首先可以发现当 k ≤ l o g 2 n k\le log_2^n k≤log2n 时,答案一定为 n n n,因为可以按照二进制的位数分组,然后每组的 c c c 都为 1 1 1
现在只需要考虑 k ≤ 16 k\le 16 k≤16 的情况
我们令 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=l∑rj=1∑⌊ir⌋ϕ(j)
发现这很分块
具体来说的话可以算出 ϕ \phi ϕ 的前缀和,然后分类讨论,和累加
感觉是道挺妙的题,但不太好讲,自己去看题解吧,讲的挺详细的
时间复杂度 O ( 16 n l o g n ) O(16nlogn) O(16nlogn)
#include
#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;
}
点击打开链接
感觉是一道很妙的题!!!反正我是完全没思路
考虑题目中的和限制与或限制有很好的对称性,那么不难发现 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 2i⊆a1 的满足条件的方案数的奇偶性
对于 或 操作,具有比较好的性质(只有可能从 0 0 0 变为 1 1 1),所以我们考虑容斥求解
令 f y ′ ( y ′ ⊆ y ) f_{y'}(y'\subseteq y) fy′(y′⊆y) 为 ∀ a i ⊆ y ′ \forall a_i\subseteq y' ∀ai⊆y′ 且 ∑ 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'} y′⊆y∑(−1)∣y∣−∣y′∣fy′
因为我们只关心奇偶性,所以方案数可化简为 ∑ y ′ ⊆ y f y ′ \sum\limits_{y'\subseteq y}f_{y'} y′⊆y∑fy′
需要满足 a i ⊆ y ′ a_i\subseteq y' ai⊆y′ 的条件不好办,所以我们考虑组合数逆用,可得 a i ⊆ y ′ ⟺ ( y ′ a i ) ( m o d 2 ) a_i\subseteq y'\iff \binom{y'}{a_i}(\mod 2) ai⊆y′⟺(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=x−2i∑(a1y−2i)(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)] (x−2iny−2i)(mod2)=[(x−2i)⊆(ny−2i)]
直接计算即可
时间复杂度 O ( y l o g y ) O(ylogy) O(ylogy)
#include
#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;
}
点击打开链接
还是一道很妙的题!!!可能是我太菜了
我们一开始把所有数都选入集合中,然后考虑删数使两个集合的 l c m lcm lcm 相等
这个直接暴力查找删除即可,即找到 a i ∤ ∏ b j a_i\nmid \prod b_j ai∤∏bj, b j b_j bj 是仍在答案集合中的
一个判断 a i ∤ ∏ b j a_i\nmid \prod b_j ai∤∏bj 的直观想法是质因数分解,然后看次幂,但是 a i , b i ≤ 4 ∗ 1 0 36 a_i,b_i\le 4*10^{36} ai,bi≤4∗1036,明显不够做任何的质因数分解算法( p o l l a r d − r h o pollard-rho pollard−rho 也跑不了)
所以考虑换一个角度思考问题
这里有一个很妙的判断方法是 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
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;
}
点击打开链接
挺有意思的一道题(感觉十分多合一)
首先考虑到只有 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 甚至都不能做最短路,所以我们考虑分类讨论:
我是直接暴力质因数分解的(当然要先筛出质数),也可以过
因为是多合一做法,所以时间复杂度需要一个一个单独算,我就不算了,反正可以过
#include
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;
}