チーム分け
题面
题意
每个点有限制形如这个点分的组人数 \(\le a_i\),问合法方案数。\(n\le 1000\)
题解
一个组内的限制只与 \(a_i\) 最小的元素相关,不妨将 \(a_i\) 从大到小排序延后计算贡献。
设 \(dp_{i,j}\) 表示考虑完前 \(i\) 个人,有 \(j\) 个人没有参与匹配的方案数,答案为 \(dp_{n,0}\)。转移的话:
- 第 \(i\) 个人不匹配。\(dp_{i,j}\gets dp_{i-1,j-1}\)。
- 第 \(i\) 个人新开一组。枚举前面有多少人和我放在一组,乘上一个组合数转移。即对 \(k\in [0,a_i-1]\),有 \(dp_{i,j}\gets dp_{i-1,j+k}\times \binom{j+k}{k}\)。
还有 \(O(n^2\log n)\) 的做法。考虑直接枚举大小恰为 \(i\) 的集合有多少个。设 \(dp_{i,j}\) 表示考虑完大小 \(\ge i\) 的集合,剩余 \(j\) 个数没有匹配的方案数,每次枚举选出多少个大小为 \(i\) 的集合进行转移。
code:
#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int N=2010,mod=1e9+7;
int n,m,k,T,a[N],w[N],f[N][N],jie[N],ni[N],s[N];
int inv[N][N];
vector<int> g[N];
int ksm(int x,int k){int ans=1;while(k){if(k&1) ans=ans*x%mod;x=x*x%mod;k>>=1; }return ans;
}
bool cmp(int a,int b){return a>b;
}
int c(int n,int m){return jie[m]*ni[n]%mod*ni[m-n]%mod;
}
signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>n;rep(i,1,n) cin>>a[i],w[a[i]]++; jie[0]=1;rep(i,1,n) jie[i]=jie[i-1]*i%mod;ni[n]=ksm(jie[n],mod-2);per(i,n-1,0) ni[i]=ni[i+1]*(i+1)%mod; per(i,n,1) s[i]=s[i+1]+w[i];f[n+1][0]=1;rep(i,1,n){rep(k,0,n/i){inv[i][k]=ksm(ksm(jie[i],k)%mod,mod-2)%mod;}}per(i,n,1){rep(j,0,s[i]){rep(k,0,n/i){if(f[i+1][j+k*i-w[i]]&&j+k*i<=n&&j+k*i-w[i]>=0){int x=c(k*i,j+k*i)*ni[k]%mod*jie[i*k]%mod*inv[i][k]%mod;f[i][j]=(f[i][j]+f[i+1][j+k*i-w[i]]*x)%mod;}}}}cout<<f[1][0];return 0;
}
Card Collector
题面
题意
有一些物品,其有坐标和权值,现在有如下选择:
- 第一轮:在每一行中至多选择一个。
- 第二轮:在每一列中至多选择一个。
问可获得的最大值。
题解
赛时做法:
我们有一个思路,就是从大到小,判断每个点能不能放在其中一轮中。
发现判断这玩意的过程就是二分图匹配,卡卡常就过 \(10^6\) 了。
code:
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
using namespace std;
const int N=2e6+10;
int n,r,c,x,y,a[N],idx[N],fr[N];
int g[N][2],ma[N],tim,vis[N];
long long ans;
bool bfs(int u){if(fr[u]) return 0;int v=g[u][0];if(vis[v]^tim){vis[v]=tim;if(!ma[v]||bfs(ma[v])){ma[v]=u;fr[u]=0;return 1;}else fr[u]=1;}v=g[u][1];if(vis[v]^tim){vis[v]=tim;if(!ma[v]||bfs(ma[v])){ma[v]=u;fr[u]=0;return 1;}else fr[u]=1;}return 0;
}
bool cmp(int x,int y){return a[x]>a[y];}
int main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>r>>c;rep(i,1,n){cin>>x>>y>>a[i];g[i][0]=x;g[i][1]=y+r;idx[i]=i;}sort(idx+1,idx+n+1,cmp);rep(i,1,n){int u=idx[i];tim++;if(bfs(u)) ans+=a[u];}cout<<ans;return 0;
}
正经做法
还是先连边,考虑把二分图左右合并,那么我们可以发现任意合法情况每个图都是基环树森林,或者是一些森林混合基环树。考虑最大生成树和最大生成基环树,这两做法几乎一样,区别就是加了一个最大未选边。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
using namespace std;
const int N = 2e6+5;
int f[N],n,r,c;
bool tp[N];ll ans;
struct node{int u,v,w;}a[N];
int fd(int x){return x == f[x]?x:f[x] = fd(f[x]);}
char buf[1<<21],*p1,*p2;
inline int rd()
{char c;int f = 1;while(!isdigit(c = getchar()))if(c=='-')f = -1;int x = c-'0';while(isdigit(c = getchar()))x = x*10+(c^48);return x*f;
}
int main()
{freopen("oblivious.in","r",stdin);freopen("oblivious.out","w",stdout);n = rd();r = rd();c = rd();for(int i = 1;i <= r+c;i++)f[i] = i;for(int i = 1;i <= n;i++)a[i] = {rd(),rd()+r,rd()};sort(a+1,a+n+1,[](node x,node y){return x.w > y.w;});for(int i = 1;i <= n;i++){int u = fd(a[i].u),v = fd(a[i].v),w = a[i].w;if(u != v){if(!tp[u]||!tp[v])ans += w,tp[u] |= tp[v],f[v] = u;}else if(!tp[u])tp[u] = 1,ans += w;}cout << ans << endl;return 0;
}
Bitaro's Travel 2
题面
题意
给一个矩形,每个坐标有点权,代表高度。
现在有一种操作叫跳高,具体为:
- 上升到 \(x+L+0.5\) 的位置。
- 保持当前高度不变,重复向四个相邻方向(上下左右)移动。途经的所有单元格的山峰高度必须低于当前海拔。
- 降落到某一点。
现在有询问形如 \((a,b)\to(c,d)\) 的最小跳高次数,若不可到达输出 \(-1\)。
\(n\le 3\times 10^5\)
题解
有结论:一次跳高只可能使得可以到达的点增加,不会使原来可以到达的点无法到达。
于是考虑在查询时如何快速得知从起点到终点需要经过的最高的山的最低高度 \(h\)(也就是说,只要在跳跃过程中跳到了这个高度即可)。这是一个经典问题,可以使用 Kruskal 重构树简单地求解。
接下来处理每次最多跳 \(L\) 的限制。根据上面的结论,除了最后一次跳跃,每次贪心地跳到当前能跳到的最高的山是正确的。对于每个坐标 \((x,y)\) 处理出 \(\mathrm{nx}_{x,y}\) 表示 \((x,y)\) 跳一次可以到达的最高的山的坐标,发现这个东西可以倍增;于是对于每个询问就能求出最少跳几次可以到达高度 \(h\)。将这个答案 \(+1\) 即为最终答案。
\(\mathrm{nx}_{x,y}\) 可以用扫描线的技巧处理。把所有山按照高度排序,从低到高扫描所有的高度 \(h'\):对于高度 \(<h'-L\) 的山找出当前加入过的山中与它连通的高度最大的山(可以使用并查集维护),作为其 \(\mathrm{nx}\) 的值;接着把 \(\le h'\) 且未加入的山加入,并与四联通且已加入的其他山合并。
\(nx_{x,y}\) 还可以在遍历重构树的时候用一个 \(set\) 维护,因为某个点的 \(nx\) 一定在其重构树的祖先上。这样很好写,考场上没调出来,反思一下。
code:
#include<bits/stdc++.h>
#define pb push_back
#define tpi tuple<int,int,int>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int N=6e5+10;
int n,w[N],W,H,L,fa[N],dep[N],f[N][21],vis[N];
int mx[N][21],tot,ce[N],nw[N],q,ff[N][21];
vector<int> g[N];
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int idx(int i,int j){return (i-1)*W+j;
}
inline int find(int x){if(fa[x]==x) return x;return fa[x]=find(fa[x]);
}
void dfs(int u,int fa){dep[u]=dep[fa]+1;f[u][0]=fa;for(int i=1;(1<<i)<=dep[u];i++){f[u][i]=f[f[u][i-1]][i-1];}for(int v:g[u]){if(v==fa) continue;dfs(v,u);}
}
int lca(int x,int y){if(dep[x]<dep[y]) swap(x,y);per(i,20,0){if(dep[f[x][i]]>=dep[y]) x=f[x][i];}if(x==y) return x;per(i,20,0){if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];}return f[x][0];
}
void getst(){rep(i,1,20){rep(j,1,n){ff[j][i]=ff[ff[j][i-1]][i-1];}}rep(i,1,20){rep(j,1,n){mx[j][i]=max(mx[j][i-1],mx[ff[j][i-1]][i-1]);}}
}
int solve(int x,int w){int ans=0;per(i,20,0){if(mx[x][i]<w){ans+=(1<<i);x=ff[x][i];}}if(mx[x][0]>=w) return ans+1;return -1;
}
signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);vector<tpi> w,e;cin>>H>>W>>L;tot=H*W;n=H*W;rep(i,1,H){rep(j,1,W){int x;cin>>x;ce[idx(i,j)]=x;w.emplace_back(x,i,j);rep(k,0,1){int fx=i-dx[k],fy=j-dy[k];if(fx<=0||fy<=0||fx>H||fy>W) continue;int ww=max(ce[idx(i,j)],ce[idx(fx,fy)]);e.emplace_back(ww,idx(i,j),idx(fx,fy));}}}sort(e.begin(),e.end());sort(w.begin(),w.end());rep(i,1,N-1) fa[i]=i;for(auto t:e){int w=get<0>(t),u=get<1>(t),v=get<2>(t);if(find(u)!=find(v)){u=find(u);v=find(v);fa[u]=fa[v]=++tot;nw[tot]=w;g[u].pb(tot);g[v].pb(tot);g[tot].pb(u);g[tot].pb(v);}}int sum=0; dfs(tot,0);int l=0;rep(i,1,N-1) fa[i]=i;for(auto t:w){int ww=get<0>(t),x=get<1>(t),y=get<2>(t);vis[idx(x,y)]=1;while(get<0>(w[l])+L<ww){int i=get<1>(w[l]),j=get<2>(w[l]);mx[idx(i,j)][0]=ce[find(idx(i,j))];ff[idx(i,j)][0]=find(idx(i,j));l++;}rep(k,0,3){int fx=x+dx[k],fy=y+dy[k];if(fx<=0||fy<=0||fx>H||fy>W) continue;if(!vis[idx(fx,fy)]) continue;fa[find(idx(fx,fy))]=idx(x,y);}}while(l<n){int i=get<1>(w[l]),j=get<2>(w[l]);ff[idx(i,j)][0]=find(idx(i,j));mx[idx(i,j)][0]=ce[find(idx(i,j))];l++;}getst();cin>>q;while(q--){int a,b,c,d;cin>>a>>b>>c>>d;int res=nw[lca(idx(a,b),idx(c,d))];cout<<solve(idx(a,b),res)<<'\n';}return 0;
}
Equalization
题面
P7718 「EZEC-10」Equalization
题意
给你一个长为 \(n\) 的数组 \(a_1,a_2,\ldots,a_n\)。
你可以任选三个整数 \(l,r,x\ (1\le l\le r\le n\),\(x\in \mathbb Z)\),并将 \(a_l,a_{l+1},\ldots,a_r\) 均加上 \(x\),称为一次操作。
问最少进行几次操作,才能使 \(a\) 中所有元素均相等?并求出能使操作次数最少的不同方案数。
由于方案数可能很大,请对 \(10^9+7\) 取模。
两种方案相同,当且仅当两方案每次操作选择的 \((l,r,x)\) 均相同。
特别地,不进行任何操作也算一种方案。
对于 \(100\%\) 的数据,\(1\le n\le 18\),\(-10^9\le a\le 10^9\)。
题解
首先看到区间操作我们可以想到差分转换,将区间操作转化为差分序列上的一个或两个单点操作,具体来说我们设 \(b_i=a_{i+1}-a_i\),那么对于一次形如 \(\forall i\in[l,r],a_i\leftarrow a_i+x\) 的操作三元组 \((l,r,x)\),我们有:
- \(l=1,r=n\),等于啥也没干,那么我们显然不会选择这样的区间进行操作,否则就会浪费一次操作次数,所以我们 duck 不必考虑这种情况。
- \(l=1,r\ne n\):相当于令 \(b_{r}\leftarrow b_r-x\)
- \(l\ne 1,r=n\):相当于令 \(b_{l-1}\leftarrow b_{l-1}+x\)
- \(l\ne 1,r\ne n\):相当于令 \(b_{l-1}\leftarrow b_{l-1}+x,b_r\leftarrow b_r-x\)
于是问题转化为:给定长度为 \(n-1\) 的差分序列,每次可以进行以下两种操作之一:修改一个单点的值,或者指定三个整数 \(x,y,z\),将 \(b_x\) 改为 \(b_x+z\),\(b_y\) 改为 \(b_y-z\),最少需要多少次操作才能将所有数变为 \(0\)。
直接处理不太容易,因此考虑挖掘一些性质。做过这道题的同学应该不难想到,我们可以在每次操作的两个点之间连一条边,如果咱们操作的是一个单点那么就连一条自环,这样显然会得到一张点数为 \(n-1\),边数等于操作次数的图。那么有一个性质:在最优策略连出的图中,对于每个连通块 \((V',E')\),记 \(S=\sum\limits_{x\in V'}b_x\),有
- 如果 \(S=0\),那么该连通块必然是一棵树,耗费 \(|V'|-1\) 次操作。
- 如果 \(S\ne 0\),那么该连通块必然是一棵树加一个自环,耗费 \(|V'|\) 次操作。
证明不会,感性理解一下即可
观察到这个性质之后,求解第一问就易如反掌了,注意到 \(n\) 很小,因此考虑状压,记 \(dp_S\) 表示将 \(S\) 中的元素变为 \(0\) 的最少操作次数,枚举子集转移即可,复杂度 \(3^{n-1}\)。
接下来考虑第二问,显然每次操作都是不同的,因此我们可以只用考虑操作方案的集合有哪些,最后乘个操作次数的阶乘即可。其次,如果我们能够知道对于一个连通块而言,将它按照最优策略变为 \(0\) 的方案数,我们就能用乘法原理一边 DP 一遍记录将每个集合变为 \(0\) 的最优策略的方案数了。因此考虑将每个集合变为 \(0\) 的方案数,还是分 \(S=0\) 和 \(S\ne 0\) 两种情况处理:
- \(S=0\),那么该连通块是一棵无根树,那么可以很自然地猜到应该跟什么有标号树计数有关,Prufer 序列算一算结果是 \(|V'|^{|V'|-2}\),事实上结论也的确如此,这里稍微口胡一下证明:显然一个操作集合唯一对应一棵树,而对于一棵无根树而言,能够得到它的操作集合也是唯一的,证明可以通过构造方案说明:我们假设这棵树中编号最大(其实大不大无所谓,只要形成一个严格的偏序关系即可)的叶子节点为 \(u\),与其相连的点为 \(v\),那么我们必须让 \(u\) 的权值变为 \(0\),因为否则进行完此次操作之后,点 \(u\) 就孤立了,无法再次通过两点的操作变回 \(0\) 了,因此这次操作 \(u\) 的权值必须减去 \(a_u\),\(v\) 的权值也就必然加上 \(a_u\),如此进行下去直到还剩一个点为止,而由于该连通块中权值之和为 \(0\),因此最终剩下的那个点权值也是 \(0\),故我们构造出的方案合法。又因为我们每一次操作唯一,因此操作集合唯一;如果我们改变下操作的边集的顺序那么显然操作集合不会变,因此操作集合与无根树形成双射关系,证毕。
- \(S\ne 0\),其实不过是在无根树的基础上加了一个自环,加这个自环的侯选位置总共有 \(|V'|\) 个,再加上对于这个自环而言有两个区间能够改变差分序列上这个点的值(假设我们要改变 \(b_x\),那么我们可以操作 \([1,x]\),也可以操作 \([x+1,n]\)),因此还需乘个 \(2\),总方案数 \(2|V'|^{|V'|-1}\),如果你
力求极致、追求严谨,那么也可以仿照 \(S=0\) 的证明方式。
时间复杂度 \(\mathcal O(3^{n-1})\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,cnt,ans1,ans2,a[18],d[18],c[18],fact[18],power[18],f[1 << 17],g[1 << 17],v[1 << 17],val[1 << 17],bit[1 << 17],mod = 1e9 + 7;
inline ll read(){ll s = 0,w = 1;char ch = getchar();while (ch < '0'|| ch > '9'){ if (ch == '-') w = -1; ch = getchar();}while (ch >= '0'&& ch <= '9') s = (s << 1) + (s << 3) + (ch ^ 48),ch = getchar();return s * w;
}
ll qp(ll x,ll y){ll a = 1,b = x;while (y){if (y & 1) a = a * b % mod;b = b * b % mod,y >>= 1;}return a;
}
int main(){n = read();for (ll i = 0;i < n;i += 1) a[i] = read();n -= 1;for (ll i = 0;i < n;i += 1) d[i] = a[i + 1] - a[i];c[0] = fact[0] = power[1] = 1;for (ll i = 1;i <= n;i += 1) fact[i] = fact[i - 1] * i % mod;for (ll i = 2;i <= n;i += 1) power[i] = qp(i,i - 2);for (ll i = 1;i <= n + 1;i += 1) c[i] = 2 * qp(i + 2,i - 1) % mod;for (ll i = 1;i < (1 << n);i += 1){ll sum = 0;for (ll j = 0;j < n;j += 1) if (i & (1 << j)) sum += d[j],bit[i] += 1;if (!sum) f[i] = 1,g[i] = power[bit[i]];}for (ll i = 1;i < (1 << n);i += 1) if (f[i]){bool fl = 1;for (ll j = (i - 1) & i;j;j = (j - 1) & i) if (f[j]){fl = 0; break;}val[i] = fl;}for (ll i = 1;i < (1 << n);i += 1) if (f[i]){for (ll j = i;j;j = (j - 1) & i) v[j] = 0;for (ll j = (i - 1) & i;j;j = (j - 1) & i) if (val[j] && !v[j]){if (f[j] + f[i ^ j] > f[i]) f[i] = f[j] + f[i ^ j],g[i] = g[j] * g[i ^ j] % mod;else if (f[j] + f[i ^ j] == f[i]) g[i] = (g[i] + g[j] * g[i ^ j] % mod) % mod;for (ll k = (i ^ j);k;k = (k - 1) & (i ^ j)) v[k] = 1;}}ans1 = n - *max_element(f,f + (1 << n));if (ans1 == n){cout<<n<<endl<<c[n] * fact[ans1] % mod; return 0;}for (ll i = 0;i < (1 << n);i += 1) if (f[i] == n - ans1) ans2 = (ans2 + g[i] * c[n - bit[i]] % mod) % mod;cout<<ans1<<endl<<ans2 * fact[ans1] % mod;return 0;
}