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\),你可以進行無限次操作,每次操作有兩個選擇:
- 選定\(x,y(x≠y)\)行,如果\(a_{x,i}+a_{y,i}≤K(1≤i≤n)\),那麼交換這兩行。
- 選定\(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\),求有多少個集滿足一下要求:
- 集中的數字個數是\(n\)個。
- 集中的每個數字都能被表示為:\(\frac{1}{2^t}(t≥0)\)
- 集中每個數字的和是\(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;
}
小結
還不夠強,後面兩道題還做不出來。
找規律太草了