X000100
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\))
\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的更多相关文章
随机推荐
- .NET Core 实现动态代理做AOP(面向切面编程)
1.介绍 1.1 动态代理作用 用动态代理可以做AOP(面向切面编程),进行无入侵式实现自己的扩展业务,调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,比如:日志记录.性能统计.安全控制.事务 ...
- Cube 技术解读 | Cube 小程序技术详解
本文为<Cube 技术解读>系列第三篇文章,之前上线的<支付宝新一代动态化技术架构与选型综述>与<Cube卡片技术栈解读>欢迎大家回顾. 魔方卡片(Cube)已在「 ...
- SpringBoot+神通数据库+JPA
先上原文 https://blog.csdn.net/Helloworld_pang/article/details/114266130 一.SpringBoot + 神通数据库 基本上按照上面的参考 ...
- 【java多线程】synchronized和volatile
文章目录 一.synchronized 1.synchronized使用的方法 2.注意 3.不要以字符串作为锁的对象 4.`synchronized`锁的是什么? 二.volatile 1.引出问题 ...
- PPT制作图片磨砂玻璃艺术效果
如果图片损害,点击链接:https://www.toutiao.com/i6488928834799272462/ 选择"插入"选项卡,"图像"功能组,&quo ...
- CentOS6.4安装Zookeeper-3.4.12图解教程
注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6595380916590215683/ 安装工具 VMware_workstation_full_12.5.2 Ce ...
- WebRTC本地选择codec(web本地模拟)
视频编码后,再进行发送.WebRTC建立视频连接前,可以选择codec.一般来说支持多种codec,以VP8和H264为代表. Codec: 编码译码器,编解码器 示例代码 写一个示例,用户可以在发送 ...
- uboot无法通过nfs加载ubuntu18.04中的文件(转)
问题描述: i.mx6ull开发板,采用alientek官方维护的uboot,使用ubuntu18.04 lts作为nfs server,导致开发板uboot上nfs命令无法加载网络文件系统. 解决: ...
- day 21 C语
(1).有以下程序: 执行后的输出结果是[A] (A).256,1 (B).1,256 (C).255,1 (D).256,0 (2).以下选项中与(!a==0)的逻辑值不等价的表达式是[B] (A) ...
- Go语言测试:testing
学习参考来源:https://www.liwenzhou.com/posts/Go/16_test/ go test工具 必须导入包: import "testing" go te ...