zoukankan      html  css  js  c++  java
  • 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(Llog L))
    这里主要介绍容斥做法

    首先,设 (f_i) 表示选出的数最大公约数为 (i)这些数不全相同的方案数。
    Q:为什么要不全相同?
    A:这样设状态之后容易发现 (forall i>L,f_i=0) ,接下来的容斥就只用考虑 (f_1sim 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
    ",f[1]);
    	return 0;
    }
    

    P3911 最小公倍数之和

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

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

    [egin{aligned} sum_{i=1}^nsum_{j=1}^n{ m lcm}(i,j)a_ia_j &= sum_{i=1}^nsum_{j=1}^ndfrac{(ia_i)(ja_j)}{(i,j)} \ &= sum_{d=1}^nsum_{i=1}^{n/d}sum_{j=1}^{n/d}[(i,j)=1]dfrac{(ida_{id})(jda_{jd})}d \ &= sum_{d=1}^nsum_{i=1}^{n/d}sum_{j=1}^{n/d}(ia_{id})(ja_{jd})dsum_{d'mid (i,j)}mu(d') \ &= sum_{d=1}^ndsum_{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}^ndsum_{d'=1}^{n/d}mu(d')d'^2sum_{i=1}^{n/dd'}sum_{j=1}^{n/dd'}(ia_{idd'})(ja_{jdd'}) \ &= sum_{T=1}^nTsum_{d'mid T}mu(d')d'left(sum_{i=1}^{n/T}ia_{iT} ight)^2 \ &= sum_{T=1}^nTleft(sum_{i=1}^{n/T}ia_{iT} ight)^2left(sum_{dmid T}mu(d)d ight) end{aligned}]

    后面括号里的东西可以 (O(nlog n)) 预处理
    前面的括号直接暴力算,时间复杂度也是 (O(nlog n))

    时间复杂度 (O(nlog 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
    ",ans);
    	return 0;
    }
    

    CF1301E Nanosoft

    (2021.5.23重做)

    二分答案。对于确定的参数 (t) 和子矩阵 ((x1sim x2,y1sim y2)) ,我们需要判断这个子矩阵内是否存在边长不小于 (t) 的 logo
    显然大 logo 里面会包含小 logo ,所以这等价于 矩阵内是否存在边长等于 (t) 的 logo

    我们整个数组 (s_{x,y}) 存以 ((x,y)) 为左上角的最大 logo 的边长(其实左上角确定后最多只有一个合法 logo ,没必要说最大)
    则只需判断是否存在 (x1le xle x2-t+1,y1le yle y2-t+1) 满足 (s_{x,y}=t) 即可
    (s_{x,y}) 的取值范围比较小(不大于 (min(n,m)) ),所以我们可以用二维前缀和记录每个取值的出现次数

    然后就能 (O(1)) 查询了:
    (S[x][y][t]) 表示 (s[1sim x][1sim 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+qlog 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
    ",(l*l<<2));
    	}
    	return 0;
    }
    

    SP21615 NAJPWG - Playing with GCD

    (2021.5.26重做)

    题意:求 (sumlimits_{j=1}^nsumlimits_{i=1}^j[gcd(i,j)>1])
    (i,j) 反了有点不爽 重写一下:(sumlimits_{i=1}^nsumlimits_{j=1}^i[gcd(i,j)>1])
    (T) 组数据, (1le T,nle 10^5)

    看到这样的式子容易想到莫反
    (这题莫反时间复杂度会爆炸,不可行,看看思路就好)
    (T=sumlimits_{i=1}^nsumlimits_{j=1}^n[gcd(i,j)=1]) ,易得 (sumlimits_{i=1}^nsumlimits_{j=1}^i[gcd(i,j)>1]=dfrac{n^2+n-1-T}2)
    而众所周知, (sumlimits_{i=1}^nsumlimits_{j=1}^n[gcd(i,j)=1]=sumlimits_{i=1}^nsumlimits_{j=1}^nsumlimits_{dmid i,dmid j}mu(d)=sumlimits_{d=1}^nmu(d)(n/i)^2)
    数论分块即可 (O(sqrt n)) 处理询问
    但这样总复杂度达到 (O(n+Tsqrt n)) 会超时
    所以还是要写正解

    正解其实比这简单:
    (sumlimits_{j=1}^i[gcd(i,j)>1]=i-varphi(i))
    所以 (sumlimits_{i=1}^nsumlimits_{j=1}^i[gcd(i,j)>1]=sumlimits_{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
    ",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
    %d
    ",s1,s2);
    	return 0;
    }
    

    CF1322B Present

    按位考虑
    对于第 (k) 位(指权重为 (2^k) 的数位,如第 (0) 位表示末位),显然只有最后 (k) 位能对答案产生影响
    所以我们只需考虑 (a'_i=a_imod 2^{k+1}) 即可

    显然 (0<a'_i+a'j<2^{k+2})
    那么要让第 (k) 位为 (1) ,就必须有 (a'_i+a'_jin[2^k,2^{k+1})cup[2^{k+1}+2^k,2^{k+2}))
    到这一步就和异或没关系了 然后就可以比较方便地解决
    排序之后直接双指针就可以了
    时间复杂度 (O(nlog nlog a_i))

    但其实没有必要对每个 (k) 都重新排一遍序
    写个二路归并即可 可以做到 (O(nlog 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
    ",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 imes cnt) ,即 (dfrac yx<i<dfrac yx imes cnt)
    用这两个约束条件就可以缩小枚举范围了

    最后注意分母不能出现 (q_{1sim 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
    ",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
    ",f[mx-1]);
    	return 0;
    }
    

    P1835 素数密度

    先筛出 (sqrt R) 以内的素数,再用这些素数去做筛法
    只需筛 (Lsim R) 的数即可
    (R-Lle 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
    ",s);
    	return 0;
    }
    
  • 相关阅读:
    一些关于"虚拟交易"的有趣文章
    Windows中的消息队列:Message Queuing (MSMQ)
    ATLStyle模板 不用虚函数实现多态
    AIX 下获取系统CPU及内存的使用情况方法
    关于HPUNIX C 兼容性
    Android进阶Acticivity的启动模式
    ViewState & UpdatePanle & ReadOnly属性
    由网站构架演变说起
    '操作必须使用一个可更新的查询'解决方法
    ScriptManager & ClientScriptManager
  • 原文地址:https://www.cnblogs.com/REKonib/p/13081411.html
Copyright © 2011-2022 走看看