1. 程式人生 > 實用技巧 >ARC106 遊記

ARC106 遊記

目錄

ARC106 遊記

完了,這波要沒臉見人了。 /dk

掉了 16 分。/dk

A 106

題意簡述

給定整數 \(n\) ,詢問是否存在正整數 \(a,b\) ,滿足 \(3^a+5^b=n\)

\(1\le n\le 10^{18}\)

題目分析

直接無腦列舉 \(a\) 就行了,我一開始沒有看清楚整數,結果就錯了兩次。

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
int main(){
	long long n,sm=5;read(n);
	for(int b=1;sm<=n;++b,sm*=5){
		long long tn=n-sm;int a=0;
		while(tn>1&&tn%3==0)tn/=3,++a;
		if(tn==1&&a>0){
			return write(a),pc(' '),write(b),pc('\n'),0;
		}
	}
	puts("-1");
	return 0;
}

B Values

題意簡述

給定一個 \(n\) 個點 \(m\) 條邊的無向圖,第 \(i\) 個點一開始有一個點權 \(a_i\) ,每次可以讓一條邊連線的兩個點 \(u,v\) 中的某一個的點權減一,另一個加一,詢問能否使得所有第 \(i\) 個點的點權最終變成 \(b_i\)

\(1\le n\le 2\times 10^5,0\le m\le 2\times 10^5,-10^9\le a_i,b_i\le 10^9\)

題目分析

對於在一個連通塊裡面的點,它們兩兩之間的點權都是可以傳遞的,所以可以滿足題目要求的充分必要條件就是每個連通塊裡面的 \(a_i\) 之和和 \(b_i\)

之和相等,直接並查集維護即可。

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
const int maxn=200005;
int a[maxn],b[maxn],pa[maxn];
int ac(int x){
	return pa[x]==x?x:pa[x]=ac(pa[x]);
}
long long sz[maxn];
int main(){
	int n,m;read(n),read(m);
	for(int i=1;i<=n;++i)read(a[i]),sz[i]=a[i],pa[i]=i;
	for(int i=1;i<=n;++i)read(b[i]);
	while(m--){
		int u,v;read(u),read(v);u=ac(u);v=ac(v);
		if(u!=v)pa[u]=v,sz[v]+=sz[u];
	}
	for(int i=1;i<=n;++i)sz[ac(i)]-=b[i];int ok=true;
	for(int i=1;i<=n;++i)if(ac(i)==i)ok&=(sz[i]==0);
	puts(ok?"Yes":"No");
	return 0;
}

C Solutions

題意簡述

有一個這樣的問題:給定 \(n\) 個閉區間 \([l_i,r_i]\) ,請選出儘可能多的區間使得這些區間兩兩不相交。

有兩個演算法:第一個演算法是將所有區間按 \(r_i\) 升序排序,然後從左往右能選就選;第二個演算法是將所有區間按 \(l_i\) 升序排序,然後從左往右能選就選。

給定 \(n,m\) ,你需要構造這樣 \(n\) 個閉區間,滿足第一個演算法選出來的區間數量減去第二個演算法選出來的區間數量恰好為 \(m\) ,如果無解,輸出 -1

\(1\le n\le 2\times 10^5,-n\le m\le n\) ,你需要保證你輸出的數字兩兩不同,並且都是整數,取值範圍為 \([1,10^9]\)

題目分析

首先要知道按第一個演算法的方法去貪心就是正確的,這個很顯然,所以當 \(m\) 為負數時無解;兩種演算法至少都會選出一個區間,並且當第一個演算法可以選出 \(n\) 個區間的時候第二個演算法一定也可以選出 \(n\) 個區間,所以第一個演算法選出來的區間數量不可能比第二個演算法大 \(n-1\) 個,所以當 \(m\) 大於等於 \(n-1\) 時也無解,這裡需要額外注意的就是當 \(n=1,m=0\) 時是有解的

\(0\le m<n-1\) 時,必然是存在一種構造方案,這裡介紹一下我的構造方案:

比賽的時候 WA 了兩發,因為一開始沒有注意到取值範圍,沒有特判 \(n=1,m=0\) 的情況。

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
int main(){
	int n,m;read(n),read(m);
	if(n==1&&m==0)puts("1 2");
	else if(m>n-2||m<0)puts("-1");
	else if(m==0){
		for(int i=1;i<=n;++i)
			write(i),pc(' '),write(i+n),pc('\n');
	}
	else{
		const int Base=300000;
		for(int i=1;i<=m+1;++i)
			write(i*2-1+Base),pc(' '),write(i*2+Base),pc('\n');
		int l=0,r=(m+1)*2+1;
		for(int i=m+2;i<=n;++i)
			write((l--)+Base),pc(' '),write((r++)+Base),pc('\n');
	}
	return 0;
}

