zoukankan      html  css  js  c++  java
  • CSP考前总结

    10.2

    考试:

    1.数位DP 或者找规律

    2.SB题,扫一遍找最大最小即可

    3.莫比乌斯反演

    出题人相出个数论和数据结构的综合题,但是找不到NOIP级别的,没办法只能忍痛割爱出个莫比乌斯,话说回来,莫比乌斯要是会了,其他的应该也就会了……(吧

    看这个操作 1 n d v

    相当于 (a[x] += v[gcd(x,n)=d])

    (v[gcd(x,n) = d] = v [gcd(frac{x}{d},frac{n}{d})=1] = vsumlimits_{k|gcd(frac{x}{d},frac{n}{d})} mu(k) = vsumlimits_{k|frac{x}{d},k|frac{n}{d}} mu(k))

    (=sumlimits_{k|frac{n}{d},kd|x} vmu(k))

    如果按照操作来说我们现在其实就是枚举了所有的 (frac{n}{d}) 的约数 (k), 然后枚举所有 (kd) 的倍数,对应位置加上 (vmu(k)) 就行了。 查询 (O(1)) 查询

    但是这样修改的复杂度太大,查询的复杂度太低,不妨让修改的时候枚举约数,查询的时候也枚举约数。也即我们修改的时候,新开一个数组 (f),枚举约数之后只让 (kd) 加上 (vmu(k)) 查询的时候查询(sumlimits_{i=1}^nsumlimits_{d|i} f(d)=sumlimits_{d=1}^n f(d)lfloor frac{n}{d} floor)

    后面的东西跟根号分块就行了,前面的东西始终是个区间和,单点修改区间求和,使用树状数组就可以了

    时间复杂度$O(qsqrt{l}log l+ l log l) $

    4.代码:

    int mu[N],prime[N],vis[N],cnt,tot,p[N],l,m,val[N];
    void add(int x,int v){
    	while(x<=l){
    		val[x]+=v; x+=x&(-x);
    	}
    }
    int ask(int pos){
    	int sum=0;
    	while(pos){
    		sum+=val[pos];
    		pos-=pos&(-pos);
    	}
    	return sum;
    }
    void yilin()
    {
    	mu[1]=1;
    	for(int i=2;i<=200000;i++){
    		if(!vis[i]){
    			prime[++cnt]=i;
    			mu[i]=-1;
    		}
    		for(int j=1;j<=cnt;j++){
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0){
    				mu[i*prime[j]]=0;
    				break;
    			}
    			mu[i*prime[j]]=-mu[i];
    		}
    	}
    }
    int main(){
    	yilin();
    	while(1){
    		memset(val,0,sizeof(val));
    		printf("Case #%lld:
    ",++tot);
    		l=read(); m=read();
    		if(l==0 && m==0)break;
    		for(int i=1;i<=m;i++){
    			int opt,n,d,v; opt=read();
    			if(opt==1){
    				n=read(); d=read(); v=read();
    				if(n%d!=0)continue;
    				for(int k=1; k*k<=(n/d); k++)
    				{
    					if((n/d)%k==0)add(k*d,v*mu[k]);
    					if(k*k!=n/d)add( d*( (n/d)/k ),v*mu[(n/d)/k]);
    				}
    			}
    			else{
    				x=read();
    				int ans=0;
    				for(int l=1,r;l<=x;l=r+1){
    					r=min(x,x / (x / l));
    					ans+=(x/l) * ((ask(r)-ask(l-1));
    				}
    				printf("5d
    ",ans);
    			}
    		}
    	}
    }
    

    5.裴蜀定理

    (ax+by=m)有整数解时当且仅当m是(gcd(a,b))的倍数。裴蜀等式有解时必然有无穷多个整数解,每组解x、y都称为裴蜀数,可用扩展欧几里得算法求得。

    例如,12和42的最大公约数是6,则方程(12x+42y=6​)有解。事实上有(-3)×12 + 1×42 = 6及4×12 + (-1)×42 =6。特别来说,方程 ax+by=1 有整数解当且仅当整数a和b互素。

    6.数论分块

    整除分块快速处理:(hugesum_{i=1}^{n}{lfloor frac{n}{i} floor})

    通过打表找规律,可以发现 特定块的(lfloor frac{n}{i} floor)的值是相同的,假设起始位置为l,那么结束位置(lfloor frac{n}{lfloor frac{n}{l} floor} floor)

    for(int l=1,r;l<=x;l=r+1){
    			r=min(x,x / (x / l));
    			ans+=(x/l) * ((ask(r)-ask(l-1));}
    

    10.3

    BZOJ 4879

    对于每个点分三种情况

    • 没走过这个点,有颜色,无解
    • 走过这个点,有颜色,那么最早时刻在最后一次经过这个点之后
    • 走过这个点,没颜色,那么最晚时刻在最后一次经过这个点之后

    原问题变为了求最后一次经过某个点的时间

    倒过来做变成第一次经过某个点的时间

    一次操作无非就是横着擦一下或者竖着擦一下,先只考虑横着擦一下,我们把原图从上到下,从左到右编号,那么每次操作相当于把原序列中没有数字的部分都抹成某个数字,这个题就变成了BZOJ 疯狂的馒头,直接并查集找最右面的点就 OK 了

    还有竖着的怎么半?分两次做,一次只做横着的,从上到下从左到右编号,一次只做竖着的从左到右从上到下编号,两次答案取交集就可以了

    时间复杂度 (O( ext{union-find-set}))

    即使你写的是 (nlog n) 也能通过测试数据,但是 (set) 模拟的做法大概率会 T

    ​ 可以用最小生成树,建边从0~n.

    10.4

    1.能量项链 考虑中间的十分,每种颜色值出现了两次我们可以在一种颜色出现第一次的时候 +1, 出现第二次的时候 -1,那么前缀和相等的一对点一定可以成为一对分割点 ,如果颜色出现了不止一次的话,+1,-1显然不行

    所以我们可以每次加减一个不同的很大整数来避免重复。

    2.动态规划,每次倒着扫就行

    3.树剖加DP

    ​ 考虑这样一个问题,要怎么样的点才能满足三个点两两距离相等呢?

    1、存在三个点有共同的 (lca)

    2、存在一个点,使得它到它两颗不同的子树种两点的距离为 (d) 且它存在 (d) 级祖先。

    (f(x,i)) 表示以 (x) 为根的子树中,距离 (x)(i) 的点数

    (g(x,i)) 表示以 (x) 为根的子树中, 形如下图的的 ((a,b)) 数量

    其中 (d) 可以是任意值(说白了就是都考虑进去),或者说只要在上面街上一个长度为 (i) 的边就可以构成一个合法的三元组。

    考虑转移,枚举 (x) 的下一个儿子 (u)

    (g(x,i) += f(x,i) imes f(u,i-1) + g(u,i+1))

    (f(x,i) += f(u,i-1))

    4.线段树 [POI2015]KIN 看电影

    10.5

    1.旅行者和火把问题,首先贪心有两种方式,一种是每次最快的运一个人再回来,第二种是前两个人先过去,最快的送火把回来,然后两个最慢的过去,次快的再回来,这样是运两个人,动态规划维护这个贪心,设(f[x])是从最后运到x位置的最小代价

    第一种方案:(a[1]+a[i]+f[i+1])
    第二种方案:(f[i+2]+a[1]+2*a[2]+a[i+1])

    所以方程:(f[i]=min(a[1]+a[i]+f[i+1],f[i+2]+a[1]+2*a[2]+a[i+1]);)

    注意特判,因为给出的按升序排列,所以当(n<=2)时直接输出(a[n]).

    2.动态规划,

    (g[l][r][i][j])为将l~r删至剩下的数的最小值为i,最大值为j,的最小代价。

    (f[l][r])为将l~r的序列全部删掉的最小总代价。

    可以离散化一下

    (g[l][r][min(i,w[r])][max(j,w[r])]=g[l][r-1][i][j]) 和前面的l~r-1放到一起删。

    (g[l][r][i][j]=min(g[l][r][i][j],g[l][k][i][j]+f[k+1][r])) 和后面的k+1~r作为被包含子区间删去。

    (f[l][r]=g[l][r][i][j]+a+b*(j-i)^2)

    3.动态DP,

    这个DP在树上进行,考虑轻重链剖分,设(f[i])为以i 为跟的字数满足条件的最小总代价

    (f[i]= ext{min}(v[i],sumlimits_{i o j}f[j]))

    由于需要DDP,所以转化为

    设y 为x的重儿子,x的所有轻儿子的f之和为g[x],则有(f[x]= ext{min}(v[x],f[y]+g[x]))

    接下来可以用矩阵转移这个东西,每个叶子结点的代价已知

    0 f[u] * 0 a[x] = 0 f[x]

    (infty) g[x]

    第二行的转移结果不考虑

    10.6

    10.7

    1.每次选三个,直接枚举所有情况,看哪个更优

    2.概率DP

    3.开一个bitset即可

    代码:

    #include<cstdio>
    #include<iostream>
    #include<cstdlib>
    #include<bitset>
    #define N 10005
    #define M 100005
    using namespace std;
    bitset<N> to[N];
    bitset<N> vis,quan;
    int f[N][N],a[N],n,m,ans=2147483647;
    int main()
    {
    	n=read(); m=read();
    	for(int i=1;i<=n;i++)quan[i]=1;
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=n;j++)f[i][j]=2147483647;
    	int x,y,c;
    	for(int i=1;i<=m;i++)
    	{
    		x=read();y=read();c=read();
    		if(f[x][y]==2147483647)a[x]++;
    		to[x][y]=1;
    		f[x][y]=min(f[x][y],c);
    	}
    	int mx;
    	for(int i=1;i<n;i++)
    	for(int j=i;j<=n;j++)
    	{
    		mx=0;
    		if(a[i]+a[j] < n)continue;
    		vis=to[i]|to[j];
    		if(vis != quan)continue;
    		for(int k=1;k<=n;k++) mx=max(mx,min(f[i][k],f[j][k]));
    		if(mx)ans=min(ans,mx);
    	}
    	if(ans==2147483647)puts("No solution");
    	else printf("%d
    ",ans);
    }
    /*
    4 13
    1 1 1
    1 2 3
    1 3 3
    1 4 5
    2 1 2
    2 2 1
    2 3 2
    3 1 4
    3 3 4
    3 4 1
    4 1 2
    4 2 3
    4 4 3
    */
    

    对拍

    while( ((double)clock()-yilr) / CLOCKS_PER_SEC < 0.75) dfs(1,0,++dep);
    

    CLOCKS_PER_SEC 这个常数在Windows下等于1000

    迭代加深搜会用

    对拍程序:

    #include<cstdio>
    #include<ctime>
    #include<iostream>
    #include<cstdlib>
    using namespace std;
    int main()
    {
    	int T=0;
    	while(1)
    	{
    		double t1=clock();
    		system("F:\random.exe");
    		double t2=clock();
    		system("F:\baoli.exe");
    		double t3=clock();
    		system("F:\sol.exe");
    		if(system("fc F:\baoli.out F:\sol.out"))
    		{
    			puts("yelir doesn't love you");
    			break;
    		}
    		else 
    		printf("yelir love you,测试点 %d 总时间 %.0f",++T,(clock()-t1)/1000.0 );
    	}
    	return 0;
    }
    
    #include<cstdlib>
    #include<cstdio>
    #include<ctime>
    using namespace std;
    int main() 
    {
    	int T = 0;
        while(1) 
    	{
        	double ti = clock();
            system("F:\random.exe");
            double st = clock();
            system("F:\baoli.exe");
            double ed = clock();
            system("F:\sol.exe");
            if (system("fc F:\baoli.out F:\sol.out")) {
                puts("Wrong Answer");
                return 0;
            }
            else 
    		{
                printf("Accepted, 测试点 #%d, 用时 %.0lfms 总用时 %.0lfs
    ", ++ T, ed - st, (clock() - ti) / 1000);
            }
        }
    }
    

    各种排序

    稳定的排序:

    插入排序(直接插,二分插),冒泡排序,归并排序,基数排序,

    不稳定的排序:

    希尔排序,堆排序,快速排序,选择排序

    15703

    yelir

    1.注意撞关键字,比如next, end,y1,y2,x1,x2,j1,j2

    2.注意超空间,时间问题,卡评测要专业

    qsing2

    1.是一道物理题,分三种情况,从高到低,从低到高,平跳,推式子即可

    2.回声也就说明重复,可证最多重复一千次,然后建一棵Tire树,每次加入前查询,不必配直接返回即可。

    3.线段树,当前点若没有人则赋为inf,单点修改,最后一问,可维护一下区间有多少学生,线段树区间求和即可,注意最后要从tail向前扫,因为他到队尾后还可能向队尾走,而且不一定总的队长为n+m,可能更长,而且注意build传参的范围,不是n,而是n+m.

    #define N 20050
    using namespace std;
    int tr[20005005][2];
    char s[2000];
    int ans,tot,n;
    inline void Insert(){
    	int k = 0;
    	for(int i = 0;i <= 1000;i ++){
    		int o = s[i] - '0';
    		if(!tr[k][o]) tr[k][o] = ++ tot;
    		k = tr[k][o];
    	}
    }
    inline int Ask() {
    	int k = 0,res = 0;
    	for(int i = 0;i <= 1000;i ++) {
    		int o = s[i] - '0';
    		if(!tr[k][o]) {
    			return res;
    		}
    		else k = tr[k][o],res ++;
    	}
    	return res;
    }
    int main() {
    	scanf("%d",&n);
    	for(int i = 1;i <= n;i ++) {
    		scanf("%s",s);
    		int len = strlen(s),cnt = 0;
    		for(int j = len;j <= 1000;j ++)
    			s[j] = s[cnt],cnt = (cnt + 1) % len;
    		ans = max(ans,Ask()); Insert();
    	}
    	printf("%d
    ",ans);
    	fclose(stdin); fclose(stdout);
    	return 0;
    }
    
  • 相关阅读:
    Linux基本命令
    LR之流程
    Jmeter&Ant构建自动化测试平台
    正则表达式
    搭建wordpress-安装xshell
    git本地文件提交
    Git基本操作
    python-之基本语法
    SQL语句之-简单查询
    postman之请求&断言
  • 原文地址:https://www.cnblogs.com/yelir/p/11657130.html
Copyright © 2011-2022 走看看