首页 > 其他分享 >[USACO20OPEN]Sprinklers 2_ Return of the Alfalfa(dp)

[USACO20OPEN]Sprinklers 2_ Return of the Alfalfa(dp)

时间:2022-10-28 18:34:00浏览次数:43  
标签:Alfalfa Return int sum add 分割线 dp mod

先讲一下等会要用到的定义:

  1. 红格代表被甜玉米洒水器喷洒到的方格,蓝格代表被苜蓿洒水器喷洒到的方格。橙格代表有甜玉米洒水器的方格,紫格代表有苜蓿洒水器。

  2. \((a,b)\) 指网格线的交点,\([a,b]\) 指网格。

    比如下面这个图中,黄代表的是 \((4,3)\),黄代表的是 \([2,3]\)。

  3. \(sum_i\) 表示网格图的第 \(i\) 行中有多少个没被占据的网格。

然后说说考试时的心路历程。(其实是用来帮助你如何由暴力思考到正解的)

\(25\ pts\)

第一眼看到这道题是不会的……甚至连部分分都不知道怎么写

但是仔细想了一想,发现红蓝两方阵营肯定是被一条由 \((0,0)\) 到 \((n,n)\) 的分割线分开的。

比如说这样:

这种情况是被如图的黄色的分割线分开的。

然后考虑哪些地方需要洒水器:

显然,我们发现,在分割线的拐角处都需要洒水器(即图中的橙格和紫格),其他地方可填可不填,但是只能填一种洒水器。

我们假设某一种分割线有 \(k\) 个拐角(也就是有 \(k\) 个洒水器是一定要放的),方阵总面积为 \(S\)(面积指没有被占据的网格的总数)。

那么对于这种分割线,一共有 \(2^{S-k}\) 种方案。(记住这条公式)

那么暴力的方法就显而易见了:每次枚举一条分割线,并记录分割线的拐角数,最后统计答案。

时间复杂度貌似是 \(O(2^n+n^2)\),只有 \(25pts\)。

代码:

#include<bits/stdc++.h>
 
#define N 2010
#define mod 1000000007
 
using namespace std;
 
int n,ans,poww[N*N],sum;
char ch[N][N];
 
//n=4
// 0 1 2 3 4 
//0#########
// #.#.#W#.#
//1#########
// #.#.#W#W#
//2#########
// #W#W#.#.#
//3#########
// #.#.#.#W#
//4#########
 
//last=0向下,last=1向上 

void dfs(const int x,const int y,const int a,const int b,const int last)
{
    if(x==n&&y==n)
    {
        ans=(0ll+ans+1ll*poww[sum-a-b])%mod;
        return;
    }
    if(!last)
    {
        if(x+1<=n) dfs(x+1,y,a,b,0);
        if(y+1<=n&&ch[x][y+1]!='W') dfs(x,y+1,a,b+1,1);
    }
    else
    {
        if(x+1<=n&&ch[x+1][y]!='W') dfs(x+1,y,a+1,b,0);
        if(y+1<=n) dfs(x,y+1,a,b,1);
    } 
}
 
inline int read()
{
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^'0');
        ch=getchar();
    }
    return x;
}
 
int main()
{
    n=read();
    poww[0]=1;
    for(register int i=1;i<=n*n;i++) poww[i]=(2ll*poww[i-1])%mod;
    for(register int i=1;i<=n;i++) scanf("%s",ch[i]+1);
    for(register int i=1;i<=n;i++)
        for(register int j=1;j<=n;j++)
            sum+=(ch[i][j]=='.');
    dfs(1,0,0,0,0);
    dfs(0,1,0,0,1);
    printf("%d\n",ans);
    return 0;
}

\(100\ pts\)

考虑 \(dp\),设 \(dp(i,j,0/1)\) 指已经把前 \(i\) 行灌溉满了,分割线的终止在 \((i,j)\),分割线最后的方向是向右/向下的。

那么答案显然就是 \(dp(n,n,0)+dp(n,n,1)\)。

