AtCoder Grand Contest 038题解

好久没更了
写点东西吧= =

A 01Matrix

简单构造

左上角和右下角染成1其他染成0即可

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[1010][1010];
int main(){
	int n,m,A,B;
	cin>>n>>m>>A>>B;
	if(A*2>m||B*2>n)return puts("-1"),0;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++){
		if(i<=B&&j<=A)putchar('1');
		else if(i>B&&j>A)putchar('1');
		else putchar('0');
		if(j==m)puts("");
	}
	return 0;
}

B Sorting a Segment

考虑若(L,R)和(L',R')排序后相同

则 $ \forall $ L<L''<L',(L,R)和(L'',R'')排序后相同

因此只需处理相邻两个是否排序后是否相同即左右两端点是否恰是最值即可

注意若两个区间排序后不改变任何数位置需要特殊处理

形式化的,我们先拉出所有排序后不改变位置的区间然后数前面相邻相同的数量即可

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[200010],b[2000010],n,m;
int qzl[200010],qzr[200010],hzl[200010],hzr[200010]; 
int calc(int L,int R){
	return a[L]==min(hzl[L],qzl[R])&&a[R]==max(hzr[L],qzr[R]);
}
int main(){
	scanf("%d%d",&n,&m);
	int tag=-1;
	for(int i=0;i<n;i++){
		scanf("%d",&a[i]);
		if(i>0&&a[i]>a[i-1])b[i]=b[i-1]+1;
		else b[i]=1;
		if(b[i]>=m)tag++;
	}
	for(int i=0;i<n;i++){
		if(i%m==0)qzl[i]=qzr[i]=a[i];
		else qzl[i]=min(qzl[i-1],a[i]),qzr[i]=max(qzr[i-1],a[i]);
	}
	for(int i=n-1;i>=0;i--){
		if(i==n-1||(i+1)%m==0)hzl[i]=hzr[i]=a[i];
		else hzl[i]=min(hzl[i+1],a[i]),hzr[i]=max(hzr[i+1],a[i]);
	}
	int ans=n-m+1;
	for(int i=m-1;i<n-1;i++)
	if(b[i]<m&&b[i+1]<m&&calc(i-m+1,i+1))ans--;
	ans-=max(0,tag);cout<<ans;
	return 0;
}

C LCMs

简单数论题

\[\begin{aligned} &\sum_{i=1}^n\sum_{j=i+1}^n\frac{a_ia_j}{\gcd(a_i,a_j)}\\ &=\sum_{d=1}^{max}\frac{1}{d}\sum_{i=1}^n[d|a_i]\sum_{j=i+1}^n[d|a_j]a_ia_j[\gcd(a_i,a_j)=1]\\ &=\sum_{d=1}^{max}\frac{1}{d}\sum_{i=1}^n[d|a_i]\sum_{j=i+1}^n[d|a_j]a_ia_j\sum_{k|a_i,k|a_j}\mu(k)\\ &=\sum_{d=1}^{max}\frac{1}{d}\sum_{i=1}^n[d|a_i]\sum_{j=i+1}^n[d|a_j]a_ia_j\sum_{k|a_i,k|a_j}\mu(k)\\ &=\sum_{d=1}^{max}\frac{1}{d}\sum_{k=1}^{\frac{max}{d}}\mu(k)(\sum_{i=1}^n[dk|a_i]\sum_{j=i+1}^n[dk|a_j]a_ia_j)\\ \end{aligned} \]

右边部分显然是关于dk的一个函数 预处理就做完了

#include<bits/stdc++.h>
#define ll long long
#define p 998244353
#define inv2 499122177
using namespace std;
int mu[1000010],ss[1000010],cnt;bool pri[1000010];
int a[200010],tong[1000010];
int h[1000010],f[1000010];
int n,m,k;
int inv[1000010];
void init(int N){
	mu[1]=1;
	for(int i=2;i<=N;i++){
		if(!pri[i])ss[++cnt]=i,mu[i]=-1;
		for(int j=1;i*ss[j]<=N&&j<=cnt;j++){
			pri[i*ss[j]]=1;
			if(i%ss[j]==0){
				mu[i*ss[j]]=0;
				break;
			}
			mu[i*ss[j]]=-mu[i];
		}
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),tong[a[i]]++;
	inv[1]=1;
	for(int i=2;i<=1000000;i++)inv[i]=1ll*inv[p%i]*(p-p/i)%p;
	init(1000000);
	for(int i=1;i<=1000000;i++){
		for(int j=i;j<=1000000;j+=i)(h[i]+=1ll*tong[j]*j%p)%=p,
		(f[i]+=1ll*j*tong[j]%p*j%p)%=p;
	}
	for(int i=1;i<=1000000;i++)
	f[i]=1ll*((1ll*h[i]*h[i]%p-f[i])%p+p)%p*inv2%p;
	ll ans=0;
	for(int k=1;k<=1000000;k++)
	for(int T=k;T<=1000000;T+=k)
	(ans+=1ll*inv[k]*mu[T/k]%p*f[T]%p)%=p;cout<<ans;
	return 0;
}

