D - Double Landscape
考虑某一行某一列被限制后,该行列只能填小于等于被限制的数。
没被限制的行列可以任意填,由于先填大的不影响后续填小的,所以考虑从大到小填数。
如果这个数要被限制填在某一行或某一列,就填在某一行或列。
否则可以任意填在限制最大值比它大的位置,如果填不了就-1。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+5,mod=1e9+7;
int n,m,x[N*N],y[N*N],a[N][N],sum[N*N];
bool vis[N*N];
int main()
{
scanf("%d%d",&n,&m);
bool flag=true;
for(int i=1;i<=n;i++)
{
int s;scanf("%d",&s);
if(x[s]) flag=false;
x[s]=i;
vis[s]=true;
}
for(int i=1;i<=m;i++)
{
int s;scanf("%d",&s);
if(y[s]) flag=false;
y[s]=i;
vis[s]=true;
}
for(int i=n*m;i>=1;i--)
{
if(x[i])
for(int j=1;j<=m;j++) a[x[i]][j]=i;
if(y[i])
for(int j=1;j<=n;j++) a[j][y[i]]=i;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i][j]) sum[a[i][j]]++;
else sum[n*m+1]++;
ll ans=1;
for(int i=n*m;i>=1;i--)
{
if(vis[i])
{
if(!sum[i]) flag=false;
if(!(x[i]&&y[i])) ans=ans*sum[i]%mod;
sum[i]--;
sum[i]+=sum[i+1];
}
else
{
sum[i]+=sum[i+1];
if(!sum[i]) flag=false;
ans=ans*sum[i]%mod,sum[i]--;
}
}
if(!flag) ans=0;
printf("%lld\n",ans);
}
E Connecting Cities
分治,考虑一段区间l,r,在l,mid区间中找一个最小的i0
mid+1,r区间找一个最小的j0
用i0给右区间全部连边,j0给左区间全部连边
对于边i,j,有边i0,j,边i0,j0,边i,j0都比它小
而一个环中的最大边不会是最小生成树中的边。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
typedef long long ll;
int n,tot,d,a[N],f[N];
struct node
{
int u,v;
node(int u=0,int v=0,ll w=0):u(u),v(v),w(w){
}
ll w;
bool operator<(const node&o)const
{
return w<o.w;
}
}e[N*20];
void solve(int l,int r)
{
if(l==r) return;
int m=l+r>>1;
ll p1=l,p2=m+1,s=1e18;
for(ll i=l;i<=m;i++)
if(-i*d+a[i]<s)
s=-i*d+a[i],p1=i;
s=1e18;
for(ll i=m+1;i<=r;i++)
if(i*d+a[i]<s)
s=i*d+a[i],p2=i;
for(ll i=l;i<=m;i++)
{
tot++;
e[tot]=node(i,p2,-i*d+a[i]+p2*d+a[p2]);
}
for(ll i=m+1;i<=r;i++)
{
tot++;
e[tot]=node(p1,i,-p1*d+a[p1]+i*d+a[i]);
}
solve(l,m);solve(m+1,r);
}
int getf(int x){
return f[x]==x?x:f[x]=getf(f[x]);}
int main()
{
scanf("%d%d",&n,&d);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
solve(1,n);
sort(e+1,e+1+tot);
for(int i=1;i<=n;i++) f[i]=i;
ll ans=0;
for(int i=1;i<=tot;i++)
{
int fu=getf(e[i].u),fv=getf(e[i].v);
if(fu==fv) continue;
ans+=e[i].w;
f[fu]=fv;
}
printf("%lld\n",ans);
}
F Paper Cutting
从n+m中选择k条线进行全排列的方案数是C(n+m,k)*f[k]。
考虑计算随机进行选k条线进行切割的期望,答案就是期望乘以所有方案。
考虑一个以i,j为左下角的矩形,它第一次出现时得到的期望是1。
以后只要进行一次切割,期望就加1。
那么对于一个以i,j为左下角的矩形在k次操作里面出现的概率为C(k,2)/C(n+m,2)
其中C(k,2)为把这两条线放入k次序列的方案数(不考虑顺序,因为只有两个同时出现才有贡献)
而能排列的前提是这两个被同时选中了,同时选中的概率是1/C(n+m,2),乘起来就是概率
现在考虑随后每切一次的概率,考虑i,j,k,如果k在i,j后面出现,那么i,j这个矩形的贡献就加1。
那么在其后面的概率为1/3(可以是任意一个在其后面)
三个同时被选中的概率为C(k,3)/C(n+m,3),同时k的候选个数有除了i,j还有n+m-2个
所以总期望是ans=C(k,2)/C(n+m,2)+C(k,3)/C(n+m,3)*1/3*(n-m+2)
而i,j配对的方案数是n*m
所以对于i>0,j>0的总方案数是n*m*ans
其它还有i>0,j=0 i=0,j>0 i=0,j=0的情况,可以用同样的方法推出来。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e7+5,mod=1e9+7;
int n,m,k;
ll f[N],invf[N];
ll inv(ll x){
return x<=1?x:(mod-mod/x)*inv(mod%x)%mod;}
ll C(ll n,ll m)
{
return m<=n?f[n]*invf[n-m]%mod*invf[m]%mod:0;
}
int main()
{
f[0]=invf[0]=f[1]=invf[1]=1;
for(int i=2;i<N;i++) f[i]=f[i-1]*i%mod,invf[i]=(mod-mod/i)*invf[mod%i]%mod;
for(int i=2;i<N;i++) invf[i]=invf[i]*invf[i-1]%mod;
scanf("%d%d%d",&n,&m,&k);
ll ans=C(k,2)*inv(C(n+m,2))%mod+C(k,3)*inv(C(n+m,3))%mod*(n+m-2)%mod*inv(3)%mod;
ans=ans*n%mod*m%mod;
ll res=C(k,1)*inv(C(n+m,1))%mod+C(k,2)*inv(C(n+m,2))%mod*(n+m-1)%mod*inv(2)%mod;
ans=(ans+res*(n+m))%mod;
ans=(ans+k)%mod;
ans=ans*C(n+m,k)%mod*f[k]%mod;
printf("%lld\n",ans);
}