Fork me on github

CCPC Wannafly Winter Camp Div2 部分题解

Day 1, Div 2, Prob. B - 吃豆豆

题目大意

wls有一个\(n\)\(m\)列的棋盘,对于第\(i\)行第\(j\)列的格子,每过\(T[i][j]\)秒会在上面出现一个糖果,糖果只存在一秒,下一秒就会消失。

假如wls第\(k\)秒在第\(i\)行第\(j\)列的格子上,满足\(T[i][j]|k\),则wls会得到一个糖果。

wls每一秒只可以上下左右移动一格或停在原地。

请问wls从指定的\(S(xs,ys)​\)出发到达指定的\(T(xt,yt)​\),并且在路上得到至少\(C​\)个糖果最少需要多少时间?

wls在\(S\)的初始时间是第0秒。

\[1\leq n,m,T[i][j]\leq 10; 1\leq C\leq 1018 \]

解题思路

\(dp[i][j][k]\)表示吃到至少\(k\)颗豆豆,并且停在\((i,j)\)处的最少时间(“最少”其实有些不准确,这个后面再解释)。那么\(dp[i][j][1]\)就可以这样求出来了:

\[dp[i][j][1]=dist(xs,ys,i,j)+\Delta t \]

\(\Delta t\)主要用于对\(T[i][j]\)取整,注意\(dp[xs][ys][1]\)就等于\(T[xs][ys]\)。接下来可以递推求\(dp[i][j][k]\)了:

\[dp[i][j][k]=min\{dp[i'][j'][k-1]+dis(i',j',i,j)+\Delta t\} \]

同样\((i',j')\)\((i,j)\)相同时也要特殊考虑。

所以重新说明一下\(dp[i][j][k]\)的意义。它表示吃到至少\(k\)颗豆豆,并且停在\((i,j)\)的某个时间。这个时间满足:给定\(k\),由所有的\(dp[i'][j'][k]\)走到\((i,j)\),取最小值就是至少吃\(k\)颗豆豆,并且停在\((i,j)\)处的最少时间。因为由\(dp[i'][j'][k-1]\)转移到\(dp[i][j][k]\)时,路途上可能吃了豆豆,但这一定也会被某个\(dp[i''][j''][k]\)存储起来。

还是有点难理解,\(dp\)的题还是要多做一些才能有这样的思路。

#include <bits/stdc++.h>

typedef long long ll;
const int inf=0x3f3f3f3f;
const double eps=1e-5;
const int mod=1000000007;
const int maxn=10;
const int maxm=1018;

using namespace std;

int t[maxn+5][maxn+5];
int dp[maxn+5][maxn+5][maxm+10];

int dist(int x1,int y1,int x2,int y2)
{
    return abs(x1-x2)+abs(y1-y2);
}

int main()
{
    int n,m,c;
    scanf("%d%d%d",&n,&m,&c);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",t[i]+j);
    int xs,ys,xt,yt;
    scanf("%d%d%d%d",&xs,&ys,&xt,&yt);

    memset(dp,0x3f,sizeof(dp));
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
       	if(i!=xs||j!=ys)
         	dp[i][j][1]=(dist(xs,ys,i,j)+t[i][j]-1)/t[i][j]*t[i][j];
     	else
         	dp[i][j][1]=t[i][j];
    }

    for(int k=2;k<=c;k++)
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    for(int ii=1;ii<=n;ii++)
    for(int jj=1;jj<=m;jj++)
    {
        int temp;
        if(ii==i&&jj==j)
            temp=dp[ii][jj][k-1]+t[ii][jj];
        else
            temp=(dp[ii][jj][k-1]+dist(ii,jj,i,j)+t[i][j]-1)/t[i][j]*t[i][j];
        dp[i][j][k]=min(dp[i][j][k],temp);
    }

    int ans=inf;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            int temp=dp[i][j][c]+dist(i,j,xt,yt);
            ans=min(ans,temp);
        }
    }

    printf("%d\n",ans);

    return 0;
}

Day 4, Div 2, Prob. G - 置置置换

题目大意

wls有一个整数\(n\),他想请你算一下有多少\(1...n\)的排列(permutation)满足:对于所有的\(i(2\leq i\leq n)\),若\(i\)为奇数,则\(a[i-1]<a[i]\),否则\(a[i-1]>a[i]\)。请输出答案mod 1e9+7。

\[1\leq n\leq 1000 \]

解题思路

\(dp[i][j]\)表示使用\(1...i\),开头为\(j\)的符合条件的排列数。

容易发现,将\(dp[i][j]\)排列对\(i+1\)取补,在开头加上一个比\(i+1-j\)大的数\(j'\),再把不比\(j'\)小的数都加一,就构造了一个新的\(1...i+1\)的排列。转移方程如下:

