10.week1

杭州集训周

Polygon

经典区间dp.
看上去是板子,但是有点东西要注意.
就是负数×负数是正数.
这就意味着答案的可能也是从最小值转移的.
注意这个就是真板子了.
结果是区间dp板子一直写不对然后把这题浪费了

软件安装

是很典的树形背包.
但是这个可能有环.很简单,tarjan一下然后树上背包就对了
以防出事,贴一个 \(O(n^2)\)的树形背包

窗口的星星

扫描线,有点板但不完全是,主要是有不少恶心人的细节和一些tricks.
我们首先考虑如何让一个点被看到(考虑贡献)

x0,y0是指所选矩形位置的右上角

\[ x_0-W<x_i<x_0\\ y_0-H<y_i<y_0\]

移项一下

\[ x_i<x_0<x_i+W\\ y_i<y_0<y_i+H\]

发现可以抽象为矩形可以在长宽\(H,W\)的矩形贡献亮度,叠加可以求和,然后找出最大值的建造位置
这样只需要维护一个区间求和,查询最大值就行了.

MEX Queries

你说的对,但是被ODT水过了,甚至不用写离散化.
太强了太强了

Prime queries

继续是ODT水题时间.
我们对于一个颜色段是否是质数,可以先把质数表打出来然后Lower bound看找不到得到一样的值.
直接就过了.

种花

直接乘法原理和前缀和.
看来NOIP是把\(O(n^3)\)放过去了.
但是luogu上要求\(O(n^2)\).
这其实也很简单.
我们只需要对于每个点,处理出他的右连续段的后缀和就是"\(C\)","\(F\)"就是前面的基础上乘上一个竖着连续段的长度.
说来简单,但是还是调了很一会的,有些细节要注意.
贴一段关键代码:

void get_c(){
    F(i,1,n)D(j,m-1,1){
        if(!a[i][j]||!a[i][j+1])continue;
        c[i][j]=c[i][j+1]+1;
    }
    F(j,1,m)D(i,n,1){
        if(!a[i][j])continue;
        sc[i][j]=sc[i+1][j]+c[i][j];
    }
    F(i,1,n-1)
        F(j,1,m-1)
            if(a[i+1][j])(ans_c+=c[i][j]*sc[i+2][j]%mod)%=mod;
    cout<<ans_c*cc<<' ';
}
int ans_f;
void get_f(){
    D(i,n-1,1){
        F(j,1,m){
            if(!a[i+1][j]||!a[i][j])continue;
            r[i][j]=r[i+1][j]+1;
        }
    }
    F(j,1,m){
        D(i,n-1,1){
            if(!a[i][j])continue;
            sr[i][j]=sr[i+1][j]+r[i][j]*c[i][j];
        }
    }
    F(i,1,n)
        F(j,1,m)
            if(a[i+1][j])(ans_f+=c[i][j]*sr[i+2][j]%mod)%=mod;
    cout<<ans_f*ff<<'\n';
}

希望今年的T1不要让我调吧,可恶.

报数

无语题,直接类似埃筛,对于所有直接暴力筛去就行了,复杂度很宽绰.

数列

有点极端的DP题.
要意识到,这里的二进制限制涉及到了进位问题.
那么我们就要从低位开始DP.
现在我们直接设状态

\[对于dp[i][j][k][p],我们当中i是第几个数,j是已经确定的序列中的元素个数,k是已经确定的元素带来的1的个数,p是上一个位的进位 \]

刷表转移(还要枚举一位该种放\(t\)个)

\[dp[i+1][j+t][k+(t+p)\%2][(t+p)/2]\lArr dp[i][j][k][p]*C^{n-j}_t*v^t[i] \]

答案统计呢?
自然是\(dp[m+1][n][枚举][枚举]\)了,主义这里也要考虑进位上的问题
其实整体看来不算太难?
贴一小段