D Unique Path

先考虑所有"只能有一条路径"的对,这两个点显然在同一联通块中

且所有联通块不能出现环 即类似克鲁斯卡尔的加边

然后再考虑所有"有多条路径"的对,如果这个点对在同一联通块内则GG

因为这会导致连通块内出现环即导致"只能有一条路径"的对GG

然后发现任意两个联通块之间只可能有一条边,否则会出现环

计算m是否超过能插入的最大边数即可

#include<bits/stdc++.h>
int n,k,a,b,c,ds,fa[200010],L[200010],R[200010],d;
int ask(int x){return x==fa[x]?x:fa[x]=ask(fa[x]);}
long long m;
int main(){
	scanf("%d%lld%d",&n,&m,&k);ds=n;
	for(int i=0;i<n;i++)fa[i]=i;
	while(k--){
		scanf("%d%d%d",&a,&b,&c);
		int p=ask(a),q=ask(b);
		if(c==0&&p!=q)ds--,fa[q]=p;
		if(c==1)L[++d]=a,R[d]=b;
	}
	for(int i=1;i<=d;i++)if(ask(L[i])==ask(R[i]))return puts("No"),0;
	puts((m+ds-n>1ll*ds*(ds-1)/2)||(d&&(ds==2||m<n))?"No":"Yes");
	return 0;
}

E Gachapon

神仙题

考虑minmax反演,转换为了求每个子集的“存在一个元素 $ i $ 出现次数超过 $ b_i $” 期望次数

期望次数等价于所有“不存在一个元素 $ i $ 出现次数超过 $ b_i $” 的状态的期望出现次数之和

因为假如某个操作序列经过q次走出去了,前q个前置状态都相当于没走出去

而每个没走出去的状态的出现次数等价于出现概率乘上 $ \frac{S}{U} $ 其中U是当前子集的a的总和

这是因为到达该状态之后只要接下来选中的元素在集合外都会使得出现次数++

等比数列收敛后可得到上述结论

接下来我们只需要计算每个状态的出现概率 这样我们可以暂时忽略走到子集外的情况

我们枚举每个子集,考虑枚举操作序列的长度(不超过 $ \sum b_i $ )并计算这样的操作序列的数量

容易发现这可以由指数生成函数 $ E_d(x)=\sum\limits_{i=0}{B_d-1}\frac{xi(\frac{A_d}{U})^i}{i!}$ 代替

其中 $ x^i $ 的系数就是操作序列长度为 $ i$ 的答案

枚举子集复杂度显然GG 我们注意到我们没有必要枚举子集,只需要枚举U即可

然后做类似背包的DP,每次转移进行多项式乘法(这里可以暴力乘)

就做完了... 感觉讲的很不清楚啊... 还有问题的话私信我吧...(没有语言表达能力)

#include<bits/stdc++.h>
#define ll long long
#define p 998244353
using namespace std;
int a[1010],b[1010];
int jc[1010],njc[1010],inv[1010];
vector<int>ret,E[1010],all[1010];
vector<int>mul(vector<int>x,vector<int>y){
	vector<int>ret;
	int n=x.size(),m=y.size();
	ret.resize(n+m-1);
	for(int i=0;i<n;i++)
	for(int j=0;j<m;j++)
	(ret[i+j]+=1ll*x[i]*y[j]%p)%=p;return ret;
}
void del(vector<int>&x,vector<int>y){
	int n=x.size(),m=y.size();
	x.resize(max(n,m));
	for(int i=0;i<m;i++)(x[i]-=y[i])%=p; 
}
void add(vector<int>&x,vector<int>y){
	int n=x.size(),m=y.size();
	x.resize(max(n,m));
	for(int i=0;i<m;i++)(x[i]+=y[i])%=p; 
}
int main(){
	int n;scanf("%d",&n);
	for(int i=0;i<2;i++)jc[i]=njc[i]=inv[i]=1;
	for(int i=2;i<=405;i++){
		jc[i]=1ll*jc[i-1]*i%p;
		inv[i]=1ll*inv[p%i]*(p-p/i)%p;
		njc[i]=1ll*njc[i-1]*inv[i]%p;
	}
	for(int i=0;i<n;i++)scanf("%d %d",&a[i],&b[i]); 
	int S=0,ans=0;
	for(int i=0;i<n;i++)S+=a[i];
	for(int i=0;i<n;i++){
		for(int k=0,P=1;k<b[i];k++,P=1ll*P*a[i]%p)E[i].push_back(1ll*P*njc[k]%p);
	}
	for(int i=0;i<n;i++){
		for(int k=S-a[i];k>=1;k--)if(all[k].size())
		del(all[a[i]+k],mul(E[i],all[k]));
		add(all[a[i]],E[i]);
	}
	for(int i=1;i<=S;i++){
		for(int j=0,u=inv[i];j<all[i].size();j++,u=1ll*u*inv[i]%p)
		(ans+=1ll*all[i][j]*u%p*jc[j]%p)%=p;
	}
	(ans+=p)%=p;
	cout<<1ll*ans*S%p;
	return 0;
}

