1. 程式人生 > >BITACM2018第一輪積分賽(三)題解

BITACM2018第一輪積分賽(三)題解

A. 很會dp

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

題解: 因為 V V 實在太大了,所以做揹包 d p dp 肯定不行,這一點在題目中已經很明顯的提示了。那麼另一種做法只能是暴力列舉每一個物品取還是不取。每件物品可以選取或不取,這樣方案數會有 2

n 2^n ,是不能接受的。注意到如果 n n 變成 n 2
\frac{n}{2}
,暴力列舉似乎就可以接受了。再考慮到如果 n n 件物品分成兩部分,分別以 2 n
2 2^\frac{n}{2}
列舉這兩部分的所有可能的體積之和,合併的時候不需要在兩區間列舉各取什麼再加起來,因為這樣還是 2 n 2^n ,而對於一個區間列舉取的是什麼體積(假如取了體積是 s u m sum ),另一個區間排序之後直接二分小等於 V s u m V-sum 的最大值即可。顯然只有這個值對於 s u m sum 才是有用的,其他值要麼加起來超過 V V ,要麼加起來不如它優。因此對 n n 個物品折半之後分別暴力處理所有可能體積,然後列舉前半部分,在後半部分二分即可。這個演算法有個專門的名字,叫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

題解: 注意到只要各位有一個零,那麼乘積就是零了。因此 t t 是零與非零的情況分類討論。

  • 對於乘積為零的情況,考慮到用總方案數減去不合法方案數,即得到合法的方案數。總方案數是 1 0 k 10^k ,不合法方案就是 k k 位都不是零,共 9 k 9^k 個。所以答案就是 1 0 k 9 k 10^k-9^k 。注意答案取模必須是非負的。
  • 對於乘積不為零的情況,注意到填的數字只能是 1...9 1...9 ,那麼 t t 分解質因數其實只能有 2 3 5 7 2、3、5、7 四個質因數,否則一定無解。如果 t = 2 a 3 b 5 c 7 d t=2^a3^b5^c7^d ,只要取 k k 個數,乘起來得到 a a 2 2 b b 3 3 c c 5 5 d d 7 7 即可。

考慮動態規劃: f [ i ] [ j ] [ k ] [ l ] [ m ] f[i][j][k][l][m] 表示前 i i 個位置、已經有 j j 2 2 k k 3 3 l l 5 5 m m 7 7 的方案數,考慮取 1...9 1...9 每個數能貢獻幾個什麼數字即可,比如 6 6 貢獻了 1 1 2 2 1 1 3 3 ,因此 f [ i ] [ j ] [ k ] [ l ] [ m ] + = f [ i 1 ] [ j 1 ] [ k 1 ] [ l ] [ m ] f[i][j][k][l][m]+=f[i-1][j-1][k-1][l][m] ,其他 8 8 個式子同理。最後答案即為 f [ k ] [ a ] [ b ] [ c ] [ d ] f[k][a][b][c][d] 。而 2 27 3 17 5 12 7 10 1 0 8 2^{27}\approx3^{17}\approx5^{12}\approx7^{10}\approx10^8 ,所以 a b c d a、b、c、d 不會很大。

參考程式碼:

#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

題解: 把所有熟人的位置扔進佇列,然後做 b f s bfs 往外擴充套件,即可知道每個點到它最近熟人的距離 d i s t [ i ] [ j ] dist[i][j] 。顯然如果存在一條最小距離是 k k 的路徑,也肯定存在一條最小距離是 k 1 k-1 的路徑,因此最大的最小距離會在 [ k , + ) [k,+\infty) 。而如果不存在最小距離是 k k 的路徑,那也一定不存在最小距離是 k + 1 k+1 的路徑,因此最大的最小距離會在 [ 0 , k ) [0,k) 。因此可以二分最小距離,不管我們判斷某個答案可不可行,一定可以將範圍至少縮小一半。考慮判定最小距離 m i d mid 可不可行,此時只能走滿足 d i s t [ x ] [ y ] m i d dist[x][y]\ge mid 的點 ( x , y ) (x,y) ,相當於在原有條件下多了一個合法性的判定。在此條件下, b f s bfs S S T T 的最短距離即可。

參考程式碼:

#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))