• 洛咕11月月赛部分题解 By cellur925


    听说是你谷史上最水月赛?我不听我最菜

    T1:终于结束的起点 月天歌名好评

    给你一个模数 (M),请你求出最小的
    (n > 0),使得(fib(n)) (mod) (m=0)(fib(n+1)) (mod) (m=1)

    数学题,开始还想打表验证下,但是我不会告诉你我打表的时候没有很及时地取膜,然后中间有结果溢出,耽误了很长时间,企图找了很久规律。结果发现暴力就能过。hhh。

    这个故事告诉我们要及时取膜

    #include<cstdio>
    #include<algorithm>
    
    using namespace std;
    typedef long long ll;
    
    ll m;
    ll f[7000001];
    
    int main()
    {
        scanf("%lld",&m);
        f[0]=0;f[1]=1;
        for(ll i=2;;i++)
        {
            (f[i]=f[i-1]+f[i-2])%=m;
            if(f[i-1]==0&&f[i]==1)
            {
                printf("%lld
    ",i-1);
                return 0;
            }
        }
        return 0;
    }
    

    T2:跳跳!

    对于(20)%的数据,直接全排列枚举。

    对于(50)%的数据,开始还想状压一下,结果交后发现超时,是我自己傻了!!企图用(O(2^n*n*2))(n<=20)!!!其实是四亿hhh。

    本来还觉得自己想不出正解了,但是(Mathison)巨佬说他(O(n))写出了...然后自己又冷静分析了下,发现是个非常简单的贪心==。

    可以重构一个序列:先对原序列排序,每次先选择剩余序列中的最大值,再选最小值直到原序列为空。这个序列就是最优排列。

    最后交的时候求稳分别交了全排列/状压/yy的正解,结果状压被卡T掉了30分:(。可能脸太黑了

    这个故事告诉我们,一定算好时间复杂度

    #include<cstdio>
    #include<algorithm>
    
    using namespace std;
    typedef long long ll;
    
    int n,fake,tot;
    ll ans,f[1200000][25];
    int h[1000],a[100],b[100];
    int seq[1000],tmp[1000];
    
    void review()
    {
        ll qwq=0;
        for(int i=1;i<=n;i++)
            qwq+=1ll*(h[a[i-1]]-h[a[i]])*(h[a[i-1]]-h[a[i]]);
        ans=max(ans,qwq);
    }
    
    bool cmp(int a,int b)
    {
        return a>b;	
    }
    
    void dfs(int x)
    {
        if(x==n+1)
        {
            review();
            return ;
        }
        for(int i=1;i<=n;i++)
            if(!b[i])
            {
                b[i]=1;
                a[x]=i;
                dfs(x+1);
                b[i]=0;
                a[x]=0;
            }
    }
    
    int main()
    {
        scanf("%d",&n);
        if(n<=10)
        {
            for(int i=1;i<=n;i++) scanf("%d",&h[i]);
            dfs(1);
            printf("%lld
    ",ans);
            return 0;
        }
    /*	if(n<=20)
        {
            for(int i=0;i<n;i++) scanf("%d",&h[i]);
            fake=(1<<n)-1;
            for(int i=0;i<n;i++)
                f[(1<<i)][i]=1ll*h[i]*h[i];
            for(int i=0;i<=fake;i++)
                for(int j=0;j<n;j++)//枚举当前在哪
                {
                    if(!(i&(1<<j))) continue;
                    for(int k=0;k<n;k++)//枚举上一步在哪
                    {
                        if(k==j||!(i&(1<<k))) continue;
                        f[i][j]=max(f[i][j],f[i^(1<<j)][k]+1ll*(h[j]-h[k])*(h[j]-h[k]));
                    } 
                } 
            for(int i=0;i<n;i++) ans=max(ans,f[fake][i]);
            printf("%lld
    ",ans);
            return 0;
        }*/
        else
        {
            for(int i=1;i<=n;i++) scanf("%d",&seq[i]); 
            seq[++n]=0;
            sort(seq+1,seq+1+n,cmp);
            int i=1,j=n;
            while(tot<n)
            {
                tmp[++tot]=seq[j],j--;
                tmp[++tot]=seq[i],i++;
            }
            for(int i=2;i<=n;i++)
                ans+=1ll*(tmp[i]-tmp[i-1])*(tmp[i]-tmp[i-1]);
            printf("%lld
    ",ans);
        }
        return 0;
    }
    

    T3:咕咕咕

    (n<=20)的数据范围让我从头到尾都以为是状压,并在这个思路上死磕了很久...最后手码前三个点搞到了30分hhh。

    其实是可以想出一个(O(4^n))的状压暴力的:枚举子集。首先我们确定对于状态(i),它的子集(j)一定在(0)~(i)中,若(i)&(j)==(j),那么可以确定(j)(i)的子集。

    没有写=w=,下面是lwz dalao的程序==

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #define MAXN 2147483647
    #define ll long long
    
    using namespace std;
    const int mod=998244353;
    int n,m;
    ll sum;
    int a[2001000];
    ll f[2001000],p[20010000];
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
        {
            char s[30];
            scanf("%s",s+1);
            int ret=0;
            for(int j=1;j<=n;j++)
                ret+=(s[j]-'0')*(1<<n-j);
            int x;
            scanf("%d",&x);
            a[ret]=x%mod;
        }
        f[0]=a[0];//f记录到状态的花费
        p[0]=1;//p记录此状态出现的次数
        for(int i=1;i<(1<<n);i++)
        {
            for(int j=0;j<i;j++)
                if((i&j)==j){
                    p[i]+=p[j];//记录自己的次数
                    (f[i]+=f[j])%=mod;//加上子集的花费
                } 
            (f[i]+=a[i]%mod*p[i]%mod)%=mod;//加上自己
        }
        printf("%lld
    ",f[(1<<n)-1]%mod);
        return 0;
    }
    

    正解其实是..数学题?我们完全抛开状压dp的限制,考虑最后的答案其实是可以统计每个带权状态所有可能出现的次数,再乘上最后的权值就行了。

    而如何算出现的次数?考虑乘法原理:把这些状态看成中间状态,分别算从0到它的次数、它到满状态的次数,二者相乘。

    "具有相同1/0个数的状态出现次数一样”,开始并不能发现这个性质:(。其实出现01的位置并不重要重要的是数量,所以我们可以预处理出每个方案的出现次数。

    (f[i])为填(i)个1的方案数,设最后一次填了(j)个,那么(f[i]+=f[i-j]*C(i,j))。其中(j)从1到(i)循环。

    这个故事告诉我们,不要一条路走到黑,有时你觉得十分正确的算法方向也可能不是正解,要灵活啊(qwq)。而且要多膜膜,没坏处的。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    
    using namespace std;
    typedef long long ll;
    ll moder=998244353;
    
    int n,m;
    char op[100];
    ll val,ans,C[50][50],f[50];
    
    void prework()
    {
    	C[0][0]=1;C[1][1]=1;
    	for(int i=1;i<=20;i++) C[i][0]=1,C[i][i]=1;
    	for(int i=1;i<=20;i++)
    		for(int j=1;j<=i;j++)
    			(C[i][j]=C[i-1][j]+C[i-1][j-1])%=moder;
    	f[0]=1;
    	for(int i=1;i<=20;i++)
    		for(int j=1;j<=i;j++)
    			(f[i]=f[i]+f[i-j]*C[i][j])%=moder;
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	prework();
    	for(int i=1;i<=m;i++)
        {
            scanf("%s",op);
            int cnt=0;
            for(int j=0;j<n;j++)
                if(op[j]=='1') cnt++;
            scanf("%lld",&val);
            (ans+=val*f[cnt]%moder*f[n-cnt]%moder)%=moder;
        }
        printf("%lld
    ",ans);
    	return 0;
    }
    

    T4:不围棋

    本想搞到10分的,我输出的是1 1 -1 -1;其实应该直接输出-1 -1.因为放到((1,1))后,自己就没有“气”了。嘤。

  • 相关阅读:
    PHP四种界定符
    设计模式 单例模式与工厂模式
    PHP include与require的区别
    面向对象 static abstract interface 等知识点
    gogland golang 颜色&字体 colors&font 配置文件
    什么是游戏中的帧同步
    kcp协议详解
    kcp流模式与消息模式对比
    kcp源码走读
    kcp结构体字段含义
  • 原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9906045.html
Copyright © 2020-2023  润新知