P3172 [CQOI2015]选数

gcd 为 \(K\) 不太好办,所以我们先把它转化成 gcd 为 1 的问题:

	scanf("%d%d%d%d",&n,&k,&l,&r);
l=l/k+!!(l%k),r/=k,L=r-l; // L 表示区间长度

现在我们需要求的是:在 \([l,r]\) 区间内选出 \(n\) 个数,使它们的最大公约数为 1 的方案数。

有两种做法:莫反+杜教筛 \(O(r^{2/3})\) / 容斥 \(O(L\log L)\)

这里主要介绍容斥做法

首先,设 \(f_i\) 表示选出的数最大公约数为 \(i\) 且这些数不全相同的方案数。

Q:为什么要不全相同?

A:这样设状态之后容易发现 \(\forall i>L,f_i=0\) ,接下来的容斥就只用考虑 \(f_1\sim f_L\) 了

从 \(i=L\) 开始倒推

设区间内 \(i\) 的倍数的个数为 \(x\) ,暂时令 \(s=x^n-x\)

可以看出这时的 \(s\) 表示的是选出的数都是 \(i\) 的倍数的方案数(并且去掉全相同的情况)

也就是选出 \(n\) 个数,使它们的公约数为 \(i\) 且这些数不全相同的方案数。

再看一下 \(f_i\) 的定义:选出的数最大公约数为 \(i\) 且这些数不全相同的方案数。

显然有 \(s=f_i+f_{2i}+\dots\) ,那么 \(f_i\) 即为 \(s-f_{2i}-f_{3i}-\dots\)

最后我们就可以得到 \(f_1\) ,即选出 \(n\) 个数,使它们的最大公约数为 1 且它们不全相等的方案数

但题目并没有要求它们不全相等,所以全选 1 也是一种需要考虑的情况。

所以在 \(l>1\) 时,答案为 \(f_1\) ;在 \(l=1\) 时,答案为 \(f_1+1\)

#include<stdio.h>
const int P=1e9+7; int n,k,l,r,l1,r1,L,f[100010];
inline int _power(int x,int y) {
int s=1;
while (y) (y&1)&&(s=1ll*s*x%P),x=1ll*x*x%P,y>>=1;
return s;
}
int main() {
scanf("%d%d%d%d",&n,&k,&l,&r);
l=l/k+!!(l%k),r/=k,L=r-l;
for (int i=1; i<=L; ++i)
l1=l/i+!!(l%i),r1=r/i-l1+1,
r1>=1&&(f[i]=_power(r1,n)-r1)<0&&(f[i]+=P); //这里把所有的fi暂时赋为s
for (int i=L; i; --i)
for (int j=(i<<1); j<=L; j+=i)
(f[i]-=f[j])<0&&(f[i]+=P); //这里才计算出了真正的fi
l==1&&(++f[1])==P&&(f[1]=0);
printf("%d\n",f[1]);
return 0;
}

P3911 最小公倍数之和

\(A_i\) 很小,所以可以桶排预处理出每个数出现的次数 \(a_x\)

然后我们要求的是 \(\sum\limits_{i=1}^n\sum\limits_{j=1}^n{\rm lcm}(i,j)a_ia_j\) (这里的 \(n\) 是 \(a_x>0\) 的 \(x\) 的个数而不是原题中的 \(N\))

