Atcoder ARC 107赛后总结

前言

比赛链接:https://atcoder.jp/contests/arc107

止步\(E\)题,完全不会。。。

上飞分啦!!!!

一下子从0变到900+,一场就绿啦,哈哈哈(雾

只可惜今天的CF比赛和ATcoder的比赛重时间了,还是先以codeforces为主吧。

A

题意: 给你\(A,B,C\),让你计算\(\sum\limits_{a=1}^{A}\sum\limits_{b=1}^{B}\sum\limits_{c=1}^{C}abc\mod{998244353}\)

做法:首先根据乘法分配律,不难化成这个式子:\(\frac{A(A+1)}{2}\frac{B(B+1)}{2}\frac{B(B+1)}{2}\),然后直接计算即可。

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  LL;
const  LL  mod=998244353;
LL  a,b,c;
int  main()
{
	scanf("%lld%lld%lld",&a,&b,&c);
	a=(a+1)*a/2%mod;
	b=(b+1)*b/2%mod;
	c=(c+1)*c/2%mod;
	printf("%lld\n",a*b%mod*c%mod);
	return  0;
}

B

题意:给你\(N,K\),让你算满足要求的四元组个数。
对于四元组\((a,b,c,d)\),其需要满足:\(1≤a,b,c,d≤N,a+b-c-d=K\)

做法:\(O(n)\)可以过,不妨考虑\(O(n)\)的做法。
首先,我们可以再\(O(1)\)的时间在算出满足\(1≤a,b≤N,a+b=t\)的二元组\((a,b)\)的个数,考虑化化式子:\((a+b)-(c+d)=K\),那么只需要枚举\(a+b=t\)中的\(t\),然后两边计算方案相乘即可,而且通过计算过程不难看出,答案是在\(n^3\)级别的,所以不会爆\(long\) \(long\)

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  LL;
inline  LL  mymin(LL  x,LL  y){return  x<y?x:y;}
inline  LL  mymax(LL  x,LL  y){return  x>y?x:y;}
LL  ans,n,k;
inline  LL  fangan(LL  x)
{
	LL  l=mymax(1,x-n),r=mymin(n,x-1);
	return  r-l+1;
}
int  main()
{
	scanf("%lld%lld",&n,&k);
	if(k<0)k=-k;
	LL  ed=n<<1;
	for(LL  i=k+2;i<=ed;i++)ans+=fangan(i)*fangan(i-k);
	printf("%lld\n",ans);
	return  0;
}

C

题意:对于给定的\(n*n\)的矩阵,满足\(a_{i,j}\)各不相同且在\([1,n^2]\)范围内。
现在给定一个\(K\),你可以进行无限次操作,每次操作有两个选择:

  1. 选定\(x,y(x≠y)\)行,如果\(a_{x,i}+a_{y,i}≤K(1≤i≤n)\),那么交换这两行。
  2. 选定\(x,y(x≠y)\)列,如果\(a_{i,x}+a_{i,y}≤K(1≤i≤n)\),那么交换这两列。
    问你通过操作最多可以得到多少个不同的矩阵,对\(998244353\)取模?

做法:不难发现,如果把每一行单独看成一个集合,那么不管进行哪个操作,这些集合都不会有任何改变,改变的只会是这些集合到底在哪一行,对列也是如此。

我们把行的编号列出一个数组\(a\)\(a\)数组初始就为\(1,2,3,...,n\),每交换一次行,比如交换行\(x,y\),那么交换\(a_{x},a_{y}\),设\(b_{a_{i}}=i\)\(b\)数组表示每个行在\(a\)数组中的位置),根据上面的发现,不难推敲出:对于初始的\(a\)数组,\(i\)行和\(j\)行能交换,那么不管任何时候,\(a_{b_{i}}\)\(a_{b_{j}}\)都能进行交换(相应地,\(b_{i},b_{j}\)也会进行交换)。

那么发现了上述规律,如何统计方案呢?

我们把列的编号也列出一个数组\(c\),对应的便有\(d\)数组,不难发现,初始的矩阵以及\(a,c\)数组就可以确定一种不同的矩阵,特别的,初始的\(a,c\)数组其实就对应了初始的矩阵,这样,我们只需要分别的统计\(a\)数组能有多少种可能乘以\(c\)数组的便是答案(事实上,\(b,d\)数组更加的直观,后面直接讲\(b,d\)数组)。

