zoukankan      html  css  js  c++  java
  • 「笔记」关于乱搞

    一、关于乱搞

    乱搞:通过一些方式 尝试 得到不该得到的分数

    为什么要写乱搞:1. 正解不会   2. 正解太难写/正解太难调/正解难写又难调   3. 考试时间还有15min   4. 自信感觉能水过去

    乱搞包括但不限于:写个非完美算法、写个 O(松) 暴力、瞎搜一通、把几个贪心拼起来、猜想出题人造的数据满足某些性质。

    二、非完美算法

    适用于大部分最优化问题。

    • 退火:正确率高
    • 爬山:收敛快
    • 遗传(貌似不大行,我也不会 qwq)

    如何选取:① 原则:两个都写然后对拍看哪个效果好。② 定性分析:收敛慢的(题目)爬山,收敛快的(题目)退火。

    1. 爬山算法

    伪代码:

    ans=gen();    //随机一个初始解
    for t=1 to lim do {
        int tmp=move(ans);    //在当前解的附近随机一个新的解
        ans=max(ans,tmp);    //打擂 ¿
    }
    print(ans);    //输出解

    两个问题:

    ① 正确率过低

    • 多次选取初始解运行算法取最优值。
    • 改成退火。
    • 放弃

    ② 如何在解的附近生成新解

    • 解是一个数:ans+=rand(-lim,lim),lim*=1-eps。
    • 解是一个串:截取子串、循环移位、修改其中一个字符、交换其中两个字符。
    • 解是乱七八糟的东西:放弃

    2. 模拟退火

    对爬山算法改动一个地方: 若新解劣于当前解,以 (1-eps)t 的概率接受。

    伪代码:

    data ans=gen();
    int p=Rnd_Max;
    for t=1 to lim do {
        data tmp=move(ans);
        if(tmp>ans or rand()>p) ans=tmp;
        p*=1-eps;
    }
    print(ans);

    eps 取多少:一般来说 (1-eps)lim 大约取到 1e-5。

    收敛慢:eps 改大,在 t 大的时候相当于爬山。

    3. 结合两种方法

    用退火生成数个初始解,对每个初始解进行爬山以快速收敛。

    三、O(松) 暴力

    适用于已经存在一个在可观时间内能跑出更大数据范围的代码时。

    1. 压位:

    A. 把每 32 个位压到一个 int 里去    B. 把每 64 个位压到一个 long long 里去    C. 使用 bitset(速度 B>A>C)

    手写 bitset:① 如何 | & ^ >> <<:¿   ② 如何 bitcount:查表   ③ 如何可持久化:分块

    2. 缓存优化:A. 大量访问的数组能开多小开多小    B. 滚动数组

    3. 内存连续访问优化:矩阵乘法/floyd

    4. 循环展开:

    //原来: 
    for(i=1;i<=n;i++) do sth; 
    //循环展开后: 
    for(i=1;i<=n;i+=8){
        do sth; do sth; do sth; do sth;
        do sth; do sth; do sth; do sth;
    }
    for(;i<=n;i++) do sth;

    5. 变量运算速度优化:优化取模,long long 的运算变成 int 运算,struct 封装去掉,临时变量开 register,能三目不要 if

    6. 输入输出优化:一般 getchar() 和 putchar() 已经够用了。当然还有 fread 和 fwrite。

    读入优化模板(fread 只需把注释去掉即可) :

    //#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 
    //char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
    template<typename T>
    inline void read(T& x){
        x=0;int f=1;
        char ch=getchar();
        while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch-'0'),ch=getchar();
        x*=f;
    }

    四、瞎搜一通

    适用于数据范围不大的题目(一般数据范围<20) 

    1. 剪枝:不用多讲,减枝减错掉也问题不大(¿)
    2. 估价函数:A.严格优于解    B.严格劣于解   C.既不也不   D.等于解(B>A>C) 
    3. 迭代加深:字面意思。“搜索层数”可以灵活加权

    五、题目特殊性质

    这个就没什么好写的了,主要是(一看数据就很难造的)字符串题。

    六、例题

    1. 「HAOI 2006」均分数据

    题目大意:已知 n 个正整数 a1,a2,...,an。今要将它们分成 m 组,使得各组数据的数值和最平均,即各组的均方差最小。均方差公式如下:

    (displaystylesigma = sqrt{frac 1n sumlimits_{i=1}^n(overline x - x_i)^2},overline x = frac 1n sumlimits_{i=1}^n x_i)

    其中 σ 为均方差,(overline x) 为各组数据和的平均值,xi 为第 i 组数据的数值和。

    Solution:

    每组的和越接近越好,于是把每个数加入当前和最小的组里。

    每次贪心都把序列打乱,多次运算取最优解。

    循环 5e5 次,正确率大大提高。

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=30;
    int n,m,sum,a[N],x[N];
    double ans=1e18,tmp,k;
    double solve(){
        memset(x,0,sizeof(x)),tmp=0;
        for(int i=1;i<=n;i++){
            int p=1;
            for(int j=1;j<=m;j++)
                if(x[j]<x[p]) p=j;
            x[p]+=a[i];
        }    //把每个数加入当前和最小的组里
        for(int i=1;i<=m;i++)
            tmp+=(x[i]-k)*(x[i]-k);
        return tmp*1.0/m;
    }
    signed main(){
        scanf("%lld%lld",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]),sum+=a[i];
        k=1.0*sum/m;
        for(int i=1;i<=5e5;i++){ 
            random_shuffle(a+1,a+1+n);
            tmp=solve(),ans=min(ans,tmp);
        } 
        printf("%.2lf
    ",sqrt(ans));
        return 0;
    }

    2. 「JSOI 2004」平衡点 / 吊打XXX

    题目大意:给出平面中的 n 个点,求这 n 个点的广义费马点(费马点:在三角形内到各个顶点距离之和最小的点)。

    Solution:

    这道题需要一些物理知识,于是啥都不会的我看懵了(

    大概是这样的:

    涉及力的正交分解。如图所示,将力 F 沿力 x、y 方向分解,可得:

    ( egin{cases} F_x=F cos heta\ F_y=F sin heta end{cases} Rightarrow F=sqrt{F_x^2+F_y^2} )

    (以上只是稍微提一下力的正交分解)

    我们可以确定一个原点,将所有的力在这个原点上正交分解,最终得到所有的力的一个合力,而平衡点一定在合力所指向的方向。

    每当分得到一个合力之后,将原点在合力的方向上位移一定的距离。

    因为绳结不断移动的过程中,系统是不断趋向平衡的,因此每次移动的长度会不断缩小,当移动长度缩小到无法改变最终结果时输出当前位置,结束。

    (我也不大懂,反正大概就酱紫了。赶紧去学物理 QwQ)

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=1e4+5;
    int n,x[N],y[N],w[N];
    double ansx,ansy,fx,fy,F;
    void solve(double l){
        fx=fy=0;
        for(int i=1;i<=n;i++){    //将绳结上的力正交分解
            double p=sqrt((x[i]-ansx)*(x[i]-ansx)+(y[i]-ansy)*(y[i]-ansy));
            if(p==0) continue;
            fx+=w[i]*(x[i]-ansx)/p;    //fx:水平合外力
            fy+=w[i]*(y[i]-ansy)/p;    //fy:竖直合外力
        }
        F=sqrt(fx*fx+fy*fy);    //F:合外力,(fx/F,fy/F)为合外力的方向向量
        ansx+=l*fx/F,ansy+=l*fy/F;    //向合力方向移动 l
    }
    signed main(){
        scanf("%lld",&n);
        for(int i=1;i<=n;i++)
            scanf("%lld%lld%lld",&x[i],&y[i],&w[i]);
        for(double l=1e4;l>1e-5;l*=0.79) solve(l);    //l:移动长度
        printf("%.3lf %.3lf
    ",ansx,ansy);
        return 0;
    }

    3. 「AHOI2014 / JSOI2014」保龄球

    题目大意:点此看题

    Solution:

    每次交换当前排列的两个位置,多次运算取最优解。

    循环 5e5 次,正确率大大提高。(事实上 1e5 也能过)

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=60;
    int n,sum,flag,x,y,tmp,ans;
    struct data{
        int x,y;
    }a[N];
    int solve(){
        int sum=0;
        for(int i=1;i<=n+flag;i++){
            sum+=a[i].x+a[i].y;
            if(a[i-1].x==10) sum+=a[i].x+a[i].y;    //全中:下一轮的得分将会被乘2计入总分 
            else if(a[i-1].x+a[i-1].y==10) sum+=a[i].x;    //补中:下一轮中的第一次尝试的得分将会以双倍计入总分 
        }
        return sum;
    }
    signed main(){
        srand(time(0));
        scanf("%lld",&n);
        for(int i=1;i<=n;i++)
            scanf("%lld%lld",&a[i].x,&a[i].y);
        if(a[n].x==10) scanf("%lld%lld",&a[n+1].x,&a[n+1].y),flag=1;
        ans=solve();
        for(int i=1;i<=1e5;i++){
            x=rand()%(n+flag)+1,y=rand()%(n+flag)+1;
            while(x==y||(flag&&(x==n||y==n))) x=rand()%(n+flag)+1,y=rand()%(n+flag)+1;
            swap(a[x],a[y]),tmp=solve();    //每次交换当前排列的两个位置
            if(tmp>=ans) ans=tmp;
            else swap(a[x],a[y]);
        }
        printf("%lld
    ",ans);
        return 0;
    }

    4. CF914F Substrings in a String

     题目大意:维护一个字符串 S,支持以下操作:

    1. 修改 S 中一个位置的字符
    2. 询问串 T 在 S[l..r] 中出现次数

    |S|≤105,Σ|T|≤105,字符集 26。

    Solution:

    时限是 6s,想到可以用暴力的 bitset 维护。

    用一个 bitset 的二维数组记录 S 中每个字母的位置信息,再把串 T 中每个字母与 S 的字母进行比较。

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=1e5+5,M=30;
    int q,len,opt,x,l,r;
    char s[N],c,t[N];
    bitset<N>b[M],ans;
    signed main(){
        scanf("%s%lld",s+1,&q),len=strlen(s+1);
        for(int i=1;i<=len;i++)
            b[s[i]-'a'][i]=1;    //记录每个字母的位置信息 (s[i] 在第 i 位出现过)
        while(q--){
            scanf("%lld",&opt);
            if(opt==1){
                scanf("%lld %c",&x,&c);
                b[s[x]-'a'][x]=0,b[c-'a'][x]=1,s[x]=c;
            }
            else{
                scanf("%lld%lld%s",&l,&r,t+1),ans.set(),len=strlen(t+1);     //ans.set():将整个 bitset (ans)设置成 1
                for(int i=1;i<=len;i++)
                    ans&=b[t[i]-'a']>>(i-1);    //比较完一个就右移一位把是否包含该串的信息保存在一个位置
                //ans 中有一个 1 就代表以这个位置开头包括一个串 t 
                printf("%lld
    ",max(0ll,(int)(1ll*(ans>>l).count()-1ll*(ans>>(r-len+2)).count())));    //计算 l 右边和 r 右边有多少个串 t
            }
        }
        return 0;
    }

    5. CF896E Welcome home, Chtholly

    题目大意:维护一个序列,支持以下操作:

    • 把一个区间中 >x 的数都减掉 x
    • 询问一个区间中有几个 x

    n≤105,q≤105,值域 105

    Solution:

    暴力+卡常即可。需要亿点点信仰。

    #pragma comment(linker,"/stack:200000000")
    #pragma GCC optimize("Ofast,no-stack-protector")
    #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
    #include<bits/stdc++.h>
    #define re register
    using namespace std;
    const int N=1e5+5;
    int n,m,a[N];
    float x;
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        while(m--){ 
            re int opt,l,r,ans=0;
            scanf("%d%d%d%f",&opt,&l,&r,&x);
            if(opt==1){
                for(re int i=l;i<=r;i++) a[i]-=(a[i]>x)?x:0;
            }
            else{
                for(re int i=l;i<=r;i++) ans+=(a[i]==x);
                printf("%d
    ",ans);
            }
        }
        return 0;
    }

    接下来讲一下正解。(¿)

    值域范围小,我们可以直接记录 cnt[i][j] 表示块 i 中值为 j 的数个数。

    然后再用并查集把同一块中数值相同的元素缩在一起。

    对于第一个操作:首先两边的散块可以暴力修改。

    对于中间的块:设 mx 表示该块中的最大值。分以下几种情况:

    1. x≥mx,此时不用做任何修改

    2. x<mx<2*x,此时可以暴力把区间 [x+1,mx] 和 [1,mx-x] 对应的元素合并

    3. 2*x≤mx,我们注意到:第一个操作等价于先把所有元素减 x,然后把小于等于 0 的加 x。所以我们可以设一个 tag[i] 表示块 i 中这种情况减少的总数。对于这种情况,先暴力把区间 [1,x] 和 [x+1,x+x] 对应的元素合并,然后 tag[i]+=x

    第二个操作:两边的散块暴力查询,中间的 ans+=cnt[i][x+tag[i]] 即可。

    我们发现,对于修改操作:每一块合并的次数不超过 100000 次。所以整体时间复杂度是 O(n1.5)。

  • 相关阅读:
    NTP服务编译安装报错:/usr/bin/ld: cannot find –lcap
    NTP服务编译安装报错:ntpd.c:124:29: 致命错误:sys/capability.h:没有那个文件或目录
    yum运行报错:/usr/lib64/python2.7/site-packages/pycurl.so: undefined symbol: CRYPTO_num_locks
    yum运行报错:libcurl.so.4: cannot open shared object file: No such file or directory
    Xftp工具连接 报错: 无法与 “ 目标IP ” 连接
    yum运行报错:File "/usr/libexec/urlgrabber-ext-down", line 28
    webkit content-type missing in HTTP
    快速排序算法中选择“基准“点的方法
    数据结构与算法--归并排序
    数据结构与算法--拓扑排序
  • 原文地址:https://www.cnblogs.com/maoyiting/p/13402189.html
Copyright © 2011-2022 走看看