void Dp(){
    dp[0][0][0][0]=1;
    F(i,0,m)
        F(j,0,n)
            F(k,0,K)
                F(p,0,n)
                    F(t,0,n-j)
                        (dp[i+1][j+t][k+(t+p)%2][(t+p)/2]+=dp[i][j][k][p]*C[n-j][t]%mod*v[i][t]%mod)%=mod; 
}
signed main(){
    n=rd(),m=rd(),K=rd();
    F(i,0,m)vv[i]=rd();
    prew();
    Dp();
    int ans=0;
    F(k,0,K)
        F(p,0,n)
            if(k+ppc(p)<=K)(ans+=dp[m+1][n][k][p])%=mod;
    cout<<ans<<'\n';
    return 0;
}

排水系统

简单的\(Topo\)套上一个分数类.
我们考虑到这个分母完全没有爆__int128,我们就利用这个来实现分数类.
只要注意时时刻刻除以gcd,就不会爆炸
贴一下简单分数类的实现:

struct Fs{
    ll zi,mu;
    Fs operator+(const Fs &a){
        Fs c;
        if(!zi)return a;
        if(!a.zi)return (*this);
        ll g=__gcd(a.mu,mu);
        c.zi=(a.zi*mu+a.mu*zi)/g;
        c.mu=a.mu/g*mu;
        g=__gcd(c.zi,c.mu);
        c.zi/=g;
        c.mu/=g;
        return c; 
    }
    void out(){
        if(!zi){
            cout<<0<<'\n';
            return ;
        }
        ll g=__gcd(zi,mu);
        zi/=g;
        mu/=g;
        wrt(zi);printf(" ");wrt(mu);printf("\n");
    }
}own[N];

Emiya 家今天的饭

永远没有解决的心魔,终于是杀掉了!

首先是题面有点复杂,相当于问的就是:

  1. 选择非全0
  2. 每行(方法)选择一个食材
  3. 每种食材占选择的总餐数的一半以下.
    img

我们发现如果要记录每种食材选多少个来最后判断是不现实的,我们考虑容斥,先算出总方案数然后减去不合法的.
为什么这么想呢?因为不合法的只可能是一种数.
那么我们可以在一切开始之前直接钦定选某个食材为不合法食材,然后开始方案计数.
DP是这样设计状态的:

\[dp_{i,j,k}表示考虑到第几个方法,在此之前有多少非法食材,有多少合法食材,这所对应的方案. \]

\(dp_{i,j,k}+=dp_{i-1,j,k}(不选择)\)
\(if(j)dp_{i,j,k}+=dp_{i-1,j-1,k}*a_{i,co}(选非法)\)
\(if(k)dp_{i,j,k}+=dp_{i-1,j,k-1}*(s_i-a_{i,co})(选合法)\)
对着不合法的情况统计答案就是\(de\)
当然\(all=(\prod_{i=1}^{n}s[i]+1)-1\)
最后就是\(all-de\)

但是这个复杂度还是不对,只有88分.
我们发现其实这个后两维也是可以继续压缩的.
我们不在乎各自选了多少,而且在乎那个差值.
那我们状态直接记录差值,这样就能加速转移了.
对于选择非法,就是\(+1\),选择合法就是\(-1\),我们要的就是所有最后大于0的值.
由于不能出现负数下标,我们选择平移dp数组.
这里贴一段代码:

void Dp(){
    F(co,1,m){//谁是限制 
        memset(dp,0,sizeof(dp));
        dp[0][n]=1; //平移了n格
        F(i,1,n)
            F(j,n-i,n+i){
                dp[i][j]+=dp[i-1][j];
                if(j)dp[i][j]+=dp[i-1][j-1]*a[i][co];
                if(j<=n*2)dp[i][j]+=dp[i-1][j+1]*(s[i]-a[i][co]);
             } 
        F(j,n+1,2*n)de+=dp[n][j]; 
    }
}

现在看来似乎又没有那么难,还是题面太搞人了.

posted @ 2023-10-11 00:09  ussumer  阅读(18)  评论(0)    收藏  举报