D Powers

題意簡述

給定 \(n\) 個整數 \(a_1,a_2,\dots,a_n\) 和整數 \(k\) ,對於任意的 \(1\le x\le k\) 求:

\[\sum_{l=1}^{n-1}\sum_{r=l+1}^n(a_l+a_r)^x \pmod{998244353} \]

\(2\le n\le 2\times 10^5,1\le k\le 300,1\le a_i\le 10^8\)

題目分析

這波是噩夢的開始,想看題解的直接跳到下面分割線處。

先講一下我比賽時的情況,當時我看到 \(x\) 次方,心中大喜,打算直接拆斯特林數,於是。。。

\[\sum_{l=1}^{n-1}\sum_{r=l+1}^n(a_l+a_r)^x=\sum_{l=1}^{n-1}\sum_{r=l+1}^n\sum_{t=0}^x{a_l+a_r\choose t}{x\brace t}t! \]

\[=\sum_{t=0}^x{x\brace t}t!\sum_{l=1}^{n-1}\sum_{r=l+1}^n\sum_{s=0}^t{a_l\choose s}{a_r\choose t-s} \]

\[=\sum_{t=0}^x{x\brace t}t!\sum_{l=1}^{n-1}\sum_{s=0}^t{a_l\choose s}\sum_{r=l+1}^n{a_r\choose t-s} \]

\(f(i,j)=\sum_{t=i}^{n}{a_t\choose j}\) ,那麼:

\[=\sum_{t=0}^x{x\brace t}t!\sum_{l=1}^{n-1}\sum_{s=0}^t{a_l\choose s}f(l+1,t-s) \]

\(g(l,t)=\sum_{s=0}^t{a_l\choose s}f(l+1,t-s)\) ,那麼:

\[=\sum_{t=0}^x{x\brace t}t!\sum_{l=1}^{n-1}g(l,t) \]

後面的顯然可以字首和,如果不考慮求 \(g(l,t)\) ,那麼時間複雜度就是 \(\mathcal O(nk+k^2)\) 的,問題就是如何快速求出 \(g(l,t)\) ,發現 \(g(l,t)\) 是一個標準的卷積形式,所以可以用 NTT 在 \(\mathcal O(nk\log_2k)\) 的時間內解決,仔細想想,感覺挺懸,不過時限貌似是 3s ,而且模數強烈暗示 NTT ,於是賭一把,直接開始碼。

然後就是這樣

沒關係,看看排行榜冷靜一下,然後就看到有人已經 AK 了。。。

這波是直接自閉好吧,也沒有優化,也沒有想到其他思路,時間也只有 30 分鐘不到了,然後開始考慮放棄。

結果在比賽還剩 5 分鐘的時候,臨機一動,然後想到了下面的解題思路,然後瘋狂碼,最後總算是碼完了,排名也從 600 多變成了 300 多,才沒有掉得很慘。。。


事實上,這道題目只考了二項式定理。。。果然自己還是太先入為主了一點。。。

\[\sum_{l=1}^{n-1}\sum_{r=l+1}^n(a_l+a_r)^x=\sum_{l=1}^{n-1}\sum_{r=l+1}^n\sum_{t=0}^x{x\choose t}a_l^ta_r^{x-t} \]

\[=\sum_{t=0}^x{x\choose t}\sum_{l=1}^{n-1}\sum_{r=l+1}^na_l^ta_r^{x-t} \]

\[=\sum_{t=0}^x{x\choose t}\dfrac{\sum_{l=1}^n\sum_{r=1}^na_l^ta_r^{x-t}-\sum_{i=1}^na_i^ta_i^{x-t}}{2} \]

\[=\sum_{t=0}^x{x\choose t}\dfrac{\sum_{l=1}^na_l^t\sum_{r=1}^na_r^{x-t}-\sum_{i=1}^na_i^x}{2} \]

\(h(t)=\sum_{i=1}^na_i^t\) ,那麼

\[=\sum_{t=0}^x{x\choose t}\dfrac{h(t)h(x-t)-h(x)}{2} \]

時間複雜度 \(\mathcal O(nk+k^2)\)

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
const int maxn=200005,maxk=305,mod=998244353,inv2=(mod+1)/2;
int mo(const int x){
	return x>=mod?x-mod:x;
}
int h[maxk],C[maxk][maxk];
int main(){
	int n,k;read(n),read(k);
	for(int i=1;i<=n;++i){
		int a;read(a);
		for(int j=0,sm=1;j<=k;++j,sm=1ll*sm*a%mod)
			h[j]=mo(h[j]+sm);
	}
	C[0][0]=1;
	for(int x=1;x<=k;++x){
		C[x][0]=1;
		for(int y=1;y<=k;++y){
			C[x][y]=mo(C[x-1][y]+C[x-1][y-1]);
		}
	}
	for(int x=1;x<=k;++x){
		int ans=0;
		for(int y=0;y<=x;++y){
			ans=mo(ans+1ll*C[x][y]*inv2%mod*mo(mod-h[x]+1ll*h[y]*h[x-y]%mod)%mod);
		}
		write(ans),pc('\n');
	}
	return 0;
}