\[dp[i][j]=\sum\limits _{j'=i-j+1}^{i-1} dp[i-1][j'] \]

容易发现,给定\(j\),这样构造是不重不漏的:每个\(dp[i][j]\),都可以有一个\(dp[i-1][j']\)与之对应;而每个\(dp[i-1][j']\),都可以对指定的\(j\),构造出一个\(dp[i][j]\)

再利用一个前缀和,将复杂度降到\(O(n^2)\)

#include <bits/stdc++.h>

typedef long long ll;
const int inf=0x3f3f3f3f;
const double eps=1e-5;
const int mod=1000000007;
const int maxn=1000;
const int maxm=300000;

using namespace std;

ll dp[maxn+10][maxn+10];

int main()
{
    int n;
    scanf("%d",&n);

    memset(dp,0,sizeof(dp));
    dp[1][1]=1;
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=i;j++)
        {
            dp[i][j]=(dp[i-1][i-1]-dp[i-1][i-j])%mod;
        }
        for(int j=1;j<=i;j++)
            dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
    }

    printf("%lld\n",(dp[n][n]%mod+mod)%mod);

    return 0;
}

Day 5, Div 2, Prob. F - Kropki

题目大意

你有一个\(1\)\(n\)的排列 \(p_1\), \(p_2\), ... , \(p_n\),对于所有的\(i(1\leq i\leq n-1)\), 如果\(p_i\)\(p_{i+1}\)中,有一个数是另一个数的两倍,那么这两个数之间标一个1,否则标0。

给定及长度为\(n-1(2\leq n\leq 15)\)的01串\(str[1:n-1]\),问有多少\(1\)\(n\)的排列对应该01串。

解题思路

状压DP。\(dp[i][j][k]​\)表示\(1...i​\)位,使用过的数字状态是\(j​\),末位是\(k​\)的排列数。

\(i​\)\(i+1​\)之间标1时,若\(k\times 2\leq n​\)且未被使用,则\(dp[i][j][k]​\)可以向\(dp[i+1][j'][k\times 2]​\)转移:

\[dp[i+1][j|(1<<(2k-1))][2k]+=dp[i][j][k],str[i]==1,2k\leq n,j\&(1<<(2k-1))==0 \]

同理可得:

\[dp[i+1][j|(1<<(k/2-1))][k/2]+=dp[i][j][k],str[i]==1,k\%2==0,j\&(1<<(k/2-1))==0 \]

\[dp[i+1][j|(1<<(tmp-1))][tmp]+=dp[i][j][k],str[i]==0,1\leq tmp\leq n,k\%2==1||tmp\neq k/2,tmp\neq 2k,j\&(1<<(tmp-1))==0​ \]

#include<bits/stdc++.h>

typedef long long ll;
const int inf=0x3f3f3f3f;
const double eps=1e-5;
const double pi=3.141592653589793;
const int mod=1000000007;
const int maxn=1000;
const int maxm=1000;

using namespace std;

char str[maxn+10];
int dp[20][40000][20];

int main()
{
   int n;
   scanf("%d",&n);
   scanf("%s",str+1);

   memset(dp,0,sizeof(dp));
   for(int i=1; i<=15; i++)
       dp[1][1<<(i-1)][i]=1;

   for(int i=1; i<=n-1; i++)
   for(int j=1;j<=(1<<n)-1;j++)
   for(int k=1;k<=n;k++)
       if(dp[i][j][k]>0)
       {
           if(str[i]=='1')
           {
               if(k%2==0&&(j&(1<<((k/2)-1)))==0)
                   dp[i+1][j|(1<<((k/2)-1))][k/2]=(dp[i+1][j|(1<<((k/2)-1))][k/2]+dp[i][j][k])%mod;
               if(k*2<=n&&(j&(1<<((k*2)-1)))==0)
                   dp[i+1][j|(1<<((k*2)-1))][k*2]=(dp[i+1][j|(1<<((k*2)-1))][k*2]+dp[i][j][k])%mod;
           }
           else
           {
               for(int tmp=1;tmp<=n;tmp++)
                   if((k%2==1||tmp!=k/2)&&tmp!=k*2&&(j&(1<<((tmp)-1)))==0)
                       dp[i+1][j|(1<<((tmp)-1))][tmp]=(dp[i+1][j|(1<<((tmp)-1))][tmp]+dp[i][j][k])%mod;
           }
       }
   ll ans=0;
   for(int i=1;i<=n;i++)
       ans=(ans+1LL*dp[n][(1<<n)-1][i])%mod;
   printf("%lld\n",ans);

   return 0;
}
posted @ 2019-01-29 16:12  acboyty  阅读(324)  评论(0编辑  收藏  举报