容斥专题总结
A:How many integers can you find
过于水,但是有细节
B: Eddy's爱好
枚举k的值,直接开k次方根,为了防止爆精度,我手写了一下
然后就是对于因数容斥,或者是直接套莫比乌斯系数
const int N=4e5+10;
const ll INF=(1ll<<62)-1;
ll n;
ll qsm(ll x,ll k) {
ll res=1;
while(k) {
if(k&1) {
double t=(double)res*x;
if(t>INF) {
res=INF;
break;
}
else res=res*x;
}
double t=(double)x*x;
if(t>INF) x=INF;
else x=x*x;
k>>=1;
}
return res;
}
ll yroot(ll x,ll y){
ll l=1,r=2e9,res=1;
while(l<=r) {
ll mid=(l+r)>>1;
if(qsm(mid,y)<=x) res=mid,l=mid+1;
else r=mid-1;
}
return res;
}
ll dp[100];
int main() {
while(~scanf("%lld",&n)) {
ll res=0;
rep(i,2,60) dp[i]=yroot(n,i)-1;
drep(i,60,2) for(int j=i+i;j<=60;j+=i) dp[i]-=dp[j];//k=i的情况会在j中被多次计算
rep(i,2,60) res+=dp[i];
printf("%lld\n",res+1);
}
}
C: Coprime
提出n,m的质因数,然后二分答案,直接二进制枚举来容斥
const int N=4e5+10;
const ll INF=(1ll<<62)-1;
int k;
int fac[N],n;
void Div(int x) {
for(int i=2;i*i<=x;++i) if(x%i==0) {
fac[n++]=i;
while(x%i==0) x/=i;
}
if(x>1) fac[n++]=x;
}
ll Check(ll mid){
ll res=0;
rep(S,0,(1<<n)-1) {
ll t=1,cnt=0;
rep(i,0,n-1) if(S&(1<<i)) cnt^=1,t=t*fac[i];
if(cnt) res-=mid/t;
else res+=mid/t;
}
return res;
}
int main(){
rep(kase,1,rd()) {
n=0;Div(rd()),Div(rd());
k=rd();
sort(fac,fac+n);
n=unique(fac,fac+n)-fac;
ll l=1,r=1e15,res=-1;
while(l<=r) {
ll mid=(1ll*l+1ll*r)>>1;
if(Check(mid)>=k) res=mid,r=mid-1;
else l=mid+1;
}
printf("Case %d: %lld\n",kase,res);
}
}
D: GCD
首先外层的区间可以容斥
然后是对于每个\(1 \leq i \leq n\)二进制枚举容斥\(1..m\)中互质的个数
const int N=1e5+10;
const ll INF=(1ll<<62)-1;
int a,b,c,d,k;
vector <int> fac[N];
ll Solve(int n,int m,int k) {
if(!k||!n||!m) return 0;
n/=k,m/=k;
if(n<m) swap(n,m);
ll res=0;
rep(i,1,n) {
int t=min((int)i,m);
int k=fac[i].size();
rep(j,0,(1<<k)-1){
ll x=1,cnt=0;
rep(o,0,k-1) if(j&(1<<o)) x=x*fac[i][o],cnt^=1;
if(cnt) res-=t/x;
else res+=t/x;
}
}
return res;
}
int main(){
rep(i,2,N-1) if(!fac[i].size()) for(int j=i;j<N;j+=i) fac[j].push_back((int)i);
rep(kase,1,rd()) {
a=rd(),b=rd(),c=rd(),d=rd(),k=rd();
ll res=Solve(b,d,k)-Solve(a-1,d,k)-Solve(b,c-1,k)+Solve(a-1,c-1,k);
printf("Case %d: %lld\n",kase,res);
}
}
E: Co-prime
基本思想之前都出现过了,跳过吧
F: Frogs
这个题还是非常有点东西的对吧
首先你要知道这样一件事
对于任何的\(gcd(n,m)=1\),$\lbrace n*i \ mod \ m|i \in Z \rbrace \(=\){0,1,..,m-1}$
然后其实对于任意\(gcd(n,m)=g\),$\lbrace ni \ mod \ m|i \in Z \rbrace \(=\){0,g,g2,..,m-1}$
所以每个青蛙能走到的地方就确定了,设这些\(gcd\)的值为\(a[1..n]\)
这时候我们模拟一下二进制枚举的过程,即取出其中的一些数,求出他们的\(lcm(lowest \ common \ multiple)\),当数的个数为奇数时加,偶数时减
即当你每次多选择一个数时,系数的符号取相反数,而\(lcm\)的情况数并不多,也就是\(m\)的因子个数
所以直接dp转移求系数即可
const int N=1e4+10;
ll gcd(ll a,ll b) {return b==0?a:gcd(b,a%b); }
int n,m;
int a[N];
int fac[N],c;
ll dp[N];
int lcm[2000][2000];
int gcd(int a,int b){ return b==0?a:gcd(b,a%b); }
int main(){
rep(kase,1,rd()) {
n=rd(),m=rd();
rep(i,1,n) a[i]=gcd(rd(),m);
sort(a+1,a+n+1);
n=unique(a+1,a+n+1)-a-1;
c=0;
rep(i,1,sqrt(m+0.5)) if(m%i==0) {
fac[++c]=i;
if(i*i!=m) fac[++c]=m/i;
}
sort(fac+1,fac+c+1);
rep(i,1,c) dp[i]=0;
rep(i,1,n) a[i]=lower_bound(fac+1,fac+c+1,a[i])-fac;
rep(i,1,c) rep(j,1,c) lcm[i][j]=lower_bound(fac+1,fac+c+1,fac[i]/gcd(fac[i],fac[j])*fac[j])-fac;
rep(i,1,n) {
int x=a[i];
drep(j,c,1) {
dp[lcm[x][j]]-=dp[j];
}
dp[a[i]]++;
}
ll ans=0;
rep(i,1,c) ans+=1ll*dp[i]*(m/fac[i])*(m-fac[i])/2;
printf("Case #%d: %lld\n",kase,ans);
}
}
G: The Monkey King
刚开始做的时候没想到可以\(n\ ln \ n\)写,头都想破了。。。
枚举大猴子拿了\(x\)个,然后容斥其他人拿的比他多的情况
统计时候,枚举有至少\(i\)个人比他多,答案是\(C(n+m-x*(i+1)-1,m-2)\)
其实是插板法求组合,然后就可以顺利的容斥了
int n,m;
ll po[N]={1},Inv[N]={1,1};
ll C(int n,int m){
if(n<0||m<0||n<m) return 0;
return po[n]*Inv[m]%P*Inv[n-m]%P;
}
ll Solve(int x) {
ll ans=C(n+m-x-2,m-2);
rep(i,1,min(m-1,n/x-1)) {
if(i&1) (ans=ans-C(m-1,i)*C(n+m-x*(i+1)-2,m-2))%=P;
else ans=(ans+C(m-1,i)*C(n+m-x*(i+1)-2,m-2))%P;
}
return ans=(ans%P+P)%P;
}
int main(){
rep(i,1,N-1) po[i]=po[i-1]*i%P;
rep(i,2,N-1) Inv[i]=(P-P/i)*Inv[P%i]%P;
rep(i,1,N-1) Inv[i]=Inv[i]*Inv[i-1]%P;
rep(kase,1,rd()) {
n=rd(),m=rd();
if(m==1) {
puts("1");
continue;
}
ll ans=0;
rep(i,(n-1)/m+1,n) ans+=Solve(i);
ans%=P;
ans=(ans%P+P)%P;
printf("%lld\n",ans);
}
}
H: Y sequence
这个题是真的没有素质
首先它和B题比就多了个二分,但如果真的写二分就TLE了! 必须写迭代
其次是不能手写开次方根了,只能用pow函数,而且还会爆精度
const ll INF=(1ll<<62)-1;
ll k;
int r;
int w[100],notpri[100],pri[100],cp;
int num[100],maxpri[100],nc;
inline ll Solve(ll n) {
ll res=0;
rep(i,1,nc) if(maxpri[i]<=r) res+=1ll*w[i]*((ll)pow(n+0.1,1.0/num[i])-1);
return n-res-1;
}
int main() {
rep(i,2,65) {
if(!notpri[i]) {
pri[++cp]=i;
for(int j=i+i;j<=65;j+=i) notpri[j]=1;
}
}
rep(i,2,65) {
int x=i,t=-1,ma=0;
rep(j,1,cp) {
int cnt=0;
while(x%pri[j]==0) cnt++,x/=pri[j];
if(cnt>1) {
t=0;
break;
}
if(cnt) t=-t,ma=max(ma,pri[j]);
}
if(t) w[++nc]=t,num[nc]=i,maxpri[nc]=ma;
}
rep(kase,1,rd()){
scanf("%lld",&k);r=rd();
ll ans=k;
while(1) {
ll tmp=Solve(ans);
if(k==tmp) break;
ans+=k-tmp;
}
printf("%lld\n",ans);
}
}
I: MG loves string
目测这题做法也奇多无比,我用了矩阵乘法
首先26个字母组成多个环,环长的种类最多只有6种
所以我直接将这6种环长状压,矩阵快速幂即可
const int N=1e5+10,P=1e9+7;
int n;
int nxt[26],c[26];
char str[30];
int vis[27];
int A,k[N],id[N];
int cnt;
ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); }
struct Mat{
int a[1<<6][1<<6];
void init(){ rep(i,0,A) rep(j,0,A) a[i][j]=0; }
void Get1(){ rep(i,0,A) a[i][i]=1; }
Mat operator * (const Mat x) const{
Mat res; res.init();
rep(i,0,A) rep(j,0,A) rep(o,0,A) (res.a[i][o]+=1ll*a[i][j]*x.a[j][o]%P)%=P;
return res;
}
}res,x;
ll f[1][1<<6],ans[1][1<<6];
int main(){
rep(kase,1,rd()) {
n=rd();
scanf("%s",str);
rep(i,0,25) nxt[i]=str[i]-'a';
cnt=0;
memset(vis,0,sizeof vis);
rep(i,0,25) {
int p=i;c[i]=0;
do {
p=nxt[p];
c[i]++;
} while(p!=i);
if(!vis[c[i]]) {
vis[c[i]]=1;
id[c[i]]=cnt;
k[cnt++]=c[i];
}
}
A=(1<<cnt)-1;
x.init(),res.init();res.Get1();
rep(i,0,A) {
rep(j,0,25) {
x.a[i][i|(1<<id[c[j]])]++;
}
}
while(n) {
if(n&1) res=res*x;
x=x*x;
n>>=1;
}
memset(f,0,sizeof f);memset(ans,0,sizeof ans);
f[0][0]=1;
rep(i,0,0) rep(j,0,A) rep(o,0,A) (ans[i][o]+=1ll*f[i][j]*res.a[j][o]%P)%=P;
ll Ans=0;
rep(S,0,A) {
ll t=1;
rep(i,0,cnt-1) if(S&(1<<i)) t=t*k[i]/gcd(t,k[i]);
(Ans+=t*ans[0][S]%P)%=P;
}
printf("%lld\n",Ans);
}
}
J: Harry And Magic Box
dp容斥就好了,\(dp[i][j]\)表示\(i\)行\(j\)列的都放了的方案数,枚举小于\(i\)或小于\(j\)的方案减掉就好了
int n,m,q;
ll Inv[N*N]={1,1},po[N*N]={1};
ll B[N][N],p2[N*N]={1};
inline ll C(int n,int m){
if(n<0||m<0||n<m) return 0;
return po[n]*Inv[m]%P*Inv[n-m]%P;
}
int main(){
rep(i,1,N*N-1) po[i]=po[i-1]*i%P,p2[i]=p2[i-1]*2%P;
rep(i,2,N*N-1) Inv[i]=(P-P/i)*Inv[P%i]%P;
rep(i,1,N*N-1) Inv[i]=Inv[i]*Inv[i-1]%P;
rep(i,0,50) {
rep(j,0,50) {
B[i][j]=p2[i*j];
rep(a,0,i) {
rep(b,0,j) if(a!=i||b!=j) {
(B[i][j]-=C(i,a)*C(j,b)%P*B[a][b]%P)%=P;
}
}
B[i][j]=(B[i][j]%P+P)%P;
}
}
while(~scanf("%d%d",&n,&m)) printf("%lld\n",B[n][m]);
}
K: Count the Grid
看起来非常吓人,但其实还好
总体思想就是给定每个点一个\(lim\)值,求出矩阵在限制条件下的方案数容斥
然后就是二进制枚举,对于每个被枚举到的\(lim\)值要减一
其实就是把选出的数全部在\(lim\)值以下的方案减去,这样得到的就是最大值为\(lim\)的方案了
如何统计矩阵的\(lim\)值,直接离散然后循环赋值就好了,但是由于被卡常了所以我不得不加了一些优化
代码写的比较诡异不建议看,拿去对拍可以
#include<bits/stdc++.h>
using namespace std;
#define reg register
typedef long long ll;
#define rep(i,a,b) for(reg int i=a;i<=b;++i)
#define drep(i,a,b) for(reg int i=a;i>=b;--i)
char IO;
int rd(){
int s=0,f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=110,P=1e9+7;
int n,m,k,q;
int hx[N],cx,hy[N],cy;
struct REC{
int x1,y1,x2,y2,lim;
void Get() {
x1=rd(),y1=rd();
x2=rd(),y2=rd();
hx[++cx]=x1,hx[++cx]=x2;
if(x1>1) hx[++cx]=x1-1;
if(x1<n) hx[++cx]=x1+1;
if(x2>1) hx[++cx]=x2-1;
if(x2<n) hx[++cx]=x2+1;
hy[++cy]=y1,hy[++cy]=y2;
if(y1>1) hy[++cy]=y1-1;
if(y1<m) hy[++cy]=y1+1;
if(y2>1) hy[++cy]=y2-1;
if(y2<m) hy[++cy]=y2+1;
lim=rd();
}
void Hash() {
x1=lower_bound(hx+1,hx+cx+1,x1)-hx;
x2=lower_bound(hx+1,hx+cx+1,x2)-hx;
y1=lower_bound(hy+1,hy+cy+1,y1)-hy;
y2=lower_bound(hy+1,hy+cy+1,y2)-hy;
}
}R[N];
inline ll qsm(reg ll x,reg int k){
reg ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}
int a[N][N],b[N][N],vis[N][N];
ll c[2][N][N];
ll Solve() {
ll ans=1;
rep(i,1,cx) rep(j,1,cy) a[i][j]=k;
for(reg int i=0; i<q; ++i) rep(x,R[i].x1,R[i].x2) rep(y,R[i].y1,R[i].y2) a[x][y]=min(a[x][y],R[i].lim);
rep(i,1,cx) rep(j,1,cy) ans=ans*qsm(a[i][j],(hx[i]-hx[i-1])*(hy[j]-hy[j-1]))%P;
return ans;
}
int main(){
int T=rd();
rep(kase,1,T){
n=rd(),m=rd(),k=rd(),q=rd();
hx[cx=1]=n,hy[cy=1]=m;
hx[++cx]=1,hy[++cy]=1;
rep(i,0,q-1) R[i].Get();
sort(hx+1,hx+cx+1),sort(hy+1,hy+cy+1);
cx=unique(hx+1,hx+cx+1)-hx-1,cy=unique(hy+1,hy+cy+1)-hy-1;
rep(i,0,q-1) R[i].Hash();
rep(i,1,cx) rep(j,1,cy) b[i][j]=k;
for(reg int i=0; i<q; ++i) rep(x,R[i].x1,R[i].x2) rep(y,R[i].y1,R[i].y2) b[x][y]=min(b[x][y],R[i].lim);
rep(i,1,cx) rep(j,1,cy){
c[0][i][j]=qsm(b[i][j],(hx[i]-hx[i-1])*(hy[j]-hy[j-1]))%P;
c[1][i][j]=qsm(b[i][j]-1,(hx[i]-hx[i-1])*(hy[j]-hy[j-1]))%P;
a[i][j]=b[i][j];
}
int A=(1<<q)-1;
ll ans=0;
rep(S,0,A) {
int cnt=0;
rep(i,0,q-1) if(S&(1<<i)) {
cnt^=1;
rep(x,R[i].x1,R[i].x2) rep(y,R[i].y1,R[i].y2) if(a[x][y]==R[i].lim) a[x][y]=R[i].lim-1,vis[x][y]=R[i].lim;
}
ll res=1;
rep(i,1,cx) rep(j,1,cy) res=res*c[b[i][j]-a[i][j]][i][j]%P;
if(cnt) ans-=res;
else ans+=res;
//cout<<S<<" "<<Solve()<<endl;
rep(i,0,q-1) if(S&(1<<i)) rep(x,R[i].x1,R[i].x2) rep(y,R[i].y1,R[i].y2) if(vis[x][y]) a[x][y]=vis[x][y],vis[x][y]=0;
}
ans=(ans%P+P)%P;
printf("Case #%d: %lld\n",kase,ans);
}
}
L: Visible Trees
和前面的好几道题基本同理
#include<bits/stdc++.h>
using namespace std;
#define reg register
typedef long long ll;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
int s=0,f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=1e5+10,P=1e9+7;
int n,m,k,q;
vector <int> fac[N];
ll Solve(int n,int m) {
ll res=0;
rep(i,1,n) {
int k=fac[i].size();
int x=min(i,m);
rep(S,0,(1<<k)-1) {
int cnt=0,t=1;
rep(j,0,k-1) if(S&(1<<j)) t*=fac[i][j],cnt^=1;
if(cnt) res-=x/t;
else res+=x/t;
}
}
//cout<<res<<endl;
return res;
}
int main(){
rep(i,2,N-1) if(!fac[i].size()) for(int j=i;j<N;j+=i) fac[j].push_back((int)i);
rep(kase,1,rd()){
n=rd(),m=rd();
ll ans=Solve(n,m)+Solve(m,n)-1;
printf("%lld\n",ans);
}
}
M: A Simple Chess
跳马?
首先我们讨论在空的\(n,m\)棋盘上跳的方案数
由于必须是前向跳,所以每次坐标都会增加,设横着跳\(a\)次,纵着跳\(b\)次
则有
解得方程的两根即可得到a,b的值,若不是整数则无解
然后其实方案数就是\(C(a+b,a)\)
容斥的过程依旧是dp转移,每次转移时会多经过一个障碍点,容斥系数取反
但是由于这题范围较大,组合数不好求,鉴于模数只有110119,所以可以用\(Lucas\)定理
即\(C(n,m) mod \ p =C(n \ mod \ p,m \ mod \ p)*(n/p,m/p) mod \ p\)
预处理后直接做
#include<bits/stdc++.h>
using namespace std;
#define reg register
typedef long long ll;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
char IO;
ll rd(){
ll s=0,f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=110,P=110119;
ll n,m;
int r;
struct NODE {
ll x,y;
bool operator < (const NODE __ )const{
return x<__.x;
}
bool operator == (const NODE __) const{
return x==__.x&&y==__.y;
}
}A[N];
int k;
ll po[P]={1},Inv[P]={1,1};
ll C(ll n,ll m){
if(n<0||m<0||n<m) return 0;
if(n<P&&m<P) return po[n]*Inv[m]%P*Inv[n-m]%P;
return C(n%P,m%P)*C(n/P,m/P)%P;
}
ll Calc(ll n,ll m) {
if((n+m-2)%3!=0) return 0;
if((2*n-m-1)%3!=0) return 0;
ll a=(n+m-2)/3,b=(2*n-m-1)/3;
return C(a,b);
}
//(i+j-2)/3
//(2*i-j-1)/3
ll dp[N];
int kase;
int main(){
rep(i,1,P-1) po[i]=po[i-1]*i%P;
rep(i,2,P-1) Inv[i]=(P-P/i)*Inv[P%i]%P;
rep(i,1,P-1) Inv[i]=Inv[i-1]*Inv[i]%P;
while(~scanf("%lld%lld%d",&n,&m,&r)) {
k=0;
A[++k]=(NODE){1ll,1ll};
int f=0;
rep(i,1,r) {
ll x=rd(),y=rd();
A[++k]=(NODE){x,y};
if((x==n&&y==m)||(x==1&&y==1)) f=1;
}
if(f) {
printf("Case #%d: 0\n",++kase);
continue;
}
if(n==1&&m==1) {
printf("Case #%d: 1\n",++kase);
continue;
}
A[++k]=(NODE){n,m};
sort(A+1,A+k+1);
k=unique(A+1,A+k+1)-A-1;
memset(dp,0,sizeof dp);
dp[1]=1;
rep(i,1,k) rep(j,i+1,k) if(A[j].x>A[i].x&&A[j].y>A[i].y) (dp[j]-=dp[i]*Calc(A[j].x-A[i].x+1,A[j].y-A[i].y+1)%P)%=P;
ll ans=dp[k];
ans=(P-ans)%P;
ans=(ans%P+P)%P;
printf("Case #%d: %lld\n",++kase,ans);
}
}
N: TrickGCD
看到这个题就想到对于整个序列的\(gcd\)的值容斥
如何快速求出某个值限制下的序列方案数呢?
直接前缀和,\(n \ln n\)枚举,快速幂即可,最后乘一个莫比乌斯系数
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<iostream>
using namespace std;
#define reg register
typedef long long ll;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
int s=0,f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar())) ;
return f?-s:s;
}
const int N=2e5+10,P=1e9+7;
int n;
int a[N];
inline ll qsm(reg ll x,reg int k) {
if(x==1||k<=0) return 1;
reg ll res=1;
for(;k;k>>=1,x=x*x%P) ((k&1)&&(res=res*x%P));
return res;
}
int pri[N],cp;
int notpri[N];
int mo[N];
int main(){
rep(i,2,N-1) {
if(!notpri[i]) pri[++cp]=i,mo[i]=1;
rep(j,1,cp){
int t=i*pri[j];
if(t>=N) break;
notpri[t]=1;
if(i%pri[j]==0) {
mo[t]=0;
break;
}
mo[t]=-mo[i];
}
}
int T=rd();
rep(kase,1,T) {
int Up=1e9,ma=0;
scanf("%d",&n);
rep(i,1,n) {
int x; scanf("%d",&x);
a[x]++;
Up=min(Up,x);
ma=max(ma,x);
}
rep(i,Up,N-1) a[i]+=a[i-1];
ll ans=0;
for(reg int i=2;i<=Up;++i) if(mo[i]) {
ll res=1;
rep(j,1,ma/i) {
res=res*qsm(j,a[(j+1)*i-1]-a[j*i-1])%P;
}
ans+=mo[i]*res;
}
rep(i,Up,N-1) a[i]=0;
ans=(ans%P+P)%P;
printf("Case #%d: %lld\n",kase,ans);
}
}
O: Puzzled Elena
大水题
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
#define reg register
typedef long long ll;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
int s=0,f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar())) ;
return f?-s:s;
}
const int N=1e5+10,P=1e9+7;
int n;
struct Edge{
int to,nxt;
}e[N<<1];
int head[N],ecnt;
void AddEdge(int u,int v){
e[++ecnt]=(Edge){v,head[u]};
head[u]=ecnt;
}
vector <int> fac[N];
int a[N],ans[N];
int c[N];
void dfs(int u,int f) {
int n=fac[a[u]].size();
ans[u]=0;
rep(S,0,(1<<n)-1) {
int t=1,cnt=0;
rep(i,0,n-1) if(S&(1<<i)) cnt^=1,t*=fac[a[u]][i];
if(cnt) ans[u]+=c[t];
else ans[u]-=c[t];
}
rep(S,0,(1<<n)-1) {
int t=1;
rep(i,0,n-1) if(S&(1<<i)) t*=fac[a[u]][i];
c[t]++;
}
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].to;
if(v==f) continue;
dfs(v,u);
}
rep(S,0,(1<<n)-1) {
int t=1,cnt=0;
rep(i,0,n-1) if(S&(1<<i)) cnt^=1,t*=fac[a[u]][i];
if(cnt) ans[u]-=c[t];
else ans[u]+=c[t];
}
}
int kase;
int main(){
rep(i,2,N-1) if(!fac[i].size()) for(int j=i;j<N;j+=i) fac[j].push_back((int)i);
while(~scanf("%d",&n)) {
memset(c,0,sizeof c);memset(head,0,sizeof head);ecnt=0;
rep(i,2,n) {
int u=rd(),v=rd();
AddEdge(u,v);
AddEdge(v,u);
}
rep(i,1,n) a[i]=rd();
dfs(1,0);
printf("Case #%d:",++kase);
rep(i,1,n) printf(" %d",ans[i]);
puts("");
}
}
P: Just Random
\(O(1)\) 的题为甚么在最后一题
对于两个数\(n,m(n \leq m)\),它们累和为\(x\)的方案数其实是有一定规律的
当\(x\leq n\)时,方案数为\(x+1\)
当\(n\leq x \leq m\)时,方案数为\(n+1\)
当\(m\leq x\)时,方案数为\(max\{0,n+m-x+1\}\)
然后就ojbk了
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
#define reg register
typedef long long ll;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
char IO;
int rd(){
int s=0,f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar())) ;
return f?-s:s;
}
const int N=1e5+10;
ll a,b,c,d;
ll p,m;
ll Solve(ll a,ll b) {
if(a<0||b<0) return 0;
if(a>b) swap(a,b);
ll ans=0,t;
if(a>=m) {
t=1ll*(a-m)/p*p+m;
ans+=1ll*(m+t+2)*((t-m)/p+1)/2;
}
t=0;
if(b>=m) t=(b-m)/p+1;
if(a>=m) t-=(a-m)/p+1;
ans+=t*(a+1);
if(b>=m) {
t=1ll*(b-m)/p*p+m;
ans-=1ll*(2*a+2*b-m-t+2)*((t-m)/p+1)/2;
}
if(a+b>=m) {
t=1ll*(a+b-m)/p*p+m;
ans+=1ll*(2*a+2*b-m-t+2)*((t-m)/p+1)/2;
}
return ans;
}
ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); }
int main(){
int cnt=0;
rep(i,0,0) rep(j,0,5) if((i+j)%3==2) cnt++;
rep(kase,1,rd()) {
a=rd(),b=rd(),c=rd(),d=rd(),p=rd(),m=rd();
ll ans=Solve(b,d)-Solve(a-1,d)-Solve(b,c-1)+Solve(a-1,c-1);
printf("Case #%d: ",kase);
if(!ans) {
puts("0/1");
continue;
}
ll t=1ll*(b-a+1)*(d-c+1);
ll g=gcd(t,ans);
ans/=g,t/=g;
printf("%lld/%lld\n",ans,t);
}
}