【NOIP模拟】赤壁情(DP)

92 篇文章 0 订阅
87 篇文章 0 订阅

Description

这里写图片描述

Solution

这是一个计数的问题,一个关于排列的方案数的问题。
但是用一般的排列求是不行的,对于插入排列因为要去绝对值,所以很麻烦。
对于绝对值来说,我们可以把贡献给拆开,|i-j|把它拆开,那么就从小到大插入,先放入的j放入值-j,后放入的值i放入值i,因为不知道最后的位置是什么,所以一开始只存储相对的位置,那么到最后个数成为n个的时候就是一个排列了。
然后每次插入的值有几种情况。
1、首先可以对立成一段,这样也有两种情况,可以在中间独立成一段放入-2*i,还可以在边界上独立成一段放入-i。
2、也可以在已经有的段上,接上去,那么如果不是边界上的话,那么贡献是0的,因为在另一边上放的数肯定还会比它大。如果是边界上的话,那么直接贡献i就可以了。
3、还可以把相邻的两段合并,就是让这些数所有的相邻关系确定下来,那么直接贡献2*i的值。
那么DP方程设f[i][j][k][l]为做到第i个数,j段,值为k,边界确定好的个数为l。
这里的段的意思就是相邻关系已经确定的数量,这之间是不能插入数的。
因为k有可能为负数,所以还要同时加上一个数。
最后这道题还要卡精度,可以用__float128,这个精度可以,输出的时候*10一个个输出个位。但是float128很慢,所以特判如果要求的精度较大的话,那么就用float128,否则就用double。

Code

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef __float128 d;
typedef double db;
const int maxn=107;
int i,j,k,l,t,n,m,u,v,p,z;
db f[2][maxn][maxn*maxn][3],y;
d f1[2][maxn][maxn*maxn][3],yy,ans;
d an1;
void w(d a){ 
    t=a;printf("%d.",t);
    while(p--){
        a=(a-t*1.0)*10.0+(!p)*0.5;
        t=a;printf("%d",t);
    }printf("\n");
}
int main(){
//  freopen("river.in","r",stdin);
//  freopen("river.out","w",stdout);
    scanf("%d%d%d",&n,&m,&p);z=n*n/2;
    if(n*(n-1)/2<m){printf("-1\n");return 0;}m+=z;
    if(p<=8){
    f[0][0][z][0]=1;
    fo(i,1,n){
        v=(u^1);memset(f[v],0,sizeof(f[v]));
        fo(j,0,i){
            fo(k,0,z*2){
                fo(l,0,2){
                    y=f[u][j][k][l];if(!y)continue;
                    f[v][j+1][k-2*i][l]+=y*(j-l+1);
                    if(l<2){
                        f[v][j+1][k-i][l+1]+=y*(2-l);
                        if(j)f[v][j][k+i][l+1]+=y*(2-l);
                    }
                    if(j)f[v][j][k][l]+=y*(2*j-l);
                    if(j>1)f[v][j-1][k+2*i][l]+=y*(j-1);
                }
            }
        }
        u=v;
    }fo(i,m,n*n)ans+=f[u][1][i][2];
    }
    else{
    f1[0][0][z][0]=1;
    fo(i,1,n){
        v=(u^1);memset(f1[v],0,sizeof(f1[v]));
        fo(j,0,i){
            fo(k,0,z*2){
                fo(l,0,2){
                    yy=f1[u][j][k][l];if(!yy)continue;
                    f1[v][j+1][k-2*i][l]+=yy*(j-l+1);
                    if(l<2){
                        f1[v][j+1][k-i][l+1]+=yy*(2-l);
                        if(j)f1[v][j][k+i][l+1]+=yy*(2-l);
                    }
                    if(j)f1[v][j][k][l]+=yy*(2*j-l);
                    if(j>1)f1[v][j-1][k+2*i][l]+=yy*(j-1);
                }
            }
        }
        u=v;
    }fo(i,m,n*n)ans+=f1[u][1][i][2];
    }
    fo(i,1,n)ans=ans/i;
    an1=ans;
    w(an1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值