AtCoder Grand Contest 038

A - 01 Matrix

可以将左上角的 \(B\times A\) 和右下角的 \((H-B)\times (W-A)\) 块填上 \(1\),剩下的填 \(1\) 即可。


#include<iostream>
#include<cstdio>
using namespace std;
const int N=1005;
int h,w,A,B;
int a[N][N];
int main()
{
	scanf("%d%d%d%d",&h,&w,&A,&B);
	for(int i=1;i<=B;i++)
		for(int j=1;j<=A;j++)
			a[i][j]=1;
	for(int i=B+1;i<=h;i++)
		for(int j=A+1;j<=w;j++)
			a[i][j]=1;
	for(int i=1;i<=h;i++)
	{
		for(int j=1;j<=w;j++)
			printf("%d",a[i][j]);
		printf("\n");
	}
	return 0;
}

B - Sorting a Segment

可以发现,操作两个相邻的区间 \([l-1,r-1],[l,r]\) 最后的结果相同当且仅当最小值为 \(l-1\),最大值为 \(r\)。从左往右扫一遍即可,用 ST 表维护区间最大值和最小值。注意还有就是区间有序的情况只能算一次。


#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int N=200005;
int n,k;
int p[N];
int a[N];
pair<int,int> Min[N][20],Max[N][20];
void init()
{
	for(int i=1;i<=n;i++)
		Min[i][0]=Max[i][0]=make_pair(p[i],i);
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			Min[i][j]=min(Min[i][j-1],Min[i+(1<<(j-1))][j-1]);
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			Max[i][j]=max(Max[i][j-1],Max[i+(1<<(j-1))][j-1]);
	return;
}
int querymin(int l,int r)
{
	int k=log2(r-l+1);
	return min(Min[l][k],Min[r-(1<<k)+1][k]).second;
}
int querymax(int l,int r)
{
	int k=log2(r-l+1);
	return max(Max[l][k],Max[r-(1<<k)+1][k]).second;
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%d",&p[i]);
	for(int i=1;i<=n;i++)
		a[i]=p[i-1]>p[i];
	for(int i=1;i<=n;i++)
		a[i]+=a[i-1];
	init();
	int ans=0;
	bool flag=false;
	for(int r=k;r<=n;r++)
	{
		int l=r-k+1;
		if(a[l]==a[r])
		{
			flag=true;
			continue;
		}
		if(l==1)
		{
			ans++;
			continue;
		}
		if(querymin(l-1,r-1)==l-1&&querymax(l,r)==r) continue;
		ans++;
	}
	if(flag) ans++;
	printf("%d",ans);
	return 0;
}

C - LCMs

问题要我们求的是 \(\sum\limits_{i=1}^{n-1} \sum\limits_{j=i+1}^n \operatorname{lcm}(a_i,a_j)\),不太好处理,转化一下:

\[\frac{\sum\limits_{i=1}^n \sum\limits_{j=1}^n \operatorname{lcm}(a_i,a_j)-\sum\limits_{i=1}^na_i}{2} \]

考虑计算 \(\sum\limits_{i=1}^n \sum\limits_{j=1}^n \operatorname{lcm}(a_i,a_j)\)

因为 \(\operatorname{lcm}(a,b)=\frac{ab}{\gcd(a,b)}\),可以化成:

\[\sum\limits_{i=1}^n \sum\limits_{j=1}^n \frac{a_ia_j}{\gcd(a_i,a_j)} \]

枚举 \(d=\gcd(a_i,a_j)\)

\[\sum\limits_{i=1}^n \sum\limits_{j=1}^n\sum\limits_d \frac{a_ia_j}{d}[\gcd(a_i,a_j)=d] \]

\(d\) 提出来:

\[\sum\limits_d \frac{1}{d}\sum\limits_{i=1}^n \sum\limits_{j=1}^n a_i[d|a_i] a_j [d|a_j][\gcd(\frac{a_i}{d},\frac{a_j}{d})=1] \]

莫反一波:

