A. 很会dp

出题人: zhber
AC/TOT: 1/50

题解: 因为 VVV 实在太大了,所以做背包 dpdpdp 肯定不行,这一点在题目中已经很明显的提示了。那么另一种做法只能是暴力枚举每一个物品取还是不取。每件物品可以选取或不取,这样方案数会有 2n2^n2n,是不能接受的。注意到如果 nnn 变成 n2\\frac{n}{2}2n,暴力枚举似乎就可以接受了。再考虑到如果 nnn 件物品分成两部分,分别以 2n22^\\frac{n}{2}22n 枚举这两部分的所有可能的体积之和,合并的时候不需要在两区间枚举各取什么再加起来,因为这样还是 2n2^n2n,而对于一个区间枚举取的是什么体积(假如取了体积是 sumsumsum ),另一个区间排序之后直接二分小等于 VsumV-sumVsum 的最大值即可。显然只有这个值对于 sumsumsum 才是有用的,其他值要么加起来超过 VVV,要么加起来不如它优。因此对 nnn 个物品折半之后分别暴力处理所有可能体积,然后枚举前半部分,在后半部分二分即可。这个算法有个专门的名字,叫meet-in-middle有兴趣的同学可以去学习一下“折半搜索”!

参考代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,len;
ll a[40],V,ans;
ll s[200010];
inline ll bsearch(int l,int r,ll x)
{
    ll t=0;
    while (l<=r)
    {
        int mid=(l+r)>>1;
        if (s[mid]<=x)t=s[mid],l=mid+1;
        else r=mid-1;
    }
    return t;
}
int main()
{
    scanf(\"%d%lld\",&n,&V);
    for (int i=1;i<=n;i++)scanf(\"%lld\",a+i);
    for (int i=0;i<(1<<(n/2));i++)
    {
        ll sum=0;
        for (int j=1;j<=n/2;j++)
            if (i & (1<<(j-1)) )sum+=a[j];
        if(sum<=V)s[++len]=sum;
    }
    s[++len]=0;
    sort(s+1,s+len+1);
    ans=s[len];
    for (int i=0;i<(1<<(n-n/2));i++)
    {
        ll sum=0;
        for (int j=n/2+1;j<=n;j++)
            if (i & (1<<(j-n/2-1)) )sum+=a[j];
        if(sum<=V)ans=max(ans,sum+bsearch(1,len,V-sum));
    }
    printf(\"%lld\\n\",ans);
}

B. 秀外慧中

出题人: zhber
AC/TOT: 0/0

题解: 注意到只要各位有一个零,那么乘积就是零了。因此ttt是零与非零的情况分类讨论。

  • 对于乘积为零的情况,考虑到用总方案数减去不合法方案数,即得到合法的方案数。总方案数是 10k10^k10k,不合法方案就是 kkk 位都不是零,共 9k9^k9k 个。所以答案就是 10k9k10^k-9^k10k9k。注意答案取模必须是非负的。
  • 对于乘积不为零的情况,注意到填的数字只能是 1...91...91...9,那么ttt分解质因数其实只能有 23572、3、5、72357 四个质因数,否则一定无解。如果 t=2a3b5c7dt=2^a3^b5^c7^dt=2a3b5c7d,只要取 kkk 个数,乘起来得到 aaa222bbb333ccc555ddd777 即可。

考虑动态规划:f[i][j][k][l][m]f[i][j][k][l][m]f[i][j][k][l][m]表示前iii个位置、已经有 jjj222kkk333lll555mmm777 的方案数,考虑取 1...91...91...9 每个数能贡献几个什么数字即可,比如 666 贡献了 111222111333,因此 f[i][j][k][l][m]+=f[i1][j1][k1][l][m]f[i][j][k][l][m]+=f[i-1][j-1][k-1][l][m]f[i][j][k][l][m]+=f[i1][j1][k1][l][m],其他 888 个式子同理。最后答案即为 f[k][a][b][c][d]f[k][a][b][c][d]f[k][a][b][c][d]。而 2273175127101082^{27}\\approx3^{17}\\approx5^{12}\\approx7^{10}\\approx10^8227317512710108,所以abcda、b、c、dabcd不会很大。

参考代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
int n,t;
int p[20],q[20],len;
ll f[50][30][20][15][15];
int main()
{
    scanf(\"%d%d\",&n,&t);
    if (t==0)
    {
        int s1=1,s2=1;
        for (int i=1;i<=n;i++)s1=(s1*10)%mod,s2=(s2*9)%mod;
        printf(\"%d\\n\",(s1-s2+mod)%mod);
        return 0;
    }
    for (int i=2;i*i<=t;i++)
    if (t%i==0)
    {
        p[++len]=i;q[len]=1;t/=i;
        while (t%i==0)t/=i,q[len]++;
    }
    if (len==0||t!=1)
    {
        p[++len]=t;
        q[len]=1;
    }
    for (int i=1;i<=len;i++)if (p[i]!=1&&p[i]!=2&&p[i]!=3&&p[i]!=5&&p[i]!=7){puts(\"0\");return 0;}
    int a=0,b=0,c=0,d=0;
    for(int i=1;i<=len;i++)
    {
        if (p[i]==2)a=q[i];
        if (p[i]==3)b=q[i];
        if (p[i]==5)c=q[i];
        if (p[i]==7)d=q[i];
    }
    f[0][0][0][0][0]=1;
    for (int i=1;i<=n;i++)
        for (int j=0;j<=a;j++)
            for (int k=0;k<=b;k++)
                for(int l=0;l<=c;l++)
                    for (int m=0;m<=d;m++)
                    {
                        f[i][j][k][l][m]+=f[i-1][j][k][l][m];//1
                        if (j>=1)f[i][j][k][l][m]+=f[i-1][j-1][k][l][m];//2
                        if (k>=1)f[i][j][k][l][m]+=f[i-1][j][k-1][l][m];//3
                        if (j>=2)f[i][j][k][l][m]+=f[i-1][j-2][k][l][m];//4
                        if (l>=1)f[i][j][k][l][m]+=f[i-1][j][k][l-1][m];//5
                        if (j>=1&&k>=1)f[i][j][k][l][m]+=f[i-1][j-1][k-1][l][m];//6
                        if (m>=1)f[i][j][k][l][m]+=f[i-1][j][k][l][m-1];//7
                        if (j>=3)f[i][j][k][l][m]+=f[i-1][j-3][k][l][m];//8
                        if (k>=2)f[i][j][k][l][m]+=f[i-1][j][k-2][l][m];//9
                        f[i][j][k][l][m]%=mod;
                    }
    printf(\"%lld\\n\",f[n][a][b][c][d]);
}

C. 背着大家偷偷交题

出题人: zhber
AC/TOT: 0/0

题解: 把所有熟人的位置扔进队列,然后做 bfsbfsbfs 往外扩展,即可知道每个点到它最近熟人的距离 dist[i][j]dist[i][j]dist[i][j]。显然如果存在一条最小距离是 kkk 的路径,也肯定存在一条最小距离是 k1k-1k1 的路径,因此最大的最小距离会在 [k,+)[k,+\\infty)[k,+)。而如果不存在最小距离是 kkk 的路径,那也一定不存在最小距离是 k+1k+1k+1 的路径,因此最大的最小距离会在 [0,k)[0,k)[0,k)。因此可以二分最小距离,不管我们判断某个答案可不可行,一定可以将范围至少缩小一半。考虑判定最小距离 midmidmid 可不可行,此时只能走满足 dist[x][y]middist[x][y]\\ge middist[x][y]mid 的点 (x,y)(x,y)(x,y),相当于在原有条件下多了一个合法性的判定。在此条件下,bfsbfsbfsSSSTTT 的最短距离即可。

参考代码:

#include<bits/stdc++.h>
#define pa pair<int,int>
#define mkp make_pair
using namespace std;
char mp[1010][1010];
int dist[1010][1010];
int dis2[1010][1010];
int n,m;
int sx,sy,ex,ey;
int mx[4]={1,0,-1,0},my[4]={0,1,0,-1};
inline void bfs1()
{
    memset(dist,-1,sizeof dist);
    queue<pa>q;while (!q.empty())q.pop();
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
            if (mp[i][j]==\'#\')q.push(mkp(i,j)),dist[i][j]=0;
    while (!q.empty())
    {
        int nx=q.front().first,ny=q.front().second;q.pop();
        for (int k=0;k<4;k++)
        {
            int wx=nx+mx[k];
            int wy=ny+my[k];
            if (wx<1||wx>n||wy<1||wy>m||dist[wx][wy]!=-1)continue;
            dist[wx][wy]=dist[nx][ny]+1;
            q.push(mkp(wx,wy));
        }
    }
}
inline int bfs2(int x)
{
    memset(dis2,-1,sizeof dis2);
    queue<pa>q;while (!q.empty())q.pop();
    q.push(mkp(sx,sy));dis2[sx][sy]=0;
    while (!q.empty())
    {
        int nx=q.front().first,ny=q.front().second;q.pop();
        for (int k=0;k<4;k++)
        {
            int wx=nx+mx[k];
            int wy=ny+my[k];
            if (wx<1||wx>n||wy<1||wy>m||dis2[wx][wy]!=-1||dist[wx][wy]<x)continue;
            dis2[wx][wy]=dis2[nx][ny]+1;
            q.push(mkp(wx,wy));
        }
    }
    return dis2[ex][ey];
}
int main()
{
    scanf(\"%d%d\",&n,&m);
    for (int i=1;i<=n;i++)scanf(\"%s\",mp[i]+1);
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=m;j++)
            if (mp[i][j]==\'S\')sx=i,sy=j;
            else if (mp[i][j]==\'T\')ex=i,ey=j;
    }
    bfs1();
    int l=0,r=2*n-1,ans=0,ans2=0;
    while (l<=r)
    {
        int mid=(l+r)>>1,cal=bfs2(mid);
        if (cal!=-1)ans=mid,ans2=cal,l=mid+1;
        else r=mid-1;
    }
    printf(\"%d %d\\n\",ans,ans2);
}

D. I Love Diamond Forever.jpg

出题人: konnyaku
AC/TOT: 8/52

题解:

diamond裸 dp,不会就把讲 dp 的学长拖出去枪毙五分钟 。 ——天爸爸

卖完萌还是讲一下吧,分析一下问题,钻石血量多少不会影响它受多少伤害,反而一旦月狗质量确定,则钻石受的伤害是确定的,明白了这一点,这符合动态规划的子问题性质,考虑 f(i)f(i)f(i) 表示一只血量为 iii 的月狗能对钻石造成的最小伤害,它下一轮只会转移到 f(i)f(ik)+f(k), 0&lt;k&lt;if(i)\\to f(i-k)+f(k),\\ 0&lt; k&lt; if(i)f(ik)+f(k), 0<k<i 枚举 kkk 就好了,而且因为有对称性, kkk 只需要枚举到 [i2][\\frac{i}{2}][2i] 就好了,由于 NNNMMM 都很大我们可以考虑记忆化搜索。

参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 5;
int a[maxn];
ll f[maxn];
ll DP(int i) {
    if (f[i] != -1) return f[i];
    if (i == 1) return 0;
    ll ans = 1e18;
    for (int k = 1; k <= i / 2; k++) ans = min(ans, DP(k) + DP(i - k) + a[k] 					
收藏 打印