AGC038 题解
[AGC038A] 01 Matrix
题意
构造一个 \(n\times m\) 矩阵,满足对于每一行,\(\min(cnt_0,cnt_1)=a\),对于每一列 \(\min(cnt_0,cnt_1)=b\),或报告无解。
\(n,m\le 10^3,0\le 2a \le n,0\le 2b \le m\)
idea
简单构造。
取前 \(b\) 行,每行先放 \(a\) 个 \(0\),再放 \(m-a\) 个 \(1\)。
取后 \(n-b\) 行,每行先放 \(a\) 个 \(1\),再放 \(m-a\) 个 \(0\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n"
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,m,x,y;
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>n>>m>>x>>y;
for(int i=1;i<=y;i++){
for(int j=1;j<=x;j++)cout<<0;
for(int j=x+1;j<=m;j++)cout<<1;
cout<<endl;
}
for(int i=y+1;i<=n;i++){
for(int j=1;j<=x;j++)cout<<1;
for(int j=x+1;j<=m;j++)cout<<0;
cout<<endl;
}
return 0;
}
[AGC038B] Sorting a Segment
题意
给定长为 \(n\) 的排列,问对恰好一个长为 \(k\) 的区间升序排序,最后可能的本质不同序列是多少。
idea
如果一个长为 \(k\) 的区间本来就是升序,排序完不变,这种情况只统计一次答案。
对于不完全升序的情况,考虑什么情况下,两个区间排序后仍然相同。
设第一个排序区间为 \([i,i+k-1]\),第二个与其有交的排序区间为 \([j,j+k-1]\),如果他们排序后相同,当且仅当 \([i,j-1]\) 和 \([i+k,j+k-1]\) 在排序后均不会改变位置。
如果 \([j,j+k-1]\) 与 \([i,i+k-1]\) 排序后相同,那么一定有 \([j-1,j+k-2]\) 和 \([i,i+k-1]\) 相同。 考虑取 \(j=i+1\),只需要判断 \(a_i\) 是区间最小值,\(a_{i+k}\) 是区间最大值即可。
总结
- 重要观察:两区间重排后相等的条件。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 800005
#define endl "\n"
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,k,a[N];
ll l[N],sum[N];
struct sgt{
#define mid ((l+r)>>1)
#define ls (p<<1)
#define rs (p<<1|1)
ll mx[N],lzy[N],mi[N];
void build(ll p,ll l,ll r){
if(l==r){
mx[p]=a[l];
mi[p]=a[l];
return ;
}
build(ls,l,mid);
build(rs,mid+1,r);
mx[p]=max(mx[ls],mx[rs]);
mi[p]=min(mi[ls],mi[rs]);
}
ll qrmax(ll p,ll l,ll r,ll le,ll ri){
if(le<=l&&ri>=r)return mx[p];
ll ans=-inf;
if(le<=mid)ans=max(ans,qrmax(ls,l,mid,le,ri));
if(ri>mid)ans=max(ans,qrmax(rs,mid+1,r,le,ri));
return ans;
}
ll qrmin(ll p,ll l,ll r,ll le,ll ri){
if(le<=l&&ri>=r)return mi[p];
ll ans=inf;
if(le<=mid)ans=min(ans,qrmin(ls,l,mid,le,ri));
if(ri>mid)ans=min(ans,qrmin(rs,mid+1,r,le,ri));
return ans;
}
}T;
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
T.build(1,1,n);
l[0]=inf;
for(int i=1;i<=n;i++)l[i]=min(l[i-1],a[i]);
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+(a[i]<a[i-1]);
ll res=0;
ll p=1;
for(int i=1;i+k-1<=n;i++){
if(sum[i+k-1]-sum[i]==0){
res+=p,p=0;
continue;
}
if(T.qrmin(1,1,n,i,i+k-1)==a[i]&&T.qrmax(1,1,n,i+1,i+k)==a[i+k])continue;
res++;
}
cout<<res<<endl;
return 0;
}
[AGC038C] LCMs
题意
求:
idea
推公式后莫比乌斯反演板题。
开始推公式:
中间后面都可以枚举倍数刷表做,时间复杂度 \(O(n\log n)\)
总结
- 写公式累死了。
- 重要化简:把 \(a_i\) 转化为 \(i\cdot c_i\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 1000005
#define endl "\n"
#define fi first
#define se second
using namespace std;
const ll mod=998244353;
const ll inf=1e18;
const double eps=1e-6;
ll mu[N],smu[N],phi[N],f[N],g[N];
bool vis[N];
vector<ll>prim;
ll fpow(ll x,ll y){
ll res=1;
while(y){
if(y&1)res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
void init(ll n){
mu[1]=phi[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i]){
mu[i]=-1;
phi[i]=i-1;
prim.push_back(i);
}
for(auto j:prim){
if(i*j>n)break;
vis[i*j]=1;
if(i%j==0){
phi[j*i]=phi[i]*j;
mu[j*i]=0;
break;
}
phi[j*i]=phi[i]*phi[j];
mu[j*i]=-mu[i];
}
}
for(int i=1;i<=n;i++)for(int j=1;i*j<=n;j++)g[i*j]+=mu[i]*i;
}
ll sold(ll n){
ll res=0;
for(int x=1;x<=n;x++)res=(res+x*g[x]%mod*f[x]%mod*f[x]%mod+mod)%mod;
return res;
}
ll n,m,a[N],mp[N];
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>n;
ll sum=0,m=0;
for(int i=1;i<=n;i++){
cin>>a[i];
mp[a[i]]++;
sum=(sum+a[i])%mod;
m=max(m,a[i]);
}
init(m);
for(int i=1;i<=m;i++)for(int j=1;i*j<=m;j++)f[i]=(f[i]+mp[i*j]*j%mod)%mod;
ll res=sold(m);
cout<<(res-sum+mod)%mod*fpow(2,mod-2)%mod;
return 0;
}
[AGC038D] Unique Path
题意
有一张联通图 \(n\) 个点 \(m\) 条边。
给定 \(Q\) 条限制,每条限制形如\(A_i,B_i,C_i\)
若 \(C_i = 0\),则 \(A_i\) 到 \(B_i\) 仅有一条简单路径。
若 \(C_i=1\),则 \(A_i\) 到 \(B_i\) 有多条简单路径。
判定在这 \(Q\) 条限制下能否构造出合法的图。可以输出 Yes,否则输出 No。
\(n,Q\le 10^5,m\le \frac{n(n-1)}{2}\)
idea
考虑若 \(a\rightarrow b\) 简单路径唯一, \(b\rightarrow c\) 路径唯一,则 \(a\rightarrow c\) 简单路径唯一,具有传递性,考虑用并查集维护,得到 \(k\) 个连通块。
然后不会连多条路径的边了。
Sol
如果没有多条简单路径的限制的情况:
-
最小连边数量为 \(k-1\),这是为了保证图连通。
-
最大连边数量为 \(\frac{k(k-1)}{2}\),由于同一个连通块不可能有两个点连向同一个连通块,所以只取每个连通块的一个点作为代表,这 \(k\) 个点两两相连。
如果有多条简单路径的限制的情况:
-
如果简单路径不唯一的点位于同一个连通块无解。
-
只有两个连通块无解,无论如何也不满足条件。
-
最小连边数量为 \(k\),在保证图连通的基础上,要求多条简单路径,每个连通块的代表点构成环。
-
最大连边数量仍然为 \(\frac{k(k-1)}{2}\)。
只需要判断 \(m\) 条边够用且能用完即可。
总结
- 重要观察 \(1\):简单路径唯一的传递性。
- 重要观察 \(2\):连边数量的上下界。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 100005
#define endl "\n"
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
int n;
ll k,m;
int q;
int fa[N];
int fd(ll x){
return fa[x]==x?fa[x]:fd(fa[x]);
}
void mer(ll x,ll y){
x=fd(x),y=fd(y);
if(x==y)return ;
fa[y]=x;
}
vector<pair<ll,ll> >v;
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>n>>m>>q;
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=q;i++){
ll x,y,op;
cin>>x>>y>>op;
x++,y++;
if(op==0)mer(x,y);
else v.push_back({x,y});
}
for(int i=1;i<=n;i++)if(fa[i]==i)k++;
if(k==2&&v.size()){
cout<<"No\n";
return 0;
}
for(auto [x,y]:v){
if(fd(x)==fd(y)){
cout<<"No\n";
return 0;
}
}
m-=(n-k);
if(m<k-(v.empty())){
cout<<"No\n";
return 0;
}
if(k*(k-1)/2<m){
cout<<"No\n";
return 0;
}
cout<<"Yes\n";
return 0;
}
[AGC038E] Gachapon
题意
一个随机数生成器,生成 \([1,n]\) 之间的整数,其中生成 \(i\) 的概率为 \(\frac{A_i}{\sum A_i}\)。
当 \(\forall i\in[1,n]\),\(i\) 至少出现了 \(B_i\) 次时,停止生成,否则继续生成。
求期望生成随机数的次数,输出答案对 \(998244353\) 取模的结果。
\(a_i,b_i\geq 1\),\(\sum a_i,\sum b_i,n\leq 400\)。
idea
一眼 \(\text{min-max}\) 容斥,考虑如下式子:
\(E(\min S)\) 表示 \(S\) 集合内至少有一个元素生成规定次数的期望时间,求出这个即可求出答案。但是我不会求。
Sol
令 $ A=\sum\limits_{i=1}^n a_i,A_S=\sum\limits_{i\in S}a_i$,选到 \(S\) 集合内的元素的期望次数是 \(\frac{A}{A_S}\),于是我们可以认为每 \(\frac{A}{A_S}\) 次让 \(S\) 集合内的某个元素增加,现在只需要考虑集合内的元素即可。
\(E(\min S)\) 不好算,考虑拆成概率,设 \(P(X=i)\) 表示进行了 \(i\) 次集合内操作后,恰好有一个位置刚好达到目标的概率:
考虑 \(P(X\ge i)\) 的实际意义:至少 \(i\) 次集合内操作后,恰好有一个位置刚好达到目标的概率,等价于 \(i-1\) 次集合内操作时没有一个位置实现目标的概率。
设 \(x_i\) 满足 \(i\in S,x_i\in[0,b_i)\),表示当前目标加了几次,则有 \(X=\sum\limits_{i\in S}x_i\),我们需要计算的就是 \(X\) 次集合内操作后,\(i\) 位置上为 \(\{x_i\}\) 的概率,设其为 \(P(x_i)\)。
实际意义:选到 \(x_i\) 次 \(i\) 位置,并对其进行多重集的排序。
于是我们可以得到:
于是有:
考虑计算右边那坨东西,设 \(f[i][j][k]\) 表示考虑由前 \(i\) 个元素,当前集合的 \(A_S=j,X=k\) 的对答案贡献和。
初始化 \(f[0][0][0]=1\)。
有转移:
如果 \(i\) 不选:
如果 \(i\) 选,需要满足 \(d< b_{i+1}\):
答案为:
时间复杂度 \(O(nAB)\)。
总结
- 题解写的好抽象,理解了两天半。
- 重要观察 \(1\):期望转化概率,通过实际意义再次转化。
- 重要观察 \(2\):枚举集合贡献用 dp 求解。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 405
#define endl "\n"
#define fi first
#define se second
using namespace std;
const ll mod=998244353;
const ll inf=1e18;
const double eps=1e-6;
ll n,a[N],b[N];
ll f[N][N][N];
namespace math_permutation{
ll fpow(ll x,ll y){
ll res=1;
while(y){
if(y&1)res=res*x%mod;
y>>=1,x=x*x%mod;
}
return res;
}
ll fac[N],ifac[N],inv[N];
void work(ll r){
fac[0]=inv[1]=1;
for(int i=1;i<=r;i++)fac[i]=fac[i-1]*i%mod;
ifac[r]=fpow(fac[r],mod-2);
for(int i=r;i>0;i--){
ifac[i-1]=ifac[i]*i%mod;
inv[i]=fac[i-1]*ifac[i]%mod;
}
}
ll C(ll n,ll m){
if(n<0||n<m)return 0;
return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
void add(ll &x,ll y){
x%=mod,y%=mod;
x=(x+y+mod)%mod;
}
}using namespace math_permutation;
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>n;
work(400);
f[0][0][0]=1;
ll A=0,B=0;
for(int i=0,a,b;i<n;i++){
cin>>a>>b;
for(int j=0;j<=A;j++){
for(int k=0;k<=B;k++){
if(f[i][j][k]==0)continue;
add(f[i+1][j][k],f[i][j][k]);
for(int l=0;l<b;l++){
add(f[i+1][j+a][k+l],-1*ifac[l]*fpow(a,l)%mod*f[i][j][k]%mod);
}
}
}
A+=a,B+=b;
}
ll res=0;
for(int i=0;i<=A;i++){
for(int j=0;j<=B;j++){
ll p=fpow(i,j+1);
add(res,-fac[j]*fpow(p,mod-2)%mod*f[n][i][j]%mod);
}
}
res=res*A%mod;
cout<<res<<endl;
return 0;
}
[AGC038F] Two Permutations
题意
两个 \([1,n]\) 的排列 \(P_i,Q_i\),构造两组排列满足:
- \(A_i=P_i\) 或 \(A_i=i\)
- \(B_i=Q_i\) 或 \(B_i=i\)
最大化 $ \sum\limits_{i=1}^n[A_i\neq B_i]$。
idea
最大化 $ \sum\limits_{i=1}^n[A_i\neq B_i]$,相当于最大化 \(n- \sum\limits_{i=1}^n[A_i=B_i]\),即最小化 \(\sum\limits_{i=1}^n[A_i=B_i]\)。
感觉题目限制很像网络流,考虑如何建图。
注意到由于排列会形成若干个度数均为 \(2\) 的环,对于一个环上的点如果取 \(A_i=i\),那么整个环都要跟着一起取,如果取 \(A_i=P_i\),整个环仍然要跟着一起取。
但是不会建图,情况有点多。
Sol
对于 \(P_i\) 形成的环称为 \(p_i\) ,对于 \(Q_i\) 形成的环称为 \(q_i\),称使 \(A_i=P_i\) 的操作为旋转。
设源点 \(S\) 和汇点 \(T\) ,若存在 \(S\rightarrow p_i\) 则 \(p_i\) 旋转,\(q_i\rightarrow T\) 则 \(q_i\) 旋转。
考虑分情况讨论使最后 \(A_i=B_i\):
- \(P_i=Q_i=i\),无论如何都有 \(A_i=B_i\),需要特判。
- \(P_i=i,Q_i\neq i\),\(p_i\) 随便取,\(q_i\) 不旋转,连边 \(q_i\rightarrow T\) 表示割掉该边后 \(q_i\) 不旋转。
- \(P_i\neq i,Q_i= i\),\(q_i\) 随便取,\(p_i\) 不旋转,连边 $S\rightarrow p_i $ 表示割掉该边后 \(p_i\) 不旋转。
- \(P_i\neq Q_i \neq i\),\(p_i\) 和 \(q_i\) 都不得旋转,连边 \(q_i\rightarrow p_i\) 表示割掉该边后 \(p_i,q_i\) 都不旋转。
- \(P_i=Q_i \neq i\),连边 \(q_i\rightarrow p_i\) 表示割掉该边后 \(p_i,q_i\) 都不旋转,连边 \(p_i\rightarrow q_i\) 表示割掉该边后 \(p_i,q_i\) 都旋转。
流量都是 \(1\),于是跑最小割即可,由于是二分图时间复杂度 \(O(n\sqrt n)\)。
总结
- 重要观察:排列会成环。
- 重要观察:对于分类讨论后使用文理分科模型建图。
Code
#include<bits/stdc++.h>
#define ll long long
#define N 400005
#define endl "\n"
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,p[N],q[N];
ll a[N],b[N];
ll cnt=0;
ll st,ed;
namespace dinic{
struct edge{
ll y,val,id;
};
vector<edge>v[N];
queue<ll>qu;
ll dep[N],now[N];
bool bfs(){
for(int i=st;i<=ed;i++)dep[i]=-1,now[i]=0;
dep[st]=0;
qu.push(st);
while(!qu.empty()){
auto t=qu.front();
qu.pop();
for(auto [y,val,id]:v[t]){
if(dep[y]>=0||!val)continue;;
dep[y]=dep[t]+1;
qu.push(y);
}
}
return dep[ed]>=0;
}
ll dfs(ll x,ll flow){
if(x==ed)return flow;
for(int i=now[x];i<v[x].size();i++){
now[x]=i;
auto [y,val,id]=v[x][i];
if(dep[y]!=dep[x]+1||!val)continue;
ll t=dfs(y,min(flow,val));
if(t){
v[x][i].val-=t;
v[y][id].val+=t;
return t;
}else dep[y]=-1;
}
return 0;
}
ll sol(){
ll res=0,ans=0;
while(bfs())while(ans=dfs(st,inf))res+=ans;
return res;
}
void add(ll x,ll y,ll z){
ll sid=v[x].size();
ll eid=v[y].size();
v[x].push_back({y,z,eid});
v[y].push_back({x,0,sid});
}
}using namespace dinic;
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>n;
ll res=n;
for(int i=1;i<=n;i++){
cin>>p[i];
p[i]++;
}
for(int i=1;i<=n;i++){
if(a[i])continue;
a[i]=++cnt;
for(int j=p[i];j!=i;j=p[j])a[j]=cnt;
}
for(int i=1;i<=n;i++){
cin>>q[i];
q[i]++;
}
for(int i=1;i<=n;i++){
if(b[i])continue;
b[i]=++cnt;
for(int j=q[i];j!=i;j=q[j])b[j]=cnt;
}
st=0,ed=cnt+1;
for(int i=1;i<=n;i++){
if(q[i]==p[i]&&p[i]==i){
res--;
continue;
}
if(q[i]==p[i]){
add(a[i],b[i],1);
add(b[i],a[i],1);
continue;
}
if(p[i]!=i&&q[i]!=i){
add(b[i],a[i],1);
continue;
}
if(p[i]==i){
add(b[i],ed,1);
continue;
}
if(q[i]==i){
add(st,a[i],1);
continue;
}
}
cout<<res-sol();
return 0;
}

浙公网安备 33010602011771号