NOIP2021 題解(T1-T3)


我太弱了,改不出T4,就把T1-3題解碼了。

T1 報數

題目鏈接

想着T2,T3的題解都寫了,就補一下T1的吧。

典型的篩法。

假如一個數含有7,則把它的倍數全篩走。

這里可以加一個小優化,假如這個數已經被篩過,就不需要再篩它的倍數了。

最后再倒着預處理每個數的下一個沒被篩的是什么。

如果不預處理,不斷6999999就可以卡死你。

Code

#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();
}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();}return x*f;}
//#define M
//#define mo
#define N 10000050
int n, m, i, j, k; 
int f[N], s[N], t, x; 

int pan(int x)
{
	while(x)
	{
		if(x%10==7) return 1; 
		x/=10; 
	} 
	return 0; 
}

signed main()
{
//	freopen("number.in", "r", stdin);
//	freopen("number.out", "w", stdout);
	for(i=1; i<=10000005; ++i)
	{
		if(pan(i))  
		{
			if(!f[i])
			for(j=i; j<=10000005; j+=i)  f[j]=1; 
		} 
//		if(!f[i]) printf("%d\n", i); 
	}
	for(i=10000005; i>=1; --i)
	{
		if(!f[i]) s[i]=i; 
		else s[i]=s[i+1]; 
//		printf("s[%d]=%d\n", i, s[i]); 
	} 
	t=read(); 
	while(t--)
	{
		x=read(); 
		if(f[x]) printf("-1\n"); 
		else printf("%d\n", s[x+1]); 
	}
	return 0;
}

T2 數列

題目鏈接

首先dp得從低位向高位枚舉,因為高位無論如果使用 \(2^{a_i}\) 都對低位二進制1的個數無影響,滿足dp的無后效性。

\(dp(k, i, x, y)\)\(S\) 從低的高二進制的前 \(k\) 位中,用了數列 \(a\) 的前 \(i\) 項,且此時 \(S\) 中共有 \(x\) 個二進制位為1,第 \(i+1\) 位進了 \(y\) 過去。

則:

\[dp(k, i, x, y)=\sum_{j=0}^{n-i}dp(k+1, i+j, x+(y+j\&1),y+j>>1)\times C_{i+j}^j\times v_k^j \]

假設這一位有 \(j\)\(a_j\) 滿足 \(a_j=k\),則下一位就有 \(i+j\)\(a\) 的元素確定,如果這一位是1,則 \(x+1\),在轉移中的 \(x+(y+j\&1)\) 體現。而進位到下一位的就是 \(y+j>>1\) 了。

對於每種方案,其對於答案的貢獻為 \(v_k^j\),而方案相當於在 \(i+j\) 個數中插 \(j\) 塊板,即 \(C_{i+j}^j\)

建議用記憶化搜索實現。

Code

// Problem: P7961 [NOIP2021] 數列【民間數據】
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7961
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define mo 998244353
#define N 40
#define M 110
int n, m, i, j, k; 
int c[M][M], s[M][M]; 
int f[M][N][N][N], v[M], K; 

int count(int x)
{
	int ans=0; 
	while(x) x-=x&-x, ++ans; 
	return ans; 
}

int dfs(int k, int u, int x, int y)
{
	if(u==n)
	{
		// printf("> f[%lld][%lld][%lld][%lld]=%lld\n", k, u, x, y, x+count(y)<=K);
		if(x+count(y)>K) return 0; 
		// printf("----------"); 
		return 1; 
	}
	if(k>m) return 0; 
	if(f[k][u][x][y]!=-1) return f[k][u][x][y]; 
	int ans=0;  
	for(int i=0; i<=n-u; ++i)
	{
		ans=(ans+dfs(k+1, u+i, x+((y+i)&1), (y+i)>>1)
						*s[k][i]%mo*c[u+i][i]%mo)%mo; 
		// printf("%lld %lld\n", s[k][i], c[n-u][i]); 
	}
	f[k][u][x][y]=ans; 
	// printf("f[%lld][%lld][%lld][%lld]=%lld\n", k, u, x, y, f[k][u][x][y]); 
	return f[k][u][x][y]; 
}


signed main()
{
//	freopen("tiaoshi.in","r",stdin);
//	freopen("tiaoshi.out","w",stdout); 
	memset(f, -1, sizeof(f)); 
	n=read(); m=read(); K=read(); 
	for(i=0; i<=m; ++i) v[i]=read(); 
	c[0][0]=1; 
	for(i=1; i<=n; ++i)
		for(j=c[i][0]=1; j<=i; ++j) 
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mo; 
	for(i=0; i<=m; ++i)
	{
		s[i][0]=1; 
		for(j=1; j<=n; ++j) s[i][j]=(s[i][j-1]*v[i])%mo; 
	}
	printf("%lld\n", dfs(0, 0, 0, 0)); 
	return 0;
}