E Medals

這道題目考場上沒有寫出來,不過覺得不應該,可能是一些特殊的圖在應用方面還是沒有很熟悉吧。

題意簡述

\(n\) 個工人,第 \(i\) 個工人會先工作 \(a_i\) 天,然後休息 \(a_i\) 天,然後再工作 \(a_i\) 天……

每一天你只可以給當天來工作的工人中的一個頒發獎章,詢問要讓每一個工人獲得至少 \(k\) 個獎章,至少需要多少天。

\(1\le n\le 18,1\le k,a_i\le 10^5\)

題目分析

考慮使用二分圖來建模,假設過了 \(x\) 天,那麼這個二分圖左邊有 \(n\times k\) 個點,分別表示每個工人的每一個獎章,右邊有 \(x\) 個點,分別表示每一天,如果第 \(i\) 天工人 \(j\) 來了,那麼右邊的第 \(i\) 個點就向左邊表示第 \(j\) 個工人的所有獎章連邊,這樣的話如果 \(x\) 天所有工人是可以獲得 \(k\) 個獎章的,這張二分圖的最大匹配就是 \(n\times k\)

根據 Hall 定理我們可以得出,這張二分圖的最大匹配就是 \(n\times k\) 的充分必要條件就是對於左邊任意的 \(i\) 個點,和這 \(i\) 個點中的任意一個有連邊的右邊的點的數量要大於等於 \(i\) 。左邊的兩個點如果代表的是同一個工人的兩個獎章,那麼它們連著的右邊的點集是相同的,貪心地想,我們只需要考慮任意 \(i\) 個工人即可,相當於要求就是任意 \(i\) 個工人中至少一個工人來工作的天數要大於等於 \(i\times k\)

考慮到如果天數大於等於 \(3nk\) ,那麼任何一個工人來工作的天數都大於等於 \(n\times k\) ,此時必然滿足條件。

考慮二分天數 \(x\) ,然後統計任意 \(i\) 個工人中至少一個工人來工作的天數,這個可以補集轉化為任意 \(i\) 個工人中只有這些工人來工作的天數,先用 \(\mathcal O(nk)\) 的複雜度求恰好任意 \(i\) 個工人來工作的天數,然後通過字首和來求只有,字首和的複雜度是 \(\mathcal O(n2^n)\) 的,這樣的話總的複雜度就是 \(\mathcal O((nk+n2^n)\log_2(nk))\) ,還需要加上預處理每天是哪些工人來工作的複雜度,即 \(\mathcal O(n^2k)\) 或者更優。

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
const int maxnk=6000006,maxk=100005;
int cnt1[1<<18],pos[maxnk],cnt[maxk],num[1<<18];
int main(){
	int n,k;read(n),read(k);
	for(int i=0;i<n;++i){
		int a;read(a);cnt[a]^=1<<i;
	}
	int nk=n*k,nk3=3*nk;
	for(int i=1;i<=100000;++i){
		if(!cnt[i])continue;
		for(int j=1;j<=nk3;j+=i)
			pos[j]^=cnt[i];
	}
	int U=1<<n,_U=1<<(n-1),l=n*k,r=3*n*k;
	for(int s=1;s<U;++s)cnt1[s]=cnt1[s&(s-1)]+k;
	while(l<r){
		int mid=(l+r)>>1,sum=0;
		memset(num,0,sizeof num);
		for(int i=1;i<=mid;++i){
			sum^=pos[i];++num[sum];
		}
		for(int i=0;i<n;++i){
			int d=(1<<i)-1;
			for(int s=0;s<_U;++s){
				int rs=(s&d)|((s&(~d))<<1);
				num[rs|(1<<i)]+=num[rs];
			}
		}
		int ok=true;
		for(int s=0;s<U&&ok;++s)
			ok&=((mid-num[(U-1)^s])>=cnt1[s]);
		if(ok)r=mid;else l=mid+1;
	}
	write(l),pc('\n');
	return 0;
}

F Figures

這道題目沒有寫出來也算是一個遺憾。

題意簡述

\(n\) 個有標號的點,第 \(i\) 個點有 \(d_i\) 個有標號的插孔,你需要連線恰好 \(n-1\) 對插孔使得這些點連通,其中一個插孔只可以連一條邊,問方案數對 \(998244353\) 取模的值。