\[\sum\limits_d \frac{1}{d}\sum\limits_{i=1}^n \sum\limits_{j=1}^n a_i[d|a_i] a_j [d|a_j] \sum\limits_{t|\frac{a_i}{d},t|\frac{a_j}{d}} \mu(t) \]

\(t\) 提出来:

\[\sum\limits_d \sum\limits_t \frac{\mu(t)}{d}\sum\limits_{i=1}^n \sum\limits_{j=1}^n a_i[td|a_i] a_j [td|a_j] \]

转化一下:

\[\sum\limits_d \sum\limits_t \frac{\mu(t)}{d}\left(\sum\limits_{i=1}^n a_i[td|a_i]\right)^2 \]

\(f(x)\) 表示 \(\sum\limits_{i=1}^n a_i[x|a_i]\),令 \(C_x=\sum\limits_{i=1}^n a_i[a_i=x]\),则 \(f(x)=\sum\limits_{x|v} C_v\)

\(f(x)\) 带入原式,可以得到:

\[\sum\limits_d \sum\limits_t \frac{\mu(t)}{d}f(td)^2 \]

可以发现,\(t\) 的上界为 \(\frac{\max\{a_i\}}{d}\),直接暴力枚举复杂度就是 \(O(\max\{a_i\}\ln (\max\{a_i\}))\)


#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int N=1000005;
const int MOD=998244353;
int n,m;
int a[N];
long long c[N];
long long f[N];
int mu[N];
int prime[N],tot;
bool isprime[N];
void init(int n=1000000)
{
	for(int i=1;i<=n;i++)
		isprime[i]=true;
	isprime[1]=false;
	mu[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(isprime[i]) prime[++tot]=i,mu[i]=-1;
		for(int j=1;j<=tot&&i*prime[j]<=n;j++)
		{
			isprime[i*prime[j]]=false;
			if(i%prime[j]==0)
			{
				mu[i*prime[j]]=0;
				break;
			}
			mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=1;i<=n;i++)
		c[a[i]]=(c[a[i]]+a[i])%MOD;
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j+=i)
			f[i]=(f[i]+c[j])%MOD;
	return;
}
long long ksm(long long a,long long b)
{
	long long res=1;
	while(b)
	{
		if(b&1) res=res*a%MOD;
		a=a*a%MOD,b>>=1;
	}
	return res;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	init();
	m=1000000;
	long long ans=0;
	for(int d=1;d<=m;d++)
	{
		long long id=ksm(d,MOD-2);
		for(int t=1;t<=m/d;t++)
			ans=(ans+(id*mu[t]+MOD)*f[t*d]%MOD*f[t*d]%MOD)%MOD;
	}
	for(int i=1;i<=n;i++)
		ans=(ans-a[i]+MOD)%MOD;
	ans=ans*ksm(2,MOD-2)%MOD;
	printf("%lld",ans);
	return 0;
}

D - Unique Path

考虑先将 \(c_i=0\) 的边连成若干个联通块,然后在中心连出一个或多个环。可以发现,\(c_i=1\) 的点如果在同一个连通块内肯定是不合法的,否则一定是满足的。

边数最少的情况即为中心点只有一个环即一棵基环树,边数最多的情况即为中心点中任意两点都连边,判下 \(m\) 是否在这个区间内即可。

注意特判联通块数为 \(1\) 和联通块数为 \(2\) 的情况。


#include<iostream>
#include<cstdio>
#include<vector>
#include<tuple>
using namespace std;
const int N=100005;
int n;
long long m;
int Q;
int fa[N];
int getf(int v)
{
	if(v==fa[v]) return v;
	fa[v]=getf(fa[v]);
	return fa[v];
}
bool merge(int u,int v)
{
	int f1=getf(u),f2=getf(v);
	if(f1!=f2)
	{
		fa[f2]=f1;
		return true;
	}
	else return false;
}
int main()
{
	scanf("%d%lld%d",&n,&m,&Q);
	for(int i=1;i<=n;i++)
		fa[i]=i;
	vector<tuple<int,int,int> >G;
	int num=0;
	for(int i=1;i<=Q;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		u++,v++;
		if(w==0) num+=merge(u,v);
		else if(w==1) G.push_back(make_tuple(u,v,w));
	}
	for(auto [u,v,w]:G)
		if(getf(u)==getf(v))
		{
			printf("No");
			return 0;
		}
	int cnt=0;
	for(int i=1;i<=n;i++)
		if(getf(i)==i) cnt++;
	if(cnt==2&&!G.empty())
	{
		printf("No");
		return 0;
	}
	long long l=num+cnt,r=num+1LL*cnt*(cnt-1)/2;
	if(cnt==1) l=num,r=num;
	if(G.empty()) l=num-1;
	if(l<=m&&m<=r) printf("Yes");
	else printf("No");
	return 0;
}

