noip模拟2[排序·划艇·放棋子]

今天比昨天更惨,惨炸了

看到T1不会,跳!!!

T2不会,再跳!!!!

T3不会,后面没题了;;;;

无奈无奈,重新看T1,然鹅时间已经过去了一个小时

然而我一想不出题来就抱着水瓶子喝水,然后跑厕所,然后思路一直断。。。。

但是吧看到题解之后一切就恍然大悟了

哈哈哈哈没事慢慢锻炼,以后的考试一定得上100吧

 

看到这分数,怎么样,震惊到了吧,倒数第三那个是我,这都是大佬,别看我还有15分,蒙出来的,一点技术含量都没有

T1排序

 

 

N<=12;;;;

可以看到这数据属实很小啊

其实考场上我想到了正解,可惜我没敢打,(凭什么这题正解是dfs)那可是深搜啊!!!(暴力象征啊,怎么能是他呢?!?!?!?)

说正解吧;;

可以得到:对于所有操作来说,操作顺序对结果没有任何影响

(哼考场上我发现这个规律了,所以打了个阶乘就走了,没想到还有15pts)

所以我们可以从最小的i开始dfs

首先我们要判断这样的块中有多少不符合规律的(注意当前如果是2x那么判断的时候要判断长度为2x-1的区间(这很显然吧))

有四种情况:

  1、有2个以上的不符合规律的区间。那就没法交换啦,只让交换一次,所以可以直接返回了,00000

  2、没有不符合规律的。那就直接去下一层x+1

  3、有1个不符合规律的区间。那就交换它前后的区间就行了

  4、有2个不符合规律的区间。那么这时候可以两个不符合的互相交换,没准就成了呢(注意交换完了之后要判断,然后再换回去)判断成功后才能进入下一层

再定义一个计数器sum,记录一共交换了多少次

还有dfs的时候别忘了sum++,然后用完之后在sum--;

判断是否符合规律的时候有两种情况:1、前面的比后面的大

                 2、前后的差值大于1(这个我是考后才发现的,原来这个区间里的数是连续的,一开始我还想离散化嘞!!!)

#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1<<13;
int n,m,nn;
int a[N];
int jc[15],po[15];
int ans,sum;
bool flag(int l,int r){
    if(l==r)return true;
    for(re i=l+1;i<=r;i++)
        if(a[i]<a[i-1]||a[i]-a[i-1]>1)return false;
    return true;
}
void swp(int l,int r,int len){
    int b[N];
    for(re i=0;i<len;i++)b[i]=a[l+i];
    for(re i=0;i<len;i++)a[l+i]=a[r+i];
    for(re i=0;i<len;i++)a[r+i]=b[i];
}
void dfs(int x){
    int num=0;
    int p[100];
    for(re i=1;i<=po[n];i+=po[x])
        if(!flag(i,i+po[x]-1))
            p[++num]=i,p[++num]=i+po[x-1];
    if(num>4)return ;
    if(num==0)
        if(x==n)ans+=jc[sum];
        else dfs(x+1);
    else if(num==2){
        sum++;
        swp(p[1],p[2],po[x-1]);
        if(flag(p[1],p[1]+po[x]-1))
            if(x==n)ans+=jc[sum];
            else dfs(x+1);
        swp(p[1],p[2],po[x-1]);
        sum--;
    }
    else if(num==4){
        sum++;
        for(re i=1;i<=2;i++){
            for(re j=3;j<=4;j++){
                swp(p[i],p[j],po[x-1]);
                if(flag(p[1],p[1]+po[x]-1)&&flag(p[3],p[3]+po[x]-1))
                    if(x==n)ans+=jc[sum];
                    else dfs(x+1);
                swp(p[i],p[j],po[x-1]);
            }
        }
        sum--;
    }
}
int main(){
    scanf("%d",&n);
    po[0]=1;jc[0]=1;
    for(re i=1;i<=n;i++)jc[i]=jc[i-1]*i,po[i]=po[i-1]*2;
    for(re i=1;i<=po[n];i++){
        scanf("%d",&a[i]);
    }
    dfs(1);
    printf("%d",ans);
}
Code

