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] = v\sum\limits_{k|\gcd(\frac{x}{d},\frac{n}{d})} \mu(k) = v\sum\limits_{k|\frac{x}{d},k|\frac{n}{d}} \mu(k)\)

\(=\sum\limits_{k|\frac{n}{d},kd|x} v\mu(k)\)

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

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

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

时间复杂度$O(q\sqrt{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:\n",++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\n",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.数论分块

整除分块快速处理:\(\huge\sum_{i=1}^{n}{\lfloor \frac{n}{i} \rfloor}\)

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

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(\text{union-find-set})\)

即使你写的是 \(n\log 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)\times 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]=\text{min}(v[i],\sum\limits_{i\to j}f[j])\)

由于需要DDP,所以转化为

设y 为x的重儿子,x的所有轻儿子的f之和为g[x],则有\(f[x]=\text{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\n",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\n", ++ 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\n",ans);
    fclose(stdin); fclose(stdout);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/yelir/p/11657130.html