F Two Permutations

比E清真多了...

考虑两个排列的每个环上,所有点要么一起动要么一起不动

因此就变成了以下问题:

  • 每个环可以选择动(1)或不动(0)
  • 两个环若一起动或一起不动会有一个代价
  • 某个环若动或不动会有一个代价
  • 要求代价最小值

这是经典的二元关系最小割 这里有

注意一点细节如把第二个序列改成动(0)或不动(1)这样就是两个环若不同则有代价

注意这张图是二分图 直接dinic就好了 复杂度是$ O(n \sqrt{n})$的

ISAP好像过不去..?

#include<bits/stdc++.h>
#define ll long long
#define M 400010
using namespace std;
int n,k,cnt,S,T,h,t;
int F[M],L[M],N[M],a[M],c[M],Gap[M],dis[M],q[M],cur[M];
void add(int x,int y,int z,int fla=0){
	a[++k]=y;c[k]=z;
	N[k]=F[x];F[x]=k;
	if(!fla)add(y,x,0,1);
}
bool BFS(int x,int y){
	q[t=1]=x;h=0;
	for(int i=1;i<=n;i++)cur[i]=F[i],dis[i]=1000000000;
	dis[x]=0;
	while(h<t){
		x=q[++h];
		for(int i=F[x];i;i=N[i])if(c[i]&&dis[a[i]]>n){
			dis[a[i]]=dis[x]+1;
			if(a[i]==y)return 1;
			q[++t]=a[i];
		}
	}
	return dis[y]<=n;
}
int dfs(int x,int T,int flow){
	if(x==T)return flow;
	int used=0;
	for(int i=cur[x];i;i=cur[x]=N[i])if(c[i]&&dis[a[i]]==dis[x]+1){
		const int v=dfs(a[i],T,min(flow-used,c[i]));
		if(!v)continue;c[i]-=v;c[i^1]+=v;used+=v;
		if(used>=flow)return used;
	}
	return used;
}
int dinic(int S,int T){
	int ans=0;
	while(BFS(S,T))ans+=dfs(S,T,1000000000);
	return ans;
}
int A[200010],B[200010],P[200010],Q[200010],cnt1;
int main(){
	scanf("%d",&n);k=1;
	for(int i=1;i<=n;i++)scanf("%d",&A[i]),A[i]++;
	for(int i=1;i<=n;i++)scanf("%d",&B[i]),B[i]++;
	for(int i=1;i<=n;i++)if(!P[i]){
		P[i]=++cnt1;
		for(int x=A[i];!P[x];x=A[x])P[x]=cnt1;
	}
	for(int i=1;i<=n;i++)if(!Q[i]){
		Q[i]=++cnt1;
		for(int x=B[i];!Q[x];x=B[x])Q[x]=cnt1;
	}	
	int all=n;
	S=cnt1+1;T=cnt1+2;
	for(int i=1;i<=n;i++){
		if(A[i]==B[i]){
			if(A[i]==i)all--;
			else add(P[i],Q[i],1),add(Q[i],P[i],1);
		}
		else {
			if(A[i]==i)add(Q[i],T,1);
			else if(B[i]==i)add(S,P[i],1);
			else add(Q[i],P[i],1);
		}
	}
	n=T;
	cout<<all-dinic(S,T);
	return 0;
}
posted @ 2019-09-28 19:04  Kananix  阅读(491)  评论(0编辑  收藏  举报

Contact with me