1. 程式人生 > 實用技巧 >Atcoder ARC 107賽後總結

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;
}

小結

還不夠強,後面兩道題還做不出來。

找規律太草了