那么如何求\(b\)数组有多少种呢?我们不妨建一个\(n\)个点的图,\(i,j\)连边表示在初始矩阵中第\(i,j\)行能够交换,现在证明,对于一个联通块内的两个点,其可以自由♂的交换。

考虑联通块中的一条链:\(x-t_1-t_2-...-t_k-y\),只要我们能证明能够在不改变\(b_{t_{i}}\)的情况下,让\(b_{x}\)\(b_{y}\)交换即可,考虑数学归纳法,当\(k=1\)时,\(x,t_1\)交换,然后然后\(t_1\)\(y\)交换,\(x\)再和\(t_{1}\)交换便可以了,对于\(k>1\)的情况,我们只需要交换\(x,t_{k}\),然后再交换\(t_{k},y\),最后再交换\(x,t_{k}\)即可,证毕,因此,一个联通块中的点可以互相交换,所以一个联通块最多会有\(联通块大小!\)个不同的方案,\(d\)数组也是同理。

时间复杂度:\(O(nlogn)\)(因为用了并查集)

#include<cstdio>
#include<cstring>
#define  N  60
using  namespace  std;
typedef  long  long  LL;
const  LL  mod=998244353;
int  a[N][N],n,k;
inline  bool  check1(int  x,int  y)//行比较 
{
	for(int  i=1;i<=n;i++)
	{
		if(a[x][i]+a[y][i]>k)return  0;
	}
	return  1;
}
inline  bool  check2(int  x,int  y)//行比较 
{
	for(int  i=1;i<=n;i++)
	{
		if(a[i][x]+a[i][y]>k)return  0;
	}
	return  1;
}
LL  fc[N];
int  fa[N],siz[N];
int  findfa(int  x)
{
	if(fa[x]!=x)fa[x]=findfa(fa[x]);
	return  fa[x];
}
inline  void  mer(int  x,int  y)
{
	int  tx=findfa(x),ty=findfa(y);
	if(tx!=ty)
	{
		fa[tx]=ty;
		siz[ty]+=siz[tx];
	}
}
int  main()
{
	scanf("%d%d",&n,&k);
	fc[1]=1;for(int  i=2;i<=n;i++)fc[i]=(fc[i-1]*i)%mod;
	for(int  i=1;i<=n;i++)
	{
		for(int  j=1;j<=n;j++)scanf("%d",&a[i][j]);
		fa[i]=i;
		siz[i]=1;
	}
	for(int  i=1;i<=n;i++)
	{
		for(int  j=i+1;j<=n;j++)
		{
			if(findfa(i)!=findfa(j)  &&  check1(i,j)==1)mer(i,j);
		}
	}
	LL  ans=1;
	for(int  i=1;i<=n;i++)
	{
		if(findfa(i)==i)ans=ans*fc[siz[i]]%mod;
	}
	for(int  i=1;i<=n;i++)fa[i]=i,siz[i]=1;
	for(int  i=1;i<=n;i++)
	{
		for(int  j=i+1;j<=n;j++)
		{
			if(findfa(i)!=findfa(j)  &&  check2(i,j)==1)mer(i,j);
		}
	}
	for(int  i=1;i<=n;i++)
	{
		if(findfa(i)==i)ans=ans*fc[siz[i]]%mod;
	}
	printf("%lld\n",ans);
	return  0;
}

D

题意:对于给定的正整数\(N,K\),求有多少个集满足一下要求:

  1. 集中的数字个数是\(n\)个。
  2. 集中的每个数字都能被表示为:\(\frac{1}{2^t}(t≥0)\)
  3. 集中每个数字的和是\(K\)
    需要注意的是,集是无序的,即\(1,\frac{1}{2}\)\(\frac{1}{2},1\)是等价的。