E - Gachapon

假如在第 \(t\) 时刻停止,贡献为 \(t\)。 我们可以将 \(t\) 的贡献看成第 \(0\) 时刻没有停止贡献 \(1\),第 \(1\) 时刻没有停止贡献 \(1\),…,第 \(t-1\) 时刻没有停止贡献 \(1\),总共贡献 \(t\)

\(p_i\) 表示第 \(i\) 时刻依然没有停止的概率,则有停止的期望时刻为 \(\sum_{i=0}p_i\)

考虑怎么求 \(p_i\)。一种思路是对于所有长度为 \(i\) 的未停止序列计算概率之和,不太好处理。我们可以换种思考方式,用 \(1\) 减去已停止序列的概率之和,从而得到 \(p_i\)

\(q_i\) 表示在第 \(i\) 时刻停止的概率,令 \(Q(x)\) 表示 \(q_i\) 的生成函数,\(sa=\sum a_i\),则:

\[\begin{aligned} Q(x)&=\prod\limits_{i=1}^{n}\left(\sum\limits_{j \ge b_i} \frac{\left(\frac{a_i}{sa}\right)^jx^j}{j!}\right) \\ &= \prod_{i=1}^{n}\left(e^{\frac{a_i}{sa}x}-\sum_{j=0}^{b_i-1}\frac{\left(\frac{a_i}{sa}\right)^jx^j}{j!}\right)\end{aligned} \]

考虑 \(p\) 的生成函数 \(P(x)\)