考虑状态转移方程。

  1. 先考虑 \(dp(i,j,0)\) 怎么转移:

    如图,我们现在要求出 \(dp(3,3,0)\)(黄点),那么只能从 \(dp(3,2,0/1)\)(绿点)转移,因为分割线最后的方向是向右的。

    第一种情况:从 \(dp(3,2,0)\) 转移,如图:

    那么显然有 \(dp(3,3,0)=dp(3,2,0)\),因为 \(S\) 没变,\(k\) 也没变。

    第二种情况:从 \(dp(3,2,1)\) 转移,如图:

    我们发现,原来的分割线终止在 \((3,2)\) 时,\(k=1\),就是只有 \([1,2]\) 一个灌溉器,但是现在分割线种植在 \((3,3)\) 时,\(k=2\),也就是新增了一个灌溉器 \([3,3]\)。那么原来的方案数 \(dp(3,2,1)=2^{S-k}\),现在 \(k\gets k+1\),所以 \(dp(3,3,0)=\frac{dp(3,2,1)}{2}\)。

    整合一下,就有 \(dp(3,3,0)=dp(3,2,0)+\frac{dp(3,2,1)}{2}\)。

    即 \(dp(i,j,0)=dp(i,j-1,0)+\frac{dp(i,j-1,1)}{2}\)

  2. 考虑 \(dp(i,j,1)\) 怎么转移:

    同理,这次需要从绿点转移:

    第一种情况:从 \(dp(2,3,0)\) 转移,如图:

    第一幅图是 \(dp(2,3,0)\) 的情况,第二幅图是 \(dp(3,3,1)\) 的情况。

    显然从第一幅图变成第二幅图时 \(S\) 增加了 \(sum_3\),\(k\) 增加了 \(1\),那么 \(dp\) 值从 \(2^{S-k}\) 变成了 \(2^{S+sum_3-k-1}\),也就是说 \(dp(3,3,1)=dp(2,3,0)\times 2^{sum_3-1}\)。

    第二种情况:从 \(dp(2,3,1)\) 转移,如图:

    然后我们发现,\(S\gets S+sum_3\),\(k\) 没变,则 \(dp(3,3,1)=dp(2,3,1)\times 2^{sum_3}\)

    整合一下,\(dp(3,3,1)=dp(2,3,0)\times 2^{sum_3-1}+dp(2,3,1)\times 2^{sum_3}\)。

    即 \(dp(i,j,1)=dp(i-1,j,0)\times 2^{sum_i-1}+dp(i-1,j,1)\times 2^{sum_i}\)。

到这里,整道题就做完了,已经可以 AC 了。

时间复杂度 \(O(n^2)\)。

但是还有一些可以优化的地方,这里就大致讲一下,就是把求面积的部分直接提出来,放到最后输出的时候乘上一个 \(2^{\sum_{i=1}^n sum_i}\)(也就是全局的面积)就好了。

代码(未加优化):

#include<bits/stdc++.h>
 
#define N 2010
#define mod 1000000007
#define half 500000004
 
using namespace std;
 
void add(int &a,int b){a=(0ll+a+b)%mod;}
int mul(int a,int b){return (1ll*a*b)%mod;}
 
int n,dp[N][N][2],sum[N],poww[N*N];
char ch[N][N];
 
int main()
{
    scanf("%d",&n);
    poww[0]=1;
    for(int i=1;i<=n*n;i++)
        poww[i]=mul(2,poww[i-1]);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",ch[i]+1);
        for(int j=1;j<=n;j++) 
            sum[i]+=(ch[i][j]=='.');
    } 
    dp[0][0][0]=dp[0][0][1]=1;
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=n;j++)
        {
            if(!i&&!j) continue;
            if(j>0)
            {
                add(dp[i][j][0],dp[i][j-1][0]);
                if(ch[i][j]=='.') add(dp[i][j][0],mul(dp[i][j-1][1],half));
            }
            if(i>0)
            {
                add(dp[i][j][1],mul(dp[i-1][j][1],poww[sum[i]]));
                if(ch[i][j]=='.') add(dp[i][j][1],mul(dp[i-1][j][0],poww[sum[i]-1]));
            }
        }
    }
    printf("%d\n",(0ll+dp[n][n][0]+dp[n][n][1])%mod);
    return 0;
}

代码(加优化):

#include<bits/stdc++.h>
 
#define N 2010
#define mod 1000000007
#define half 500000004
 
using namespace std;
 
void add(int &a,int b){a=(0ll+a+b)%mod;}
int mul(int a,int b){return (1ll*a*b)%mod;}
 
int n,dp[N][N][2],sum;
char ch[N][N];
 