\(2\le n\le 2\times 10^5,1\le d_i<998244353\)

題目分析

這道題目真是一道神仙題。

先講生成函式的做法。

如果第 \(i\) 個點的最終度數為 \(a_i+1\) ,那麼它給答案造成貢獻的時候就需要額外乘以 \({d_i\choose a_i+1}(a_i+1)!\) ,考慮 prufer 序列和樹一一對應,所以我們可以列舉每個點的度數減一:

\[\sum_{\sum a_i=n-2}{n-2\choose a_1,a_2,\dots,a_n}\prod {d_i\choose a_i+1}(a_i+1)! \]

構建插孔數量為 \(t\) 的指數型生成函式 \(F_t(x)\)

\[F_t(x)=\sum_{i\ge 0}\frac{1}{i!}{t\choose i+1}(i+1)!x^i \]

\[=\sum_{i\ge 0}(i+1)\frac{t!}{(i+1)!(t-i-1)!}x^i \]

\[=\sum_{i\ge 0}t\frac{(t-1)!}{i!(t-i-1)!}x^i \]

\[=\sum_{i\ge 0}t{t-1\choose i}x^i \]

\[=t(x+1)^{t-1} \]

答案就是:

\[[x^{n-2}]\prod_{i=1}^nF_{d_i}(x)=[x^{n-2}]\prod_{i=1}^n(d_i(x+1)^{d_i-1}) \]

\[=[x^{n-2}](x+1)^{\sum_{i=1}^n(d_i-1)}\prod_{i=1}^nd_i \]

\[={\sum_{i=1}^n(d_i-1)\choose n-2}(n-2)!\prod_{i=1}^nd_i \]

\[=(\sum_{i=1}^n(d_i-1))^{\underline{n-2}}\prod_{i=1}^nd_i \]

再講一下官方給出來的神仙構造做法。

考慮依次選邊,最後再除以 \((n-1)!\) ,給每個點欽定一個特殊的插孔,那麼一開始特殊的插孔就有 \(n\) 個,不特殊的插孔就有 \(\sum_{i=1}^n(d_i-1)\) 個,執行以下操作 \((n-2)\) 次,在執行以下操作中的每一步的時候每一個特殊的插孔都表示一個連通塊:

  • 在所有不特殊的插孔中選出一個插孔。
  • 在所有特殊的插孔中選出一個和之前選出來的不特殊插孔不連通的一個特殊插孔。
  • 連線這兩個插孔,刪除選出來的這兩個特殊插孔和不特殊插孔。

最後還會剩下兩個特殊的插孔,表示還有兩個連通塊,直接連線這兩個特殊的插孔。

可以發現,每一個特殊的插孔都是我們給這個點選擇的最後一次連邊,所以每一種依次連邊方式的特殊點都是確定的,可以發現,這種構造方式和每一種依次連邊方式都是一一對應的,所以依次連邊的方案數就是這樣子構造的方案數。

考慮這樣子構造的方案數,首先選出特殊插孔 \(\prod_{i=1}^nd_i\)如果操作從 0 開始編號,那麼在第 \(k\) 次的操作中,選擇不特殊的插孔的方案數就是 \((\sum_{i=1}^n(d_i-1)-k)\) ,選出特殊插孔的方案數就是 \((n-k-1)\) ,所以依次連邊的方案數就是:

\[(\prod_{i=1}^nd_i)\times \prod_{k=0}^{n-3}(\sum_{i=1}^n(d_i-1)-k)(n-k-1) \]

\[=(\prod_{i=1}^nd_i)\times(\prod_{k=0}^{n-3}(\sum_{i=1}^n(d_i-1)-k))\times(\prod_{k=0}^{n-3}(n-k-1)) \]

\[=(\prod_{i=1}^nd_i)\times(\sum_{i=1}^n(d_i-1))^{\underline{n-2}}\times(n-1)! \]

記得除以 \((n-1)!\) ,所以答案就是:

\[(\sum_{i=1}^n(d_i-1))^{\underline{n-2}}\prod_{i=1}^nd_i \]

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
const int mod=998244353;
int mo(const int x){
	return x>=mod?x-mod:x;
}
int main(){
	int n,S=0,T=1;read(n);
	for(int i=1;i<=n;++i){
		int d;read(d);
		T=1ll*T*d%mod;
		S=mo(S+d);
	}
	S-=n;
	for(int i=0;i<n-2;++i,--S)
		T=1ll*T*S%mod;
	write(T),pc('\n');
	return 0;
}

總結

這次比賽還是暴露了很多的問題的,下一次爭取不要再犯這種 nt 錯誤了。