T3 方差

題目鏈接

Part A 式子化簡

首先題目要求的式子就是 \(n^2\) 乘上 \(\frac{1}{n}\sum_{i=1}^n(a_i-\bar a)^2\),其中 \(\bar a=\frac{1}{n}\sum_{i=1}^n a_i\)

我們把這三合在一起也就是:

\[n^2\times\frac{1}{n}\sum_{i=1}^n(a_i-\frac{1}{n}\sum_{j=1}^n a_j)^2 \]

前面化簡一下,后面用完全平方公式展開:

\[n\times\sum_{i=1}^n(a_i^2-2\times(\frac{1}{n}\sum_{j=1}^n a_j)\times a_i+\frac{1}{n^2}(\sum_{j=1}^n a_j)^2) \]

拆開:

\[n\times\sum_{i=1}^n a_i^2-n\times\sum_{i=1}^n(2\times \frac{1}{n}(\sum_{j=1}^n a_j)\times a_i)+n\times \sum_{i=1}^n(\frac{1}{n^2}(\sum_{j=1}^n a_j)^2) \]

對於第二坨,把與 \(a_i\) 無關的抽出來:

\[n\times\sum_{i=1}^n a_i^2-n\times 2\times \frac{1}{n}(\sum_{j=1}^n a_j)\times\sum_{i=1}^n a_i+n\times \sum_{i=1}^n(\frac{1}{n^2}(\sum_{j=1}^n a_j)^2) \]

把第二坨化簡一下:

\[n\times\sum_{i=1}^n a_i^2-2\times\sum_{i=1}^n a_i\times\sum_{i=1}^n a_i+n\times \sum_{i=1}^n(\frac{1}{n^2}(\sum_{j=1}^n a_j)^2) \]

把第二坨的后兩個寫成平方的形式:

\[n\times\sum_{i=1}^n a_i^2-2\times(\sum_{i=1}^n a_i)^2+n\times \sum_{i=1}^n(\frac{1}{n^2}(\sum_{j=1}^n a_j)^2) \]

看第三坨,發現沒有和 \(i\) 有關的項,之間把 \(\sum_{i=1}^n\) 變成乘 \(n\)

\[n\times\sum_{i=1}^n a_i^2-2\times(\sum_{i=1}^n a_i)^2+n\times n\times\frac{1}{n^2}(\sum_{j=1}^n a_j)^2 \]

化簡一下第三坨:

\[n\times\sum_{i=1}^n a_i^2-2\times(\sum_{i=1}^n a_i)^2+(\sum_{i=1}^n a_i)^2 \]

然后我們發現二、三坨可以合並:

\[n\times(\sum_{i=1}^n a_i^2)-(\sum_{i=1}^n a_i)^2 \]

於是對於每種 \(a\) 我們都有直接算的方法了。

Part B 差分交換

現在讓我們考慮令一個問題。

對於相鄰的三個數 \(a_{i-1}\)\(a_i\)\(a_{i+1}\),我們計算相鄰兩數的差分別為 \(a_i-a_{i-1}\)\(a_{i+1}-a_i\)

先在我們把 \(a_i\) 變成 \(a_{i-1}+a_{i+1}-a_i\),現在就是 \(a_{i-1}\)\(a_{i-1}+a_{i+1}-a_i\)\(a_{i+1}\)

然后我們再對相鄰兩數作差分別為:\(a_{i+1}-a_i\)\(a_i-a_{i-1}\)

發現了什么?

沒錯,我們對每個數進行交換,只不過是把相鄰兩項的差交換

於是我們對 \(a\) 作差分序列 \(d\),這樣無論我們對 \(a\) 序列進行怎樣的變換,\(d\) 序列的元素始終不變,只是順序改變

基於這個結論,我們可以枚舉 \(d\) 的順序,時間復雜度 \(O(n!)\)

Part C 差分單谷性

這里我們可以引出一個結論:\(d\) 的排列必然是先從大到小,再從小到大。

這里可以感性理解一下,因為對於每個 \(a\) 只有不斷靠近平均數,方差才最小。

我們也可以大致運用反證法證明。(有誤請指出)

看不到?戳這里查看圖片

圖中 \(d_1>d_2\),我們發現 \(a_1\)\(a_3\) 的值不變,圖2相比圖1中只有 \(a_2\) 變大了。

