2017-11-5离线赛总结(NOIP七连测第三场)

失分小结:
估分:100+80+40=220
实际分数:100+10+35=145
和估分相差甚远,主要是由于第一题的难度提升
又在第一题上卡了太久,导致第二题随便打了个暴力后又去水第三题
虽然主体顺序是没有错的,但是心态就不一样了,想着多水点分,又感觉时间来不及,虽然最后第二题想到了最小生成树,但又因为取边取错爆炸,暴力(dp)写错
题解:
Task 1:
第一题如果直接考虑两个数之间的关系的话,还是较为容易的,方案一共就只有 n2 种,我们也只用枚举这么多,然后剩下的就用排列组合推就好了
tip:当无法方案数很多时,可以把方案拆开,对单独的一个小步骤进行分析,然后利用排列组合求解
Task 2:
第二题也较为玄学,直接使用最小生成树算法可以卡过去
dp的大体转移好想,但具体的实现就较为困难
可以推出一个点有几种连接方案:
1.与自己这一列的前一个点相连
2.与自己这一列的后一个点相连
3.与自己对面一列的一个点相连
对于第三种情况,可以贪心地实现
可以推算出若其他点已经相连,那么若要把这个点加入,
就要选择离自己最近的点,然这里又有一个特殊情况:
在我们对面的右边的点实际上并没有被加入到点集里,所以连接离自己最近的右边的点可能才是最优的(在最小生成树的算法中,就是要把这两个离自己最近的点加进去)
然后先不考虑空间优化,可以发现自己在对面的转移实际只有两种情况
而在自己这一列的转移只有一种情况,所以递推式就很容易出来了
代码实现:

#include<bits/stdc++.h>
using namespace std;
#define M 1000005
#define du double

int A[M],B[M];
du dp[2][2][2];
int x3,x1,x2;

du dist(int x,int y){
    return sqrt((long long)(A[x]-B[y])*(A[x]-B[y])+x3);
}

int main(){
    int n,m;
    scanf("%d%d%d%d",&n,&m,&x1,&x2);
    x3=1ll*(x1-x2)*(x1-x2);
    for(int i=1;i<=n;i++)scanf("%d",&A[i]),A[i]+=A[i-1];
    for(int i=1;i<=m;i++)scanf("%d",&B[i]),B[i]+=B[i-1];

    int j=1;
    dp[1][1][1]=dist(1,1);
    for(int i=1;i<=n;i++){//  1  i与j连通    0  i与j不连通 
        if(i>1){
            dp[i&1][j&1][1]=min(dp[!(i&1)][j&1][1]+dist(i,j),
            min(dp[!(i&1)][j&1][1]+A[i]-A[i-1],dp[!(i&1)][j&1][0]+A[i]-A[i-1]+dist(i,j)));
            dp[i&1][j&1][0]=min(dp[!(i&1)][j&1][1],dp[!(i&1)][j&1][0]+A[i]-A[i-1]);
        }   
        while(j<m&&(A[i]>=B[j]||i==n)){

min(dp[i&1][!(j&1)][1]+B[j]-B[j-1],dp[i&1][!(j&1)][0]+B[j]-B[j-1]+dist(i,j)));
            dp[i&1][j&1][0]=min(dp[i&1][!(j&1)][1],dp[i&1][!(j&1)][0]+B[j]-B[j-1]);
        }
    }
    printf("%.2f\n",dp[n&1][m&1][1]);
    return 0;
}

Task 3:
感觉这次的第三题相比于第二题的正解来说更好搞一些
第三题就是dfs加玄学剪枝
如果切了m=1这一档就可以清楚地发现这个方案数是可以根据组合数递推的
这题给出的诡异的切分也很玄妙,前面五十分n小一些,后面50分m小一些
前50分可以直接状压,后50分就直接枚举m的位置,然后用组合数算就好了
代码实现(只有后50分)

#include<bits/stdc++.h>
using namespace std;
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define ll long long
int n,m,P;
struct node{
    int id,pos;
    bool operator <(const node &s)const{return id<s.id;}
}D[10];


int C[35][35],fac[35];
void init(){
    FOR(i,0,30){cout<<666<<endl;
        C[i][0]=C[i][i]=1;
        FOR(j,1,i-1)C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
    }
    fac[0]=1;
    FOR(i,1,30)fac[i]=1ll*fac[i-1]*i%P;
}

bool mark[35],used[35];
int ans;

void dfs(int x,int res,int tmp){
    res+=D[x].pos-D[x-1].pos;
    if(x>m){
        tmp=1ll*tmp*fac[res]%P;
        ans=(ans+tmp)%P;
        return;
    }
    int sum=0;
    for(int i=1;i<=n;i++)used[i]=mark[i];
    for(int i=D[x].pos;i<=n;i++)if(!mark[i]){
        sum++;
        if(sum>res)break;
        dfs(x+1,res-sum,tmp*fac[sum]%P*C[sum][res]%P);
        mark[i]=1;
    }
    for(int i=1;i<=n;i++)mark[i]=used[i];
}


int main(){

    init();
    scanf("%d%d%d",&n,&m,&P);
    FOR(i,1,m)scanf("%d%d",&D[i].id,&D[i].pos);
    sort(D+1,D+1+m);
    D[m+1].pos=n;
    dfs(1,0,1);
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值