20191016

前言

  • 虽然还是不行,但状态稍有回升??
  • T1又出现了做达哥的「周」时的情况,就是觉得太简单了而不敢轻易结束。
  • 考试10分钟就打完了T1,然而又花了近20分钟验证一个结论,而且也不敢交,考试快结束时才怼了上去……
  • 啊还得感谢出题人,太良心了,三个题代码竟然都都不上K,T1T2甚至没上500B,出题人rp++。

T1

  • 如果n>a*b或n<a+b-1直接puts("No")。
  • 否则我们先分出b段,每段只有1个数。
  • 然后我们将剩下的n-b个数插入段,从前往后扫描段,如果当前段的数量不是a并且还有剩余的数就一直加。
  • 然后保证段内元素递增,段间元素递减即可。
  • 具体做法是从前往后扫描段,设当前段的数量为k,那么把最大的k个数从小到大输出,然后将最大的数n变为n-k即可。
  • 比如n=8,a=4,b=3,那么三段的数量为4,3,1,最终得到的序列是5,6,7,8,2,3,4,1。
  • 时间复杂度$\Theta(TN)$,空间复杂度$\Theta(1)$。代码复杂度O(0)。
#include<cstdio>
int main(){
    int T,n,a,b,la;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&n,&a,&b);
        if(n<a+b-1 || n>1ll*a*b){puts("No");continue;}
        puts("Yes"),la=n-b;
        for(register int i=1,now;i<=b;++i){
            now=1;
            while(now<a&&la)--la,++now;
            for(register int j=n-now+1;j<=n;++j)printf("%d ",j);
            n-=now;
        }
        puts("");
    }
    return 0;
}
View Code

T2

  • 先将给出的a数组从小到大排序。
  • 设前缀和为sum,则当$\frac{a_i}{2}>sum_{i-1}$时,$(sum_{i-1},\frac{a_i}{2}]$区间中的值一定不美妙。
  • 那么我们只需证明$[\frac{a_i}{2},sum_i]$区间中的值都是美妙的就可以线性递推了对吧。
  • 首先区间T$[\frac{sum_i}{2},sum_i]$,区间Q$[\frac{sum_{i-1}}{2},sum_{i-1}]$和区间L$[\frac{a_i}{2},a_i]$中的值一定美妙。
  • $\frac{a_i}{2}\geqslant sum_{i-1}$时$a_i\geqslant\frac{sum_i}{2}$,将T和L合并得证。
  • 当$\frac{a_i}{2}<sum_{i-1}$时$sum_{i-1}>\frac{sum_i}{2}$,将T和Q合并后就成了$[\frac{a_{i-1}}{2},sum_{i-1}]$的子问题了。
  • 看上去可以?
  • 那么直接线性递推即可,时间复杂度$\Theta(NlogN)$,空间复杂度$\Theta(N)$。
#include<cstdio>
#include<algorithm>
int n;
int a[100000];
inline int read(){
    int ss(0);char bb(getchar());
    while(bb<48||bb>57)bb=getchar();
    while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar();
    return ss;
}
int main(){
    n=read();
    for(register int i=0;i<n;++i)a[i]=read();
    std::sort(a,a+n);
    long long w=0,ans=0;
    for(register int i=0;i<n;++i){
        if((a[i]+1>>1)>w)ans-=(a[i]+1>>1)-w-1;
        w+=a[i];
    }
    printf("%lld",ans+w);
    return 0;
}
View Code

T3

  • 考场没有想到DP。
  • 那些性质倒是都知道,就是不会用,只能打暴搜。
  • 无限制的卡特兰数还没开双倍数组直接挂20。
  • 暴搜用了一些非常骚的位运算和检验操作,放个代码纪念一下。
  • 代码。
  • 好的继续。