\[\begin{aligned}
\sum_{i=1}^n\sum_{j=1}^n{\rm lcm}(i,j)a_ia_j
&= \sum_{i=1}^n\sum_{j=1}^n\dfrac{(ia_i)(ja_j)}{(i,j)} \\
&= \sum_{d=1}^n\sum_{i=1}^{n/d}\sum_{j=1}^{n/d}[(i,j)=1]\dfrac{(ida_{id})(jda_{jd})}d \\
&= \sum_{d=1}^n\sum_{i=1}^{n/d}\sum_{j=1}^{n/d}(ia_{id})(ja_{jd})d\sum_{d'\mid (i,j)}\mu(d') \\
&= \sum_{d=1}^nd\sum_{d'=1}^{n/d}\mu(d')\sum_{i=1}^{n/dd'}\sum_{j=1}^{n/dd'}(id'a_{idd'})(jd'a_{jdd'}) \\
&= \sum_{d=1}^nd\sum_{d'=1}^{n/d}\mu(d')d'^2\sum_{i=1}^{n/dd'}\sum_{j=1}^{n/dd'}(ia_{idd'})(ja_{jdd'}) \\
&= \sum_{T=1}^nT\sum_{d'\mid T}\mu(d')d'\left(\sum_{i=1}^{n/T}ia_{iT}\right)^2 \\
&= \sum_{T=1}^nT\left(\sum_{i=1}^{n/T}ia_{iT}\right)^2\left(\sum_{d\mid T}\mu(d)d\right)
\end{aligned}\]

后面括号里的东西可以 \(O(n\log n)\) 预处理

前面的括号直接暴力算,时间复杂度也是 \(O(n\log n)\)

时间复杂度 \(O(n\log n)\)

#include<stdio.h>
#include<ctype.h>
const int N=50010; long long s,ans;
int n,m,cnt,x,p[10000],a[N],mu[N],f[N],v[N];
int main() {
scanf("%d",&m),mu[1]=1;
for (int i=1; i<=m; ++i)
scanf("%d",&x),++a[x],x>n&&(n=x);
for (int i=2; i<=n; ++i) {
v[i]||(p[++cnt]=i,mu[i]=-1);
for (int j=1; j<=cnt&&(x=p[j]*i)<=n; ++j) {
v[x]=1;
if (i%p[j]==0) break; mu[x]=-mu[i];
}
}
for (int i=1; i<=n; ++i)
for (int j=i,t=mu[i]*i; j<=n; j+=i) f[j]+=t;
for (int i=1; i<=n; ++i) {
for (int j=1,k=i; k<=n; ++j,k+=i) s+=j*a[k];
ans+=s*s*i*f[i],s=0;
}
printf("%lld\n",ans);
return 0;
}

CF1301E Nanosoft

(2021.5.23重做)

二分答案。对于确定的参数 \(t\) 和子矩阵 \((x1\sim x2,y1\sim y2)\) ,我们需要判断这个子矩阵内是否存在边长不小于 \(t\) 的 logo

显然大 logo 里面会包含小 logo ,所以这等价于 矩阵内是否存在边长等于 \(t\) 的 logo

我们整个数组 \(s_{x,y}\) 存以 \((x,y)\) 为左上角的最大 logo 的边长(其实左上角确定后最多只有一个合法 logo ,没必要说最大)

则只需判断是否存在 \(x1\le x\le x2-t+1,y1\le y\le y2-t+1\) 满足 \(s_{x,y}=t\) 即可

\(s_{x,y}\) 的取值范围比较小(不大于 \(\min(n,m)\) ),所以我们可以用二维前缀和记录每个取值的出现次数

然后就能 \(O(1)\) 查询了:

设 \(S[x][y][t]\) 表示 \(s[1\sim x][1\sim y]\) 中取值 \(t\) 的出现次数,

S[x2-t+1][y2-t+1][t]-S[x1-1][y2-t+1][t]-S[x2-t+1][y1-1][t]+S[x1-1][y1-1][t] 不等于 \(0\) 时即为存在

最后补一下 \(s\) 的求法

其实就是枚举所有可能的边长并检验是否合法

但暴力检验肯定会超时 所以要对每种颜色都算一遍二维前缀和,然后就又能 \(O(1)\) 查询了

时间复杂度 \(O(n^3+q\log n)\) ( \(n,m\) 同级)

(为了方便,代码实际用的不是 logo 边长而是它的一半)

#include<stdio.h>
const char cc[4]={'R','G','Y','B'};
int n,m,q,t,l,r,x1,y1,x2,y2; char ch[510];
int sc[510][510][5],s[510][510][260];
inline int min(int x,int y) { return x<y?x:y; }
inline int Sc(int x1,int y1,int x2,int y2,int c) {
return sc[x2][y2][c]-sc[x2][y1][c]-sc[x1][y2][c]+sc[x1][y1][c];
}
inline int S(int x1,int y1,int x2,int y2,int k) {
return s[x2][y2][k]-s[x2][y1][k]-s[x1][y2][k]+s[x1][y1][k];
} int main() {
scanf("%d%d%d",&n,&m,&q);
for (int i=1; i<=n; ++i) {
scanf("%s",ch+1);
for (int j=1; j<=m; ++j) for (int k=0; k<4; ++k)
sc[i][j][k]=(ch[j]==cc[k])-Sc(i-1,j-1,i,j,k);
}
for (int i=0,i1,i2; i<n; ++i) for (int j=0,j1,j2; j<m; ++j) {
for (int k=min(n,m); k; --k)
s[i+1][j+1][k]=s[i+1][j][k]+s[i][j+1][k]-s[i][j][k];
for (int k=min(n-i,m-j)>>1; k; --k) { // k 为边长的一半
i1=i+k,j1=j+k,t=Sc(i1,j1,i2=i1+k,j2=j1+k,3);
t+=Sc(i,j,i1,j1,0)+Sc(i,j1,i1,j2,1)+Sc(i1,j,i2,j1,2);
// t = 左上R的个数 + 右上G的个数 + 左下Y的个数 + 右下B的个数
if (t==(k*k<<2)) { ++s[i+1][j+1][k]; break; }
}
}
while (q--) {
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
for (l=1,r=min(x2-x1+1,y2-y1+1)>>1,t=l+r>>1; l<r; t=l+r>>1) //二分答案
S(x1-1,y1-1,x2-t-t+1,y2-t-t+1,t)?l=t+1:r=t;
S(x1-1,y1-1,x2-l-l+1,y2-l-l+1,l)||(--l);
printf("%d\n",(l*l<<2));
}
return 0;
}

SP21615 NAJPWG - Playing with GCD

(2021.5.26重做)

题意:求 \(\sum\limits_{j=1}^n\sum\limits_{i=1}^j[\gcd(i,j)>1]\)

\(i,j\) 反了有点不爽 重写一下:\(\sum\limits_{i=1}^n\sum\limits_{j=1}^i[\gcd(i,j)>1]\)

\(T\) 组数据, \(1\le T,n\le 10^5\)

看到这样的式子容易想到莫反

(这题莫反时间复杂度会爆炸,不可行,看看思路就好)

设 \(T=\sum\limits_{i=1}^n\sum\limits_{j=1}^n[\gcd(i,j)=1]\) ,易得 \(\sum\limits_{i=1}^n\sum\limits_{j=1}^i[\gcd(i,j)>1]=\dfrac{n^2+n-1-T}2\)

而众所周知, \(\sum\limits_{i=1}^n\sum\limits_{j=1}^n[\gcd(i,j)=1]=\sum\limits_{i=1}^n\sum\limits_{j=1}^n\sum\limits_{d\mid i,d\mid j}\mu(d)=\sum\limits_{d=1}^n\mu(d)(n/i)^2\)

数论分块即可 \(O(\sqrt n)\) 处理询问

但这样总复杂度达到 \(O(n+T\sqrt n)\) 会超时

所以还是要写正解

正解其实比这简单:

\(\sum\limits_{j=1}^i[\gcd(i,j)>1]=i-\varphi(i)\)

所以 \(\sum\limits_{i=1}^n\sum\limits_{j=1}^i[\gcd(i,j)>1]=\sum\limits_{i=1}^n(i-\varphi(i))\)

预处理一下前缀和 然后就可以 \(O(1)\) 处理询问了

时间复杂度 \(O(n+T)\)

#include<stdio.h>
const int N=100001; int n,T,cnt,t,i,p[20000],v[N],phi[N];
int main() {
for (int i=2; i<N; ++i) { //线性筛求 phi ,求 i-phi(i) 的前缀和
v[i]||(p[++cnt]=i,phi[i]=i-1);
for (int j=1; j<=cnt&&(t=p[j]*i)<N; ++j) {
v[t]=1;
if (i%p[j]==0) { phi[t]=phi[i]*p[j]; break; }
phi[t]=phi[i]*(p[j]-1);
}
phi[i]=phi[i-1]+i-phi[i]; // 这里前缀和刚好不会爆int
}
for (scanf("%d",&T),i=1; i<=T; ++i)
scanf("%d",&n),printf("Case %d: %d\n",i,phi[n]); //注意一下输出格式
return 0;
}

P1880 [NOI1995] 石子合并

很裸的环形DP题

直接丢代码了 相信将来自己还能看得懂

#include<stdio.h>
#include<string.h>
int n,n2,t,s1=1e9,s2,a[210],s[210],f1[210][210],f2[210][210];
inline int min(int x,int y) { return x<y?x:y; }
inline int max(int x,int y) { return x>y?x:y; } int main() {
scanf("%d",&n),n2=n+n,memset(f1,0x3f,sizeof f1);
for (int i=1; i<=n; ++i)
scanf("%d",a+i),a[n+i]=a[i]; //破环成链
for (int i=1; i<=n2; ++i) s[i]=s[i-1]+a[i],f1[i][i]=0;
for (int i=1; i<n; ++i)
for (int j=1; j<=n+n-i; ++j) {
for (int k=j; k<j+i; ++k)
f1[j][j+i]=min(f1[j][j+i],f1[j][k]+f1[k+1][j+i]),
f2[j][j+i]=max(f2[j][j+i],f2[j][k]+f2[k+1][j+i]);
t=s[j+i]-s[j-1],f1[j][j+i]+=t,f2[j][j+i]+=t;
}
for (int i=1; i<=n; ++i)
s1=min(s1,f1[i][i+n-1]),s2=max(s2,f2[i][i+n-1]);
printf("%d\n%d\n",s1,s2);
return 0;
}

CF1322B Present

按位考虑

对于第 \(k\) 位(指权重为 \(2^k\) 的数位,如第 \(0\) 位表示末位),显然只有最后 \(k\) 位能对答案产生影响

所以我们只需考虑 \(a'_i=a_i\bmod 2^{k+1}\) 即可

显然 \(0<a'_i+a'j<2^{k+2}\)

那么要让第 \(k\) 位为 \(1\) ,就必须有 \(a'_i+a'_j\in[2^k,2^{k+1})\cup[2^{k+1}+2^k,2^{k+2})\)

到这一步就和异或没关系了 然后就可以比较方便地解决

排序之后直接双指针就可以了

时间复杂度 \(O(n\log n\log a_i)\)

但其实没有必要对每个 \(k\) 都重新排一遍序

写个二路归并即可 可以做到 \(O(n\log a_i)\)

实现要注意一下避免重复计数

#include<stdio.h>
#include<ctype.h>
#define gc (l==r&&(r=(l=c)+fread(c,1,1<<21,stdin),l==r)?EOF:*l++)
const int N=400010;
int n,m,m1,m2,p1,p2,s,ans,a[N];
struct node { int x,num; }b[N],b1[N],b2[N];
char c[1<<21],*l=c,*r=c;
inline int read() {
int x=0; char ch=gc;
while (!isdigit(ch)) ch=gc;
while (isdigit(ch)) x=x*10+(ch^48),ch=gc;
return x;
} int main() {
n=read();
for (int i=1; i<=n; ++i) a[i]=read(),b[i]={0,i};
for (int i=1,mx=(1<<25); i<mx; i<<=1) { //枚举位数
s=m=m1=m2=0,p1=p2=1;
for (int j=1; j<=n; ++j)
(a[b[j].num]&i)?b1[++m1]={b[j].x|i,b[j].num}:b2[++m2]=b[j];
while (p1<=m1&&p2<=m2)
b[++m]=(b1[p1].x<b2[p2].x?b1[p1++]:b2[p2++]);
while (p1<=m1) b[++m]=b1[p1++];
while (p2<=m2) b[++m]=b2[p2++]; //二路归并
for (int j=1,k1=n,k2=n; j<=n; ++j) {
while (b[j].x+b[k1].x>=i&&k1) --k1;
while (b[j].x+b[k2].x>=(i<<1)&&k2) --k2;
(s+=k2-k1-(k1<j&&k2>=j))&=3;
} // [ 2^i , 2^(i+1) )
for (int j=1,k1=n,k2=n; j<=n; ++j) {
while (b[j].x+b[k1].x>=i*3&&k1) --k1;
while (b[j].x+b[k2].x>=(i<<2)&&k2) --k2;
(s+=k2-k1-(k1<j&&k2>=j))&=3;
} // [ 2^(i+1)+2^i , 2^(i+2) )
(s>>1)&&(ans|=i);
}
printf("%d\n",ans);
return 0;
}

UVA12558 埃及分数 Egyptian Fractions

搜索题

思路:DFS+剪枝(可行性)

为了保证搜索到的是最优解 需要先枚举拆分出分数的个数

爆搜显然会超时 所以还要剪枝

不直接用BFS也是因为这题BFS剪枝不方便 反而会慢

在确定了拆分出分数的个数之后,关于最优性,题目有这样的要求:

如果还有多种情况,应使第一大的分母最小,如果还不行,第二大最小,以此类推。

所以必须把所有解都搜出来才能确定最优解 不适合使用最优性剪枝

考虑可行性剪枝

dfs(x,y,cnt) 表示尝试将 \(\dfrac xy\) 拆分为 \(cnt\) 个分数的搜索状态(其中 \(x,y\) 互质)

枚举当前分数的分母 \(i\)

为了方便,不妨规定从小到大枚举,则 \(i\) 必须 严格大于 拆分出的上一个分数的分母

同时显然有 \(\dfrac 1i<\dfrac xy<\dfrac 1i\times cnt\) ,即 \(\dfrac yx<i<\dfrac yx\times cnt\)

用这两个约束条件就可以缩小枚举范围了

最后注意分母不能出现 \(q_{1\sim k}\) ,而且要开 long long

#include<stdio.h>
#include<string.h>
typedef long long ll;
int T,x,y,n,t,v[1010]; ll a[1010],b[1010];
ll gcd(ll x,ll y) { return y?gcd(y,x%y):x; }
int check() {
for (int i=1; i<=t; ++i)
if (a[i]!=b[i]) return (a[i]>b[i]);
}
int dfs(ll x,ll y,int cnt) { // 1/0 表示 是/否 找到解
if (cnt==1) {
if (x>1||y<=b[2]||(y<=1000&&v[y])) return 0;
if (b[1]=y,!check()) return 0;
return memcpy(a,b,sizeof a),1;
}
ll f=0,tx,ty,g,i=(y-1)/x+1;
i<=b[cnt+1]&&(i=b[cnt+1]+1);
while (i<=y*cnt/x&&i<=a[1]) {
if (i<=1000&&v[i]) { ++i; continue; }
b[cnt]=i,g=gcd(tx=x*i-y,ty=y*i); // tx/ty = x/y - 1/i
f|=dfs(tx/g,ty/g,cnt-1),++i; //约分
}
return f;
} int main() {
scanf("%d",&T);
for (int i=1; i<=T; ++i) {
memset(v,0,sizeof v),scanf("%d%d%d",&x,&y,&n);
while (n--) scanf("%d",&t),v[t]=1;
t=1;
do ++t,memset(a,0x3f,sizeof a),b[t+1]=0;
while (!dfs(x,y,t));
printf("Case %d: %d/%d=",i,x,y);
for (int j=t; j>1; --j) printf("1/%lld+",a[j]);
printf("1/%lld\n",a[1]);
}
return 0;
}

P3052 [USACO12MAR]Cows in a Skyscraper G

状压DP

将物品的使用情况用一个 \(n\) 位二进制数 \(j\) 表示, 1/0 分别表示 是/否 已被分组

不妨规定尚未被分组的物品只能分入最后一组或重开新组

这并不影响我们求得最优解(读者自证不难)

显然,组数越少越好,组数相等时,最后一组的总体积越小越好

定义 \(f_j\) 表示已被分组的这些物品所需的最小组数, \(g_j\) 表示组数取最小值时最后一组的最小总体积

然后枚举当前要分组的物品 \(i\)

根据我们的规定,如果最后一组还装得下,就一定分到最后一组(贪心思想),否则就新开一组

状态转移就完成了

取最优解时仍然是取组数最小,其次最后一组的总体积最小

#include<stdio.h>
#include<string.h>
int n,m,mx,t,tf,tg,a[20],f[1<<18],g[1<<18];
int main() {
scanf("%d%d",&n,&m),mx=(1<<n);
memset(f,0x3f,sizeof f),f[0]=1;
for (int i=0; i<n; ++i) scanf("%d",a+i);
for (int j=0; j<mx; ++j)
for (int i=0; i<n; ++i) if ((t=j|(1<<i))!=j)
tf=f[j],(tg=g[j]+a[i])>m&&(++tf,tg=a[i]),
(tf==f[t]&&tg<g[t]||tf<f[t])&&(f[t]=tf,g[t]=tg);
printf("%d\n",f[mx-1]);
return 0;
}

P1835 素数密度

先筛出 \(\sqrt R\) 以内的素数,再用这些素数去做筛法

只需筛 \(L\sim R\) 的数即可

\(R-L\le 10^6\) ,所以不会超时

运算过程中一定要注意别爆 int

一定要考虑 1

#include<stdio.h>
const int N=50000; long long t;
int l,r,s,cnt,p[10000],v[N],v2[1000010];
int main() {
scanf("%d%d",&l,&r),s=r-l+1;
for (int i=2; i<N; ++i) { //线性筛
v[i]||(p[++cnt]=i);
for (int j=1; j<=cnt&&(t=p[j]*i)<N; ++j) {
v[t]=1;
if (i%p[j]==0) break;
}
}
for (int i=1; i<=cnt; ++i) {
t=l-(l-1)%p[i]+p[i]-1; t==p[i]&&(t+=p[i]);
while (t<=r) v2[t-l]=1,t+=p[i];
}
for (int i=0; i<=r-l; ++i) s-=v2[i];
l==1&&(--s),printf("%d\n",s);
return 0;
}

X000100的更多相关文章

随机推荐

  1. SOA 和微服务

    ====>场景 不可能让客户端与6个不同的应用/系统都一一去通信来去完成数据的展示.而是6个应用/系统之间进行彼此通信来完成调用,最后客户端只需要调用一个接口来获取数据即可. SOA架构 SOA ...

  2. 第三十个知识点:大致简述密钥协商中的BR安全定义。

    第三十个知识点:大致简述密钥协商中的BR安全定义. 在两方之间建密钥共享是一件密码学中古老的问题.就算只考虑定义也比标准加密困难的多.尽管古典的Diffie-Hellman协议在1976年思路解决了这 ...

  3. <学习opencv>跨平台和本机windows

    /*=========================================================================*/ // 跨平台和本机Windows /*=== ...

  4. 编写Java程序,实现简单的五子棋博弈游戏(美和易思Java练习习题)

    package com.qq.gb; import java.util.Scanner; public class GoBang { Scanner sc = new Scanner(System.i ...

  5. 加深理解Java异常概念并熟记5个最常见的运行时异常

    加深理解Java异常概念并熟记5个最常见的运行时异常 说明Error与Exception的联系和区别有哪些? 列举最常见的5个运用时异常. 1.Error和Exception的联系和区别: Error ...

  6. JDK中的BitMap实现之BitSet源码分析

    前提 本文主要内容是分析JDK中的BitMap实现之java.util.BitSet的源码实现,基于JDK11编写,其他版本的JDK不一定合适. 文中的图比特低位实际应该是在右边,但是为了提高阅读体验 ...

  7. 2.spring系列之404异常的捕获

    回顾 我在之前发布了一篇spring统一返回的文章,最后提到是无法捕获404异常的,这里我们先来测试一下 @RestController public class TestController { @ ...

  8. 使用 SSH 隧道实现端口转发、SOCKS 代理

    SSH隧道 本地端口转发 本地客户端通过 local_port 连接到 MobaXterm: MobaXterm 绕过防火墙,使用 user 用户连接到 ssh_server_ip:ssh_serve ...

  9. STM32新建模板之库文件

    一.新建项目文件夹 在桌面创建项目文件夹,用于存放工程,命名为"stm32f10x_project" 在项目文件夹中创建3个文件夹,分别命名为:CMSIS.OUTPUT.STM32 ...

  10. JUC之Callable接口回顾和JUC辅助类

    Callable接口和JUC辅助类 Callable接口: 回顾: 创建线程的四种方式: 继承Thread 实现runnable接口 实现callable接口 使用线程池 之前的文章:多线程编程1-定 ...