T2划艇

 

 

 

这是这场考试中最难的一个题了

考试的时候完全没有思路,想要通过dfs骗来9分没想到爆零了 ,害害害害害

 首先想到的一定是dp;

(不对不对不对,首先想到的应该是离散化,毕竟数据范围太大了。。。QAQ)

设f[i][j]表示第i个学校派出j条船所能达到的方案数

啊呀,你突然就发现,这每个学校的a[i]和b[i]达到的区间会有重复的部分啊(完了完了完了转移不了了,然后你就放弃了dp,成功的躲避了正解)

凡事都得试试看啊,不试试就放弃,是不是傻啊

突然你就发现好像可以转移诶

这时候涉及到离散化的时候的一个小细节------------左闭右开(其实左开右闭也可以啦,都是为了让整个区间不重不漏,就看个人习惯了)

将a[i]和b[i]+1塞到离散化数组里,在设计转移方程的时候,注意循环的范围是(<)

转移的时候要分两种情况:

    1、i-1这个学校派出的划艇不在a[i]~b[i]-1中,那么就可以直接转移,此时的i学校可以有方案数就是C(b[i]-a[i],1),那么再乘上前面所有的方案数就好了

  (那么问题来了,怎么得到前面所有的方案数呢。。

   设sum[i][j]表示f[i][j]的二维前缀和,怎么求呢:sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+f[i][j];

   可能这也叫容斥原理吧,把多加上的给剪掉就好了)

    2、i-1和它以前的学校有一部分派出了在这个区间之内的划艇,那么就要再设一个未知数k,它表示从第k个开始,后面的学校派出了a[i]~b[i]-1只划艇

     这个转移比较难设计,f[i][j]+=组合数*sum[k-1][j-1];这个组合数怎么求呢

   (再引入一个数组,len[ ],表示离散化数组里lsh[i]~lsh[i+1]的长度,(向后lsh[i+1]的长度,而不是向lsh[i-1]的,不然转移方程要修改的

     首先我们要在了len[i]这么长的区间内寻找k个数(就像在一个递增区间内找k个数递增一样)有C(len[i],k)这么多种选择,当然这其中会有学校不派出划艇

     所以在i-k-1个学校中选出p-2学校来(不包括i和k)C(i-k+1,p-2)这个要乘好多p从1~i-k+1所以要手动搞一下,把它展开在合并,最后变成一个可以边转移边求出来的组合数,乘上就好了

还要注意,sum[i][j],不仅要吧a[i]~b[i]-1的f[i][j]加上,别的地方也要加上

然后我们就可以开开心心的把代码粘上了!!!!

记得开long long哟,不开long long见祖宗哦

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register int
const int mod=1e9+7;
const int N=505;
int n;
int l[N],r[N],len[N*2];
int lsh[N*2],lh,ls[N*2];
int inv[N];
int dp[N][N*2],sum[N][N*2];
map<int,int> p;
signed main(){
    scanf("%lld",&n);
    inv[1]=inv[0]=1;
    for(re i=2;i<=500;i++)
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    for(re i=1;i<=n;i++){
        scanf("%lld%lld",&l[i],&r[i]);
        lsh[i*2-1]=l[i];
        lsh[i*2]=r[i]+1;
    }
    sort(lsh+1,lsh+2*n+1);
    for(re i=1;i<=2*n;i++){
        if(lsh[i]!=lsh[i-1]){
            ls[++lh]=lsh[i];
            p[lsh[i]]=lh;
        }
    }
    for(re i=1;i<=n;i++){
        l[i]=p[l[i]];
        r[i]=p[r[i]+1];
    }
    sum[0][0]=dp[0][0]=1;
    ls[lh+1]=ls[lh]+1;
    for(re i=1;i<=lh;i++)len[i]=ls[i+1]-ls[i];
    for(re i=1;i<=n;i++)sum[i][0]=1;
    for(re i=1;i<=lh;i++)sum[0][i]=1;
    for(re i=1;i<=n;i++){
        for(re j=1;j<l[i];j++)
            sum[i][j]=(sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+dp[i][j]+mod)%mod;
        for(re j=l[i];j<r[i];j++){
            dp[i][j]=sum[i-1][j-1]*len[j]%mod;
            int c=len[j]-1;
            int can=1;
            for(re k=i-1;k>=1;k--){
                if(l[k]<=j&&j<r[k]){
                    can++;
                    c=c*((can+len[j]-2)*inv[can]%mod)%mod;
                    dp[i][j]=(dp[i][j]+sum[k-1][j-1]*c%mod)%mod;
                }
            }
            sum[i][j]=(sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+dp[i][j]+mod)%mod;
        }
        for(re j=r[i];j<=lh;j++)
            sum[i][j]=(sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+dp[i][j]+mod)%mod;
    }
    printf("%lld",sum[n][lh]-1);
}
Code

 

T3放棋子

感谢赵队一句话点懂了我

二维转移不了就用三维的啊

那么我瞬间感觉自己又行了

设f[i][j][k]为占据i行j列并且放了前k种颜色的棋子的方案数,然后你发现这根本不能直接求出来!!!

所以再设,g[i][j][k]为占据i行j列(这里指的是占满,就是前i行和前j列中哪个格子都不能再放其他种类的棋子了)并且放了k个相同种类的棋子

可以看到f[i][j][k]可以由f[1~i][1~j][ ]转移过来,那么这个式子我打不出来,所以你们自己看代码,总共五层循环!!!

  f[i][j][k]转移的时候要乘组合数,就是空出哪几行和哪几列的方案数,最后要乘上g[i][j][col[k]](col[k]就是输入的第k种棋子的个数)

那么g[i][j][k]咋求嘞,你又有重大发现,这个不能直接求 ------废话!!

还是容斥原理一下呗:g[i][j][k]=C(i*j,k)-g[a][b][k]*C(i,i-a)*C(j,j-b)就是减去那些有空行空列的情况

然后就是代码时间

 

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int mod=1e9+9;
int n,m,e;
int col[15];
ll f[35][35][15];
ll g[35][35][1005];
ll c[1005][1005];
ll ans;
signed main(){
    scanf("%d%d%d",&n,&m,&e);
    for(re i=1;i<=e;i++)
        scanf("%d",&col[i]);
    c[0][0]=1;
    for(re i=1;i<=1000;i++){
        c[i][0]=c[i][i]=1;
        for(re j=1;j<i;j++){
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
        }
    }
    for(re i=1;i<=n;i++)
        for(re j=1;j<=m;j++)
            for(re k=max(i,j);k<=i*j;k++){
                g[i][j][k]=c[i*j][k];
                for(re a=1;a<=i;a++)
                    for(re b=1;b<=j;b++){
                        if(a*b<k||(a==i&&b==j))continue;
                        g[i][j][k]=(g[i][j][k]-g[a][b][k]*c[i][a]%mod*c[j][b]%mod+mod)%mod;
                    }
            }
    f[0][0][0]=1;
    for(re i=1;i<=n;i++)
        for(re j=1;j<=m;j++){
            for(re k=1;k<=e;k++){
                if(i*j<col[k])continue;
                for(re a=0;a<=i;a++)
                    for(re b=0;b<=j;b++)
                        if((i-a)*(j-b)>=col[k])
                            f[i][j][k]=(f[i][j][k]+f[a][b][k-1]*c[n-a][i-a]%mod*c[m-b][j-b]%mod*g[i-a][j-b][col[k]]%mod)%mod;
            }
            ans=(ans+f[i][j][e])%mod;
        }
    printf("%lld",ans);
}
View Code

 

完结

 

 

 

l=1i1r=1j1f[l][r]+l=1k1r=1j1f[l][

posted @ 2021-05-23 20:57  fengwu2005  阅读(98)  评论(1编辑  收藏  举报