#include<cstdio>
#define ll long long
using namespace std;
int const N=804,M=1004,mod=1e9+7;
int T,n,m,s,qj,tot;
int q[11],bs[11],mp[11],ps[1<<10];
int a[M],b[M];
ll fac[N],inv[N];
inline ll power(ll x,int y){
    ll ans=1;
    for(;y;y>>=1,x=x*x%mod)
        if(y&1)ans=ans*x%mod;
    return ans;
}
inline ll C(int x,int y){
    return x<y?0:fac[x]*inv[y]%mod*inv[x-y]%mod;
}
inline int read(){
    int ss(0);char bb(getchar());
    while(bb<48||bb>57)bb=getchar();
    while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar();
    return ss;
}
bool check(int l,int r,int x){
    return x>n||(q[x]^l&&check(l,q[x]-1,x+1))||(q[x]^r&&check(q[x]+1,r,x+q[x]-l+1));
}
void dfs(int x){
    if(x>n){tot+=!check(1,n,1);return ;}
    for(int i=qj^s,now;i;i-=i&-i){
        if(mp[now=ps[i&-i]]&s)continue;
        s|=bs[now],q[now]=x;
        dfs(x+1),s^=bs[now];
    }
    return ;
}
int main(){
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    T=read();
    fac[0]=fac[1]=1;
    for(register int i=2;i<N;++i)fac[i]=fac[i-1]*i%mod;
    inv[N-1]=power(fac[N-1],mod-2);
    for(register int i=N-1;i;--i)inv[i-1]=inv[i]*i%mod;
    for(register int i=1;i<=10;++i)ps[bs[i]=1<<i-1]=i;
    while(T--){
        n=read(),m=read();
        for(register int i=1;i<=m;++i)a[i]=read(),b[i]=read();
        if(!m){printf("%lld\n",(C(n<<1,n)-C(n<<1,n-1)+mod)%mod);continue;}
        if(n<=10){
            for(register int i=1;i<=n;++i)mp[i]=0;
            tot=0,qj=(1<<n)-1;
            for(register int i=1;i<=m;++i)mp[a[i]]|=bs[b[i]];
            dfs(1);
            printf("%d\n",tot);
            continue;
        }
        puts("0");
    }
    return 0;
}
真·代码
  • 正解是设dp[i][j]表示根节点为i子树大小为j的合法方案。
  • 首先前序遍历对树的约束有一些性质。如下(直接粘题解):
  • 1. 这棵树的编号满足小根堆性质。
    2. 这棵树的某个子树编号连续, 为[root,root+size-1]。
    3. 这棵树左子树中的结点编号都小于右子树。
    4. 这棵树任意一个有左子节点的节点的左子节点编号必定为该节点编号+1。
  • 然后我们再考虑题目中给出的限制。
  • 再粘一波题解……:
  • 首先我们强制a<b,然后将限制转化为“强制a在b前 或“强制a在b后。因为a<b,再加上上面的性质, 所以a和b只有两种位置关系:
    1. a是b的祖先。
    2. a在LCA的左子树且b在LCA的右子树。
  • 我们来分析一下两种限制条件:
    限制1: 强制a在b后:
    这种情况下a一定是b的祖先, 因为如果他们的位置关系是第二种的话,a在中序遍历中一定在b之前。所以b一定在a的左子树中。
    限制2: 强制a在b前:
    这种情况下两种位置关系都有可能, 如果是位置关系1则b在a的右子树, 如果是位置关系2则b不在该子树中. 这种限制和b不在a的左子树中等价。
  • 因为子树中结点编号连续, 所以上述限制条件都可以转化为b对a的左子树大小的限制。
    对于第一种限制, 则根据性质2和4,左子树大小不小于b-a是该限制被满足的充要条件。
    对于第二种限制, 左子树大小小于b-a是该限制被满足的充要条件。
  • 所以我们只需要将每个节点的左子树大小限制预处理出来即可,方式就是对于每个b都计算出限制1的最大值和限制2的最小值。
  • 这样就可以开心的DP了。
  • $dp[i][j]=\sum\limits_{k}dp[i+1][k]*dp[i+k+1][j-k-1]$。k表示符合i节点限制的节点编号。
  • 时间复杂度$\Theta(TN^3)$,空间复杂度$\Theta(N^2)$。
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
int const N=402,mod=1e9+7;
int n,m,T;
long long dp[N][N];
int pr[N],nt[N];
inline int read(){
    int ss(0);char bb(getchar());
    while(bb<48||bb>57)bb=getchar();
    while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar();
    return ss;
}
inline int min(int x,int y){
    return x<y?x:y;
}
inline int max(int x,int y){
    return x>y?x:y;
}
int main(){
    T=read();
    while(T--){
        n=read(),m=read();
        memset(dp,0,sizeof(dp));
        for(register int i=1;i<=n;++i)
            pr[i]=0,nt[i]=n,dp[i][0]=1;
        while(m--){
            int a=read(),b=read();
            if(a<b)nt[a]=min(nt[a],b-a);
            else pr[b]=max(pr[b],a-b);
        }
        dp[n+1][0]=1;
        for(register int i=n;i;--i)
            for(register int j=1,qj=pr[i];j<=n;++j)
                for(register int k=qj,lt=min(j,nt[i]);k<lt;++k){
                    dp[i][j]=(dp[i][j]+dp[i+1][k]*dp[i+1+k][j-k-1])%mod;
                    if(i+1+k>n)break;
                }
        printf("%lld\n",dp[1][n]);
    }
    return 0;
}
View Code
posted @ 2019-10-17 08:44  remarkable  阅读(522)  评论(4编辑  收藏  举报