\[\begin{aligned}P(x)&=e^x-Q(x) \\ &=e^x- \prod_{i=1}^{n}\left(e^{\frac{a_i}{sa}x}-\sum_{j=0}^{b_i-1}\frac{(\left(\frac{a_i}{sa}\right)^jx^j}{j!}\right)\end{aligned} \]

将这个东西展开,每一项大概都是 \(c_{i, j}e^{\frac{i}{sa}x}x^{j}\) 的形式,可以暴力卷积或者分治 FFT 算出 \(c_{i,j}\)。我们需要求出 \(e^{\frac{i}{sa}x}x^{j}\) 的贡献。

不妨令 \(p=\frac{i}{sa}\),设贡献为 \(f(i,j)\),将原式展开:

\[e^{px}x^j=\sum_{i=0}\frac{p^ix^{i+j}}{i!}=\sum_{i=0}\frac{p^i}{i!}x^{i+j} \]

\([x^{i+j}]P(x)=\frac{p_{i+j}}{(i+j)!}\),可知 \(p_{i+j}=[x^{i+j}]P(x)\cdot (i+j)!\)

可以得出:

\[f(i,j)=\sum_{i=0}p^i(i+j)^{\underline{j}} \]

\((i+1)^{\underline{j}} - i^{\underline{j}} = i^{\underline{j-1}} \cdot j\),则有:

\[\begin{aligned}p\cdot f(i,j) &= \sum_{i=1}p^i(i+j-1)^{\underline{j}} \\ (1-p)\cdot f(i,j) &= j! + j\cdot \sum_{i=1}p^i(i+j-1)^{\underline{j-1}} = j\cdot f(i,j-1) \\ f(i,j) &= \frac{j}{1-p}f(i,j-1)\end{aligned} \]

\(j=0\) 时是等比数列求和,\(f(i,0) = \frac{1}{1-p}\),可以得出 \(f(i,j) = (\frac{1}{1-p})^{j+1}\cdot j!\)

枚举 \(i,j\),每次计算 \(c_{i, j}f(i,j)\) 的贡献即可。


#include<iostream>
#include<cstdio>
using namespace std;
const int N=405;
const int MOD=998244353;
int n;
int a[N],b[N];
long long ksm(long long a,long long b)
{
	long long res=1;
	while(b)
	{
		if(b&1) res=res*a%MOD;
		a=a*a%MOD,b>>=1;
	}
	return res;
}
long long fac[N];
void init(int n=400)
{
	fac[0]=1;
	for(int i=1;i<=n;i++)
		fac[i]=fac[i-1]*i%MOD;
	return;
}
long long q[N][N][N];
int sa,sb;
long long c[N][N];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&a[i],&b[i]),sa+=a[i],sb+=b[i];
	init();
	q[0][0][0]=1;
	for(int i=1;i<=n;i++)
	{
		long long p=a[i]*ksm(sa,MOD-2)%MOD,fac=MOD-1;
		for(int l=0;l<b[i];l++,fac=fac*ksm(l,MOD-2)%MOD*p%MOD)
			for(int j=0;j<=sa;j++)
				for(int k=l;k<=sb;k++)
					q[i][j][k]=(q[i][j][k]+fac*q[i-1][j][k-l]%MOD)%MOD;
		for(int j=a[i];j<=sa;j++)
			for(int k=0;k<=sb;k++)
				q[i][j][k]=(q[i][j][k]+q[i-1][j-a[i]][k])%MOD;
	}
	for(int i=0;i<=sa;i++)
		for(int j=0;j<=sb;j++)
			c[i][j]=(-q[n][i][j]+MOD)%MOD;
	c[sa][0]=(c[sa][0]+1)%MOD;
	long long ans=0;
	for(int i=0;i<=sa;i++)
		for(int j=0;j<=sb;j++)
		{
			long long p=i*ksm(sa,MOD-2)%MOD;
			long long f=ksm(ksm((1-p+MOD)%MOD,MOD-2),j+1)*fac[j]%MOD;
			ans=(ans+c[i][j]*f%MOD)%MOD;
		}
	printf("%lld",ans);
	return 0;
}

F - Two Permutations

考虑 \(i\to p_i\) 连边,对于图中的一个环,环中的点的状态肯定是相同的。\(q\) 同理。

问题可以看做使得 \(a_i=b_i\) 的位置最少。

  1. \(p_i=q_i=i\) 时一定满足 \(a_i=b_i\)
  2. \(p_i=i,q_i\neq i\)\(a_i=b_i\) 相当于 \(b_i=i\)
  3. \(p_i\neq i,q_i=i\)\(a_i=b_i\) 相当于 \(a_i=i\)
  4. \(p_i=q_i\neq i\)\(a_i=b_i\) 相当于 \(a_i\)\(b_i\) 的状态相同;
  5. \(p_i\neq q_i\neq i\)\(a_i=b_i\) 相当于 \(a_i=b_i=i\)

可以发现,如果 \(a_i=b_i\) 就会有 \(1\) 的代价,可以用网络流解决。

考虑建图,加入源点 \(s\) 和汇点 \(t\),将它看做两个集合,我们需要将每个点分到两个集合中,使得 \(s\)\(t\) 不连通,这就是最小割问题。

一个简单的想法是 \(a_i\) 分到了 \(s\) 代表 \(a_i=i\)\(a_i\) 分到了 \(t\) 代表 \(a_i=p_i\)\(b_i\) 分到了 \(s\) 代表 \(b_i=i\)\(b_i\) 分到了 \(t\) 代表 \(b_i=p_i\)。但是这样好像没法体现出条件 4,5。

