HGOI 20190830 题解
Problem A 钥匙
有$n$个人和$m$个钥匙在数轴上,人的坐标为$a_i$,钥匙的坐标为$b_i$
而门的坐标为$p$,要让所有人获得一把不同钥匙,并且到达门,最长时间最短是多少。
对于$100\%$的数据满足$10^3 \leq n \leq 10^3 , n \leq k \leq 2\times 10^3$
Solution :
对于部分数据,可以二分答案然后进行二分图匹配,实测可以通过$80\%$的数据。
事实上,对上面算法的极限复杂度是$O(n^2 k log_2 10^9)$
事实上,我们可以将$nk$中人和钥匙的配对方案求出,排序后,直接从小往大贪心。
这样子复杂度是$O(n \times k \times (\ log_2 n + log_2 k))$的。
# include <bits/stdc++.h> # define int long long using namespace std; const int N=1e6+10; int n,k,p,a[N],b[N]; inline int read() { int X=0,w=0; char c=0; while(c<'0'||c>'9') {w|=c=='-';c=getchar();} while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } int calc(int i,int j) { if (a[i]<=b[j] && b[j]<=p) return p-a[i]; if (a[i]<=p && p<=b[j]) return b[j]-a[i]+b[j]-p; if (b[j]<=a[i] && a[i]<=p) return a[i]-b[j]+p-b[j]; if (b[j]<=p && p<=a[i]) return a[i]-b[j]+p-b[j]; if (p<=a[i] && a[i]<=b[j]) return b[j]-a[i]+b[j]-p; if (p<=b[j] && b[j]<=a[i]) return a[i]-p; } namespace Subtask1 { vector<int>E[N]; bool vis[N]; int pre[N]; bool find(int u) { int sz = E[u].size(); for (int i=0;i<sz;i++) { int v=E[u][i]; if (vis[v]) continue; vis[v]=true; if (pre[v]==-1 || find(pre[v])) { pre[v]=u; return true; } } return false; } int solve() { int ans = 0; memset(pre,-1,sizeof(pre)); for (int i=1;i<=n;i++) { memset(vis,false,sizeof(vis)); if (find(i)) ans++; } return ans; } bool check(int Mid) { for (int i=1;i<=n;i++) E[i].clear(); for (int i=1;i<=n;i++) for (int j=1;j<=k;j++) if (calc(i,j)<=Mid) E[i].push_back(j); int ans = solve(); return (ans==n); } void main() { int l=0,r=2e9,ans; while (l<=r) { int mid = (l+r)>>1; if (check(mid)) r=mid-1,ans=mid; else l=mid+1; } cout<<ans<<'\n'; } } namespace Subtask2 { bool vis1[N],vis2[N]; struct node { int u,v,val; }v[N]; bool cmp (node a,node b) { return a.val<b.val; } void main() { int cnt = 0; for (int i=1;i<=n;i++) for (int j=1;j<=k;j++) v[++cnt]=(node){i,j,calc(i,j)}; sort(v+1,v+1+cnt,cmp); int ans = 0 ; for (int i=1;i<=cnt;i++) { node tmp = v[i]; if (vis1[tmp.u] || vis2[tmp.v]) continue; ans=max(tmp.val,ans); vis1[tmp.u] = vis2[tmp.v] = 1; } printf("%lld\n",ans); } } signed main() { // freopen("key.in","r",stdin); //freopen("key.out","w",stdout); n=read();k=read();p=read(); for (int i=1;i<=n;i++) a[i]=read(); for (int i=1;i<=k;i++) b[i]=read(); if (n*k <= 10000000/3/n) Subtask1::main(); else Subtask2::main(); return 0; }
Problem B 汪哥图
给出$n\times m$的$01$矩阵,保证这个矩阵中任意两个$1$之间最多有一条路径不经过$0$。
给出$Q$组询问,求子矩阵联通块个数。
对于$100\%$的数据满足$2 \times 10^3 \leq n,m \leq 2\times 10^3 , 1 \leq Q \leq 2\times 10^5 $
Solution :
我们先假设这个子矩阵中所有的$1$都自成一格连通块,显然,有好多连通块可以合并。
由于矩阵中的性质: 任意两个$1$之间最多有一条路径不经过$0$,所以合并的方式一定是从左到右或者从上到下。
即如果$a[i][j] $和$a[i+1][j]$都是$1$,那么必然会减少一个连通块,如果$a[i][j]$和$a[i][j+1]$都是$1$,也必然会减少一个连通块。
所以,我们定义$f[i][j] = [a[i][j] = 1 ] \times [a[i+1][j] = 1] , g[i][j] = [ a[i][j] = 1 ] \times [a[i][j+1] = 1] $
对于左上角坐标为$(x_1,y_1)$,右下角坐标为$(x_2,y_2)$的子矩阵,答案是$ans = \sum\limits_{i=x_1}^{x_2} \sum\limits_{j=y_1} ^ {y_2} a[i][j] - \sum\limits_{i=x_1}^{x_2 - 1} \sum\limits_{j=y_1} ^ {y_2} f[i][j] - \sum\limits_{i=x_1}^{x_2} \sum\limits_{j=y_1} ^ {y_2-1} g[i][j]$
显然可以用二维前缀和优化。
复杂度是$O(nm + Q)$
# include <bits/stdc++.h> using namespace std; const int N=2e3+10; int a[N][N],c1[N][N],c2[N][N],sum1[N][N],suma[N][N],sum2[N][N]; int n,m,q; inline int read() { int X=0,w=0; char c=0; while(c<'0'||c>'9') {w|=c=='-';c=getchar();} while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } void write(int x) { if (x>9) write(x/10); putchar('0'+x%10); } int calca(int x1,int y1,int x2,int y2){ return suma[x2][y2]-suma[x2][y1-1]-suma[x1-1][y2]+suma[x1-1][y1-1]; } int calc1(int x1,int y1,int x2,int y2){ if (x2<x1 || y2<y1) return 0; return sum1[x2][y2]-sum1[x2][y1-1]-sum1[x1-1][y2]+sum1[x1-1][y1-1]; } int calc2(int x1,int y1,int x2,int y2) { if (x2<x1 || y2<y1) return 0; return sum2[x2][y2]-sum2[x2][y1-1]-sum2[x1-1][y2]+sum2[x1-1][y1-1]; } int main() { n=read();m=read();q=read(); for (int i=1;i<=n;i++) for (int j=0;j<m;j++) { char op=0; while (op!='1' && op!='0') op=getchar(); a[i][j+1]=op-'0'; } for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) c1[i][j]=a[i][j] && a[i+1][j], c2[i][j]=a[i][j] && a[i][j+1]; for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) suma[i][j]=suma[i-1][j]+suma[i][j-1]-suma[i-1][j-1]+a[i][j], sum1[i][j]=sum1[i-1][j]+sum1[i][j-1]-sum1[i-1][j-1]+c1[i][j], sum2[i][j]=sum2[i-1][j]+sum2[i][j-1]-sum2[i-1][j-1]+c2[i][j]; while (q--) { int x1=read(),y1=read(),x2=read(),y2=read(); int ans = calca(x1,y1,x2,y2)-calc1(x1,y1,x2-1,y2)-calc2(x1,y1,x2,y2-1); write(ans); putchar('\n'); } return 0; }
Problem C 时空阵
本题时间限制$4000 ms$
有$n$个点,用最多$\frac{n(n-1)}{2}$条边权为$1$的双向边连接成一个图,
给出$m$,要求在构造出的图中,有$1 - n$的最短路为$m$的图的个数$mod \ 10^9 + 7$的值。
当且仅当两个图中连边集合不同,则两个图不同。
对于$100\%$的数据满足$1 \leq m < n\leq 100 $。
Solution :
假设把这些点中的若干个点分成$m+1$个集合,编号为$[0,m]$ , 每个集合中至少有$1$个点,并且$1$个点最多在$1$个集合中。
并且保证,任取$i \in [0,m-1]$,第$i$个集合和第$i+1$个集合中的点直接的距离是$1$。强制第$0$个集合为出发点,只有一个元素$1$.
为了体现$1$开始到其他点的最短路,特殊的,要求$1-n$的最短路为$m$,我们强制编号为$0$的集合为出发集,只有一个元素$1$,$n$必须在编号为$m$的集合中
这样一来,我们只需要计算划分集合的方案数了。
我们从编号为$1$的集合开始考虑。设$f[i][j][k]$表示考虑到集合$i$,已经放置了$j$个数字,第$i$个集合放置了$k$个数字的方案数。
我们可以枚举上一个集合放置元素个数$s$,从$f[i-1][j-k][s]$转移过来。
我们考虑构成当前集合的情况:
- 要求之前被安放的数字不重复:除了元素$n$之外还剩余$n-1-(j-k)$个数字,有$\binom{n-1-(j-k)}{k}$种可能。
- 当前集合里元素之间可以无限制连边:有$2 ^ \binom{k}{2} $种可能。
- 之前一个集合和当前集合至少需要有$1$条连边:有$(2^s - 1) ^ k$种可能
注意到,要求之前被安放的数字不重复中,若$i = m$的时候,由于最后一个集合中已经被强制放入元素$n$,所以需要另外选择的元素是$k-1$个,所以有$\binom{n-1-(j-k)}{k-1}$种可能。
所以转移方程为:
$f[i][j][k] =\left\{\begin{matrix}\sum\limits_{s = 1} ^ {j-k} f[i-1][j-k][s] \times \binom{n-j+k-1}{k} \times 2 ^ {\binom{k}{2}} \times (2^s - 1) ^ k & (1 \leq i < m )\\ \sum\limits_{s = 1} ^ {j-k} f[i-1][j-k][s] \times \binom{n-j+k-1}{k-1} \times 2 ^ {\binom{k}{2}} \times (2^s - 1) ^ k & (i=m) \end{matrix}\right.$
统计答案的时候,设当前已经将$j$个元素放置在分层图当中了,最后一层中放置了$k$个,那么考虑这些点直接互相连边,这些点和最后一层中所有点之间互相连边的可能即可。
最后的答案就是$ans = \sum\limits_{j = m+1}^{n} \sum\limits_{k = 1}^{j-m} f[m][i][j] \times 2^{\binom{n-j}{2} + (n-j) \times k}$
复杂度是$O(n^4)$
# include <bits/stdc++.h> # define int long long # define fp(i,s,t) for (int i=s;i<=t;i++) using namespace std; const int mo=1e9+7; const int N=310; int c[N][N],n,m,f[N][N][N],Max; int Pow(int x,int n) { Max = max(Max,n); int ans = 1; while (n) { if (n&1) ans=ans*x%mo; x=x*x%mo; n>>=1; } return ans; } signed main() { scanf("%lld%lld",&n,&m); c[0][0]=1; fp(i,1,300) { c[i][0]=c[i][i]=1; fp(j,1,i-1) c[i][j]=(c[i-1][j-1]+c[i-1][j])%mo; } f[0][1][1]=1; fp(i,1,m) fp(j,1,n) fp(k,1,j) fp(s,1,j-k) { if (i!=m) f[i][j][k]+=Pow(2,c[k][2]) * Pow((Pow(2,s)-1+mo)%mo,k)%mo * c[n-j+k-1][k] % mo * f[i-1][j-k][s] %mo; else f[i][j][k]+=Pow(2,c[k][2]) * Pow((Pow(2,s)-1+mo)%mo,k)%mo * c[n-j+k-1][k-1] % mo * f[i-1][j-k][s] %mo; f[i][j][k]%=mo; } int ans = 0; fp(j,m+1,n) fp(k,1,j-m) { ans=(ans+f[m][j][k]*Pow(2,c[n-j][2]+(n-j)*k)%mo)%mo; } cout<<ans<<'\n'; return 0; }