int poww(int a,int b)
{
    int ans=1;
    while(b)
    {
        if(b&1) ans=(1ll*ans*a)%mod;
        a=(1ll*a*a)%mod;
        b>>=1;
    }
    return ans;
}
 
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",ch[i]+1);
        for(int j=1;j<=n;j++) 
            sum+=(ch[i][j]=='.');
    } 
    dp[0][0][0]=dp[0][0][1]=1;
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=n;j++)
        {
            if(!i&&!j) continue;
            if(j>0)
            {
                add(dp[i][j][0],dp[i][j-1][0]);
                if(ch[i][j]=='.') add(dp[i][j][0],mul(dp[i][j-1][1],half));
            }
            if(i>0)
            {
                add(dp[i][j][1],dp[i-1][j][1]);
                if(ch[i][j]=='.') add(dp[i][j][1],mul(dp[i-1][j][0],half));
            }
        }
    }
    printf("%d\n",1ll*(dp[n][n][0]+dp[n][n][1])%mod*poww(2,sum)%mod);
    return 0;
}

其实就是常数上的优化。

写码不易,留赞再走。

标签:Alfalfa,Return,int,sum,add,分割线,dp,mod
From: https://www.cnblogs.com/ez-lcw/p/16837044.html

相关文章

  • [NOI Online 2020 #2 提高 B] 子序列问题 (dp+线段树)
    真的是事后诸葛亮,一下考场就知道怎么做了,现在把题解补回来看到这个问题,我第一下想到的就是\(dp\)。如何\(dp\)?设\(dp[i]\)为以\(i\)为右端点的所有子串的答案之和......
  • NOIP 前 CF*2300 左右 dp 做题记录
    iFTL独立做出形象理解为两个横放的羽毛球盒,第一个放长t1的,第二个放长t2的,然后手推可得一次共同发射之后必是平平的盒子底部,仿佛回到了最初,至此可发现子结构。基于此......
  • 求大神解答:利用python爬取各县GDP结果为空,求大神看看我的代码问题在哪?
    目标url=红黑人口库代码importrequestsfromlxmlimportetreeimporttimeif__name__=='__main__':  url='https://pagead2.googlesyndication.com/getconfig/soda......
  • API项目附加cshtml(报错Cannot find the fallback endpoint specified by route value
    举例elsa-core项目添加_Host.cshtml。在项目中直接按照Elsa的要求添加Pages文件后,运行项目正常环境正常。由于项目是api项目发布后添加的Pages文件并不属于Views中,所以添......
  • 「REMAKE系列」概率期望dp篇
    常见模型技巧总结概率正推,期望倒推简单概率期望简单分类讨论。P1297[国家集训队]单选错位根据题目推导。UVA11021Tribles需要一些小观察的题目01最终位置确定,倒......
  • 洛谷 P1077 [NOIP2012 普及组] 摆花 (DP)
    https://www.luogu.com.cn/problem/P1077题目描述摆上m盆花。一共有n种花,从1到n标号。为了在门口展出更多种花,规定第i种花不能超过ai盆,摆花时同一种花放在一起,且不同......
  • CF1163D Mysterious Code ACA+DP
    将两个串插入AC自动机,AC自动机带点权,S串带权值1,T串带权值-1,对树在构建时求树上点权前缀和,然后设表示到的第个字符,在ACA上的第个节点时的答案,那么就有转移方程:#include<bits......
  • 【PNR#2 Div1 D】找零(DP)(贪心)
    找零题目链接:PNR#2Div1D题目大意有500,100,50,10,5,1这些面额的纸币,你有X块钱,使用最少的纸币数表示的。然后有一些物品,每种只有一个,有费用。每次你可以选择一些......
  • AcWing1069.凸多边形的划分(区间DP)
    SLOJP2067.三角剖分问题AcWing1069.凸多边形的划分(区间DP)题目描述给定由N顶点组成的凸多边形每个顶点具有权值将凸N边形剖分成N-2个三角形求N-2个三角形......
  • 完全背包问题 —— 贪心优化 DP 范围
    题意:现在有\(2n+1\)个物品(\(n\le300\)),体积分别为\(-n,-n+1,\dots,-1,0,1,\dots,n\),第\(i\)个物品有\(a_i\)个,求选出恰好\(S\)的总体积最多能选几个物品。第一......