考虑换一种方式:\(a_i\) 分到了 \(s\) 代表 \(a_i=i\)\(a_i\) 分到了 \(t\) 代表 \(a_i=p_i\)\(b_i\) 分到了 \(s\) 代表 \(b_i=p_i\)\(b_i\) 分到了 \(t\) 代表 \(b_i=i\),那么建图即为:

  1. \(p_i=q_i=i\) 时直接加上 \(1\) 的代价;
  2. \(p_i=i,q_i\neq i\) 时连 \(s\to q_i\) 代价为 \(1\) 的边,如果这条边被割掉表示 \(b_i\) 分到了 \(t\) 集,多了 \(1\) 代价;
  3. \(p_i\neq i,q_i=i\) 时连 \(p_i\to t\) 代价为 \(1\) 的边,如果这条边被割掉表示 \(a_i\) 分到了 \(s\) 集,多了 \(1\) 代价;
  4. \(p_i=q_i\neq i\) 时连 \(p_i\to q_i,q_i\to p_i\) 代价为 \(1\) 的边,如果这条边被割掉表示 \(a_i\)\(b_i\) 分到了不同的集合,多了 \(1\) 代价;
  5. \(p_i\neq q_i\neq i\) 时连 \(p_i\to q_i\) 代价为 \(1\) 的边,如果这条边被割掉表示 \(a_i\) 分到了 \(s\) 集且 \(b_i\)\(t\) 集,多了 \(1\) 代价。

可以发现这是一个二分图,跑 Dinic 的时间负责度为 \(O(m\sqrt n)\)


#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=200005;
const int INF=1061109567;
int n;
int p[N],q[N];
int ida[N],cnta;
int idb[N],cntb;
struct Edge
{
	int to,flow;
	int next;
}edge[N<<1];
int head[N],cnt=1;
void add_edge(int u,int v,int flow)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].flow=flow;
	edge[cnt].next=head[u];
	head[u]=cnt;
	return;
}
void add(int u,int v,int flow)
{
	add_edge(u,v,flow);
	add_edge(v,u,0);
	return;
}
int s,t;
int dep[N];
bool bfs()
{
	memset(dep,-1,sizeof(dep));
	queue<int>q;
	q.push(s);
	dep[s]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=edge[i].next)
		{
			int v=edge[i].to;
			if(dep[v]!=-1||edge[i].flow<=0) continue;
			dep[v]=dep[u]+1;
			q.push(v);
		}
	}
	return dep[t]!=-1;
}
int cur[N];
int dfs(int u,int flow)
{
	if(u==t) return flow;
	int used=0;
	for(int &i=cur[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(dep[v]!=dep[u]+1||edge[i].flow<=0) continue;
		int now=dfs(v,min(flow,edge[i].flow));
		flow-=now;
		edge[i].flow-=now;
		edge[i^1].flow+=now;
		used+=now;
		if(flow==0) break;
	}
	if(used==0) dep[u]=-1;
	return used;
}
int dinic()
{
	int res=0;
	while(bfs())
	{
		memcpy(cur,head,sizeof(head));
		res+=dfs(s,INF);
	}
	return res;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&p[i]),p[i]++;
	for(int i=1;i<=n;i++)
		scanf("%d",&q[i]),q[i]++;
	for(int i=1;i<=n;i++)
		if(!ida[i])
		{
			cnta++;
			ida[i]=cnta;
			int u=p[i];
			while(u!=i)
				ida[u]=cnta,u=p[u];
		}
	cntb=cnta;
	for(int i=1;i<=n;i++)
		if(!idb[i])
		{
			cntb++;
			idb[i]=cntb;
			int u=q[i];
			while(u!=i)
				idb[u]=cntb,u=q[u];
		}
	s=0,t=cntb+1;
	int ans=n;
	for(int i=1;i<=n;i++)
		if(p[i]==i&&q[i]==i) ans--;
		else if(p[i]==i&&q[i]!=i) add(s,idb[i],1);
		else if(p[i]!=i&&q[i]==i) add(ida[i],t,1);
		else if(p[i]==q[i]&&p[i]!=i) add(ida[i],idb[i],1),add(idb[i],ida[i],1);
		else add(ida[i],idb[i],1);
	ans-=dinic();
	printf("%d",ans);
	return 0;
}
posted @ 2023-09-27 18:51  Zhou_JK  阅读(9)  评论(0)    收藏  举报