做法:第一眼:肯定不是DP。
还真是DP,甚至当时都没发现自己已经想到做法了
首先,不难想的是,默认集中的数字从大到小排序,这样比较好处理,但是怎么枚举方案呢?
不妨认为一开始有\(K\)\(1\),然后枚举有多少个\(1\)分成了\(\frac{1}{2}\),设有\(i\)\(a_1\)分裂了吧,这样我们就有\(2a_1\)\(\frac{1}{2}\),然后我们可以继续枚举分裂多少个\(\frac{1}{2}\)变成\(\frac{1}{4}\),记为\(a_{2}\)。(事实上,不用考虑\(1\)直接分裂成四个\(\frac{1}{4}\)的情况,因为其必须先分裂成\(\frac{1}{2}\)

不断的枚举下去,我们可以得到\(a\)序列:\(a_1,a_2,a_3,a_4,...,a_k\),不难发现:\(a_{1}≤K,a_{i}≤2a_{i-1}(i>1)\),同时因为每分裂一次多一个数字,所以\(a_1+a_2+a_3+...+a_k=n-K\),所以只需要统计不同的\(a\)序列即可,\(DP\)完全可以\(O(n^2)\)转移。

#include<cstdio>
#include<cstring>
#define  N  3100
using  namespace  std;
const  int  mod=998244353;
int  f[N][N];//统计方案 
int  n,m;
int  main()
{
	scanf("%d%d",&n,&m);
	if(n==m)
	{
		printf("1\n");
		return  0;
	}
	int  limit=n-m;//背包数量 
	for(int  i=1;i<=limit;i++)
	{
		if(i<=m)f[i][i]=1;//a1
		for(int  j=1;j<i;j++)f[i][j]=(f[i][j]+f[i-j][(j+1)/2])%mod;
		for(int  j=i;j>=1;j--)f[i][j]=(f[i][j]+f[i][j+1])%mod;
	}
	printf("%d\n",f[limit][1]);
	return  0;
}

E

题意:对于\(n*n\)的矩阵,给了你第一行和第一列,对于\(a_{i,j}=mex(a_{i-1,j},a_{i,j-1})(i>1,j>1)\),矩阵满足:\(0≤a_{i,j}<3(∀i∈[1,n],j∈[1,n])\),其中\(mex\)是其自己定义的运算。
在这里插入图片描述
做法:神TM找规律???我一个常年走路想题的,你跟我说要暴力找规律???

给一个官方给出的\(n=20\)的随机矩阵:
在这里插入图片描述不难发现,有很多位置\(a_{i,j}=a_{i-1,j-1}\),事实上,你的感觉没错,只要\(i>4,j>4\)就满足这个性质,然后只要暴力枚举前四行四列即可。

至于证明,据说是暴力跑\(n=5\)的矩阵,枚举所有的情况,可以发现\(a_{4,4}=a_{5,5}\),然后只要对于每一个\(i>4,j>4\)的位置,就可以通过这个性质判定\(a_{i-1,j-1}=a_{i,j}\)了。

时间复杂度:\(O(n)\)

#include<cstdio>
#include<cstring>
#define  N  510000
using  namespace  std;
typedef  long  long  LL;
int  a[6][N],b[N][6];
int  n;
LL  cnt[5];
inline  int  mex(int  x,int  y)
{
	if(x>y)x^=y^=x^=y;//x<=y
	return  (x>0)?0:((y&1)?2:1);
}
int  main()
{
	scanf("%d",&n);
	for(int  i=1;i<=n;i++)scanf("%d",&a[1][i]);
	b[1][1]=a[1][1];
	for(int  i=2;i<=n;i++)scanf("%d",&b[i][1]);
	if(n<=4)
	{
		for(int  i=2;i<=n;i++)a[i][1]=b[i][1];
		for(int  i=2;i<=n;i++)
		{
			for(int  j=2;j<=n;j++)a[i][j]=mex(a[i][j-1],a[i-1][j]);
		}
		for(int  i=1;i<=n;i++)
		{
			for(int  j=1;j<=n;j++)cnt[a[i][j]]++;
		}
		printf("%lld %lld %lld\n",cnt[0],cnt[1],cnt[2]);
		return  0;
	}
	a[2][1]=b[2][1];a[3][1]=b[3][1];a[4][1]=b[4][1];
	b[1][2]=a[1][2];b[1][3]=a[1][3];b[1][4]=a[1][4];
	for(int  i=2;i<=4;i++)
	{
		for(int  j=2;j<=n;j++)a[i][j]=mex(a[i][j-1],a[i-1][j]);
	}
	for(int  i=2;i<=4;i++)
	{
		for(int  j=2;j<=n;j++)b[j][i]=mex(b[j-1][i],b[j][i-1]);
	}
	for(int  i=1;i<=4;i++)
	{
		for(int  j=1;j<=n;j++)cnt[a[i][j]]++;
	}
	for(int  i=1;i<=4;i++)
	{
		for(int  j=5;j<=n;j++)cnt[b[j][i]]++;
	}
	for(int  i=4;i<=n;i++)cnt[a[4][i]]+=n-i;
	for(int  i=5;i<=n;i++)cnt[b[i][4]]+=n-i;
	printf("%lld %lld %lld\n",cnt[0],cnt[1],cnt[2]);
	return  0;
}

F

下文有借鉴或者直接搬运https://www.cnblogs.com/gmh77/p/13908571.html博客的内容。

题意:给出一个无向图,可以删掉若干点,删\(i\)的代价是\(a_i\),一个联通块的价值为其中每个点的\(b_i\)之和的绝对值,最大化\(Σ新图中每个连通块的价值-删点代价\)
\(1≤n,m<=300\)

题解:先删掉一些点,对剩下的一个块里的贡献同为\(+1\)\(-1\),则可以转化为对每个点赋+1/-1/删掉,最终贡献为bi*点权之和,且有边相连的点的权相同

先加上\(Σ|bi|\),接下来用最小割减去使其合法的最小代价即可。

连S->i1->i2->T,对应+1/删/-1,若bi>=0则为0/bi+ai/2bi,<0则为2|bi|/|bi|+ai/0

对于一条边,其两端的点编号不能不同,所以对于(u,v)直接连v2->u1和u2->v1的inf边即可。

不难发现,这样子构造边权都是非负的,直接最小割走起。

时间复杂度:\(O(n^2(n+m))\)

#include<cstdio>
#include<cstring>
#define  N  610
#define  M  3100
using  namespace  std;
typedef  long  long  LL;
template<class  T>
inline  T  mymin(T  x,T  y){return  x<y?x:y;}
struct  node
{
	int  y,next;
	LL  c;
}a[M];int  len=1,last[N];
inline  void  insnode(int  x,int  y,LL  c){len++;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;}
inline  void  ins(int  x,int  y,LL  c){insnode(x,y,c);insnode(y,x,0);}
int  h[N],list[N],head,tail,st,ed;
bool  bfs()
{
	memset(h,0,sizeof(h));h[ed]=1;
	list[head=tail=1]=ed;
	while(head<=tail)
	{
		int  x=list[head++];
		for(int  k=last[x];k;k=a[k].next)
		{
			int  y=a[k].y;
			if(!h[y]  &&  a[k^1].c)h[y]=h[x]+1,list[++tail]=y;
		}
	}
	return  h[st];
}
LL  findflow(int  x,LL  f)
{
	if(x==ed)return  f;
	LL  s=0,t;
	for(int  k=last[x];k;k=a[k].next)
	{
		int  y=a[k].y;
		if(h[y]+1==h[x]  &&  a[k].c)
		{
			s+=t=findflow(y,mymin(f-s,a[k].c));
			a[k].c-=t;a[k^1].c+=t;
			if(s==f)return  s;
		}
	}
	if(!s)h[x]=0; 
	return  s;
}
int  n,m;
LL  ans=0,aa[N],bb[N];
int  main()
{
	scanf("%d%d",&n,&m);st=(n<<1)+1;ed=(n<<1)+2;
	for(int  i=1;i<=n;i++)scanf("%lld",&aa[i]);
	for(int  i=1;i<=n;i++)scanf("%lld",&bb[i]);
	for(int  i=1;i<=n;i++)
	{
		if(bb[i]>=0)
		{
			ans+=bb[i];
			ins(i,i+n,aa[i]+bb[i]);
			ins(i+n,ed,bb[i]<<1);
		}
		else
		{
			ans+=-bb[i];
			ins(st,i,(-bb[i])<<1);
			ins(i,i+n,aa[i]+(-bb[i]));
		}
	}
	for(int  i=1;i<=m;i++)
	{
		int  x,y;scanf("%d%d",&x,&y);
		ins(x+n,y,(LL)999999999999999);
		ins(y+n,x,(LL)999999999999999);
	}
	while(bfs()==1)
	{
		ans-=findflow(st,(LL)999999999999999);
	}
	printf("%lld\n",ans);
	return  0;
}

小结

还不够强,后面两道题还做不出来。

找规律太草了

posted @ 2020-11-01 17:16  敌敌畏58  阅读(92)  评论(0编辑  收藏  举报