BITACM2018第一輪積分賽(三)題解
A. 很會dp
出題人: zhber
AC/TOT: 1/50
題解: 因為
實在太大了,所以做揹包
肯定不行,這一點在題目中已經很明顯的提示了。那麼另一種做法只能是暴力列舉每一個物品取還是不取。每件物品可以選取或不取,這樣方案數會有
,是不能接受的。注意到如果
變成
,暴力列舉似乎就可以接受了。再考慮到如果
件物品分成兩部分,分別以
列舉這兩部分的所有可能的體積之和,合併的時候不需要在兩區間列舉各取什麼再加起來,因為這樣還是
,而對於一個區間列舉取的是什麼體積(假如取了體積是
),另一個區間排序之後直接二分小等於
的最大值即可。顯然只有這個值對於
才是有用的,其他值要麼加起來超過
,要麼加起來不如它優。因此對
個物品折半之後分別暴力處理所有可能體積,然後列舉前半部分,在後半部分二分即可。這個演算法有個專門的名字,叫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
題解: 注意到只要各位有一個零,那麼乘積就是零了。因此 是零與非零的情況分類討論。
- 對於乘積為零的情況,考慮到用總方案數減去不合法方案數,即得到合法的方案數。總方案數是 ,不合法方案就是 位都不是零,共 個。所以答案就是 。注意答案取模必須是非負的。
- 對於乘積不為零的情況,注意到填的數字只能是 ,那麼 分解質因數其實只能有 四個質因數,否則一定無解。如果 ,只要取 個數,乘起來得到 個 、 個 、 個 、 個 即可。
考慮動態規劃: 表示前 個位置、已經有 個 、 個 、 個 、 個 的方案數,考慮取 每個數能貢獻幾個什麼數字即可,比如 貢獻了 個 、 個 ,因此 ,其他 個式子同理。最後答案即為 。而 ,所以 不會很大。
參考程式碼:
#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
題解: 把所有熟人的位置扔進佇列,然後做 往外擴充套件,即可知道每個點到它最近熟人的距離 。顯然如果存在一條最小距離是 的路徑,也肯定存在一條最小距離是 的路徑,因此最大的最小距離會在 。而如果不存在最小距離是 的路徑,那也一定不存在最小距離是 的路徑,因此最大的最小距離會在 。因此可以二分最小距離,不管我們判斷某個答案可不可行,一定可以將範圍至少縮小一半。考慮判定最小距離 可不可行,此時只能走滿足 的點 ,相當於在原有條件下多了一個合法性的判定。在此條件下, 求 到 的最短距離即可。
參考程式碼:
#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))