兩個 \(a_2\) 相比,顯然圖1中的 \(a_2\) 更靠近谷底,也就是 \(\bar a\)。按照開始的式子 \(\frac{1}{n}\sum_{i=1}^n(a_i-\bar a)^2\) 我們發現 \(a_2\) 取圖1的情況更優。

有了這個結論,我們可以把 \(d\) 按從大到小排序,對於 \(d_i\),要么放左邊,要么放右邊,通過此方法,我們就得到了一種 \(O(2^n)\) 的暴力。

48分Code
// Problem: P7962 [NOIP2021] 方差【民間數據】
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7962
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
// #define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define N 410
//#define mo
int n, m, i, j, k; 
int a[N], d[N], b[N]; 
int ans=0x7fffffff; 
int sum, num; 

bool cmp(int x, int y)
{
	return x>y; 
}

void dfs(int x, int l, int r, int s, int t)
{
	if(l==r-1)
	{
		ans=min(ans, n*t-s*s);
		return ; 
	}
	int k; 
	k=a[l+1]=a[l]+d[x]; dfs(x+1, l+1, r, s+k, t+k*k); 
	k=a[r-1]=a[r]-d[x]; dfs(x+1, l, r-1, s+k, t+k*k); 
}

signed main()
{
//	freopen("tiaoshi.in","r",stdin);
//	freopen("tiaoshi.out","w",stdout);
	n=read(); 
	for(i=1; i<=n; ++i) a[i]=read(); 
	for(i=2; i<=n; ++i) d[i-1]=a[i]-a[i-1];  
	sort(d+1, d+n, cmp); 
	dfs(1, 1, n, a[1]+a[n], a[1]*a[1]+a[n]*a[n]); 
	printf("%d", ans); 
	return 0;
}

Part D 相對位置代替絕對位置

可以發現,我們在上面的方法中每次都把 \(a\) 的絕對位置求出來。但其實我們可以只求出其相對位置。

我們先對 \(d\) 從小到達排序,再一條一條放進去。此時 \(a_i\) 的位置是在這種放進去的方法下的 \(\sum_{j=1}^i d_j\)

首先讓我們把之前的式子搬回出來:

\[n\times(\sum_{i=1}^n a_i^2)-(\sum_{i=1}^n a_i)^2 \]

我們要讓結果最小,就是要讓 \(\sum_{i=1}^n a_i\) 最大,\(\sum_{i=1}^n a_i^2\) 最小。

讓我們設 \(f(i, j)\) 表示已經決定的 \(i\)\(a\) 里面,和為 \(j\) 的最小平方和。設 \(S\) 為當前的 \(i\)\(\sum_{j=1}^i d_i\)

\(j\) 可以理解為 \(\sum_{i=1}^n a_i\)\(f(i, j)\) 可以理解為 \(\sum_{i=1}^n a_i^2\)

現在對於 \(d_{i+1}\),要么放在左邊,要么放在右邊。

先說放在右邊,\(j\) 則會加上當前點的坐標,答案加上當前的平方,而當前點為 \(S+d_i\)

\[f(i+1, j+(S+d_i))=f(i, j)+S\times S \]

放在左邊的話,就是整體 \(a\) 右移 \(d_{i+1}\)\(j\) 就明顯是加上 \((i+1)\times d_{i+1}\),至於 \(f\) 的話,這里要手推一下。

首先至少要加上這個點 \(d_i^2\),然后后面的和變成 \(\sum_{k=1}^i(a_k+d_i)^2\),而原先是 \(\sum_{k=1}^i a_k^2\),所以加上的是:

\[\sum_{k=1}^i(a_k+d_i)^2-\sum_{k=1}^i a_k^2 \]

前面用完全平方公式拆一下:

\[\sum_{k=1}^i a_k^2+\sum_{k=1}^i(2\times a_k\times d_i)+\sum_{k=1}^i d_i^2-\sum_{k=1}^i a_k^2 \]

最前面與最后面兩坨消去:

\[\sum_{k=1}^i(2\times a_k\times d_i)+\sum_{k=1}^i d_i^2 \]

前面那坨把無關緊要的提出了:

\[2\times d_i\times\sum_{k=1} a_k+\sum_{k=1}^i d_i^2 \]

\(j\) 套進去:

\[2\times d_i\times j+\sum_{k=1}^i d_i^2 \]

后面那坨直接把 \(\sum_{k=1}^i\) 變成乘 \(i\)

\[2\times d_i\times j+i\times d_i^2 \]

根據這個,我們可以把放左邊的dp推出了:

