• [NOIP模拟33]反思+题解


    又考了一次降智题……

    拿到T1秒出正解(可能是因为我高考数学数列学的海星?),分解质因数以后用等比数列求和计算每个因子的贡献。但是当时太过兴奋把最后的$ans imes =$打成了$ans +=$,还过掉了sb样例。觉得自己AC稳了就先交了。

    然后去看T3。没什么思路就先打了个暴力,以为最后一个看似不可做的点是给特判分的就打了一堆特判(没想到真的是用来防AK的)。

    最后搞T2,实在是搞不懂题就打了个乱搞,样例也可过就扔掉了。

    最后对拍T1的时候发现答案完全不对,因为只剩30min辽所以我当场慌的一批,压根就没想改之前的代码,xjb打了一个70分的暴力赶在考试结束前调了出来。

    考后4minA掉T1。一个字符的差距。

    如果在最后发现自己之前的代码有错误,一定要先想能不能改过来再考虑打暴力止损。一开始心态平稳时想的思路大概率是正确的,如果对拍出错很有可能是细节问题。

    A.春思

    水题。对A分解质因数,把因子的次数都乘上B就得到了原数。之后考虑因数和$d(x)$的积性函数性质。对于每一个质因子次幂,它的约数和相当与一个等比数列和,那么把每一个质因子次幂的约数和乘起来就得到了最终结果。(别告诉我您不知道等比数列求和公式)

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const ll mod=9901;
    const int N=3e6+5;
    ll a,b;
    ll fact[N],cnt,mi[N],ans=1;
    ll qpow(ll x,ll y)
    {
    	ll res=1;x=x%mod;
    	while(y)
    	{
    		if(y&1)res=res*x%mod;
    		x=x*x%mod;
    		y>>=1;
    	}
    	return res;
    }
    int main()
    {
    	scanf("%lld%lld",&a,&b);
    	ll tmp=a;
    	for(ll i=2;i*i<=tmp;i++)
    	{
    		if(tmp%i==0)
    		{
    			fact[++cnt]=i;ll num=0;
    			while(tmp&&tmp%i==0)num++,tmp/=i;
    			mi[cnt]=num;
    		}
    	}
    	if(tmp>1)fact[++cnt]=tmp,mi[cnt]=1;
    	for(int i=1;i<=cnt;i++)
    	{
    		ll m=mi[i]*b;
    		(ans*=(qpow(fact[i],m+1)-1)*qpow(fact[i]-1,mod-2)%mod)%=mod;
    	}
    	cout<<ans%mod<<endl;
    	return 0;
    }
    

     B.密州盛宴

    显然,如果想符合要求就必须保证每个人随时都在吃,那么自然0越靠前越优,而且0的个数不能超过n个。

    考虑比较直观地确定方案是否合法的方式。把0的值赋成-1,从序列末尾往前扫,维护后缀和。一旦某时刻后缀和$< -1$,就可以确定方案是不合法的。

    所以可以从末尾挑C个0挪到开头,二分C即可。这是70分的做法。

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    const int N=2e7+5;
    int n,m,a[N],tot;
    bool check(int x)
    {
        int val=x,sum=0;
        for(int i=2*n;i;i--)
        {
            if(sum<=-2)return 0;
            if(a[i]==-1)
                if(val){val--;continue;}
            sum+=a[i];
        }
        if(sum+x*(-1)<=-2)return 0;
        return 1;
    }
    
    void work()
    {
        tot=0;
        char s[1000005];
        for(int i=1;i<=m;i++)
        {
            scanf("%s",s+1);int len=strlen(s+1),tmp;
            scanf("%d",&tmp);
            while(tmp--)
                for(int j=1;j<=len;j++)
                    a[++tot]=(s[j]=='1'?1:-1);
        }
        int cnt0=0;
        for(int i=1;i<=2*n;i++)
            if(a[i]==-1)cnt0++;
        if(cnt0>n)
        {
            puts("-1");
            return ;
        }
        int l=0,r=2*n,res;
        while(l<=r)
        {
            int mid=l+r>>1;
            if(check(mid))r=mid-1,res=mid;
            else l=mid+1;
        }
        cout<<res<<endl;
    }
    
    int main()
    {
        while(scanf("%d%d",&n,&m)==2)
        {
            if(!n&&!m)break;
            work();
        }
        return 0;
    }
    

     可以发现,如果从已经扫到的0里挑一个扔到前面去,目前的后缀和就会+1。所以答案转化为求整个序列后缀和的最小值后取绝对值再-1。

    对于每一个给出的循环节,计算这一段的后缀和,并记录过程中后缀和的最小值。对于一整段的后缀和是否>0分类讨论一下更新答案即可。

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<string>
    #include<vector>
    using namespace std;
    typedef long long ll;
    ll n;
    int m;
    vector<int> a[100005];
    ll num[100005];
    void work()
    {
        for(int i=1;i<=m;i++)a[i].clear();
        char s[100005];
        ll cnt0=0;
        for(int i=1;i<=m;i++)
        {
            ll cnt00=0;
            scanf("%s",s+1);
            int len=strlen(s+1);
            for(int j=1;j<=len;j++)
                a[i].push_back(s[j]=='1'?1:-1),cnt00+=(s[j]=='0');
            scanf("%lld",&num[i]);
            cnt00*=num[i];
            cnt0+=cnt00;
        }
        //cout<<"The num of 0: "<<cnt0<<endl;
        if(cnt0>n)
        {
            puts("-1");return ;
        }
        ll sum=0,cnt=0,ans=0x3f3f3f3f;
        for(int k=m;k;k--)
        {
            cnt=0;ll minn=0x3f3f3f3f;
            int sz=a[k].size();
            for(int i=sz-1;i>=0;i--)
            {
                cnt+=a[k][i];
                minn=min(minn,cnt);
            }
            if(cnt>0)
                ans=min(ans,sum+minn);
            else
                ans=min(ans,sum+cnt*(num[k]-1)+minn);
            sum+=cnt*num[k];
        }
        ans=abs(ans)-1;
        cout<<(ans>=0?ans:0)<<endl;
    }
    
    int main()
    {
        while(scanf("%lld%d",&n,&m)==2)
        {
            if(!n&&!m)break;
            work();
        }
        return 0;
    }
    

     C.赤壁情

    又是一道神dp……

    如果我们能把每一个赤壁之意对应的方案数都求出来,那么就能统计一下再除个阶乘得到答案了。所以把这题转化成计数dp。

    我们把形成排列的过程看作把$1,2,...n$放到$n$个位置的过程。为了能够转移,应该把这n个数从小到大逐个放入。

    定义状态数组$f[i][j][k][l]$。i表示从小到大放到了i,j表示目前的序列上有j段数(它们被一些空位隔开),k表示边界(最左和最右端)放了几个数(0 or 1 or 2),l表示目前赤壁之意为l。

    考虑插入第i个数对总赤壁之意的贡献:

     具体转移还是很繁琐的,有13个转移方程(可以合并成5个)。我们以其中的一个为例:

    $f[now][j][0][l]+=f[pre][j][0][l]*j*2;$

    它的含义是:插入一个数,并且这个数位于一段的左右端点(恰好延长了一段,没有单独成段或连接左右两段),那么它的方案可以是之前基础上从j段里选一个放,并且每一个都可以选左右两端。且对目前的赤壁之意没有影响。

    以此类推转移即可。第一维要滚动,因为要枚举段数所以提前算一下范围,还有就是一开始赤壁之意可能为负所以集体加上一个base防止下标溢出。

    至于最后一个防AK点……__float128水过好了QAQ。

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #define re register
    using namespace std;
    const int base=5005;
    int n,m,K;
    int part[105];
    namespace qj
    {
    	__float128 f[2][105][4][10015];
    	void Main()
    	{
    		int now=1,pre=0;
    		f[now][1][0][-2+base]=1;
    		f[now][1][1][-1+base]=2;
    		f[now][1][2][base]=1;
    		part[1]=1;
    		for(re int i=2;i<=n;i++)
    		{
    		    now^=1;pre^=1;
    		    part[i]=min(i,n-i+1);
    		    int fw=min(5000,i*(i+1));
    		    for(re int j=1;j<=part[i];j++)
    				for(re int l=-fw+base;l<=fw+base;l++)
    					for(re int k=0;k<=2;k++)
    						f[now][j][k][l]=0;
    
    			for(re int j=1;j<=part[i-1];j++)
    			{
    				for(re int l=-fw+base;l<=fw+base;l++)
    				{
    					#define val0 f[pre][j][0][l]
    					#define val1 f[pre][j][1][l]
    					#define val2 f[pre][j][2][l]
    					//cout<<i<<' '<<val0<<' '<<val1<<' '<<val2<<endl;
    					f[now][j+1][0][l-i*2]+=val0*(j+1);//1
    					f[now][j][0][l]+=val0*j*2;//2
    					f[now][j-1][0][l+i*2]+=val0*(j-1);//3
    					f[now][j+1][1][l-i]+=val0*2;//4
    					f[now][j][1][l+i]+=val0*2;//5-----------------------
    					f[now][j+1][1][l-2*i]+=val1*j;//6
    					f[now][j][1][l]+=val1*(j*2-1);//7
    					f[now][j-1][1][l+2*i]+=val1*(j-1);//8
    					f[now][j+1][2][l-i]+=val1;//9
    					f[now][j][2][l+i]+=val1;//10----------------------
    					f[now][j+1][2][l-2*i]+=val2*(j-1);//11
    					f[now][j][2][l]+=val2*(j*2-2);//12
    					f[now][j-1][2][l+2*i]+=val2*(j-1);//13
    				}
    			}
    
    		}
    		__float128  ans=0;
    		for(re int i=m;i<=base;i++)
    			ans+=f[now][1][2][i+base];
    		for(re int i=2;i<=n;i++)
    			ans/=1.0*i;
    		printf("%d",(int)ans);
    		ans-=(int)ans;
    		putchar('.');
    		for(re int i=1;i<=K;i++)
    		{
    			ans*=10.0;
    			int t=(ans+(i==K?.5:0));
    			printf("%d",t);
    			ans-=t;
    		}
    		printf("
    ");
    	}
    }
    double f[2][105][4][10015];
    int main()
    {
        scanf("%d%d%d",&n,&m,&K);
    	if(K>=15)
    	{
    		qj::Main();
    		return 0;
    	}
        int now=1,pre=0;
        f[now][1][0][-2+base]=1;
        f[now][1][1][-1+base]=2;
        f[now][1][2][base]=1;
        part[1]=1;
        for(re int i=2;i<=n;i++)
        {
            now^=1;pre^=1;
            part[i]=min(i,n-i+1);
            int fw=min(5000,i*(i+1));
            for(re int j=1;j<=part[i];j++)
                for(re int l=-fw+base;l<=fw+base;l++)
                    for(re int k=0;k<=2;k++)
                        f[now][j][k][l]=0;
    
            for(re int j=1;j<=part[i-1];j++)
            {
                for(re int l=-fw+base;l<=fw+base;l++)
                {
                    #define val0 f[pre][j][0][l]
                    #define val1 f[pre][j][1][l]
                    #define val2 f[pre][j][2][l]
                    //cout<<i<<' '<<val0<<' '<<val1<<' '<<val2<<endl;
                    f[now][j+1][0][l-i*2]+=val0*(j+1);//1
                    f[now][j][0][l]+=val0*j*2;//2
                    f[now][j-1][0][l+i*2]+=val0*(j-1);//3
                    f[now][j+1][1][l-i]+=val0*2;//4
                    f[now][j][1][l+i]+=val0*2;//5-----------------------
                    f[now][j+1][1][l-2*i]+=val1*j;//6
                    f[now][j][1][l]+=val1*(j*2-1);//7
                    f[now][j-1][1][l+2*i]+=val1*(j-1);//8
                    f[now][j+1][2][l-i]+=val1;//9
                    f[now][j][2][l+i]+=val1;//10----------------------
                    f[now][j+1][2][l-2*i]+=val2*(j-1);//11
                    f[now][j][2][l]+=val2*(j*2-2);//12
                    f[now][j-1][2][l+2*i]+=val2*(j-1);//13
                }
            }
    
        }
        double ans=0;
        for(int i=m;i<=base;i++)
            ans+=f[now][1][2][i+base];
        for(double i=2.0;i<=n;i+=1.0)
            ans/=i;
        switch(K)
        {
            case 0:printf("%d
    ",(int)ans);break;
            case 1:printf("%.1lf
    ",ans);break;
            case 2:printf("%.2lf
    ",ans);break;
            case 3:printf("%.3lf
    ",ans);break;
            case 4:printf("%.4lf
    ",ans);break;
            case 5:printf("%.5lf
    ",ans);break;
            case 6:printf("%.6lf
    ",ans);break;
            case 7:printf("%.7lf
    ",ans);break;
            case 8:printf("%.8lf
    ",ans);break;
         }
        return 0;
    }
    
  • 相关阅读:
    你所不知道的mfc…mfc项目索引 &mfc调优指南 &mfc vc添加添加子功能指南
    Cu 大彻大悟内存管理 mm (update 0410)
    [转]Linux iostat监测IO状态
    linux virtual memory layout by moniskiller upload [读书笔记]
    河畔找到的 面经笔经
    【转】Linux本地磁盘(硬盘)介绍
    读写UTF8、Unicode文件
    codesmith执行时提示“调用的目标发生了异常”的处理过程经验。
    DB2表信息以及字段信息的表
    iBatis.NET获取resultMap相关数据
  • 原文地址:https://www.cnblogs.com/Rorschach-XR/p/11437246.html
Copyright © 2020-2023  润新知