\[f(i+1, j+d_{i+1}\times (i+1))=f(i, j)+2\times d_i\times j+i\times d_i^2+d_i^2 \]

后面兩個合一下:

\[f(i+1, j+d_{i+1}\times (i+1))=f(i, j)+2\times d_i\times j+(i+1)\times d_i^2 \]

然后就行了。

轉移方程與最終代碼可能有一些細節的東西,改一改就行了。

可是按照這個方法打只有72分。

72分code
// Problem: P7962 [NOIP2021] 方差【民間數據】
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7962
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define N 410
#define M 610
//#define mo
int n, m, i, j, k; 
int a[N], d[N]; 
int ans=0x7fffffffff; 
int sum, num; 
int f[N][N*M]; 

signed main()
{
//	freopen("tiaoshi.in","r",stdin);
//	freopen("tiaoshi.out","w",stdout);
	n=read(); 
	for(i=1; i<=n; ++i) a[i]=read(); 
	for(i=2; i<=n; ++i) d[i-1]=a[i]-a[i-1];  
	sort(d+1, d+n); 
	for(i=0; i<=n; ++i) for(j=0; j<=240000; ++j) f[i][j]=ans; 
	f[0][0]=0; 
	for(i=0; i<n-1; ++i)
	{
		sum+=d[i+1]; 
		for(j=0; j<=240000; ++j)
		{
			f[i+1][j+sum]=min(f[i+1][j+sum], f[i][j]+sum*sum); 
			f[i+1][j+d[i+1]*(i+1)]=min(f[i+1][j+d[i+1]*(i+1)], f[i][j]+2*j*d[i+1]+(i+1)*d[i+1]*d[i+1]); 
			// if(f[i][j]!=ans)
			// printf("f[%lld][%lld]=%lld\n", i, j, f[i][j]); 
		}
	}
		
	for(i=0; i<=24000; ++i) 
	{
		// if(f[n-1][i]!=0x7fffffffff) printf("f[%lld][%lld]=%lld\n", n-1, i, f[n-1][i]); 
		// ans=min(ans, n*(f[n-1][i]+2*i*a[1]+(n-1)*a[1]*a[1]+a[1]*a[1])-(a[1]*n+i)*(a[1]*n+i));  
		ans=min(ans, n*f[n-1][i]-i*i); 
	}
		
	printf("%lld", ans); 
	return 0;
}

Part E 小優化

我們發現主要是集中在MLE和TLE。

MLE方面,我們可以使用滾動數組。

TLE的話,對於 \(j\) 的循環范圍,我們可以動態分配。

提一句,最后加不加上 \(a_1\) 都行(實測可以)。

最后貼上AC code:

// Problem: P7962 [NOIP2021] 方差【民間數據】
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P7962
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;
ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+
(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define N 10010
#define M 610
//#define mo
int n, m, i, j, k; 
int a[N], d[N]; 
int ans=0x7ffffffffff; 
int sum, num, p, q; 
int f[2][500010]; 

signed main()
{
//	freopen("tiaoshi.in","r",stdin);
//	freopen("tiaoshi.out","w",stdout);
	n=read(); 
	for(i=1; i<=n; ++i) a[i]=read(); 
	for(i=2; i<=n; ++i) d[i-1]=a[i]-a[i-1];  
	sort(d+1, d+n); 
	for(j=0; j<=500000; ++j) f[1][j]=f[0][j]=ans; 
	f[0][0]=0; 
	for(i=0; i<n-1; ++i)
	{
		sum+=d[i+1]; 
		p=(i+1)%2; q=i%2; num+=d[i+1]*(i+1); 
		for(j=0; j<=num; ++j) f[p][j]=ans; 
		for(j=0; j<=num; ++j)
		{
			if(j+sum<=num)
				f[p][j+sum]=min(f[p][j+sum], f[q][j]+sum*sum); 
			if(j+d[i+1]*(i+1)<=num)
				f[p][j+d[i+1]*(i+1)]=min(f[p][j+d[i+1]*(i+1)], f[q][j]+2*j*d[i+1]+(i+1)*d[i+1]*d[i+1]); 
		}
	}
		
	for(i=0; i<=500000; ++i) 
	{
		//ans=min(ans, n*(f[(n-1)%2][i]+2*i*a[1]+(n-1)*a[1]*a[1]+a[1]*a[1])-(a[1]*n+i)*(a[1]*n+i));  
//可寫可不寫,是是否加上a1的情況
		ans=min(ans, n*f[n-1][i]-i*i); 
	}
		
	printf("%lld", ans); 
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM